Skip to content

Commit 400e2c2

Browse files
authored
Defer conditional types with multi-element tuple types in extends clause (#52091)
1 parent fc85386 commit 400e2c2

File tree

5 files changed

+354
-26
lines changed

5 files changed

+354
-26
lines changed

src/compiler/checker.ts

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17302,25 +17302,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1730217302
return constraint && (isGenericObjectType(constraint) || isGenericIndexType(constraint)) ? cloneTypeParameter(p) : p;
1730317303
}
1730417304

17305-
function isTypicalNondistributiveConditional(root: ConditionalRoot) {
17306-
return !root.isDistributive && isSingletonTupleType(root.node.checkType) && isSingletonTupleType(root.node.extendsType);
17305+
function isSimpleTupleType(node: TypeNode) {
17306+
return isTupleTypeNode(node) && length(node.elements) > 0 &&
17307+
!some(node.elements, e => isOptionalTypeNode(e) || isRestTypeNode(e) || isNamedTupleMember(e) && !!(e.questionToken || e.dotDotDotToken));
1730717308
}
1730817309

17309-
function isSingletonTupleType(node: TypeNode) {
17310-
return isTupleTypeNode(node) &&
17311-
length(node.elements) === 1 &&
17312-
!isOptionalTypeNode(node.elements[0]) &&
17313-
!isRestTypeNode(node.elements[0]) &&
17314-
!(isNamedTupleMember(node.elements[0]) && (node.elements[0].questionToken || node.elements[0].dotDotDotToken));
17315-
}
17316-
17317-
/**
17318-
* We syntactually check for common nondistributive conditional shapes and unwrap them into
17319-
* the intended comparison - we do this so we can check if the unwrapped types are generic or
17320-
* not and appropriately defer condition calculation
17321-
*/
17322-
function unwrapNondistributiveConditionalTuple(root: ConditionalRoot, type: Type) {
17323-
return isTypicalNondistributiveConditional(root) && isTupleType(type) ? getTypeArguments(type)[0] : type;
17310+
function isDeferredType(type: Type, checkTuples: boolean) {
17311+
return isGenericType(type) || checkTuples && isTupleType(type) && some(getTypeArguments(type), isGenericType);
1732417312
}
1732517313

1732617314
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
@@ -17338,10 +17326,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1733817326
result = errorType;
1733917327
break;
1734017328
}
17341-
const isUnwrapped = isTypicalNondistributiveConditional(root);
17342-
const checkType = instantiateType(unwrapNondistributiveConditionalTuple(root, getActualTypeVariable(root.checkType)), mapper);
17343-
const checkTypeInstantiable = isGenericType(checkType);
17344-
const extendsType = instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), mapper);
17329+
// When the check and extends types are simple tuple types of the same arity, we defer resolution of the
17330+
// conditional type when any tuple elements are generic. This is such that non-distributable conditional
17331+
// types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`.
17332+
const checkTuples = isSimpleTupleType(root.node.checkType) && isSimpleTupleType(root.node.extendsType) &&
17333+
length((root.node.checkType as TupleTypeNode).elements) === length((root.node.extendsType as TupleTypeNode).elements);
17334+
const checkType = instantiateType(getActualTypeVariable(root.checkType), mapper);
17335+
const checkTypeDeferred = isDeferredType(checkType, checkTuples);
17336+
const extendsType = instantiateType(root.extendsType, mapper);
1734517337
if (checkType === wildcardType || extendsType === wildcardType) {
1734617338
return wildcardType;
1734717339
}
@@ -17375,7 +17367,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1737517367
}
1737617368
}
1737717369
}
17378-
if (!checkTypeInstantiable) {
17370+
if (!checkTypeDeferred) {
1737917371
// We don't want inferences from constraints as they may cause us to eagerly resolve the
1738017372
// conditional type instead of deferring resolution. Also, we always want strict function
1738117373
// types rules (i.e. proper contravariance) for inferences.
@@ -17388,16 +17380,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1738817380
combinedMapper = mapper ? combineTypeMappers(innerMapper, mapper) : innerMapper;
1738917381
}
1739017382
// Instantiate the extends type including inferences for 'infer T' type parameters
17391-
const inferredExtendsType = combinedMapper ? instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), combinedMapper) : extendsType;
17383+
const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType;
1739217384
// We attempt to resolve the conditional type only when the check and extends types are non-generic
17393-
if (!checkTypeInstantiable && !isGenericType(inferredExtendsType)) {
17385+
if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) {
1739417386
// Return falseType for a definitely false extends check. We check an instantiations of the two
1739517387
// types with type parameters mapped to the wildcard type, the most permissive instantiations
1739617388
// possible (the wildcard type is assignable to and from all types). If those are not related,
1739717389
// then no instantiations will be and we can just return the false branch type.
17398-
if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && ((checkType.flags & TypeFlags.Any && !isUnwrapped) || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) {
17390+
if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && (checkType.flags & TypeFlags.Any || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) {
1739917391
// Return union of trueType and falseType for 'any' since it matches anything
17400-
if (checkType.flags & TypeFlags.Any && !isUnwrapped) {
17392+
if (checkType.flags & TypeFlags.Any) {
1740117393
(extraTypes || (extraTypes = [])).push(instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper));
1740217394
}
1740317395
// If falseType is an immediately nested conditional type that isn't distributive or has an
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//// [deferredConditionalTypes.ts]
2+
type A<T> = { x: T } extends { x: 0 } ? 1 : 0;
3+
4+
type T0<T> = A<T> extends 0 ? 1 : 0; // Deferred
5+
type T1<T> = [A<T>] extends [0] ? 1 : 0; // Deferred
6+
type T2<T> = [A<T>, A<T>] extends [0, 0] ? 1 : 0; // Deferred
7+
type T3<T> = [A<T>, A<T>, A<T>] extends [0, 0, 0] ? 1 : 0; // Deferred
8+
9+
type T4<T> = [A<T>] extends [0, 0] ? 1 : 0; // 0
10+
type T5<T> = [A<T>, A<T>] extends [0] ? 1 : 0; // 0
11+
12+
type T6<T> = { y: A<T> } extends { y: 0 } ? 1 : 0; // 0, but should be deferred
13+
14+
// Repro from #52068
15+
16+
type Or<A extends boolean, B extends boolean> = [A, B] extends [false, false] ? false : true;
17+
type And<A extends boolean, B extends boolean> = [A, B] extends [true, true] ? true : false;
18+
type Not<T extends boolean> = T extends true ? false : true;
19+
type Extends<A, B> = A extends B ? true : false;
20+
21+
type IsNumberLiteral<T> = And<Extends<T, number>, Not<Extends<number, T>>>;
22+
23+
type IsLiteral<T> = Or<false, IsNumberLiteral<T>>;
24+
25+
// Repro from #51145#issuecomment-1276804047
26+
27+
type Values<O extends object> =
28+
O extends any[]
29+
? O[number]
30+
: O[keyof O]
31+
32+
type Equals<A, B> = [A, B] extends [B, A] ? true : false;
33+
34+
type FilterByStringValue<O extends object> = {
35+
[K in keyof O as Equals<O[K], string> extends true ? K : never]: any
36+
}
37+
38+
type FilteredValuesMatchNever<O extends object>
39+
= Equals<Values<FilterByStringValue<[O]>>, never>
40+
41+
type FilteredRes1 = FilteredValuesMatchNever<[]>
42+
43+
44+
//// [deferredConditionalTypes.js]
45+
"use strict";
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
=== tests/cases/compiler/deferredConditionalTypes.ts ===
2+
type A<T> = { x: T } extends { x: 0 } ? 1 : 0;
3+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0))
4+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 0, 7))
5+
>x : Symbol(x, Decl(deferredConditionalTypes.ts, 0, 13))
6+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 0, 7))
7+
>x : Symbol(x, Decl(deferredConditionalTypes.ts, 0, 30))
8+
9+
type T0<T> = A<T> extends 0 ? 1 : 0; // Deferred
10+
>T0 : Symbol(T0, Decl(deferredConditionalTypes.ts, 0, 46))
11+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 2, 8))
12+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0))
13+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 2, 8))
14+
15+
type T1<T> = [A<T>] extends [0] ? 1 : 0; // Deferred
16+
>T1 : Symbol(T1, Decl(deferredConditionalTypes.ts, 2, 36))
17+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 3, 8))
18+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0))
19+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 3, 8))
20+
21+
type T2<T> = [A<T>, A<T>] extends [0, 0] ? 1 : 0; // Deferred
22+
>T2 : Symbol(T2, Decl(deferredConditionalTypes.ts, 3, 40))
23+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 4, 8))
24+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0))
25+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 4, 8))
26+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0))
27+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 4, 8))
28+
29+
type T3<T> = [A<T>, A<T>, A<T>] extends [0, 0, 0] ? 1 : 0; // Deferred
30+
>T3 : Symbol(T3, Decl(deferredConditionalTypes.ts, 4, 49))
31+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 5, 8))
32+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0))
33+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 5, 8))
34+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0))
35+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 5, 8))
36+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0))
37+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 5, 8))
38+
39+
type T4<T> = [A<T>] extends [0, 0] ? 1 : 0; // 0
40+
>T4 : Symbol(T4, Decl(deferredConditionalTypes.ts, 5, 58))
41+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 7, 8))
42+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0))
43+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 7, 8))
44+
45+
type T5<T> = [A<T>, A<T>] extends [0] ? 1 : 0; // 0
46+
>T5 : Symbol(T5, Decl(deferredConditionalTypes.ts, 7, 43))
47+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 8, 8))
48+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0))
49+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 8, 8))
50+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0))
51+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 8, 8))
52+
53+
type T6<T> = { y: A<T> } extends { y: 0 } ? 1 : 0; // 0, but should be deferred
54+
>T6 : Symbol(T6, Decl(deferredConditionalTypes.ts, 8, 46))
55+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 10, 8))
56+
>y : Symbol(y, Decl(deferredConditionalTypes.ts, 10, 14))
57+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0))
58+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 10, 8))
59+
>y : Symbol(y, Decl(deferredConditionalTypes.ts, 10, 34))
60+
61+
// Repro from #52068
62+
63+
type Or<A extends boolean, B extends boolean> = [A, B] extends [false, false] ? false : true;
64+
>Or : Symbol(Or, Decl(deferredConditionalTypes.ts, 10, 50))
65+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 14, 8))
66+
>B : Symbol(B, Decl(deferredConditionalTypes.ts, 14, 26))
67+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 14, 8))
68+
>B : Symbol(B, Decl(deferredConditionalTypes.ts, 14, 26))
69+
70+
type And<A extends boolean, B extends boolean> = [A, B] extends [true, true] ? true : false;
71+
>And : Symbol(And, Decl(deferredConditionalTypes.ts, 14, 93))
72+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 15, 9))
73+
>B : Symbol(B, Decl(deferredConditionalTypes.ts, 15, 27))
74+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 15, 9))
75+
>B : Symbol(B, Decl(deferredConditionalTypes.ts, 15, 27))
76+
77+
type Not<T extends boolean> = T extends true ? false : true;
78+
>Not : Symbol(Not, Decl(deferredConditionalTypes.ts, 15, 92))
79+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 16, 9))
80+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 16, 9))
81+
82+
type Extends<A, B> = A extends B ? true : false;
83+
>Extends : Symbol(Extends, Decl(deferredConditionalTypes.ts, 16, 60))
84+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 17, 13))
85+
>B : Symbol(B, Decl(deferredConditionalTypes.ts, 17, 15))
86+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 17, 13))
87+
>B : Symbol(B, Decl(deferredConditionalTypes.ts, 17, 15))
88+
89+
type IsNumberLiteral<T> = And<Extends<T, number>, Not<Extends<number, T>>>;
90+
>IsNumberLiteral : Symbol(IsNumberLiteral, Decl(deferredConditionalTypes.ts, 17, 48))
91+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 19, 21))
92+
>And : Symbol(And, Decl(deferredConditionalTypes.ts, 14, 93))
93+
>Extends : Symbol(Extends, Decl(deferredConditionalTypes.ts, 16, 60))
94+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 19, 21))
95+
>Not : Symbol(Not, Decl(deferredConditionalTypes.ts, 15, 92))
96+
>Extends : Symbol(Extends, Decl(deferredConditionalTypes.ts, 16, 60))
97+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 19, 21))
98+
99+
type IsLiteral<T> = Or<false, IsNumberLiteral<T>>;
100+
>IsLiteral : Symbol(IsLiteral, Decl(deferredConditionalTypes.ts, 19, 75))
101+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 21, 15))
102+
>Or : Symbol(Or, Decl(deferredConditionalTypes.ts, 10, 50))
103+
>IsNumberLiteral : Symbol(IsNumberLiteral, Decl(deferredConditionalTypes.ts, 17, 48))
104+
>T : Symbol(T, Decl(deferredConditionalTypes.ts, 21, 15))
105+
106+
// Repro from #51145#issuecomment-1276804047
107+
108+
type Values<O extends object> =
109+
>Values : Symbol(Values, Decl(deferredConditionalTypes.ts, 21, 50))
110+
>O : Symbol(O, Decl(deferredConditionalTypes.ts, 25, 12))
111+
112+
O extends any[]
113+
>O : Symbol(O, Decl(deferredConditionalTypes.ts, 25, 12))
114+
115+
? O[number]
116+
>O : Symbol(O, Decl(deferredConditionalTypes.ts, 25, 12))
117+
118+
: O[keyof O]
119+
>O : Symbol(O, Decl(deferredConditionalTypes.ts, 25, 12))
120+
>O : Symbol(O, Decl(deferredConditionalTypes.ts, 25, 12))
121+
122+
type Equals<A, B> = [A, B] extends [B, A] ? true : false;
123+
>Equals : Symbol(Equals, Decl(deferredConditionalTypes.ts, 28, 16))
124+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 30, 12))
125+
>B : Symbol(B, Decl(deferredConditionalTypes.ts, 30, 14))
126+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 30, 12))
127+
>B : Symbol(B, Decl(deferredConditionalTypes.ts, 30, 14))
128+
>B : Symbol(B, Decl(deferredConditionalTypes.ts, 30, 14))
129+
>A : Symbol(A, Decl(deferredConditionalTypes.ts, 30, 12))
130+
131+
type FilterByStringValue<O extends object> = {
132+
>FilterByStringValue : Symbol(FilterByStringValue, Decl(deferredConditionalTypes.ts, 30, 57))
133+
>O : Symbol(O, Decl(deferredConditionalTypes.ts, 32, 25))
134+
135+
[K in keyof O as Equals<O[K], string> extends true ? K : never]: any
136+
>K : Symbol(K, Decl(deferredConditionalTypes.ts, 33, 3))
137+
>O : Symbol(O, Decl(deferredConditionalTypes.ts, 32, 25))
138+
>Equals : Symbol(Equals, Decl(deferredConditionalTypes.ts, 28, 16))
139+
>O : Symbol(O, Decl(deferredConditionalTypes.ts, 32, 25))
140+
>K : Symbol(K, Decl(deferredConditionalTypes.ts, 33, 3))
141+
>K : Symbol(K, Decl(deferredConditionalTypes.ts, 33, 3))
142+
}
143+
144+
type FilteredValuesMatchNever<O extends object>
145+
>FilteredValuesMatchNever : Symbol(FilteredValuesMatchNever, Decl(deferredConditionalTypes.ts, 34, 1))
146+
>O : Symbol(O, Decl(deferredConditionalTypes.ts, 36, 30))
147+
148+
= Equals<Values<FilterByStringValue<[O]>>, never>
149+
>Equals : Symbol(Equals, Decl(deferredConditionalTypes.ts, 28, 16))
150+
>Values : Symbol(Values, Decl(deferredConditionalTypes.ts, 21, 50))
151+
>FilterByStringValue : Symbol(FilterByStringValue, Decl(deferredConditionalTypes.ts, 30, 57))
152+
>O : Symbol(O, Decl(deferredConditionalTypes.ts, 36, 30))
153+
154+
type FilteredRes1 = FilteredValuesMatchNever<[]>
155+
>FilteredRes1 : Symbol(FilteredRes1, Decl(deferredConditionalTypes.ts, 37, 51))
156+
>FilteredValuesMatchNever : Symbol(FilteredValuesMatchNever, Decl(deferredConditionalTypes.ts, 34, 1))
157+
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
=== tests/cases/compiler/deferredConditionalTypes.ts ===
2+
type A<T> = { x: T } extends { x: 0 } ? 1 : 0;
3+
>A : A<T>
4+
>x : T
5+
>x : 0
6+
7+
type T0<T> = A<T> extends 0 ? 1 : 0; // Deferred
8+
>T0 : T0<T>
9+
10+
type T1<T> = [A<T>] extends [0] ? 1 : 0; // Deferred
11+
>T1 : T1<T>
12+
13+
type T2<T> = [A<T>, A<T>] extends [0, 0] ? 1 : 0; // Deferred
14+
>T2 : T2<T>
15+
16+
type T3<T> = [A<T>, A<T>, A<T>] extends [0, 0, 0] ? 1 : 0; // Deferred
17+
>T3 : T3<T>
18+
19+
type T4<T> = [A<T>] extends [0, 0] ? 1 : 0; // 0
20+
>T4 : 0
21+
22+
type T5<T> = [A<T>, A<T>] extends [0] ? 1 : 0; // 0
23+
>T5 : 0
24+
25+
type T6<T> = { y: A<T> } extends { y: 0 } ? 1 : 0; // 0, but should be deferred
26+
>T6 : 0
27+
>y : A<T>
28+
>y : 0
29+
30+
// Repro from #52068
31+
32+
type Or<A extends boolean, B extends boolean> = [A, B] extends [false, false] ? false : true;
33+
>Or : Or<A, B>
34+
>false : false
35+
>false : false
36+
>false : false
37+
>true : true
38+
39+
type And<A extends boolean, B extends boolean> = [A, B] extends [true, true] ? true : false;
40+
>And : And<A, B>
41+
>true : true
42+
>true : true
43+
>true : true
44+
>false : false
45+
46+
type Not<T extends boolean> = T extends true ? false : true;
47+
>Not : Not<T>
48+
>true : true
49+
>false : false
50+
>true : true
51+
52+
type Extends<A, B> = A extends B ? true : false;
53+
>Extends : Extends<A, B>
54+
>true : true
55+
>false : false
56+
57+
type IsNumberLiteral<T> = And<Extends<T, number>, Not<Extends<number, T>>>;
58+
>IsNumberLiteral : IsNumberLiteral<T>
59+
60+
type IsLiteral<T> = Or<false, IsNumberLiteral<T>>;
61+
>IsLiteral : IsLiteral<T>
62+
>false : false
63+
64+
// Repro from #51145#issuecomment-1276804047
65+
66+
type Values<O extends object> =
67+
>Values : Values<O>
68+
69+
O extends any[]
70+
? O[number]
71+
: O[keyof O]
72+
73+
type Equals<A, B> = [A, B] extends [B, A] ? true : false;
74+
>Equals : Equals<A, B>
75+
>true : true
76+
>false : false
77+
78+
type FilterByStringValue<O extends object> = {
79+
>FilterByStringValue : FilterByStringValue<O>
80+
81+
[K in keyof O as Equals<O[K], string> extends true ? K : never]: any
82+
>true : true
83+
}
84+
85+
type FilteredValuesMatchNever<O extends object>
86+
>FilteredValuesMatchNever : FilteredValuesMatchNever<O>
87+
88+
= Equals<Values<FilterByStringValue<[O]>>, never>
89+
90+
type FilteredRes1 = FilteredValuesMatchNever<[]>
91+
>FilteredRes1 : true
92+

0 commit comments

Comments
 (0)