-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Inconsistent behaviour of never
within a mapped type
#61602
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Comments
FAQ bot response. Your observations follow naturally from how TypeScript treats the special type "never" in mapped types. "never" is effectively the empty union, so when it's used in certain contexts like a mapped type, you end up with no members to iterate over, resulting in an empty object or the type "never". Conversely, when you write: type Keys = keyof never; then "keyof never" can appear to be "string | number | symbol". In that case, TypeScript is using the fallback for the property keys that an object type could have. But in a mapped type, the compiler does not see any actual members to operate on (because "never" is an empty union), so the result is an empty object: type T = { [K in keyof never]: string };
// ^? type T = {} Regarding generics, the same principle applies. If you do: type Test<T> = { [K in keyof T]: string };
type T = Test<never>;
// ^? type T = never there is nothing to map over when you pass When conditional types act on a generic type, they become distributive when that type could be a union. If your function or type uses an type IsString<T> = T extends string ? "yes" : "no";
type S = IsString<string>;
// "yes"
type SB = IsString<string | boolean>;
// "yes" | "no"
type N = IsString<never>
// never The same thinking extends to mapped types and explains why you get an empty object in one scenario ( type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
// This type is no longer a union of arrays
// but rather (string | number)[]
type ArrayOfStrOrNum = ToArrayNonDist<string | number>; Additional context: The second behavior follows from homomorphic mapped types being distributive. I don't recall the reason for |
TIL that homomorphic mapped types behave like distributive conditionals when instantiated with type Test<T> = { [K in Exclude<keyof T, never>]: string };
type T1 = Test<never>;
// ^? type T1 = {
// [x: string]: string;
// [x: number]: string;
// [x: symbol]: string;
// }
Yeah true, I was mostly concerned with the homomorphic case, but discovered this case while playing around. |
Acknowledgement
Comment
Why are the results different in the following cases?
The second example makes sense to me —
keyof never
evaluates tostring | number | symbol
, so we get back index signatures as expected. But it's unclear why the first example produces an empty object instead.Also, when generics are involved the result becomes
never
. This should also have been an index signature.The text was updated successfully, but these errors were encountered: