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

Mapped object type ignores readonly #39802

Closed
lsdsjy opened this issue Jul 29, 2020 · 5 comments
Closed

Mapped object type ignores readonly #39802

lsdsjy opened this issue Jul 29, 2020 · 5 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@lsdsjy
Copy link

lsdsjy commented Jul 29, 2020

TypeScript Version:
3.5, 4.0.0-beta

Search Terms:
mapped object type, readonly property, exclude, lookup type

Code

type MyOmit<T, K extends keyof T> = {
  [S in Exclude<keyof T, K>]: T[S]
}
declare const foo: MyOmit<{ readonly a: number, b: number }, 'b'>
foo.a = 1  // expects error but none

declare const foo2: Omit<{ readonly a: number, b: number }, 'b'>
foo2.a = 1  // error as expected

Expected behavior:
Assignment to foo.a is an error.

Actual behavior:
No typing error reported.

Playground Link:
https://www.typescriptlang.org/play/index.html?ts=4.0.0-dev.20200729#code/C4TwDgpgBAsiDyBbAlsAPAFQDRQNJQgA9gIA7AEwGcoBrCEAewDMoMA+KAXigG8AoKFADaAZSjJSUAKKEAxgBsAruQho6jFtjxsAugC5WonXwC+fFQoCGAJ2iyGpSsChMGDA3CSo0PKLcvkDvIgUJYGpIqIAEYQ1jhR4ZEx1lAmOADkUelsfK4MAHSWXFAAjIIA9OUEhJCywNSx1gwpUYrOpA4QfOYQVrZQ9o7OeQBMBl7ovv6BpMGhidGx8QvJqRlZOaOFxWVQlQTWTSmWDTW9JORAA

Related Issues:

@jcalz
Copy link
Contributor

jcalz commented Jul 29, 2020

This is working as intended.

MyOmit is not a homomorphic mapped type and therefore does not copy modifiers from anything. The type Exclude<keyof T, K> is not seen by the compiler as being the keys of some type from which modifiers could be copied.

Omit from the standard library is written the way it is specifically to maintain the connection between T and the keys of the output type in a way that the compiler can see as homomorphic... by using Pick. The closest I can come to your definition that works is to declare a new type parameter that is constrained to keyof T, like this:

type MyOmit<T, K extends keyof T, X extends keyof T = Exclude<keyof T, K>> = {
    [S in X]: T[S]
};

You might be getting tripped up by #34793, since the IntelliSense for Omit expands it to a form like your MyOmit which doesn't actually work if you copied it verbatim.

@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label Jul 29, 2020
@lsdsjy
Copy link
Author

lsdsjy commented Jul 30, 2020

@jcalz Thank you for your clear explanation. But I still cant understand @Kingwl 's working example using this homomorphic mapped type theory. How can Pick establish a connection between T and the keys of the output type(i.e. Exclude <keyof T, K>)?

@jcalz
Copy link
Contributor

jcalz commented Jul 30, 2020

As far as I know, there are two ways to get a homomorphic mapped type. The normal way is to explicitly write in keyof T where T is a type parameter:

type Homomorphic<T> = {[P in keyof T]: string};

The less common way is to write in K where K is a type parameter constrained to keyof T where T is a type parameter, which gives you a "partially" homomorphic type (see #12826), which is what makes Pick's behavior possible:

type PartiallyHomomorphic<T, K extends keyof T> = {[P in K]: string};

@lsdsjy
Copy link
Author

lsdsjy commented Jul 30, 2020

@jcalz Well, so in the Pick case TS sees the constraint of second type parameter and makes a homomorphic mapped type, regardless of what the type argument is? Thanks again for your patient and thorough explanation! Issue closed.

@lsdsjy lsdsjy closed this as completed Jul 30, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

4 participants