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?
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.
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.
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:
as opposed to using the generic 'send' method for every action.