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

Inconsistent behaviour of -? #59948

Open
m-shaka opened this issue Sep 12, 2024 · 4 comments Β· May be fixed by #59957
Open

Inconsistent behaviour of -? #59948

m-shaka opened this issue Sep 12, 2024 · 4 comments Β· May be fixed by #59957
Labels
Bug A bug in TypeScript Help Wanted You can do this
Milestone

Comments

@m-shaka
Copy link

m-shaka commented Sep 12, 2024

πŸ”Ž Search Terms

optional mapped type

πŸ•— Version & Regression Information

  • This changed between versions 5.4.5 and 5.5.4

⏯ Playground Link

https://www.typescriptlang.org/play/?exactOptionalPropertyTypes=true#code/C4TwDgpgBA8mwEsD2A7AhgGwCpIKouRQHUFgALfAEwgDMEUJKAeLAPigF4oBvAKCgFQA2gGko9KAGsIIJDShYAugC4FUCAA9gEFJQDOUAEoQAxkgBOzEQBoFoxa36DnAfjsjFT5wNVZ7UAB8oAFddWnpGXgBfXl5QSCgASRRtcwBbBEoENG1OWHhCTBx8QhJyKnCGZm4oNBdVPWBzegBzKCj2AHpOnlr6qEbmlDag0Oo6Kva48GgAGVIIc0w8mrqGptbAkLCJxnaoboG0NOh46DQDZNSMrJyIWLOjCD0ARjzjAEdghHNGJivFjdstouj1Vushm0Yo9jHoAEzvCBfH5-eapTCg3poCGbUY7CKUdpAA

πŸ’» Code

type OptionalToUnionWithUndefined<T> = {
    [K in keyof T]: T extends Record<K, T[K]>
        ? T[K]
        : T[K] | undefined
}

type Intermidiate = OptionalToUnionWithUndefined<{ a?: string }> // { a?: string | undefined }
type Literal = { a?: string | undefined } // same type as Intermidiate

type Res1 = Required<Intermidiate> // { a: string }
type Res2 = Required<Literal> // { a: string | undefined }

πŸ™ Actual behavior

Res1 and Res2 are different types

πŸ™‚ Expected behavior

they should be the same because the inputs are

Additional information about the issue

This issue may share the cause with #59902

@SmolPatat
Copy link

SmolPatat commented Sep 12, 2024

I think TS is tripping balls and hallucinating here. Both Res1 and Res2 are supposed to be {a: string | undefined}, and they are!
The following code compiles:

type Res1a = Res1['a']; // string | undefined
let r: Res1 = {a: undefined}; // Ok

type Id<T extends object> = {[K in (keyof T & string)]: T[K]};
type Res1Id = Id<Res1>; // { a: string | undefined }

This issue might be related to the --exactOptionalPropertyTypes flag. However, Required works differently without the flag, so I am not certain.

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Sep 12, 2024

There's no -? in the code sample; what's the relation to the title of the issue?

@SmolPatat
Copy link

There's no -? in the code sample; what's the relation to the title of the issue?

The -? is in Required. Hope this helps.

@RyanCavanaugh
Copy link
Member

I think this is just a type display issue. If you actually access the property, it has the expected type:

type OptionalToUnionWithUndefined<T> = {
    [K in keyof T]: T extends Record<K, T[K]>
        ? T[K]
        : T[K] | undefined
}

type Intermidiate = OptionalToUnionWithUndefined<{ a?: string }> // { a?: string | undefined }
type M = { a?: string }
type Literal = { a?: string | undefined } // same type as Intermidiate

type Res1 = Required<Intermidiate> // { a: string }
type Res2 = Required<Literal> // { a: string | undefined }

declare let r: Res1;
const j = r.a;
// j: string | undefined

// OK
const p: Res2['a'] = undefined;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Help Wanted You can do this
Projects
None yet
3 participants