Skip to content

Commit 345777e

Browse files
authored
Merge pull request #32208 from microsoft/fix31766
Restore union-like behavior for inference to conditional types
2 parents 47856b3 + 8e75382 commit 345777e

5 files changed

+135
-30
lines changed

src/compiler/checker.ts

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15354,36 +15354,11 @@ namespace ts {
1535415354
inferFromTypes(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target));
1535515355
}
1535615356
else if (target.flags & TypeFlags.Conditional && !contravariant) {
15357-
inferFromTypes(source, getTrueTypeFromConditionalType(<ConditionalType>target));
15358-
inferFromTypes(source, getFalseTypeFromConditionalType(<ConditionalType>target));
15357+
const targetTypes = [getTrueTypeFromConditionalType(<ConditionalType>target), getFalseTypeFromConditionalType(<ConditionalType>target)];
15358+
inferToMultipleTypes(source, targetTypes, /*isIntersection*/ false);
1535915359
}
1536015360
else if (target.flags & TypeFlags.UnionOrIntersection) {
15361-
// We infer from types that are not naked type variables first so that inferences we
15362-
// make from nested naked type variables and given slightly higher priority by virtue
15363-
// of being first in the candidates array.
15364-
let typeVariableCount = 0;
15365-
for (const t of (<UnionOrIntersectionType>target).types) {
15366-
if (getInferenceInfoForType(t)) {
15367-
typeVariableCount++;
15368-
}
15369-
else {
15370-
inferFromTypes(source, t);
15371-
}
15372-
}
15373-
// Inferences directly to naked type variables are given lower priority as they are
15374-
// less specific. For example, when inferring from Promise<string> to T | Promise<T>,
15375-
// we want to infer string for T, not Promise<string> | string. For intersection types
15376-
// we only infer to single naked type variables.
15377-
if (target.flags & TypeFlags.Union ? typeVariableCount !== 0 : typeVariableCount === 1) {
15378-
const savePriority = priority;
15379-
priority |= InferencePriority.NakedTypeVariable;
15380-
for (const t of (<UnionOrIntersectionType>target).types) {
15381-
if (getInferenceInfoForType(t)) {
15382-
inferFromTypes(source, t);
15383-
}
15384-
}
15385-
priority = savePriority;
15386-
}
15361+
inferToMultipleTypes(source, (<UnionOrIntersectionType>target).types, !!(target.flags & TypeFlags.Intersection));
1538715362
}
1538815363
else if (source.flags & TypeFlags.Union) {
1538915364
// Source is a union or intersection type, infer from each constituent type
@@ -15481,6 +15456,35 @@ namespace ts {
1548115456
return undefined;
1548215457
}
1548315458

15459+
function inferToMultipleTypes(source: Type, targets: Type[], isIntersection: boolean) {
15460+
// We infer from types that are not naked type variables first so that inferences we
15461+
// make from nested naked type variables and given slightly higher priority by virtue
15462+
// of being first in the candidates array.
15463+
let typeVariableCount = 0;
15464+
for (const t of targets) {
15465+
if (getInferenceInfoForType(t)) {
15466+
typeVariableCount++;
15467+
}
15468+
else {
15469+
inferFromTypes(source, t);
15470+
}
15471+
}
15472+
// Inferences directly to naked type variables are given lower priority as they are
15473+
// less specific. For example, when inferring from Promise<string> to T | Promise<T>,
15474+
// we want to infer string for T, not Promise<string> | string. For intersection types
15475+
// we only infer to single naked type variables.
15476+
if (isIntersection ? typeVariableCount === 1 : typeVariableCount !== 0) {
15477+
const savePriority = priority;
15478+
priority |= InferencePriority.NakedTypeVariable;
15479+
for (const t of targets) {
15480+
if (getInferenceInfoForType(t)) {
15481+
inferFromTypes(source, t);
15482+
}
15483+
}
15484+
priority = savePriority;
15485+
}
15486+
}
15487+
1548415488
function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean {
1548515489
if (constraintType.flags & TypeFlags.Union) {
1548615490
let result = false;

tests/baselines/reference/conditionalTypeRelaxingConstraintAssignability.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,21 @@ export class Elem<
2121
new Elem(undefined as ElChildren.Void);
2222
new Elem('' as ElChildren.Text);
2323
new Elem('' as ElChildren.Void | ElChildren.Text); // error
24-
new Elem('' as ElChildren); // error
24+
new Elem('' as ElChildren); // error
25+
26+
// Repro from #31766
27+
28+
interface I { a: string }
29+
30+
type DeepPartial<T> =
31+
T extends object ? {[K in keyof T]?: DeepPartial<T[K]>} : T;
32+
33+
declare function f<T>(t: T, partial: DeepPartial<T>): T;
34+
35+
function g(p1: I, p2: Partial<I>): I {
36+
return f(p1, p2);
37+
}
38+
2539

2640
//// [conditionalTypeRelaxingConstraintAssignability.js]
2741
"use strict";
@@ -37,3 +51,6 @@ new Elem(undefined);
3751
new Elem('');
3852
new Elem(''); // error
3953
new Elem(''); // error
54+
function g(p1, p2) {
55+
return f(p1, p2);
56+
}

tests/baselines/reference/conditionalTypeRelaxingConstraintAssignability.symbols

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,47 @@ new Elem('' as ElChildren); // error
7171
>Elem : Symbol(Elem, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 8, 83))
7272
>ElChildren : Symbol(ElChildren, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 0, 0), Decl(conditionalTypeRelaxingConstraintAssignability.ts, 2, 20))
7373

74+
// Repro from #31766
75+
76+
interface I { a: string }
77+
>I : Symbol(I, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 22, 27))
78+
>a : Symbol(I.a, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 26, 13))
79+
80+
type DeepPartial<T> =
81+
>DeepPartial : Symbol(DeepPartial, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 26, 25))
82+
>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 28, 17))
83+
84+
T extends object ? {[K in keyof T]?: DeepPartial<T[K]>} : T;
85+
>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 28, 17))
86+
>K : Symbol(K, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 29, 25))
87+
>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 28, 17))
88+
>DeepPartial : Symbol(DeepPartial, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 26, 25))
89+
>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 28, 17))
90+
>K : Symbol(K, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 29, 25))
91+
>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 28, 17))
92+
93+
declare function f<T>(t: T, partial: DeepPartial<T>): T;
94+
>f : Symbol(f, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 29, 64))
95+
>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 31, 19))
96+
>t : Symbol(t, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 31, 22))
97+
>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 31, 19))
98+
>partial : Symbol(partial, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 31, 27))
99+
>DeepPartial : Symbol(DeepPartial, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 26, 25))
100+
>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 31, 19))
101+
>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 31, 19))
102+
103+
function g(p1: I, p2: Partial<I>): I {
104+
>g : Symbol(g, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 31, 56))
105+
>p1 : Symbol(p1, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 33, 11))
106+
>I : Symbol(I, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 22, 27))
107+
>p2 : Symbol(p2, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 33, 17))
108+
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
109+
>I : Symbol(I, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 22, 27))
110+
>I : Symbol(I, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 22, 27))
111+
112+
return f(p1, p2);
113+
>f : Symbol(f, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 29, 64))
114+
>p1 : Symbol(p1, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 33, 11))
115+
>p2 : Symbol(p2, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 33, 17))
116+
}
117+

tests/baselines/reference/conditionalTypeRelaxingConstraintAssignability.types

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,30 @@ new Elem('' as ElChildren); // error
6262
>'' as ElChildren : ElChildren
6363
>'' : ""
6464

65+
// Repro from #31766
66+
67+
interface I { a: string }
68+
>a : string
69+
70+
type DeepPartial<T> =
71+
>DeepPartial : DeepPartial<T>
72+
73+
T extends object ? {[K in keyof T]?: DeepPartial<T[K]>} : T;
74+
75+
declare function f<T>(t: T, partial: DeepPartial<T>): T;
76+
>f : <T>(t: T, partial: DeepPartial<T>) => T
77+
>t : T
78+
>partial : DeepPartial<T>
79+
80+
function g(p1: I, p2: Partial<I>): I {
81+
>g : (p1: I, p2: Partial<I>) => I
82+
>p1 : I
83+
>p2 : Partial<I>
84+
85+
return f(p1, p2);
86+
>f(p1, p2) : I
87+
>f : <T>(t: T, partial: DeepPartial<T>) => T
88+
>p1 : I
89+
>p2 : Partial<I>
90+
}
91+

tests/cases/compiler/conditionalTypeRelaxingConstraintAssignability.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,17 @@ export class Elem<
2121
new Elem(undefined as ElChildren.Void);
2222
new Elem('' as ElChildren.Text);
2323
new Elem('' as ElChildren.Void | ElChildren.Text); // error
24-
new Elem('' as ElChildren); // error
24+
new Elem('' as ElChildren); // error
25+
26+
// Repro from #31766
27+
28+
interface I { a: string }
29+
30+
type DeepPartial<T> =
31+
T extends object ? {[K in keyof T]?: DeepPartial<T[K]>} : T;
32+
33+
declare function f<T>(t: T, partial: DeepPartial<T>): T;
34+
35+
function g(p1: I, p2: Partial<I>): I {
36+
return f(p1, p2);
37+
}

0 commit comments

Comments
 (0)