Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to assign compatible value to generic typed property using extends in generic definition. #15065

Closed
joslarson opened this issue Apr 7, 2017 · 3 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@joslarson
Copy link

joslarson commented Apr 7, 2017

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

@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label Apr 7, 2017
@RyanCavanaugh
Copy link
Member

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

@aluanhaddad
Copy link
Contributor

aluanhaddad commented Apr 7, 2017

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

@joslarson
Copy link
Author

joslarson commented Apr 7, 2017

@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>'
    }
}

@microsoft microsoft locked and limited conversation to collaborators Jun 21, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

3 participants