Skip to content

Commit 489abca

Browse files
authored
Merge pull request #32919 from microsoft/fix32752
Stricter criteria for eliminating types in unions during inference
2 parents 5380075 + af7ccf9 commit 489abca

File tree

9 files changed

+297
-49
lines changed

9 files changed

+297
-49
lines changed

src/compiler/checker.ts

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15487,8 +15487,7 @@ namespace ts {
1548715487
let visited: Map<number>;
1548815488
let bivariant = false;
1548915489
let propagationType: Type;
15490-
let inferenceMatch = false;
15491-
let inferenceIncomplete = false;
15490+
let inferencePriority = InferencePriority.MaxValue;
1549215491
let allowComplexConstraintInference = true;
1549315492
inferFromTypes(originalSource, originalTarget);
1549415493

@@ -15601,7 +15600,7 @@ namespace ts {
1560115600
clearCachedInferences(inferences);
1560215601
}
1560315602
}
15604-
inferenceMatch = true;
15603+
inferencePriority = Math.min(inferencePriority, priority);
1560515604
return;
1560615605
}
1560715606
else {
@@ -15695,19 +15694,15 @@ namespace ts {
1569515694
const key = source.id + "," + target.id;
1569615695
const status = visited && visited.get(key);
1569715696
if (status !== undefined) {
15698-
if (status & 1) inferenceMatch = true;
15699-
if (status & 2) inferenceIncomplete = true;
15697+
inferencePriority = Math.min(inferencePriority, status);
1570015698
return;
1570115699
}
15702-
(visited || (visited = createMap<number>())).set(key, 0);
15703-
const saveInferenceMatch = inferenceMatch;
15704-
const saveInferenceIncomplete = inferenceIncomplete;
15705-
inferenceMatch = false;
15706-
inferenceIncomplete = false;
15700+
(visited || (visited = createMap<number>())).set(key, InferencePriority.Circularity);
15701+
const saveInferencePriority = inferencePriority;
15702+
inferencePriority = InferencePriority.MaxValue;
1570715703
action(source, target);
15708-
visited.set(key, (inferenceMatch ? 1 : 0) | (inferenceIncomplete ? 2 : 0));
15709-
inferenceMatch = inferenceMatch || saveInferenceMatch;
15710-
inferenceIncomplete = inferenceIncomplete || saveInferenceIncomplete;
15704+
visited.set(key, inferencePriority);
15705+
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
1571115706
}
1571215707

1571315708
function inferFromMatchingType(source: Type, targets: Type[], matches: (s: Type, t: Type) => boolean) {
@@ -15779,31 +15774,32 @@ namespace ts {
1577915774
let nakedTypeVariable: Type | undefined;
1578015775
const sources = source.flags & TypeFlags.Union ? (<UnionType>source).types : [source];
1578115776
const matched = new Array<boolean>(sources.length);
15782-
const saveInferenceIncomplete = inferenceIncomplete;
15783-
inferenceIncomplete = false;
15777+
let inferenceCircularity = false;
1578415778
// First infer to types that are not naked type variables. For each source type we
15785-
// track whether inferences were made from that particular type to some target.
15779+
// track whether inferences were made from that particular type to some target with
15780+
// equal priority (i.e. of equal quality) to what we would infer for a naked type
15781+
// parameter.
1578615782
for (const t of targets) {
1578715783
if (getInferenceInfoForType(t)) {
1578815784
nakedTypeVariable = t;
1578915785
typeVariableCount++;
1579015786
}
1579115787
else {
1579215788
for (let i = 0; i < sources.length; i++) {
15793-
const saveInferenceMatch = inferenceMatch;
15794-
inferenceMatch = false;
15789+
const saveInferencePriority = inferencePriority;
15790+
inferencePriority = InferencePriority.MaxValue;
1579515791
inferFromTypes(sources[i], t);
15796-
if (inferenceMatch) matched[i] = true;
15797-
inferenceMatch = inferenceMatch || saveInferenceMatch;
15792+
if (inferencePriority === priority) matched[i] = true;
15793+
inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity;
15794+
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
1579815795
}
1579915796
}
1580015797
}
15801-
const inferenceComplete = !inferenceIncomplete;
15802-
inferenceIncomplete = inferenceIncomplete || saveInferenceIncomplete;
15803-
// If the target has a single naked type variable and inference completed (meaning we
15804-
// explored the types fully), create a union of the source types from which no inferences
15805-
// have been made so far and infer from that union to the naked type variable.
15806-
if (typeVariableCount === 1 && inferenceComplete) {
15798+
// If the target has a single naked type variable and no inference circularities were
15799+
// encountered above (meaning we explored the types fully), create a union of the source
15800+
// types from which no inferences have been made so far and infer from that union to the
15801+
// naked type variable.
15802+
if (typeVariableCount === 1 && !inferenceCircularity) {
1580715803
const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s);
1580815804
if (unmatched.length) {
1580915805
inferFromTypes(getUnionType(unmatched), nakedTypeVariable!);
@@ -15906,7 +15902,7 @@ namespace ts {
1590615902
const symbol = isNonConstructorObject ? target.symbol : undefined;
1590715903
if (symbol) {
1590815904
if (contains(symbolStack, symbol)) {
15909-
inferenceIncomplete = true;
15905+
inferencePriority = InferencePriority.Circularity;
1591015906
return;
1591115907
}
1591215908
(symbolStack || (symbolStack = [])).push(symbol);

src/compiler/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4502,8 +4502,10 @@ namespace ts {
45024502
LiteralKeyof = 1 << 5, // Inference made from a string literal to a keyof T
45034503
NoConstraints = 1 << 6, // Don't infer from constraints of instantiable types
45044504
AlwaysStrict = 1 << 7, // Always use strict rules for contravariant inferences
4505+
MaxValue = 1 << 8, // Seed for inference priority tracking
45054506

45064507
PriorityImpliesCombination = ReturnType | MappedTypeConstraint | LiteralKeyof, // These priorities imply that the resulting type should be a combination of all candidates
4508+
Circularity = -1, // Inference circularity (value less than all other priorities)
45074509
}
45084510

45094511
/* @internal */

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2442,7 +2442,9 @@ declare namespace ts {
24422442
LiteralKeyof = 32,
24432443
NoConstraints = 64,
24442444
AlwaysStrict = 128,
2445-
PriorityImpliesCombination = 56
2445+
MaxValue = 256,
2446+
PriorityImpliesCombination = 56,
2447+
Circularity = -1
24462448
}
24472449
/** @deprecated Use FileExtensionInfo instead. */
24482450
export type JsFileExtensionInfo = FileExtensionInfo;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2442,7 +2442,9 @@ declare namespace ts {
24422442
LiteralKeyof = 32,
24432443
NoConstraints = 64,
24442444
AlwaysStrict = 128,
2445-
PriorityImpliesCombination = 56
2445+
MaxValue = 256,
2446+
PriorityImpliesCombination = 56,
2447+
Circularity = -1
24462448
}
24472449
/** @deprecated Use FileExtensionInfo instead. */
24482450
export type JsFileExtensionInfo = FileExtensionInfo;

tests/baselines/reference/unionTypeInference.errors.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,30 @@ tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference
5858

5959
declare function bar<T>(x: T, y: string | T): T;
6060
const y = bar(1, 2);
61+
62+
// Repro from #32752
63+
64+
const containsPromises: unique symbol = Symbol();
65+
66+
type DeepPromised<T> =
67+
{ [containsPromises]?: true } &
68+
{ [TKey in keyof T]: T[TKey] | DeepPromised<T[TKey]> | Promise<DeepPromised<T[TKey]>> };
69+
70+
async function fun<T>(deepPromised: DeepPromised<T>) {
71+
const deepPromisedWithIndexer: DeepPromised<{ [name: string]: {} | null | undefined }> = deepPromised;
72+
for (const value of Object.values(deepPromisedWithIndexer)) {
73+
const awaitedValue = await value;
74+
if (awaitedValue)
75+
await fun(awaitedValue);
76+
}
77+
}
78+
79+
// Repro from #32752
80+
81+
type Deep<T> = { [K in keyof T]: T[K] | Deep<T[K]> };
82+
83+
declare function baz<T>(dp: Deep<T>): T;
84+
declare let xx: { a: string | undefined };
85+
86+
baz(xx);
6187

tests/baselines/reference/unionTypeInference.js

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,29 +50,64 @@ foo(x);
5050

5151
declare function bar<T>(x: T, y: string | T): T;
5252
const y = bar(1, 2);
53+
54+
// Repro from #32752
55+
56+
const containsPromises: unique symbol = Symbol();
57+
58+
type DeepPromised<T> =
59+
{ [containsPromises]?: true } &
60+
{ [TKey in keyof T]: T[TKey] | DeepPromised<T[TKey]> | Promise<DeepPromised<T[TKey]>> };
61+
62+
async function fun<T>(deepPromised: DeepPromised<T>) {
63+
const deepPromisedWithIndexer: DeepPromised<{ [name: string]: {} | null | undefined }> = deepPromised;
64+
for (const value of Object.values(deepPromisedWithIndexer)) {
65+
const awaitedValue = await value;
66+
if (awaitedValue)
67+
await fun(awaitedValue);
68+
}
69+
}
70+
71+
// Repro from #32752
72+
73+
type Deep<T> = { [K in keyof T]: T[K] | Deep<T[K]> };
74+
75+
declare function baz<T>(dp: Deep<T>): T;
76+
declare let xx: { a: string | undefined };
77+
78+
baz(xx);
5379

5480

5581
//// [unionTypeInference.js]
56-
"use strict";
57-
exports.__esModule = true;
58-
var a1 = f1(1, 2); // 1 | 2
59-
var a2 = f1(1, "hello"); // 1
60-
var a3 = f1(1, sn); // number
61-
var a4 = f1(undefined, "abc"); // undefined
62-
var a5 = f1("foo", "bar"); // "foo"
63-
var a6 = f1(true, false); // boolean
64-
var a7 = f1("hello", 1); // Error
82+
const a1 = f1(1, 2); // 1 | 2
83+
const a2 = f1(1, "hello"); // 1
84+
const a3 = f1(1, sn); // number
85+
const a4 = f1(undefined, "abc"); // undefined
86+
const a5 = f1("foo", "bar"); // "foo"
87+
const a6 = f1(true, false); // boolean
88+
const a7 = f1("hello", 1); // Error
6589
var b1 = f2(["string", true]); // boolean
66-
var c1 = f3(5); // 5
67-
var c2 = f3(sn); // number
68-
var c3 = f3(true); // true
69-
var c4 = f3(b); // true
70-
var c5 = f3("abc"); // never
71-
var d1 = f4("abc");
72-
var d2 = f4(s);
73-
var d3 = f4(42); // Error
90+
const c1 = f3(5); // 5
91+
const c2 = f3(sn); // number
92+
const c3 = f3(true); // true
93+
const c4 = f3(b); // true
94+
const c5 = f3("abc"); // never
95+
const d1 = f4("abc");
96+
const d2 = f4(s);
97+
const d3 = f4(42); // Error
7498
function qux(p1, p2) {
7599
p1 = p2;
76100
}
77101
foo(x);
78-
var y = bar(1, 2);
102+
const y = bar(1, 2);
103+
// Repro from #32752
104+
const containsPromises = Symbol();
105+
async function fun(deepPromised) {
106+
const deepPromisedWithIndexer = deepPromised;
107+
for (const value of Object.values(deepPromisedWithIndexer)) {
108+
const awaitedValue = await value;
109+
if (awaitedValue)
110+
await fun(awaitedValue);
111+
}
112+
}
113+
baz(xx);

tests/baselines/reference/unionTypeInference.symbols

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,12 +163,12 @@ declare function foo<T>(x: T | Promise<T>): void;
163163
>T : Symbol(T, Decl(unionTypeInference.ts, 45, 21))
164164
>x : Symbol(x, Decl(unionTypeInference.ts, 45, 24))
165165
>T : Symbol(T, Decl(unionTypeInference.ts, 45, 21))
166-
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --))
166+
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --))
167167
>T : Symbol(T, Decl(unionTypeInference.ts, 45, 21))
168168

169169
declare let x: false | Promise<true>;
170170
>x : Symbol(x, Decl(unionTypeInference.ts, 46, 11))
171-
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --))
171+
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --))
172172

173173
foo(x);
174174
>foo : Symbol(foo, Decl(unionTypeInference.ts, 41, 1))
@@ -187,3 +187,92 @@ const y = bar(1, 2);
187187
>y : Symbol(y, Decl(unionTypeInference.ts, 50, 5))
188188
>bar : Symbol(bar, Decl(unionTypeInference.ts, 47, 7))
189189

190+
// Repro from #32752
191+
192+
const containsPromises: unique symbol = Symbol();
193+
>containsPromises : Symbol(containsPromises, Decl(unionTypeInference.ts, 54, 5))
194+
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2019.symbol.d.ts, --, --))
195+
196+
type DeepPromised<T> =
197+
>DeepPromised : Symbol(DeepPromised, Decl(unionTypeInference.ts, 54, 49))
198+
>T : Symbol(T, Decl(unionTypeInference.ts, 56, 18))
199+
200+
{ [containsPromises]?: true } &
201+
>[containsPromises] : Symbol([containsPromises], Decl(unionTypeInference.ts, 57, 5))
202+
>containsPromises : Symbol(containsPromises, Decl(unionTypeInference.ts, 54, 5))
203+
204+
{ [TKey in keyof T]: T[TKey] | DeepPromised<T[TKey]> | Promise<DeepPromised<T[TKey]>> };
205+
>TKey : Symbol(TKey, Decl(unionTypeInference.ts, 58, 7))
206+
>T : Symbol(T, Decl(unionTypeInference.ts, 56, 18))
207+
>T : Symbol(T, Decl(unionTypeInference.ts, 56, 18))
208+
>TKey : Symbol(TKey, Decl(unionTypeInference.ts, 58, 7))
209+
>DeepPromised : Symbol(DeepPromised, Decl(unionTypeInference.ts, 54, 49))
210+
>T : Symbol(T, Decl(unionTypeInference.ts, 56, 18))
211+
>TKey : Symbol(TKey, Decl(unionTypeInference.ts, 58, 7))
212+
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --))
213+
>DeepPromised : Symbol(DeepPromised, Decl(unionTypeInference.ts, 54, 49))
214+
>T : Symbol(T, Decl(unionTypeInference.ts, 56, 18))
215+
>TKey : Symbol(TKey, Decl(unionTypeInference.ts, 58, 7))
216+
217+
async function fun<T>(deepPromised: DeepPromised<T>) {
218+
>fun : Symbol(fun, Decl(unionTypeInference.ts, 58, 92))
219+
>T : Symbol(T, Decl(unionTypeInference.ts, 60, 19))
220+
>deepPromised : Symbol(deepPromised, Decl(unionTypeInference.ts, 60, 22))
221+
>DeepPromised : Symbol(DeepPromised, Decl(unionTypeInference.ts, 54, 49))
222+
>T : Symbol(T, Decl(unionTypeInference.ts, 60, 19))
223+
224+
const deepPromisedWithIndexer: DeepPromised<{ [name: string]: {} | null | undefined }> = deepPromised;
225+
>deepPromisedWithIndexer : Symbol(deepPromisedWithIndexer, Decl(unionTypeInference.ts, 61, 9))
226+
>DeepPromised : Symbol(DeepPromised, Decl(unionTypeInference.ts, 54, 49))
227+
>name : Symbol(name, Decl(unionTypeInference.ts, 61, 51))
228+
>deepPromised : Symbol(deepPromised, Decl(unionTypeInference.ts, 60, 22))
229+
230+
for (const value of Object.values(deepPromisedWithIndexer)) {
231+
>value : Symbol(value, Decl(unionTypeInference.ts, 62, 14))
232+
>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --))
233+
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
234+
>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --))
235+
>deepPromisedWithIndexer : Symbol(deepPromisedWithIndexer, Decl(unionTypeInference.ts, 61, 9))
236+
237+
const awaitedValue = await value;
238+
>awaitedValue : Symbol(awaitedValue, Decl(unionTypeInference.ts, 63, 13))
239+
>value : Symbol(value, Decl(unionTypeInference.ts, 62, 14))
240+
241+
if (awaitedValue)
242+
>awaitedValue : Symbol(awaitedValue, Decl(unionTypeInference.ts, 63, 13))
243+
244+
await fun(awaitedValue);
245+
>fun : Symbol(fun, Decl(unionTypeInference.ts, 58, 92))
246+
>awaitedValue : Symbol(awaitedValue, Decl(unionTypeInference.ts, 63, 13))
247+
}
248+
}
249+
250+
// Repro from #32752
251+
252+
type Deep<T> = { [K in keyof T]: T[K] | Deep<T[K]> };
253+
>Deep : Symbol(Deep, Decl(unionTypeInference.ts, 67, 1))
254+
>T : Symbol(T, Decl(unionTypeInference.ts, 71, 10))
255+
>K : Symbol(K, Decl(unionTypeInference.ts, 71, 18))
256+
>T : Symbol(T, Decl(unionTypeInference.ts, 71, 10))
257+
>T : Symbol(T, Decl(unionTypeInference.ts, 71, 10))
258+
>K : Symbol(K, Decl(unionTypeInference.ts, 71, 18))
259+
>Deep : Symbol(Deep, Decl(unionTypeInference.ts, 67, 1))
260+
>T : Symbol(T, Decl(unionTypeInference.ts, 71, 10))
261+
>K : Symbol(K, Decl(unionTypeInference.ts, 71, 18))
262+
263+
declare function baz<T>(dp: Deep<T>): T;
264+
>baz : Symbol(baz, Decl(unionTypeInference.ts, 71, 53))
265+
>T : Symbol(T, Decl(unionTypeInference.ts, 73, 21))
266+
>dp : Symbol(dp, Decl(unionTypeInference.ts, 73, 24))
267+
>Deep : Symbol(Deep, Decl(unionTypeInference.ts, 67, 1))
268+
>T : Symbol(T, Decl(unionTypeInference.ts, 73, 21))
269+
>T : Symbol(T, Decl(unionTypeInference.ts, 73, 21))
270+
271+
declare let xx: { a: string | undefined };
272+
>xx : Symbol(xx, Decl(unionTypeInference.ts, 74, 11))
273+
>a : Symbol(a, Decl(unionTypeInference.ts, 74, 17))
274+
275+
baz(xx);
276+
>baz : Symbol(baz, Decl(unionTypeInference.ts, 71, 53))
277+
>xx : Symbol(xx, Decl(unionTypeInference.ts, 74, 11))
278+

0 commit comments

Comments
 (0)