-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Resolve deferred conditional types to their true
branch when instantiated with a type parameter constrained to the tested type
#52144
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
Comments
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 We don't yet have a concept of a "partial deferral"; it would be interesting to try. It'd be pretty tricky. |
true
branch when instantiated with a type parameter constrained to the tested type
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 |
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 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: /**
* Compile time assert that A is assignable to (extends) B.
* To use, simply define a type:
* `type _check = requireAssignableTo<T, Expected>;`
*/
export type requireAssignableTo<_A extends B, B> = true;
function _generic1<T extends 1>() {
type A = 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.
type B = 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.
type CheckA<T> = T extends 1 ? 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.
type CheckB<T> = [T] extends [1] ? true : false;
{
type testA = CheckA<any>;
// ^?
type testB = 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<T extends 1, T2 extends [1] & [T] = [T]>() {
type B = CheckB3<T, T2>;
// ^?
type _b = requireAssignableTo<B, true>;
}
type CheckB3<T, T2 extends [T] = [T]> = T2 extends [1] ? true : false; I also made a simplified version of the example from this tread showing the difference: Playground |
## Description This adds some tests which are generically parameterized over schema types. These are inspired by ongoing experiments with a Table schema generator, and issues encountered in its implementation. Generally it seems that we have some cases where we would really like TypeScript to simplify conditional type expressions based on extends clauses, but it does not do so causing code which would otherwise type check to fail. One such cause is covered in microsoft/TypeScript#52144 (comment) but there are likely others.
## Description This adds some tests which are generically parameterized over schema types. These are inspired by ongoing experiments with a Table schema generator, and issues encountered in its implementation. Generally it seems that we have some cases where we would really like TypeScript to simplify conditional type expressions based on extends clauses, but it does not do so causing code which would otherwise type check to fail. One such cause is covered in microsoft/TypeScript#52144 (comment) but there are likely others.
Bug Report
π Search Terms
conditional-type, distributive-type, generics
π Version & Regression Information
Version 4.9.4 and nightly
β― Playground Link
TS resolves type to different type even if I add constraint that should resolve to given type
π» Code
In the above code I have created a
PrimitiveDataType
which is a union of all primitive data types in JavaScript. Then I have created aConditionalType<T>
that will resolve to some callback only ifT
is one ofPrimitiveDataType
. 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 aSomeClass
that extendsAbstractClass<T>
and add constraints that generic parameterT
have to extendPrimitiveDataType
.π Actual behavior
For
someMethod
of thisSomeClass
conditionalFunctions.keys()
is resolved to(v: T, t: number) => void
what would implicate that my genericT
extends anArray
type.π Expected behavior
For
someMethod
of thisSomeClass
I have expected that TS should resolveconditionalFunctions.keys()
to(v: T) => void
to my surprise it is resolved to(v: T, t: number) => void
what would implicate that my genericT
extends anArray
type. This doesn't make sens to me becauseT
has constraint that has to extendsPrimitiveDataType
.Example of what I would expect:
Works for regular variables
The text was updated successfully, but these errors were encountered: