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
Resolve deferred conditional types to their true branch when instantiated with a type parameter constrained to the tested type #52144
In the above code I have created a PrimitiveDataType which is a union of all primitive data types in JavaScript. Then I have created a ConditionalType<T> that will resolve to some callback only if T is one of PrimitiveDataType. Then I have created an abstract generic class that have a field which type(ConditionalType<T>) depends on generic value of this class. In the end, I have crated a SomeClass that extends AbstractClass<T> and add constraints that generic parameter T have to extend PrimitiveDataType.
🙁 Actual behavior
For someMethod of this SomeClassconditionalFunctions.keys() is resolved to (v: T, t: number) => void what would implicate that my generic T extends an Array type.
🙂 Expected behavior
For someMethod of this SomeClass I have expected that TS should resolve conditionalFunctions.keys() to (v: T) => void to my surprise it is resolved to (v: T, t: number) => void what would implicate that my generic T extends an Array type. This doesn't make sens to me because T has constraint that has to extends PrimitiveDataType.
What's actually happening here is that the conditional type stays deferred. In its deferred from, you can still call it, as long as you provide an argument list that would satisfy every possible branch.
The reason it's deferred is that, while it would be safe to resolve to the true branch, for a generic it's not safe to resolve to the false branch because the type parameter might be instantiated with some more-specific type that actually does go to the true branch.
We don't yet have a concept of a "partial deferral"; it would be interesting to try. It'd be pretty tricky.
changed the title [-]ConditionalType resolves to different type than expected[/-][+]Resolve deferred conditional types to their `true` branch when instantiated with a type parameter constrained to the tested type[/+]on Jan 12, 2023
In its deferred from, you can still call it, as long as you provide an argument list that would satisfy every possible branch.
Is this still true? I recall being told that this logic had been removed, which seems to be corroborated by the discussion in #51488
I think this describes a bit different situation because in your example you are not getting rid of possible distributive type. In this playground, you can find fixed example that you have mentioned in #51488
What's actually happening here is that the conditional type stays deferred. In its deferred from, you can still call it, as long as you provide an argument list that would satisfy every possible branch.
The reason it's deferred is that, while it would be safe to resolve to the true branch, for a generic it's not safe to resolve to the false branch because the type parameter might be instantiated with some more-specific type that actually does go to the true branch.
We don't yet have a concept of a "partial deferral"; it would be interesting to try. It'd be pretty tricky.
Can I help with this somehow? Maybe you can give me a hint how to start work with this repository and some hint where I can start investigating this issue?
I think there is something a bit more subtle going on here. TypeScript actually does reduce distributive conditionals based on extends constraints. This is actually a questionable thing to do as some types (at least any) are allowed under such a constraint that do not evaluate to true.
What the example above does however is use a non-distributive conditional. TypeScript does not reduce this case based on the extends constraint on the generic type parameter. As far as I can tell this case should actually be more valid to do (since it handles types like any on the true branch) but it has not be implemented.
/** * Compile time assert that A is assignable to (extends) B. * To use, simply define a type: * `type _check = requireAssignableTo<T, Expected>;` */exporttyperequireAssignableTo<_AextendsB,B>=true;function_generic1<Textends1>(){typeA=CheckA<T>;// ^?// A is assignable to `true` even though it prints as a conditional (above)type_a=requireAssignableTo<A,true>;// Passes.// This seems to suggest that all possible values of T would result in A being true but this is incorrect: CheckA<any> is `boolean` for example.// This inconsistancy is likley related to the fact that the extends clause on T in this function is not distribuative, but the compiler is assuming that it can be used to infer the outcome of the distributive one in CheckA.// To be more strict we can use CheckB which should more robustly return true in all cases by avoiding distributing over conditionals. typeB=CheckB<T>;// ^?// B is not assignable to `true`, despite all possible types which could be provided for T resulting in B being true.// It would be nice it the TypeScript compiler could reduce this conditional, which is always actually true, to true.// Fixing this could prabably be done by making the evaluation logic which reduces the conditional in the A case above recognize the non-distributive extends pattern recommended in the handbook (and that case would actually be more forrect that the existing one)type_b=requireAssignableTo<B,true>;// Fails.}// True if T extends 1: Distributive// Distributive version of CheckB, see https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types// Sutable for handeling types in contexts which should behave covariently.typeCheckA<T>=Textends1 ? true : false;// True if T extends 1: Non-distributive// Non-distributive version of CheckA, see https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types// Sutable for handeling types in contexts which should behave contravariently or invariently.typeCheckB<T>=[T]extends[1] ? true : false;{typetestA=CheckA<any>;// ^?typetestB=CheckB<any>;// ^?}// It's is somewhat possible to work around this limitation in some cases by adding and extra [T] type paramater to everything that could be used in a generic context, then plumbing it through to the places that perform the extends checks.// This however is really messy and not always practical.function_generic2<Textends1,T2extends[1]&[T]=[T]>(){typeB=CheckB3<T,T2>;// ^?type_b=requireAssignableTo<B,true>;}typeCheckB3<T,T2extends[T]=[T]>=T2extends[1] ? true : false;
I also made a simplified version of the example from this tread showing the difference: Playground
Activity
RyanCavanaugh commentedon Jan 12, 2023
What's actually happening here is that the conditional type stays deferred. In its deferred from, you can still call it, as long as you provide an argument list that would satisfy every possible branch.
The reason it's deferred is that, while it would be safe to resolve to the
truebranch, for a generic it's not safe to resolve to thefalsebranch because the type parameter might be instantiated with some more-specific type that actually does go to thetruebranch.We don't yet have a concept of a "partial deferral"; it would be interesting to try. It'd be pretty tricky.
[-]ConditionalType resolves to different type than expected[/-][+]Resolve deferred conditional types to their `true` branch when instantiated with a type parameter constrained to the tested type[/+]fatcerberus commentedon Jan 13, 2023
Is this still true? I recall being told that this logic had been removed, which seems to be corroborated by the discussion in #51488
kaczor6418 commentedon Jan 20, 2023
I think this describes a bit different situation because in your example you are not getting rid of possible distributive type. In this playground, you can find fixed example that you have mentioned in #51488
kaczor6418 commentedon Jan 20, 2023
Can I help with this somehow? Maybe you can give me a hint how to start work with this repository and some hint where I can start investigating this issue?
CraigMacomber commentedon Feb 26, 2025
I think there is something a bit more subtle going on here. TypeScript actually does reduce distributive conditionals based on extends constraints. This is actually a questionable thing to do as some types (at least
any) are allowed under such a constraint that do not evaluate to true.What the example above does however is use a non-distributive conditional. TypeScript does not reduce this case based on the extends constraint on the generic type parameter. As far as I can tell this case should actually be more valid to do (since it handles types like any on the true branch) but it has not be implemented.
Here is a example on the playground showing the behavior.
Code:
I also made a simplified version of the example from this tread showing the difference: Playground
Add some (mostly failing) generic schema type tests (#23936)