Skip to content

Commit b34c73f

Browse files
authored
Merge pull request #28851 from Microsoft/deferConditionalTypes
Defer resolution of conditional types with generic check or extends types
2 parents ee987a2 + 3afa86f commit b34c73f

File tree

8 files changed

+313
-12
lines changed

8 files changed

+313
-12
lines changed

src/compiler/checker.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9785,31 +9785,29 @@ namespace ts {
97859785
if (checkType === wildcardType || extendsType === wildcardType) {
97869786
return wildcardType;
97879787
}
9788-
// If this is a distributive conditional type and the check type is generic we need to defer
9789-
// resolution of the conditional type such that a later instantiation will properly distribute
9790-
// over union types.
9791-
const isDeferred = root.isDistributive && maybeTypeOfKind(checkType, TypeFlags.Instantiable);
9788+
const checkTypeInstantiable = maybeTypeOfKind(checkType, TypeFlags.Instantiable);
97929789
let combinedMapper: TypeMapper | undefined;
97939790
if (root.inferTypeParameters) {
97949791
const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None);
9795-
if (!isDeferred) {
9792+
if (!checkTypeInstantiable) {
97969793
// We don't want inferences from constraints as they may cause us to eagerly resolve the
97979794
// conditional type instead of deferring resolution. Also, we always want strict function
97989795
// types rules (i.e. proper contravariance) for inferences.
97999796
inferTypes(context.inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict);
98009797
}
98019798
combinedMapper = combineTypeMappers(mapper, context);
98029799
}
9803-
if (!isDeferred) {
9804-
if (extendsType.flags & TypeFlags.AnyOrUnknown) {
9800+
// Instantiate the extends type including inferences for 'infer T' type parameters
9801+
const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType;
9802+
// We attempt to resolve the conditional type only when the check and extends types are non-generic
9803+
if (!checkTypeInstantiable && !maybeTypeOfKind(inferredExtendsType, TypeFlags.Instantiable)) {
9804+
if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown) {
98059805
return instantiateType(root.trueType, mapper);
98069806
}
98079807
// Return union of trueType and falseType for 'any' since it matches anything
98089808
if (checkType.flags & TypeFlags.Any) {
98099809
return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root.falseType, mapper)]);
98109810
}
9811-
// Instantiate the extends type including inferences for 'infer T' type parameters
9812-
const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType;
98139811
// Return falseType for a definitely false extends check. We check an instantations of the two
98149812
// types with type parameters mapped to the wildcard type, the most permissive instantiations
98159813
// possible (the wildcard type is assignable to and from all types). If those are not related,

tests/baselines/reference/conditionalTypes2.errors.txt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,38 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2
212212
type T1 = MaybeTrue<{ b: false }>; // "no"
213213
type T2 = MaybeTrue<{ b: true }>; // "yes"
214214
type T3 = MaybeTrue<{ b: boolean }>; // "yes"
215+
216+
// Repro from #28824
217+
218+
type Union = 'a' | 'b';
219+
type Product<A extends Union, B> = { f1: A, f2: B};
220+
type ProductUnion = Product<'a', 0> | Product<'b', 1>;
221+
222+
// {a: "b"; b: "a"}
223+
type UnionComplement = {
224+
[K in Union]: Exclude<Union, K>
225+
};
226+
type UCA = UnionComplement['a'];
227+
type UCB = UnionComplement['b'];
228+
229+
// {a: "a"; b: "b"}
230+
type UnionComplementComplement = {
231+
[K in Union]: Exclude<Union, Exclude<Union, K>>
232+
};
233+
type UCCA = UnionComplementComplement['a'];
234+
type UCCB = UnionComplementComplement['b'];
235+
236+
// {a: Product<'b', 1>; b: Product<'a', 0>}
237+
type ProductComplement = {
238+
[K in Union]: Exclude<ProductUnion, { f1: K }>
239+
};
240+
type PCA = ProductComplement['a'];
241+
type PCB = ProductComplement['b'];
242+
243+
// {a: Product<'a', 0>; b: Product<'b', 1>}
244+
type ProductComplementComplement = {
245+
[K in Union]: Exclude<ProductUnion, Exclude<ProductUnion, { f1: K }>>
246+
};
247+
type PCCA = ProductComplementComplement['a'];
248+
type PCCB = ProductComplementComplement['b'];
215249

tests/baselines/reference/conditionalTypes2.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,40 @@ type T0 = MaybeTrue<{ b: never }> // "no"
154154
type T1 = MaybeTrue<{ b: false }>; // "no"
155155
type T2 = MaybeTrue<{ b: true }>; // "yes"
156156
type T3 = MaybeTrue<{ b: boolean }>; // "yes"
157+
158+
// Repro from #28824
159+
160+
type Union = 'a' | 'b';
161+
type Product<A extends Union, B> = { f1: A, f2: B};
162+
type ProductUnion = Product<'a', 0> | Product<'b', 1>;
163+
164+
// {a: "b"; b: "a"}
165+
type UnionComplement = {
166+
[K in Union]: Exclude<Union, K>
167+
};
168+
type UCA = UnionComplement['a'];
169+
type UCB = UnionComplement['b'];
170+
171+
// {a: "a"; b: "b"}
172+
type UnionComplementComplement = {
173+
[K in Union]: Exclude<Union, Exclude<Union, K>>
174+
};
175+
type UCCA = UnionComplementComplement['a'];
176+
type UCCB = UnionComplementComplement['b'];
177+
178+
// {a: Product<'b', 1>; b: Product<'a', 0>}
179+
type ProductComplement = {
180+
[K in Union]: Exclude<ProductUnion, { f1: K }>
181+
};
182+
type PCA = ProductComplement['a'];
183+
type PCB = ProductComplement['b'];
184+
185+
// {a: Product<'a', 0>; b: Product<'b', 1>}
186+
type ProductComplementComplement = {
187+
[K in Union]: Exclude<ProductUnion, Exclude<ProductUnion, { f1: K }>>
188+
};
189+
type PCCA = ProductComplementComplement['a'];
190+
type PCCB = ProductComplementComplement['b'];
157191

158192

159193
//// [conditionalTypes2.js]
@@ -328,3 +362,33 @@ declare type T2 = MaybeTrue<{
328362
declare type T3 = MaybeTrue<{
329363
b: boolean;
330364
}>;
365+
declare type Union = 'a' | 'b';
366+
declare type Product<A extends Union, B> = {
367+
f1: A;
368+
f2: B;
369+
};
370+
declare type ProductUnion = Product<'a', 0> | Product<'b', 1>;
371+
declare type UnionComplement = {
372+
[K in Union]: Exclude<Union, K>;
373+
};
374+
declare type UCA = UnionComplement['a'];
375+
declare type UCB = UnionComplement['b'];
376+
declare type UnionComplementComplement = {
377+
[K in Union]: Exclude<Union, Exclude<Union, K>>;
378+
};
379+
declare type UCCA = UnionComplementComplement['a'];
380+
declare type UCCB = UnionComplementComplement['b'];
381+
declare type ProductComplement = {
382+
[K in Union]: Exclude<ProductUnion, {
383+
f1: K;
384+
}>;
385+
};
386+
declare type PCA = ProductComplement['a'];
387+
declare type PCB = ProductComplement['b'];
388+
declare type ProductComplementComplement = {
389+
[K in Union]: Exclude<ProductUnion, Exclude<ProductUnion, {
390+
f1: K;
391+
}>>;
392+
};
393+
declare type PCCA = ProductComplementComplement['a'];
394+
declare type PCCB = ProductComplementComplement['b'];

tests/baselines/reference/conditionalTypes2.symbols

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,3 +579,109 @@ type T3 = MaybeTrue<{ b: boolean }>; // "yes"
579579
>MaybeTrue : Symbol(MaybeTrue, Decl(conditionalTypes2.ts, 145, 63))
580580
>b : Symbol(b, Decl(conditionalTypes2.ts, 154, 21))
581581

582+
// Repro from #28824
583+
584+
type Union = 'a' | 'b';
585+
>Union : Symbol(Union, Decl(conditionalTypes2.ts, 154, 36))
586+
587+
type Product<A extends Union, B> = { f1: A, f2: B};
588+
>Product : Symbol(Product, Decl(conditionalTypes2.ts, 158, 23))
589+
>A : Symbol(A, Decl(conditionalTypes2.ts, 159, 13))
590+
>Union : Symbol(Union, Decl(conditionalTypes2.ts, 154, 36))
591+
>B : Symbol(B, Decl(conditionalTypes2.ts, 159, 29))
592+
>f1 : Symbol(f1, Decl(conditionalTypes2.ts, 159, 36))
593+
>A : Symbol(A, Decl(conditionalTypes2.ts, 159, 13))
594+
>f2 : Symbol(f2, Decl(conditionalTypes2.ts, 159, 43))
595+
>B : Symbol(B, Decl(conditionalTypes2.ts, 159, 29))
596+
597+
type ProductUnion = Product<'a', 0> | Product<'b', 1>;
598+
>ProductUnion : Symbol(ProductUnion, Decl(conditionalTypes2.ts, 159, 51))
599+
>Product : Symbol(Product, Decl(conditionalTypes2.ts, 158, 23))
600+
>Product : Symbol(Product, Decl(conditionalTypes2.ts, 158, 23))
601+
602+
// {a: "b"; b: "a"}
603+
type UnionComplement = {
604+
>UnionComplement : Symbol(UnionComplement, Decl(conditionalTypes2.ts, 160, 54))
605+
606+
[K in Union]: Exclude<Union, K>
607+
>K : Symbol(K, Decl(conditionalTypes2.ts, 164, 3))
608+
>Union : Symbol(Union, Decl(conditionalTypes2.ts, 154, 36))
609+
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
610+
>Union : Symbol(Union, Decl(conditionalTypes2.ts, 154, 36))
611+
>K : Symbol(K, Decl(conditionalTypes2.ts, 164, 3))
612+
613+
};
614+
type UCA = UnionComplement['a'];
615+
>UCA : Symbol(UCA, Decl(conditionalTypes2.ts, 165, 2))
616+
>UnionComplement : Symbol(UnionComplement, Decl(conditionalTypes2.ts, 160, 54))
617+
618+
type UCB = UnionComplement['b'];
619+
>UCB : Symbol(UCB, Decl(conditionalTypes2.ts, 166, 32))
620+
>UnionComplement : Symbol(UnionComplement, Decl(conditionalTypes2.ts, 160, 54))
621+
622+
// {a: "a"; b: "b"}
623+
type UnionComplementComplement = {
624+
>UnionComplementComplement : Symbol(UnionComplementComplement, Decl(conditionalTypes2.ts, 167, 32))
625+
626+
[K in Union]: Exclude<Union, Exclude<Union, K>>
627+
>K : Symbol(K, Decl(conditionalTypes2.ts, 171, 3))
628+
>Union : Symbol(Union, Decl(conditionalTypes2.ts, 154, 36))
629+
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
630+
>Union : Symbol(Union, Decl(conditionalTypes2.ts, 154, 36))
631+
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
632+
>Union : Symbol(Union, Decl(conditionalTypes2.ts, 154, 36))
633+
>K : Symbol(K, Decl(conditionalTypes2.ts, 171, 3))
634+
635+
};
636+
type UCCA = UnionComplementComplement['a'];
637+
>UCCA : Symbol(UCCA, Decl(conditionalTypes2.ts, 172, 2))
638+
>UnionComplementComplement : Symbol(UnionComplementComplement, Decl(conditionalTypes2.ts, 167, 32))
639+
640+
type UCCB = UnionComplementComplement['b'];
641+
>UCCB : Symbol(UCCB, Decl(conditionalTypes2.ts, 173, 43))
642+
>UnionComplementComplement : Symbol(UnionComplementComplement, Decl(conditionalTypes2.ts, 167, 32))
643+
644+
// {a: Product<'b', 1>; b: Product<'a', 0>}
645+
type ProductComplement = {
646+
>ProductComplement : Symbol(ProductComplement, Decl(conditionalTypes2.ts, 174, 43))
647+
648+
[K in Union]: Exclude<ProductUnion, { f1: K }>
649+
>K : Symbol(K, Decl(conditionalTypes2.ts, 178, 3))
650+
>Union : Symbol(Union, Decl(conditionalTypes2.ts, 154, 36))
651+
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
652+
>ProductUnion : Symbol(ProductUnion, Decl(conditionalTypes2.ts, 159, 51))
653+
>f1 : Symbol(f1, Decl(conditionalTypes2.ts, 178, 39))
654+
>K : Symbol(K, Decl(conditionalTypes2.ts, 178, 3))
655+
656+
};
657+
type PCA = ProductComplement['a'];
658+
>PCA : Symbol(PCA, Decl(conditionalTypes2.ts, 179, 2))
659+
>ProductComplement : Symbol(ProductComplement, Decl(conditionalTypes2.ts, 174, 43))
660+
661+
type PCB = ProductComplement['b'];
662+
>PCB : Symbol(PCB, Decl(conditionalTypes2.ts, 180, 34))
663+
>ProductComplement : Symbol(ProductComplement, Decl(conditionalTypes2.ts, 174, 43))
664+
665+
// {a: Product<'a', 0>; b: Product<'b', 1>}
666+
type ProductComplementComplement = {
667+
>ProductComplementComplement : Symbol(ProductComplementComplement, Decl(conditionalTypes2.ts, 181, 34))
668+
669+
[K in Union]: Exclude<ProductUnion, Exclude<ProductUnion, { f1: K }>>
670+
>K : Symbol(K, Decl(conditionalTypes2.ts, 185, 3))
671+
>Union : Symbol(Union, Decl(conditionalTypes2.ts, 154, 36))
672+
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
673+
>ProductUnion : Symbol(ProductUnion, Decl(conditionalTypes2.ts, 159, 51))
674+
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
675+
>ProductUnion : Symbol(ProductUnion, Decl(conditionalTypes2.ts, 159, 51))
676+
>f1 : Symbol(f1, Decl(conditionalTypes2.ts, 185, 61))
677+
>K : Symbol(K, Decl(conditionalTypes2.ts, 185, 3))
678+
679+
};
680+
type PCCA = ProductComplementComplement['a'];
681+
>PCCA : Symbol(PCCA, Decl(conditionalTypes2.ts, 186, 2))
682+
>ProductComplementComplement : Symbol(ProductComplementComplement, Decl(conditionalTypes2.ts, 181, 34))
683+
684+
type PCCB = ProductComplementComplement['b'];
685+
>PCCB : Symbol(PCCB, Decl(conditionalTypes2.ts, 187, 45))
686+
>ProductComplementComplement : Symbol(ProductComplementComplement, Decl(conditionalTypes2.ts, 181, 34))
687+

tests/baselines/reference/conditionalTypes2.types

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,3 +366,68 @@ type T3 = MaybeTrue<{ b: boolean }>; // "yes"
366366
>T3 : "yes"
367367
>b : boolean
368368

369+
// Repro from #28824
370+
371+
type Union = 'a' | 'b';
372+
>Union : Union
373+
374+
type Product<A extends Union, B> = { f1: A, f2: B};
375+
>Product : Product<A, B>
376+
>f1 : A
377+
>f2 : B
378+
379+
type ProductUnion = Product<'a', 0> | Product<'b', 1>;
380+
>ProductUnion : ProductUnion
381+
382+
// {a: "b"; b: "a"}
383+
type UnionComplement = {
384+
>UnionComplement : UnionComplement
385+
386+
[K in Union]: Exclude<Union, K>
387+
};
388+
type UCA = UnionComplement['a'];
389+
>UCA : "b"
390+
391+
type UCB = UnionComplement['b'];
392+
>UCB : "a"
393+
394+
// {a: "a"; b: "b"}
395+
type UnionComplementComplement = {
396+
>UnionComplementComplement : UnionComplementComplement
397+
398+
[K in Union]: Exclude<Union, Exclude<Union, K>>
399+
};
400+
type UCCA = UnionComplementComplement['a'];
401+
>UCCA : "a"
402+
403+
type UCCB = UnionComplementComplement['b'];
404+
>UCCB : "b"
405+
406+
// {a: Product<'b', 1>; b: Product<'a', 0>}
407+
type ProductComplement = {
408+
>ProductComplement : ProductComplement
409+
410+
[K in Union]: Exclude<ProductUnion, { f1: K }>
411+
>f1 : K
412+
413+
};
414+
type PCA = ProductComplement['a'];
415+
>PCA : Product<"b", 1>
416+
417+
type PCB = ProductComplement['b'];
418+
>PCB : Product<"a", 0>
419+
420+
// {a: Product<'a', 0>; b: Product<'b', 1>}
421+
type ProductComplementComplement = {
422+
>ProductComplementComplement : ProductComplementComplement
423+
424+
[K in Union]: Exclude<ProductUnion, Exclude<ProductUnion, { f1: K }>>
425+
>f1 : K
426+
427+
};
428+
type PCCA = ProductComplementComplement['a'];
429+
>PCCA : Product<"a", 0>
430+
431+
type PCCB = ProductComplementComplement['b'];
432+
>PCCB : Product<"b", 1>
433+

tests/baselines/reference/infiniteConstraints.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type AProp<T extends { a: string }> = T
1515
>a : string
1616

1717
declare function myBug<
18-
>myBug : <T extends { [K in keyof T]: T[K]; }>(arg: T) => T
18+
>myBug : <T extends { [K in keyof T]: T[K] extends infer U ? U : never; }>(arg: T) => T
1919

2020
T extends { [K in keyof T]: T[K] extends AProp<infer U> ? U : never }
2121
>(arg: T): T
@@ -24,7 +24,7 @@ declare function myBug<
2424
const out = myBug({obj1: {a: "test"}})
2525
>out : { obj1: { a: string; }; }
2626
>myBug({obj1: {a: "test"}}) : { obj1: { a: string; }; }
27-
>myBug : <T extends { [K in keyof T]: T[K]; }>(arg: T) => T
27+
>myBug : <T extends { [K in keyof T]: T[K] extends infer U ? U : never; }>(arg: T) => T
2828
>{obj1: {a: "test"}} : { obj1: { a: string; }; }
2929
>obj1 : { a: string; }
3030
>{a: "test"} : { a: string; }

tests/baselines/reference/unknownType1.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ type T31<T> = T extends unknown ? true : false; // Deferred (so it distributes)
7676
>false : false
7777

7878
type T32<T> = never extends T ? true : false; // true
79-
>T32 : true
79+
>T32 : T32<T>
8080
>true : true
8181
>false : false
8282

tests/cases/conformance/types/conditional/conditionalTypes2.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,37 @@ type T0 = MaybeTrue<{ b: never }> // "no"
156156
type T1 = MaybeTrue<{ b: false }>; // "no"
157157
type T2 = MaybeTrue<{ b: true }>; // "yes"
158158
type T3 = MaybeTrue<{ b: boolean }>; // "yes"
159+
160+
// Repro from #28824
161+
162+
type Union = 'a' | 'b';
163+
type Product<A extends Union, B> = { f1: A, f2: B};
164+
type ProductUnion = Product<'a', 0> | Product<'b', 1>;
165+
166+
// {a: "b"; b: "a"}
167+
type UnionComplement = {
168+
[K in Union]: Exclude<Union, K>
169+
};
170+
type UCA = UnionComplement['a'];
171+
type UCB = UnionComplement['b'];
172+
173+
// {a: "a"; b: "b"}
174+
type UnionComplementComplement = {
175+
[K in Union]: Exclude<Union, Exclude<Union, K>>
176+
};
177+
type UCCA = UnionComplementComplement['a'];
178+
type UCCB = UnionComplementComplement['b'];
179+
180+
// {a: Product<'b', 1>; b: Product<'a', 0>}
181+
type ProductComplement = {
182+
[K in Union]: Exclude<ProductUnion, { f1: K }>
183+
};
184+
type PCA = ProductComplement['a'];
185+
type PCB = ProductComplement['b'];
186+
187+
// {a: Product<'a', 0>; b: Product<'b', 1>}
188+
type ProductComplementComplement = {
189+
[K in Union]: Exclude<ProductUnion, Exclude<ProductUnion, { f1: K }>>
190+
};
191+
type PCCA = ProductComplementComplement['a'];
192+
type PCCB = ProductComplementComplement['b'];

0 commit comments

Comments
 (0)