Typescript: Unable to assign compatible value to generic typed property using `extends` in generic definition.

Created on 7 Apr 2017  ·  3Comments  ·  Source: microsoft/TypeScript

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'."

Question

Most helpful comment

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

All 3 comments

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

  1. Let P be the type { on: boolean }
  2. For all types T such that T is assignable to P, the definition of Stateful<T> above is valid

Disproof by counter example

  1. Let U be the type { on: boolean, value: number }
  2. U is assignable to P -> by definition
  3. Stateful<U> is invalid -> by substitution
  4. Therefore there exists a type T 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>'
    }
}
Was this page helpful?
0 / 5 - 0 ratings