Skip to content

Object type not assignable to equivalent value of mapped type #25010

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

Closed
ethanroday opened this issue Jun 15, 2018 · 7 comments
Closed

Object type not assignable to equivalent value of mapped type #25010

ethanroday opened this issue Jun 15, 2018 · 7 comments
Assignees
Labels
Fixed A PR has been merged for this issue Needs Investigation This issue needs a team member to investigate its status.

Comments

@ethanroday
Copy link

Code

// Pack<{ a: number }> is { a: { value: number } }
type Pack<T> = { [K in keyof T]: { value: T[K] } };

function patch<T, K extends keyof T>(obj: Partial<Pack<T>>, key: K, value: T[K]) {
    obj[key] = { value }; // error: Type '{ value: T[K]; }' is not assignable to type 'Pack<T>[K]'.
}

Example usage of patch():

type SomeType = {
  a: number, b: string
};

const obj: Partial<Pack<SomeType>> = {
    a: { value: 1 }
}

patch(obj, 'b', 'foo'); // desired outcome is for obj.b to equal 'foo'

Expected behavior:

Definition of patch() function should be fine, and calling should produce the desired outcome.

Actual behavior:

patch() function fails to compile with error Type '{ value: T[K]; }' is not assignable to type 'Pack<T>[K]' even though { value: T[K] } does appear to be assignable to Pack<T>[K]:

function getPack<T, K extends keyof T>(obj: T, key: K): Pack<T>[K]  {
    const value = obj[key]; // T[K]
    return { value }; // { value: T[K]; }
}

Playground Link:
Additional examples here: https://bit.ly/2le5qZq

@DanielRosenwasser
Copy link
Member

Technically, but perhaps infuriatingly, this is correct because you really don't know if an instance of Pack<T>[K] resolves to a more derived type than { value: T[K] }. All you're guaranteed is that it's an upper bound constraint on whatever obj[key] is, so you can read values out assuming they have type { value: T[K] }, but you can't write into it.

@ethanroday
Copy link
Author

@DanielRosenwasser I'm not sure I totally understand. So then why does Pack<T> (rather than Partial<Pack<T>>) work as expected? What's an example of a situation where the expected behavior I outlined above would be incorrect?

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Jun 17, 2018

Okay, a couple of things:

First, this works the way you want under strictNullChecks which is odd. I don't know why that is off the bat.

Second, really neither should work. Seems like actually that's the bug.

Third,

What's an example of a situation where the expected behavior I outlined above would be incorrect?

interface Box<T> { value: T }
type Pack<T> = { [K in keyof T]: Box<T[K]> }

function patch<T, K extends keyof T>(obj: Partial<Pack<T>>, key: K, value: T[K]) {
    obj[key] = { value };
}


let boxCollection = {
    foo: {
        value: 100,
        shapeType: "cube",
    },
    bar: {
        value: 200,
        shapeType: "sphere",
    }
};

patch(boxCollection, "foo", 100);

// Crash!
boxCollection.foo.shapeType.toLowerCase();

@DanielRosenwasser DanielRosenwasser added the Bug A bug in TypeScript label Jun 17, 2018
@ethanroday
Copy link
Author

ethanroday commented Jun 17, 2018

Thanks for the example - very helpful. If I understand correctly, the issue is that you can't just blindly assign something to obj[key] because the type of obj[key] may be more derived than Box<T>. Not to muddy the waters, then, but shouldn't the following work just fine? I would expect the type of obj[key].value to be T[K].

function patch<T, K extends keyof T>(obj: Partial<Pack<T>>, key: K, value: T[K]) {
    obj[key].value = value; // Error: Property 'value' does not exist on type 'Partial<Pack<T>>[K]'.
}

@ethanroday
Copy link
Author

@DanielRosenwasser any ideas here?

@mhegazy mhegazy added the Needs Investigation This issue needs a team member to investigate its status. label Jul 23, 2018
@mhegazy mhegazy added this to the TypeScript 3.1 milestone Jul 23, 2018
@RyanCavanaugh RyanCavanaugh removed the Bug A bug in TypeScript label Aug 23, 2018
@ethanroday
Copy link
Author

With Typescript 3.1.1, this is now workable, although still not perfect. The same code as above still results in a error, but for a different reason:

type Pack<T> = { [K in keyof T]: { value: T[K] } };

function patch<T, K extends keyof T>(obj: Partial<Pack<T>>, key: K, value: T[K]) {
  obj[key].value = value; // Error: Type 'T[K]' is not assignable to type 'T[string] | T[number] | T[symbol]'.
}

I'm not sure if this behavior is expected or not. Either way, if I enforce that T has string keys (which should be fine for most purposes), this works perfectly!

type Pack<T> = { [K in keyof T]: { value: T[K] } };

function patch<T extends {[key: string]: any}, K extends keyof T>(obj: Partial<Pack<T>>, key: K, value: T[K]) {
  obj[key].value = value; // Works!
}

type SomeType = {
  a: number, b: string
};

const obj: Partial<Pack<SomeType>> = {
    a: { value: 1 }
}

// With type checking and everything!
patch(obj, 'b', 2); // Error: Argument of type '2' is not assignable to parameter of type 'string'.

Will leave it up to you the maintainers whether to close based on this information. I don't fully understand what issues are outstanding here and which ones are actually issues in the first place.

@ahejlsberg ahejlsberg added the Fixed A PR has been merged for this issue label May 10, 2019
@ahejlsberg
Copy link
Member

Closing as the original example is no longer an error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Fixed A PR has been merged for this issue Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

No branches or pull requests

6 participants