TypeScript function generic can only work for func

2019-08-19 09:54发布

I'm defining an interface with generic function like:

export interface IState {
  send: <I, E>(message: I, callback?: (e: E) => void) => IState;
}

It works fine for classes with more than one signatures:

class Left implements IState {
  send(m: 'go-on', cb?: (e: never) => void): Left;
  send(m: 'turn-right', cb?: (e: never) => void): Right;
  send(m: 'go-on' | 'turn-right', cb?: any) {
    return m === 'go-on' ? new Left() : new Right();
  }
}

class Right implements IState {
  send(m: 'go-on', cb?: (e: never) => void): Right;
  send(m: 'turn-left', cb?: (e: never) => void): Left;
  send(m: 'go-on' | 'turn-left', cb?: any) {
    return m === 'go-on' ? new Right() : new Left();
  }
}

type Both = Left | Right;

function test(l: Both) {
  if (l instanceof Left) {
    l.send('turn-right')
      .send('turn-left')
      .send('turn-right')
      .send('turn-left');
  }
  const l2 = new Left();
  l2.send('go-on')
    .send('turn-right')
    .send('turn-left');
  l2.send('turn-right').send('turn-left');
}

However when I want to define an IState with only one send signature, I got compile errors:

class CountState implements IState {
  constructor(public readonly data: number) {}
  // send(m: 'inc', cb?: (e: number) => void): CountState;
  // send(m: 'inc', cb?: (e: number) => void): CountState;
  send(m: 'inc', cb?: (e: number) => void): CountState {
    const result = this.data + 1;
    if (cb !== undefined) {
      cb(result);
    }
    return new CountState(this.data + 1);
  }
}

Error on send method:

Property 'send' in type 'CountState' is not assignable to the same property in base type 'IState'. Type '(m: "inc", cb?: ((e: number) => void) | undefined) => CountState' is not assignable to type '(message: I, callback?: ((e: E) => void) | undefined) => IState'. Types of parameters 'm' and 'message' are incompatible. Type 'I' is not assignable to type '"inc"'.ts(2416)

If I add those two comments lines, such that

class CountState implements IState {
  constructor(public readonly data: number) {}
  send(m: 'inc', cb?: (e: number) => void): CountState;
  send(m: 'inc', cb?: (e: number) => void): CountState;
  send(m: 'inc', cb?: (e: number) => void): CountState {
    const result = this.data + 1;
    if (cb !== undefined) {
      cb(result);
    }
    return new CountState(this.data + 1);
  }
}

It compiles fine, but it looks really strange. How can I fix this?

1条回答
来,给爷笑一个
2楼-- · 2019-08-19 10:50

I agree with Titian Cernicova-Dragomir that this seems like a compiler bug. The definition of IState basically states that the "send" property is a function which can be called with any types for "message", and the callback parameter: "e", can also have any type.

export interface IState {
    send: <I, E>(message: I, callback?: (e: E) => void) => IState;
}

Meanwhile, in your example usage, you are explicitly listing the possible types, which contradicts with the interfaces definition. It's weird if this passes compilation.

class Left implements IState {
    send(m: 'go-on', cb?: (e: never) => void): Left;
    send(m: 'turn-right', cb?: (e: never) => void): Right;
    send(m: 'go-on' | 'turn-right', cb?: any) {
        return m === 'go-on' ? new Left() : new Right();
    }
}

Looking at the test code you included, you're checking for the exact type of the unknown "Both" type anyway, so it would seem that there is no functionality lost even if you'd just define separate methods in classes Left and Right for each action. eq:

class Left {
    turnRight(...) {
        return new Right();
    }
    keepGoin(...) {
        return new Left();
    }
}

as opposed to using the generic 'send' method for every action.

查看更多
登录 后发表回答