TypeScript Version: 2.2.2
I might just be misunderstanding how generics work (if so please point me in the right direction), but this seem like a bug.
Code
// A *self-contained* demonstration of the problem follows...
interface BaseState {
on: boolean;
};
class Component {
state: BaseState;
setState(state: BaseState) {
this.state = state;
}
onInput({ value }: { value: number }) {
this.setState({ on: value > 0 }); // no error
}
}
class GenericComponent<State extends BaseState> {
state: State;
setState(state: State) {
this.state = state
}
onInput({ value }: { value: number }) {
this.setState({ on: value > 0 }); // error Argument of type '{ on: boolean; }' is not assignable to parameter of type 'State'.
}
}
Expected behavior:
In the Component
class in the example above causes no errors when using the BaseState
interface directly. I expect the same behavior from the GenericComponent
class since the State
generic explicitly extends BaseState
.
Actual behavior:
Instead I get the following error related to this.setState({ on: value > 0 });
: "Argument of type '{ on: boolean; }' is not assignable to parameter of type 'State'."
Consider what happens if you write this
var t = new GenericComponent<{on: boolean, thing: string}>();
t.onInput({ value: 30 });
t.state.thing.substr(0); // 'thing' property should exist, but doesn't
Here is a simplified example:
class Stateful<State extends { on: boolean }> {
state: State = {
on: true
}; // error
}
The reason this doesn't work is that it is only ever going to be valid when Stateful
is instantiated with a type argument precisely equivalent to { on: boolean }
.
Basically, _there exists_ a type T
, satisfying the constraints of Stateful
's type argument such that the instantiation is valid but this does not hold _for all_ types T
where T
satisfies the constraint of Stateful
's type argument.
So the errant code above asserts that, for all types T<T>
where T,
Stateful
Logically, we can look at it as follows
Assertion
P
be the type { on: boolean }
T
such that T
is assignable to P
, the definition of Stateful<T>
above is validDisproof by counter example
U
be the type { on: boolean, value: number }
U
is assignable to P
-> by definitionStateful<U>
is invalid -> by substitutionT
such that T
is assignable to P
and Stateful<T>
is an invalid instantiation -> by implication@RyanCavanaugh @aluanhaddad Shoot, I knew that. I just got confused while trying to distill the problem down to something simpler. So let try that again. What about in this case using a Partial? I still get a similar error.
interface BaseState {
on: boolean;
};
class Component {
state: BaseState;
setState(partialState: Partial<BaseState>) {
this.state = { ...this.state, ...partialState };
}
onInput({ value }: { value: number }) {
this.setState({ on: value > 0 }); // no error
}
}
class GenericComponent<State extends BaseState> {
state: State;
setState(partialState: Partial<State>) {
this.state = { ...this.state, ...partialState };
}
onInput({ value }: { value: number }) {
this.setState({ on: value > 0 }); // error: Argument of type '{ on: boolean; }' is not assignable to parameter of type 'Partial<State>'
}
}
Most helpful comment
Consider what happens if you write this