From 03055d2fb698f67aaee2a638992820ff1b2e5249 Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Fri, 16 Aug 2019 11:02:00 -0700 Subject: [PATCH] Cherry-pick PR #32919 into release-3.6 (#32933) 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 9d6fa60306c..f8fd6202132 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 04c71dd76df..8106b39a619 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 c877d7d2d9a..76839966482 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 4c28abcbbc3..811f3b3507c 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 f28f32f3373..b39b8fcd15c 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 e41872eb4d4..c5157165eae 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 0d0bf6ae9be..11fdc523964 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 304bbcd33ec..4ce2d9c43a0 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 4d9a3eae21e..2252070fee3 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);