-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Generic conditional type prematurely resolves to one of the branches instead of deferring #57650
Comments
I feel like there was a recent issue about exactly this... |
A somewhat smaller repro would be this: type IsNumber<T> = [T] extends [number] ? true : false
type Shortcut<T> = false[] extends IsNumber<T>[] ? 0 : 1;
// ^? type Shortcut<T> = 1 TS tries to check if the check type is assignable to a permissive instantiation of the extends type. That permissive instantiation is Some extra mechanisms that avoid over-eager simplifications were introduced in #56004 but that was only when computing constraints of conditional types and in this situation This is the most relevant piece of the implementation: link. Perhaps the biggest surprise (for me) is that this extends type ( The definition of function isDeferredType(type: Type, checkTuples: boolean) {
return isGenericType(type) || checkTuples && isTupleType(type) && some(getElementTypes(type), isGenericType);
} I have never formed a good mental model for what a generic type is internally (again, this one depends on a type variable but it's "just" a type reference to |
That's weird that the so-called "permissive instantiation" of Footnotes
|
Especially weird to me that this works again if I define type IsFalse2<T extends boolean> = [false] extends [T] ? true : false
type Shortcut2<T> = IsFalse2<IsNumber<T>> extends true ? 0 : 1;
type Indirect2 = Shortcut2<string>;
// ^? type Indirect2 = 0 |
A permissive instantiation is an instantiation that maps type parameters to a wildcard type (an internal type of type IsNumber<T> = [T] extends [number] ? true : false
type Result = IsNumber<any>
// ^? type Result = true
To quote the source code: // When the check and extends types are simple tuple types of the same arity, we defer resolution of the
// conditional type when any tuple elements are generic. This is such that non-distributable conditional
// types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`.
const checkTuples = isSimpleTupleType(checkTypeNode) && isSimpleTupleType(extendsTypeNode) &&
length((checkTypeNode as TupleTypeNode).elements) === length((extendsTypeNode as TupleTypeNode).elements); I don't know why tuples are special this way. I understand that wrapping types in tuples might be a common way to make your conditional types non-distributive but I don't think I've ever seen this to be documented as the only way to do that. My personally mental model was always that you can make conditional types non-distributive by simply making check type "non-naked" and But as I alluded to in my previous comment - the problem is not exclusive to arrays, likely just any other type reference (other than a tuple) with generic type parameters exhibits the same problem: type IsNumber<T> = [T] extends [number] ? true : false;
type Shortcut<T> = Record<string, false> extends Record<string, IsNumber<T>> ? 0 : 1;
// ^? type Shortcut<T> = 1 |
@Andarist I believe it’s been stated by a maintainer (@ahejlsberg, perhaps?) that, while the documented behavior is “naked type parameters are distributive”, there’s special logic to handle the pattern |
🔎 Search Terms
generic, conditional, premature / eager / defer, inline,
🕗 Version & Regression Information
⏯ Playground Link
Playground link
💻 Code
🙁 Actual behavior
Direct
andIndirect
are different.🙂 Expected behavior
Direct
andIndirect
should be the same becauseDirect
is just an inlined version ofShortcut<string>
.Additional information about the issue
This feels like a similar issue to #39364, #30708, #30020 and maybe some others, but many of those have been resolved already and yet this one remains. Came from this Stack Overflow question.
The text was updated successfully, but these errors were encountered: