Skip to content

Inconsistent behaviour of -? #59948

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

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

Inconsistent behaviour of -? #59948

m-shaka opened this issue Sep 12, 2024 · 8 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 Intermediate = OptionalToUnionWithUndefined<{ a?: string }> // { a?: string | undefined }
type Literal = { a?: string | undefined } // same type as Intermediate

type Res1 = Required<Intermediate> // { 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

@Fullfungo
Copy link

Fullfungo 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?

@Fullfungo
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;

@mkantor
Copy link
Contributor

mkantor commented Apr 25, 2025

@RyanCavanaugh In TypeScript 5.5.4 (the regressed version mentioned in the issue) and newer your example has a type error, which indicates this is more than just a display issue. EDIT: Wait, I may have gotten confused by the comments in your example which don't match the type info I'm seeing. Res1 and Res2 appear to be the same in both type info and behavior in the playground above when using TypeScript 5.5.4. Is that expected?

Here's a reduced reproduction (I think).

@m-shaka
Copy link
Author

m-shaka commented Apr 26, 2025

@mkantor Your first example is related to exactOptionalPropertyTypes option

https://www.typescriptlang.org/play/?exactOptionalPropertyTypes=false&ts=5.5.4#code/PTAEG9QQwLlBnALgJwJYDsDmoA+oCu6AJgKYBmGJRoAvqKmaCQB5QDGiA8gA6KoD26KABsACsn7cSyRAE8AKrKnwAvCnwkAUCAjQ4SNFlr1GLdl14ChYiVJkKlJVWRHwtcqaHmgVoAEokAI74qMhUADyQUAD8+igY2HiEpBToVLQAfJqabIJIel4A2gDkUMUAuj4ExOSUREA

For my example, which enables exactOptionalPropertyTypes , Res1 should be displayed { a: string | undefined } but not

@mkantor
Copy link
Contributor

mkantor commented Apr 26, 2025

Your first example is related to exactOptionalPropertyTypes option

Ah, right you are! I copied the prior example into a playground without enabling that option. Thanks for catching my mistake.

I believe my latter reduced example is the same bug though (despite not requiring exactOptionalPropertyTypes). I verified that the proposed fix at #59957 addresses it. I think it's essentially the same scenario that #60411 is talking about.

@m-shaka
Copy link
Author

m-shaka commented Apr 27, 2025

I see, the latter one looks like truly minimum reproduction of this bug! Thank you

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
4 participants