You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
NonNullable is not resolved for a mapped type in a contextual parameter type from instantiated type aliasΒ #51331
The core issue here is the normalization we perform on index types. Specifically, we generally normalize keyof (A & B) into keyof A | keyof B. Turns out that doesn't always hold when A and B are generic types:
typeFoo={x: string}|undefined;typeBar={y: string};typeFooAndBar=Foo&Bar;typeKeys<T,U>=keyof(T&U);// Normalized into keyof T | keyof UtypeK1=keyofFoo;// nevertypeK2=keyofBar;// "y"typeK3=Keys<Foo,Bar>;// "y"typeK4=keyof(Foo&Bar);// "x" | "y"
Above, keyof Foo | keyof Bar is just "y", whereas keyof (Foo & Bar) is "x" | "y" because Foo & Bar is reduced to just { x: string} & { y: string }, thus eliminating the undefined type.
We already have logic in place to defer types like keyof (T & {}), but it looks like we need to defer all keyof (T & U) where one or both of the types are generic. We can then simplify the type to keyof T | keyof U where appropriate. I'm going to put together a PR and we'll see what the effects are.
typeTypePropertyNames<T,U>=NonNullable<{[KinkeyofT]: NonNullable<T[K]>extendsU ? K : never}[keyofT]>;interfaceTest{field1?: boolean;field2: boolean;field3: string;}typeTestFields=TypePropertyNames<Test,boolean>;// should be 'field1' | 'field2'// but now 'field1' | 'field2' | undefinedtypeTestFields2=NonNullable<TypePropertyNames<Test,boolean>>;// this works correct
Activity
typescript-bot commentedon Oct 27, 2022
The change between release-4.6 and main occurred at 51b346d.
ahejlsberg commentedon Dec 2, 2023
The core issue here is the normalization we perform on index types. Specifically, we generally normalize
keyof (A & B)intokeyof A | keyof B. Turns out that doesn't always hold whenAandBare generic types:Above,
keyof Foo | keyof Baris just"y", whereaskeyof (Foo & Bar)is"x" | "y"becauseFoo & Baris reduced to just{ x: string} & { y: string }, thus eliminating theundefinedtype.We already have logic in place to defer types like
keyof (T & {}), but it looks like we need to defer allkeyof (T & U)where one or both of the types are generic. We can then simplify the type tokeyof T | keyof Uwhere appropriate. I'm going to put together a PR and we'll see what the effects are.pkudryashov commentedon Jul 3, 2024
I have similar issue in 5.4.x and 5.5.x
Output
Compiler Options
{ "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, "strictPropertyInitialization": true, "strictBindCallApply": true, "noImplicitThis": true, "noImplicitReturns": true, "alwaysStrict": true, "esModuleInterop": true, "declaration": true, "target": "ES2017", "jsx": "react", "module": "ESNext", "moduleResolution": "node" } }Playground Link: Provided
Is this the same bug?