From 09f2a6b532c609ce505afac92f4ed4ae5df8ef16 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 16 Aug 2019 16:05:51 +0000 Subject: [PATCH] Cherry-pick PR #32919 into release-3.6 Component commits: a81ce061de Stricter criteria for eliminating types in unions during inference f929a25407 Add regression test 6d46850172 Accept new baselines 86d9153374 Accept new API baselines abc61a0949 Add InferencePriority.Circularity per CR feedback ac2f151412 Accept new API baselines c816cf2562 Add additional test af7ccf954a Accept new baselines --- src/compiler/checker.ts | 50 +++++----- src/compiler/types.ts | 2 + .../reference/api/tsserverlibrary.d.ts | 4 +- tests/baselines/reference/api/typescript.d.ts | 4 +- .../reference/unionTypeInference.errors.txt | 26 ++++++ .../baselines/reference/unionTypeInference.js | 71 ++++++++++---- .../reference/unionTypeInference.symbols | 93 ++++++++++++++++++- .../reference/unionTypeInference.types | 69 ++++++++++++++ .../typeInference/unionTypeInference.ts | 27 ++++++ 9 files changed, 297 insertions(+), 49 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9d6fa60306c03..f8fd620213241 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15486,8 +15486,7 @@ namespace ts { let visited: Map; let bivariant = false; let propagationType: Type; - let inferenceMatch = false; - let inferenceIncomplete = false; + let inferencePriority = InferencePriority.MaxValue; let allowComplexConstraintInference = true; inferFromTypes(originalSource, originalTarget); @@ -15600,7 +15599,7 @@ namespace ts { clearCachedInferences(inferences); } } - inferenceMatch = true; + inferencePriority = Math.min(inferencePriority, priority); return; } else { @@ -15694,19 +15693,15 @@ namespace ts { const key = source.id + "," + target.id; const status = visited && visited.get(key); if (status !== undefined) { - if (status & 1) inferenceMatch = true; - if (status & 2) inferenceIncomplete = true; + inferencePriority = Math.min(inferencePriority, status); return; } - (visited || (visited = createMap())).set(key, 0); - const saveInferenceMatch = inferenceMatch; - const saveInferenceIncomplete = inferenceIncomplete; - inferenceMatch = false; - inferenceIncomplete = false; + (visited || (visited = createMap())).set(key, InferencePriority.Circularity); + const saveInferencePriority = inferencePriority; + inferencePriority = InferencePriority.MaxValue; action(source, target); - visited.set(key, (inferenceMatch ? 1 : 0) | (inferenceIncomplete ? 2 : 0)); - inferenceMatch = inferenceMatch || saveInferenceMatch; - inferenceIncomplete = inferenceIncomplete || saveInferenceIncomplete; + visited.set(key, inferencePriority); + inferencePriority = Math.min(inferencePriority, saveInferencePriority); } function inferFromMatchingType(source: Type, targets: Type[], matches: (s: Type, t: Type) => boolean) { @@ -15778,10 +15773,11 @@ namespace ts { let nakedTypeVariable: Type | undefined; const sources = source.flags & TypeFlags.Union ? (source).types : [source]; const matched = new Array(sources.length); - const saveInferenceIncomplete = inferenceIncomplete; - inferenceIncomplete = false; + let inferenceCircularity = false; // First infer to types that are not naked type variables. For each source type we - // track whether inferences were made from that particular type to some target. + // track whether inferences were made from that particular type to some target with + // equal priority (i.e. of equal quality) to what we would infer for a naked type + // parameter. for (const t of targets) { if (getInferenceInfoForType(t)) { nakedTypeVariable = t; @@ -15789,20 +15785,20 @@ namespace ts { } else { for (let i = 0; i < sources.length; i++) { - const saveInferenceMatch = inferenceMatch; - inferenceMatch = false; + const saveInferencePriority = inferencePriority; + inferencePriority = InferencePriority.MaxValue; inferFromTypes(sources[i], t); - if (inferenceMatch) matched[i] = true; - inferenceMatch = inferenceMatch || saveInferenceMatch; + if (inferencePriority === priority) matched[i] = true; + inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity; + inferencePriority = Math.min(inferencePriority, saveInferencePriority); } } } - const inferenceComplete = !inferenceIncomplete; - inferenceIncomplete = inferenceIncomplete || saveInferenceIncomplete; - // If the target has a single naked type variable and inference completed (meaning we - // explored the types fully), create a union of the source types from which no inferences - // have been made so far and infer from that union to the naked type variable. - if (typeVariableCount === 1 && inferenceComplete) { + // If the target has a single naked type variable and no inference circularities were + // encountered above (meaning we explored the types fully), create a union of the source + // types from which no inferences have been made so far and infer from that union to the + // naked type variable. + if (typeVariableCount === 1 && !inferenceCircularity) { const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); if (unmatched.length) { inferFromTypes(getUnionType(unmatched), nakedTypeVariable!); @@ -15905,7 +15901,7 @@ namespace ts { const symbol = isNonConstructorObject ? target.symbol : undefined; if (symbol) { if (contains(symbolStack, symbol)) { - inferenceIncomplete = true; + inferencePriority = InferencePriority.Circularity; return; } (symbolStack || (symbolStack = [])).push(symbol); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 04c71dd76dfc4..8106b39a61994 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4483,8 +4483,10 @@ namespace ts { LiteralKeyof = 1 << 5, // Inference made from a string literal to a keyof T NoConstraints = 1 << 6, // Don't infer from constraints of instantiable types AlwaysStrict = 1 << 7, // Always use strict rules for contravariant inferences + MaxValue = 1 << 8, // Seed for inference priority tracking PriorityImpliesCombination = ReturnType | MappedTypeConstraint | LiteralKeyof, // These priorities imply that the resulting type should be a combination of all candidates + Circularity = -1, // Inference circularity (value less than all other priorities) } /* @internal */ diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index c877d7d2d9a72..768399664828e 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2441,7 +2441,9 @@ declare namespace ts { LiteralKeyof = 32, NoConstraints = 64, AlwaysStrict = 128, - PriorityImpliesCombination = 56 + MaxValue = 256, + PriorityImpliesCombination = 56, + Circularity = -1 } /** @deprecated Use FileExtensionInfo instead. */ export type JsFileExtensionInfo = FileExtensionInfo; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 4c28abcbbc3a3..811f3b3507c53 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2441,7 +2441,9 @@ declare namespace ts { LiteralKeyof = 32, NoConstraints = 64, AlwaysStrict = 128, - PriorityImpliesCombination = 56 + MaxValue = 256, + PriorityImpliesCombination = 56, + Circularity = -1 } /** @deprecated Use FileExtensionInfo instead. */ export type JsFileExtensionInfo = FileExtensionInfo; diff --git a/tests/baselines/reference/unionTypeInference.errors.txt b/tests/baselines/reference/unionTypeInference.errors.txt index f28f32f337398..b39b8fcd15cfe 100644 --- a/tests/baselines/reference/unionTypeInference.errors.txt +++ b/tests/baselines/reference/unionTypeInference.errors.txt @@ -58,4 +58,30 @@ tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference declare function bar(x: T, y: string | T): T; const y = bar(1, 2); + + // Repro from #32752 + + const containsPromises: unique symbol = Symbol(); + + type DeepPromised = + { [containsPromises]?: true } & + { [TKey in keyof T]: T[TKey] | DeepPromised | Promise> }; + + async function fun(deepPromised: DeepPromised) { + const deepPromisedWithIndexer: DeepPromised<{ [name: string]: {} | null | undefined }> = deepPromised; + for (const value of Object.values(deepPromisedWithIndexer)) { + const awaitedValue = await value; + if (awaitedValue) + await fun(awaitedValue); + } + } + + // Repro from #32752 + + type Deep = { [K in keyof T]: T[K] | Deep }; + + declare function baz(dp: Deep): T; + declare let xx: { a: string | undefined }; + + baz(xx); \ No newline at end of file diff --git a/tests/baselines/reference/unionTypeInference.js b/tests/baselines/reference/unionTypeInference.js index e41872eb4d4d8..c5157165eae32 100644 --- a/tests/baselines/reference/unionTypeInference.js +++ b/tests/baselines/reference/unionTypeInference.js @@ -50,29 +50,64 @@ foo(x); declare function bar(x: T, y: string | T): T; const y = bar(1, 2); + +// Repro from #32752 + +const containsPromises: unique symbol = Symbol(); + +type DeepPromised = + { [containsPromises]?: true } & + { [TKey in keyof T]: T[TKey] | DeepPromised | Promise> }; + +async function fun(deepPromised: DeepPromised) { + const deepPromisedWithIndexer: DeepPromised<{ [name: string]: {} | null | undefined }> = deepPromised; + for (const value of Object.values(deepPromisedWithIndexer)) { + const awaitedValue = await value; + if (awaitedValue) + await fun(awaitedValue); + } +} + +// Repro from #32752 + +type Deep = { [K in keyof T]: T[K] | Deep }; + +declare function baz(dp: Deep): T; +declare let xx: { a: string | undefined }; + +baz(xx); //// [unionTypeInference.js] -"use strict"; -exports.__esModule = true; -var a1 = f1(1, 2); // 1 | 2 -var a2 = f1(1, "hello"); // 1 -var a3 = f1(1, sn); // number -var a4 = f1(undefined, "abc"); // undefined -var a5 = f1("foo", "bar"); // "foo" -var a6 = f1(true, false); // boolean -var a7 = f1("hello", 1); // Error +const a1 = f1(1, 2); // 1 | 2 +const a2 = f1(1, "hello"); // 1 +const a3 = f1(1, sn); // number +const a4 = f1(undefined, "abc"); // undefined +const a5 = f1("foo", "bar"); // "foo" +const a6 = f1(true, false); // boolean +const a7 = f1("hello", 1); // Error var b1 = f2(["string", true]); // boolean -var c1 = f3(5); // 5 -var c2 = f3(sn); // number -var c3 = f3(true); // true -var c4 = f3(b); // true -var c5 = f3("abc"); // never -var d1 = f4("abc"); -var d2 = f4(s); -var d3 = f4(42); // Error +const c1 = f3(5); // 5 +const c2 = f3(sn); // number +const c3 = f3(true); // true +const c4 = f3(b); // true +const c5 = f3("abc"); // never +const d1 = f4("abc"); +const d2 = f4(s); +const d3 = f4(42); // Error function qux(p1, p2) { p1 = p2; } foo(x); -var y = bar(1, 2); +const y = bar(1, 2); +// Repro from #32752 +const containsPromises = Symbol(); +async function fun(deepPromised) { + const deepPromisedWithIndexer = deepPromised; + for (const value of Object.values(deepPromisedWithIndexer)) { + const awaitedValue = await value; + if (awaitedValue) + await fun(awaitedValue); + } +} +baz(xx); diff --git a/tests/baselines/reference/unionTypeInference.symbols b/tests/baselines/reference/unionTypeInference.symbols index 0d0bf6ae9be81..11fdc52396485 100644 --- a/tests/baselines/reference/unionTypeInference.symbols +++ b/tests/baselines/reference/unionTypeInference.symbols @@ -163,12 +163,12 @@ declare function foo(x: T | Promise): void; >T : Symbol(T, Decl(unionTypeInference.ts, 45, 21)) >x : Symbol(x, Decl(unionTypeInference.ts, 45, 24)) >T : Symbol(T, Decl(unionTypeInference.ts, 45, 21)) ->Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --)) +>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, --, --)) >T : Symbol(T, Decl(unionTypeInference.ts, 45, 21)) declare let x: false | Promise; >x : Symbol(x, Decl(unionTypeInference.ts, 46, 11)) ->Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --)) +>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, --, --)) foo(x); >foo : Symbol(foo, Decl(unionTypeInference.ts, 41, 1)) @@ -187,3 +187,92 @@ const y = bar(1, 2); >y : Symbol(y, Decl(unionTypeInference.ts, 50, 5)) >bar : Symbol(bar, Decl(unionTypeInference.ts, 47, 7)) +// Repro from #32752 + +const containsPromises: unique symbol = Symbol(); +>containsPromises : Symbol(containsPromises, Decl(unionTypeInference.ts, 54, 5)) +>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, --, --)) + +type DeepPromised = +>DeepPromised : Symbol(DeepPromised, Decl(unionTypeInference.ts, 54, 49)) +>T : Symbol(T, Decl(unionTypeInference.ts, 56, 18)) + + { [containsPromises]?: true } & +>[containsPromises] : Symbol([containsPromises], Decl(unionTypeInference.ts, 57, 5)) +>containsPromises : Symbol(containsPromises, Decl(unionTypeInference.ts, 54, 5)) + + { [TKey in keyof T]: T[TKey] | DeepPromised | Promise> }; +>TKey : Symbol(TKey, Decl(unionTypeInference.ts, 58, 7)) +>T : Symbol(T, Decl(unionTypeInference.ts, 56, 18)) +>T : Symbol(T, Decl(unionTypeInference.ts, 56, 18)) +>TKey : Symbol(TKey, Decl(unionTypeInference.ts, 58, 7)) +>DeepPromised : Symbol(DeepPromised, Decl(unionTypeInference.ts, 54, 49)) +>T : Symbol(T, Decl(unionTypeInference.ts, 56, 18)) +>TKey : Symbol(TKey, Decl(unionTypeInference.ts, 58, 7)) +>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, --, --)) +>DeepPromised : Symbol(DeepPromised, Decl(unionTypeInference.ts, 54, 49)) +>T : Symbol(T, Decl(unionTypeInference.ts, 56, 18)) +>TKey : Symbol(TKey, Decl(unionTypeInference.ts, 58, 7)) + +async function fun(deepPromised: DeepPromised) { +>fun : Symbol(fun, Decl(unionTypeInference.ts, 58, 92)) +>T : Symbol(T, Decl(unionTypeInference.ts, 60, 19)) +>deepPromised : Symbol(deepPromised, Decl(unionTypeInference.ts, 60, 22)) +>DeepPromised : Symbol(DeepPromised, Decl(unionTypeInference.ts, 54, 49)) +>T : Symbol(T, Decl(unionTypeInference.ts, 60, 19)) + + const deepPromisedWithIndexer: DeepPromised<{ [name: string]: {} | null | undefined }> = deepPromised; +>deepPromisedWithIndexer : Symbol(deepPromisedWithIndexer, Decl(unionTypeInference.ts, 61, 9)) +>DeepPromised : Symbol(DeepPromised, Decl(unionTypeInference.ts, 54, 49)) +>name : Symbol(name, Decl(unionTypeInference.ts, 61, 51)) +>deepPromised : Symbol(deepPromised, Decl(unionTypeInference.ts, 60, 22)) + + for (const value of Object.values(deepPromisedWithIndexer)) { +>value : Symbol(value, Decl(unionTypeInference.ts, 62, 14)) +>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>deepPromisedWithIndexer : Symbol(deepPromisedWithIndexer, Decl(unionTypeInference.ts, 61, 9)) + + const awaitedValue = await value; +>awaitedValue : Symbol(awaitedValue, Decl(unionTypeInference.ts, 63, 13)) +>value : Symbol(value, Decl(unionTypeInference.ts, 62, 14)) + + if (awaitedValue) +>awaitedValue : Symbol(awaitedValue, Decl(unionTypeInference.ts, 63, 13)) + + await fun(awaitedValue); +>fun : Symbol(fun, Decl(unionTypeInference.ts, 58, 92)) +>awaitedValue : Symbol(awaitedValue, Decl(unionTypeInference.ts, 63, 13)) + } +} + +// Repro from #32752 + +type Deep = { [K in keyof T]: T[K] | Deep }; +>Deep : Symbol(Deep, Decl(unionTypeInference.ts, 67, 1)) +>T : Symbol(T, Decl(unionTypeInference.ts, 71, 10)) +>K : Symbol(K, Decl(unionTypeInference.ts, 71, 18)) +>T : Symbol(T, Decl(unionTypeInference.ts, 71, 10)) +>T : Symbol(T, Decl(unionTypeInference.ts, 71, 10)) +>K : Symbol(K, Decl(unionTypeInference.ts, 71, 18)) +>Deep : Symbol(Deep, Decl(unionTypeInference.ts, 67, 1)) +>T : Symbol(T, Decl(unionTypeInference.ts, 71, 10)) +>K : Symbol(K, Decl(unionTypeInference.ts, 71, 18)) + +declare function baz(dp: Deep): T; +>baz : Symbol(baz, Decl(unionTypeInference.ts, 71, 53)) +>T : Symbol(T, Decl(unionTypeInference.ts, 73, 21)) +>dp : Symbol(dp, Decl(unionTypeInference.ts, 73, 24)) +>Deep : Symbol(Deep, Decl(unionTypeInference.ts, 67, 1)) +>T : Symbol(T, Decl(unionTypeInference.ts, 73, 21)) +>T : Symbol(T, Decl(unionTypeInference.ts, 73, 21)) + +declare let xx: { a: string | undefined }; +>xx : Symbol(xx, Decl(unionTypeInference.ts, 74, 11)) +>a : Symbol(a, Decl(unionTypeInference.ts, 74, 17)) + +baz(xx); +>baz : Symbol(baz, Decl(unionTypeInference.ts, 71, 53)) +>xx : Symbol(xx, Decl(unionTypeInference.ts, 74, 11)) + diff --git a/tests/baselines/reference/unionTypeInference.types b/tests/baselines/reference/unionTypeInference.types index 304bbcd33ec51..4ce2d9c43a08c 100644 --- a/tests/baselines/reference/unionTypeInference.types +++ b/tests/baselines/reference/unionTypeInference.types @@ -185,3 +185,72 @@ const y = bar(1, 2); >1 : 1 >2 : 2 +// Repro from #32752 + +const containsPromises: unique symbol = Symbol(); +>containsPromises : unique symbol +>Symbol() : unique symbol +>Symbol : SymbolConstructor + +type DeepPromised = +>DeepPromised : DeepPromised + + { [containsPromises]?: true } & +>[containsPromises] : true | undefined +>containsPromises : unique symbol +>true : true + + { [TKey in keyof T]: T[TKey] | DeepPromised | Promise> }; + +async function fun(deepPromised: DeepPromised) { +>fun : (deepPromised: DeepPromised) => Promise +>deepPromised : DeepPromised + + const deepPromisedWithIndexer: DeepPromised<{ [name: string]: {} | null | undefined }> = deepPromised; +>deepPromisedWithIndexer : DeepPromised<{ [name: string]: {} | null | undefined; }> +>name : string +>null : null +>deepPromised : DeepPromised + + for (const value of Object.values(deepPromisedWithIndexer)) { +>value : {} | ({ [containsPromises]?: true | undefined; } & {}) | Promise<{ [containsPromises]?: true | undefined; } & {}> | null | undefined +>Object.values(deepPromisedWithIndexer) : ({} | ({ [containsPromises]?: true | undefined; } & {}) | Promise<{ [containsPromises]?: true | undefined; } & {}> | null | undefined)[] +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>Object : ObjectConstructor +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>deepPromisedWithIndexer : DeepPromised<{ [name: string]: {} | null | undefined; }> + + const awaitedValue = await value; +>awaitedValue : {} | ({ [containsPromises]?: true | undefined; } & {}) | null | undefined +>await value : {} | ({ [containsPromises]?: true | undefined; } & {}) | null | undefined +>value : {} | ({ [containsPromises]?: true | undefined; } & {}) | Promise<{ [containsPromises]?: true | undefined; } & {}> | null | undefined + + if (awaitedValue) +>awaitedValue : {} | ({ [containsPromises]?: true | undefined; } & {}) | null | undefined + + await fun(awaitedValue); +>await fun(awaitedValue) : void +>fun(awaitedValue) : Promise +>fun : (deepPromised: DeepPromised) => Promise +>awaitedValue : {} | ({ [containsPromises]?: true | undefined; } & {}) + } +} + +// Repro from #32752 + +type Deep = { [K in keyof T]: T[K] | Deep }; +>Deep : Deep + +declare function baz(dp: Deep): T; +>baz : (dp: Deep) => T +>dp : Deep + +declare let xx: { a: string | undefined }; +>xx : { a: string | undefined; } +>a : string | undefined + +baz(xx); +>baz(xx) : { a: string | undefined; } +>baz : (dp: Deep) => T +>xx : { a: string | undefined; } + diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts b/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts index 4d9a3eae21ede..2252070fee390 100644 --- a/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts +++ b/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts @@ -1,4 +1,5 @@ // @strict: true +// @target: esnext declare const b: boolean; declare const s: string; @@ -51,3 +52,29 @@ foo(x); declare function bar(x: T, y: string | T): T; const y = bar(1, 2); + +// Repro from #32752 + +const containsPromises: unique symbol = Symbol(); + +type DeepPromised = + { [containsPromises]?: true } & + { [TKey in keyof T]: T[TKey] | DeepPromised | Promise> }; + +async function fun(deepPromised: DeepPromised) { + const deepPromisedWithIndexer: DeepPromised<{ [name: string]: {} | null | undefined }> = deepPromised; + for (const value of Object.values(deepPromisedWithIndexer)) { + const awaitedValue = await value; + if (awaitedValue) + await fun(awaitedValue); + } +} + +// Repro from #32752 + +type Deep = { [K in keyof T]: T[K] | Deep }; + +declare function baz(dp: Deep): T; +declare let xx: { a: string | undefined }; + +baz(xx);