Skip to content

Commit 09f2a6b

Browse files
ahejlsbergtypescript-bot
authored andcommitted
Cherry-pick PR microsoft#32919 into release-3.6
Component commits: a81ce06 Stricter criteria for eliminating types in unions during inference f929a25 Add regression test 6d46850 Accept new baselines 86d9153 Accept new API baselines abc61a0 Add InferencePriority.Circularity per CR feedback ac2f151 Accept new API baselines c816cf2 Add additional test af7ccf9 Accept new baselines
1 parent 3271a1c commit 09f2a6b

File tree

9 files changed

+297
-49
lines changed

9 files changed

+297
-49
lines changed

src/compiler/checker.ts

+23-27
Original file line numberDiff line numberDiff line change
@@ -15486,8 +15486,7 @@ namespace ts {
1548615486
let visited: Map<number>;
1548715487
let bivariant = false;
1548815488
let propagationType: Type;
15489-
let inferenceMatch = false;
15490-
let inferenceIncomplete = false;
15489+
let inferencePriority = InferencePriority.MaxValue;
1549115490
let allowComplexConstraintInference = true;
1549215491
inferFromTypes(originalSource, originalTarget);
1549315492

@@ -15600,7 +15599,7 @@ namespace ts {
1560015599
clearCachedInferences(inferences);
1560115600
}
1560215601
}
15603-
inferenceMatch = true;
15602+
inferencePriority = Math.min(inferencePriority, priority);
1560415603
return;
1560515604
}
1560615605
else {
@@ -15694,19 +15693,15 @@ namespace ts {
1569415693
const key = source.id + "," + target.id;
1569515694
const status = visited && visited.get(key);
1569615695
if (status !== undefined) {
15697-
if (status & 1) inferenceMatch = true;
15698-
if (status & 2) inferenceIncomplete = true;
15696+
inferencePriority = Math.min(inferencePriority, status);
1569915697
return;
1570015698
}
15701-
(visited || (visited = createMap<number>())).set(key, 0);
15702-
const saveInferenceMatch = inferenceMatch;
15703-
const saveInferenceIncomplete = inferenceIncomplete;
15704-
inferenceMatch = false;
15705-
inferenceIncomplete = false;
15699+
(visited || (visited = createMap<number>())).set(key, InferencePriority.Circularity);
15700+
const saveInferencePriority = inferencePriority;
15701+
inferencePriority = InferencePriority.MaxValue;
1570615702
action(source, target);
15707-
visited.set(key, (inferenceMatch ? 1 : 0) | (inferenceIncomplete ? 2 : 0));
15708-
inferenceMatch = inferenceMatch || saveInferenceMatch;
15709-
inferenceIncomplete = inferenceIncomplete || saveInferenceIncomplete;
15703+
visited.set(key, inferencePriority);
15704+
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
1571015705
}
1571115706

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

src/compiler/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -4483,8 +4483,10 @@ namespace ts {
44834483
LiteralKeyof = 1 << 5, // Inference made from a string literal to a keyof T
44844484
NoConstraints = 1 << 6, // Don't infer from constraints of instantiable types
44854485
AlwaysStrict = 1 << 7, // Always use strict rules for contravariant inferences
4486+
MaxValue = 1 << 8, // Seed for inference priority tracking
44864487

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

44904492
/* @internal */

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -2441,7 +2441,9 @@ declare namespace ts {
24412441
LiteralKeyof = 32,
24422442
NoConstraints = 64,
24432443
AlwaysStrict = 128,
2444-
PriorityImpliesCombination = 56
2444+
MaxValue = 256,
2445+
PriorityImpliesCombination = 56,
2446+
Circularity = -1
24452447
}
24462448
/** @deprecated Use FileExtensionInfo instead. */
24472449
export type JsFileExtensionInfo = FileExtensionInfo;

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -2441,7 +2441,9 @@ declare namespace ts {
24412441
LiteralKeyof = 32,
24422442
NoConstraints = 64,
24432443
AlwaysStrict = 128,
2444-
PriorityImpliesCombination = 56
2444+
MaxValue = 256,
2445+
PriorityImpliesCombination = 56,
2446+
Circularity = -1
24452447
}
24462448
/** @deprecated Use FileExtensionInfo instead. */
24472449
export type JsFileExtensionInfo = FileExtensionInfo;

tests/baselines/reference/unionTypeInference.errors.txt

+26
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

+53-18
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

+91-2
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)