From d8b191a671c9cddd095d52b31c06d89222e5dd77 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 16 Jul 2019 16:29:52 -0700 Subject: [PATCH 01/18] Improve algorithm for inferring to union types --- src/compiler/checker.ts | 51 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 06395d935b7df..75b12ee37bbf5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15458,6 +15458,7 @@ namespace ts { let visited: Map; let bivariant = false; let propagationType: Type; + let inferenceCount = 0; let allowComplexConstraintInference = true; inferFromTypes(originalSource, originalTarget); @@ -15561,6 +15562,7 @@ namespace ts { clearCachedInferences(inferences); } } + inferenceCount++; return; } else { @@ -15610,13 +15612,16 @@ namespace ts { inferFromTypes(getTrueTypeFromConditionalType(source), getTrueTypeFromConditionalType(target)); inferFromTypes(getFalseTypeFromConditionalType(source), getFalseTypeFromConditionalType(target)); } + else if (target.flags & TypeFlags.Union) { + inferToUnionType(source, target); + } + else if (target.flags & TypeFlags.Intersection) { + inferToMultipleTypes(source, (target).types, /*isIntersection*/ true); + } else if (target.flags & TypeFlags.Conditional && !contravariant) { const targetTypes = [getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)]; inferToMultipleTypes(source, targetTypes, /*isIntersection*/ false); } - else if (target.flags & TypeFlags.UnionOrIntersection) { - inferToMultipleTypes(source, (target).types, !!(target.flags & TypeFlags.Intersection)); - } else if (source.flags & TypeFlags.Union) { // Source is a union or intersection type, infer from each constituent type const sourceTypes = (source).types; @@ -15742,6 +15747,46 @@ namespace ts { } } + function inferToUnionType(source: Type, target: UnionType) { + const sources = source.flags & TypeFlags.Union ? (source).types : [source]; + const matched = new Array(sources.length); + let typeVariableCount = 0; + // 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. + for (const t of target.types) { + if (getInferenceInfoForType(t)) { + typeVariableCount++; + } + else { + for (let i = 0; i < sources.length; i++) { + const count = inferenceCount; + inferFromTypes(sources[i], t); + if (count !== inferenceCount) matched[i] = true; + } + } + } + // If there are naked type variables in the target, create a union of the source types + // from which no inferences have been made so far and infer from that union to each naked + // type variable. If there is more than one naked type variable, give lower priority to + // the inferences as they are less specific. + if (typeVariableCount > 0) { + const unmatched = flatMap(sources, (s, i) => matched![i] ? undefined : s); + if (unmatched.length) { + const s = getUnionType(unmatched); + const savePriority = priority; + if (typeVariableCount > 1) { + priority |= InferencePriority.NakedTypeVariable; + } + for (const t of target.types) { + if (getInferenceInfoForType(t)) { + inferFromTypes(s, t); + } + } + priority = savePriority; + } + } + } + function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { if (constraintType.flags & TypeFlags.Union) { let result = false; From 5a45d5aed8dc6b6c47d8ab23cdf161049d812afe Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 17 Jul 2019 14:53:29 -0700 Subject: [PATCH 02/18] Reduce union and intersection targets when source is singleton type --- src/compiler/checker.ts | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 75b12ee37bbf5..1a2fc79534e37 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15500,15 +15500,10 @@ namespace ts { // of all their possible values. let matchingTypes: Type[] | undefined; for (const t of (source).types) { - if (typeIdenticalToSomeType(t, (target).types)) { - (matchingTypes || (matchingTypes = [])).push(t); - inferFromTypes(t, t); - } - else if (t.flags & (TypeFlags.NumberLiteral | TypeFlags.StringLiteral)) { - const b = getBaseTypeOfLiteralType(t); - if (typeIdenticalToSomeType(b, (target).types)) { - (matchingTypes || (matchingTypes = [])).push(t, b); - } + const matched = findMatchedType(t, target); + if (matched) { + (matchingTypes || (matchingTypes = [])).push(matched); + inferFromTypes(matched, matched); } } // Next, to improve the quality of inferences, reduce the source and target types by @@ -15519,6 +15514,14 @@ namespace ts { target = removeTypesFromUnionOrIntersection(target, matchingTypes); } } + else if (target.flags & TypeFlags.Union && !(target.flags & TypeFlags.EnumLiteral) || target.flags & TypeFlags.Intersection) { + const matched = findMatchedType(source, target); + if (matched) { + inferFromTypes(matched, matched); + source = target.flags & TypeFlags.Union ? neverType : unknownType; + target = removeTypesFromUnionOrIntersection(target, [matched]); + } + } else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { target = getActualTypeVariable(target); } @@ -15955,6 +15958,19 @@ namespace ts { return false; } + function findMatchedType(type: Type, target: UnionOrIntersectionType) { + if (typeIdenticalToSomeType(type, target.types)) { + return type; + } + if (type.flags & (TypeFlags.NumberLiteral | TypeFlags.StringLiteral) && target.flags & TypeFlags.Union) { + const base = getBaseTypeOfLiteralType(type); + if (typeIdenticalToSomeType(base, target.types)) { + return base; + } + } + return undefined; + } + /** * Return a new union or intersection type computed by removing a given set of types * from a given union or intersection type. From 652bb1277cd72946fde639479e816933dfd0cbd9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 17 Jul 2019 14:54:27 -0700 Subject: [PATCH 03/18] Accept new baselines --- .../reference/unionAndIntersectionInference2.types | 2 +- .../reference/unionTypeInference.errors.txt | 13 +++++++++---- tests/baselines/reference/unionTypeInference.types | 6 +++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/tests/baselines/reference/unionAndIntersectionInference2.types b/tests/baselines/reference/unionAndIntersectionInference2.types index a163b25ce2891..031354506b795 100644 --- a/tests/baselines/reference/unionAndIntersectionInference2.types +++ b/tests/baselines/reference/unionAndIntersectionInference2.types @@ -20,7 +20,7 @@ var e1: number | string | boolean; >e1 : string | number | boolean f1(a1); // string ->f1(a1) : string +>f1(a1) : never >f1 : (x: string | T) => T >a1 : string diff --git a/tests/baselines/reference/unionTypeInference.errors.txt b/tests/baselines/reference/unionTypeInference.errors.txt index dfda0d57c2b99..ec5c0e11f8d08 100644 --- a/tests/baselines/reference/unionTypeInference.errors.txt +++ b/tests/baselines/reference/unionTypeInference.errors.txt @@ -1,7 +1,8 @@ -tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts(9,15): error TS2345: Argument of type '2' is not assignable to parameter of type 'string | 1'. +tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts(13,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'a3' must be of type 'number', but here has type 'string | number'. +tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts(31,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'c2' must be of type 'string', but here has type 'never'. -==== tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts (1 errors) ==== +==== tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts (2 errors) ==== // Verify that inferences made *to* a type parameter in a union type are secondary // to inferences made directly to that type parameter @@ -11,12 +12,13 @@ tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference var a1: number; var a1 = f(1, 2); - ~ -!!! error TS2345: Argument of type '2' is not assignable to parameter of type 'string | 1'. var a2: number; var a2 = f(1, "hello"); var a3: number; var a3 = f(1, a1 || "hello"); + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'a3' must be of type 'number', but here has type 'string | number'. +!!! related TS6203 tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts:12:5: 'a3' was also declared here. var a4: any; var a4 = f(undefined, "abc"); @@ -35,4 +37,7 @@ tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference var c1 = h(5); var c2: string; var c2 = h("abc"); + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'c2' must be of type 'string', but here has type 'never'. +!!! related TS6203 tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts:30:5: 'c2' was also declared here. \ No newline at end of file diff --git a/tests/baselines/reference/unionTypeInference.types b/tests/baselines/reference/unionTypeInference.types index e712916c2ff55..fc5b777ff5a82 100644 --- a/tests/baselines/reference/unionTypeInference.types +++ b/tests/baselines/reference/unionTypeInference.types @@ -16,7 +16,7 @@ var a1: number; var a1 = f(1, 2); >a1 : number ->f(1, 2) : any +>f(1, 2) : 1 | 2 >f : (x: T, y: string | T) => T >1 : 1 >2 : 2 @@ -36,7 +36,7 @@ var a3: number; var a3 = f(1, a1 || "hello"); >a3 : number ->f(1, a1 || "hello") : number +>f(1, a1 || "hello") : number | "hello" >f : (x: T, y: string | T) => T >1 : 1 >a1 || "hello" : number | "hello" @@ -107,7 +107,7 @@ var c2: string; var c2 = h("abc"); >c2 : string ->h("abc") : "abc" +>h("abc") : never >h : (x: string | boolean | T) => T >"abc" : "abc" From 7d4259ba9fa4cfa039c36cc54aa5f1351aa36760 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 17 Jul 2019 14:57:26 -0700 Subject: [PATCH 04/18] Update tests --- .../typeInference/unionTypeInference.ts | 73 +++++++++++-------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts b/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts index 39def70662252..14ce8e91fc775 100644 --- a/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts +++ b/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts @@ -1,31 +1,42 @@ -// Verify that inferences made *to* a type parameter in a union type are secondary -// to inferences made directly to that type parameter - -function f(x: T, y: string|T): T { - return x; -} - -var a1: number; -var a1 = f(1, 2); -var a2: number; -var a2 = f(1, "hello"); -var a3: number; -var a3 = f(1, a1 || "hello"); -var a4: any; -var a4 = f(undefined, "abc"); - -function g(value: [string, T]): T { - return value[1]; -} - -var b1: boolean; -var b1 = g(["string", true]); - -function h(x: string|boolean|T): T { - return typeof x === "string" || typeof x === "boolean" ? undefined : x; -} - -var c1: number; -var c1 = h(5); -var c2: string; -var c2 = h("abc"); +// @strict: true + +declare const b: boolean; +declare const s: string; +declare const sn: string | number; + +declare function f1(x: T, y: string | T): T; + +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 + +declare function f2(value: [string, T]): T; + +var b1 = f2(["string", true]); // boolean + +declare function f3(x: string | false | T): T; + +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 + +declare function f4(x: string & T): T; + +var d1 = f4("abc"); +var d2 = f4(s); +var d3 = f4(42); // Error + +// Repros from #32434 + +declare function foo(x: T | Promise): void; +declare let x: false | Promise; +foo(x); + +declare function bar(x: T, y: string | T): T; +const y = bar(1, 2); From ae1add72104b6e3ca7a8e765af77f3bec9003ce6 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 17 Jul 2019 15:02:20 -0700 Subject: [PATCH 05/18] Update tests --- .../typeRelationships/typeInference/unionTypeInference.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts b/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts index 14ce8e91fc775..ccedb753354ee 100644 --- a/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts +++ b/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts @@ -28,9 +28,9 @@ const c5 = f3("abc"); // never declare function f4(x: string & T): T; -var d1 = f4("abc"); -var d2 = f4(s); -var d3 = f4(42); // Error +const d1 = f4("abc"); +const d2 = f4(s); +const d3 = f4(42); // Error // Repros from #32434 From c4bad6443884ee7218f1b970784f2c03e23d52bb Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 17 Jul 2019 15:03:15 -0700 Subject: [PATCH 06/18] Accept new baselines --- .../reference/unionTypeInference.errors.txt | 85 ++++--- .../baselines/reference/unionTypeInference.js | 115 ++++----- .../reference/unionTypeInference.symbols | 226 +++++++++++------- .../reference/unionTypeInference.types | 208 +++++++++------- 4 files changed, 369 insertions(+), 265 deletions(-) diff --git a/tests/baselines/reference/unionTypeInference.errors.txt b/tests/baselines/reference/unionTypeInference.errors.txt index ec5c0e11f8d08..6993f4280a7e3 100644 --- a/tests/baselines/reference/unionTypeInference.errors.txt +++ b/tests/baselines/reference/unionTypeInference.errors.txt @@ -1,43 +1,50 @@ -tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts(13,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'a3' must be of type 'number', but here has type 'string | number'. -tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts(31,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'c2' must be of type 'string', but here has type 'never'. +tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts(13,24): error TS2345: Argument of type '1' is not assignable to parameter of type 'string'. +tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts(31,13): error TS2345: Argument of type '42' is not assignable to parameter of type 'never'. ==== tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts (2 errors) ==== - // Verify that inferences made *to* a type parameter in a union type are secondary - // to inferences made directly to that type parameter - - function f(x: T, y: string|T): T { - return x; - } - - var a1: number; - var a1 = f(1, 2); - var a2: number; - var a2 = f(1, "hello"); - var a3: number; - var a3 = f(1, a1 || "hello"); - ~~ -!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'a3' must be of type 'number', but here has type 'string | number'. -!!! related TS6203 tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts:12:5: 'a3' was also declared here. - var a4: any; - var a4 = f(undefined, "abc"); - - function g(value: [string, T]): T { - return value[1]; - } - - var b1: boolean; - var b1 = g(["string", true]); - - function h(x: string|boolean|T): T { - return typeof x === "string" || typeof x === "boolean" ? undefined : x; - } - - var c1: number; - var c1 = h(5); - var c2: string; - var c2 = h("abc"); - ~~ -!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'c2' must be of type 'string', but here has type 'never'. -!!! related TS6203 tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts:30:5: 'c2' was also declared here. + declare const b: boolean; + declare const s: string; + declare const sn: string | number; + + declare function f1(x: T, y: string | T): T; + + 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 + ~ +!!! error TS2345: Argument of type '1' is not assignable to parameter of type 'string'. + + declare function f2(value: [string, T]): T; + + var b1 = f2(["string", true]); // boolean + + declare function f3(x: string | false | T): T; + + 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 + + declare function f4(x: string & T): T; + + var d1 = f4("abc"); + var d2 = f4(s); + var d3 = f4(42); // Error + ~~ +!!! error TS2345: Argument of type '42' is not assignable to parameter of type 'never'. + + // Repros from #32434 + + declare function foo(x: T | Promise): void; + declare let x: false | Promise; + foo(x); + + declare function bar(x: T, y: string | T): T; + const y = bar(1, 2); \ No newline at end of file diff --git a/tests/baselines/reference/unionTypeInference.js b/tests/baselines/reference/unionTypeInference.js index bdaa340f6ea6b..fb94e1910f1f8 100644 --- a/tests/baselines/reference/unionTypeInference.js +++ b/tests/baselines/reference/unionTypeInference.js @@ -1,60 +1,63 @@ //// [unionTypeInference.ts] -// Verify that inferences made *to* a type parameter in a union type are secondary -// to inferences made directly to that type parameter - -function f(x: T, y: string|T): T { - return x; -} - -var a1: number; -var a1 = f(1, 2); -var a2: number; -var a2 = f(1, "hello"); -var a3: number; -var a3 = f(1, a1 || "hello"); -var a4: any; -var a4 = f(undefined, "abc"); - -function g(value: [string, T]): T { - return value[1]; -} - -var b1: boolean; -var b1 = g(["string", true]); - -function h(x: string|boolean|T): T { - return typeof x === "string" || typeof x === "boolean" ? undefined : x; -} - -var c1: number; -var c1 = h(5); -var c2: string; -var c2 = h("abc"); +declare const b: boolean; +declare const s: string; +declare const sn: string | number; + +declare function f1(x: T, y: string | T): T; + +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 + +declare function f2(value: [string, T]): T; + +var b1 = f2(["string", true]); // boolean + +declare function f3(x: string | false | T): T; + +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 + +declare function f4(x: string & T): T; + +var d1 = f4("abc"); +var d2 = f4(s); +var d3 = f4(42); // Error + +// Repros from #32434 + +declare function foo(x: T | Promise): void; +declare let x: false | Promise; +foo(x); + +declare function bar(x: T, y: string | T): T; +const y = bar(1, 2); //// [unionTypeInference.js] -// Verify that inferences made *to* a type parameter in a union type are secondary -// to inferences made directly to that type parameter -function f(x, y) { - return x; -} -var a1; -var a1 = f(1, 2); -var a2; -var a2 = f(1, "hello"); -var a3; -var a3 = f(1, a1 || "hello"); -var a4; -var a4 = f(undefined, "abc"); -function g(value) { - return value[1]; -} -var b1; -var b1 = g(["string", true]); -function h(x) { - return typeof x === "string" || typeof x === "boolean" ? undefined : x; -} -var c1; -var c1 = h(5); -var c2; -var c2 = h("abc"); +"use strict"; +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 +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 +foo(x); +var y = bar(1, 2); diff --git a/tests/baselines/reference/unionTypeInference.symbols b/tests/baselines/reference/unionTypeInference.symbols index 3388f279f4bdb..a34be27983722 100644 --- a/tests/baselines/reference/unionTypeInference.symbols +++ b/tests/baselines/reference/unionTypeInference.symbols @@ -1,94 +1,140 @@ === tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts === -// Verify that inferences made *to* a type parameter in a union type are secondary -// to inferences made directly to that type parameter - -function f(x: T, y: string|T): T { ->f : Symbol(f, Decl(unionTypeInference.ts, 0, 0)) ->T : Symbol(T, Decl(unionTypeInference.ts, 3, 11)) ->x : Symbol(x, Decl(unionTypeInference.ts, 3, 14)) ->T : Symbol(T, Decl(unionTypeInference.ts, 3, 11)) ->y : Symbol(y, Decl(unionTypeInference.ts, 3, 19)) ->T : Symbol(T, Decl(unionTypeInference.ts, 3, 11)) ->T : Symbol(T, Decl(unionTypeInference.ts, 3, 11)) - - return x; ->x : Symbol(x, Decl(unionTypeInference.ts, 3, 14)) -} - -var a1: number; ->a1 : Symbol(a1, Decl(unionTypeInference.ts, 7, 3), Decl(unionTypeInference.ts, 8, 3)) - -var a1 = f(1, 2); ->a1 : Symbol(a1, Decl(unionTypeInference.ts, 7, 3), Decl(unionTypeInference.ts, 8, 3)) ->f : Symbol(f, Decl(unionTypeInference.ts, 0, 0)) - -var a2: number; ->a2 : Symbol(a2, Decl(unionTypeInference.ts, 9, 3), Decl(unionTypeInference.ts, 10, 3)) - -var a2 = f(1, "hello"); ->a2 : Symbol(a2, Decl(unionTypeInference.ts, 9, 3), Decl(unionTypeInference.ts, 10, 3)) ->f : Symbol(f, Decl(unionTypeInference.ts, 0, 0)) - -var a3: number; ->a3 : Symbol(a3, Decl(unionTypeInference.ts, 11, 3), Decl(unionTypeInference.ts, 12, 3)) - -var a3 = f(1, a1 || "hello"); ->a3 : Symbol(a3, Decl(unionTypeInference.ts, 11, 3), Decl(unionTypeInference.ts, 12, 3)) ->f : Symbol(f, Decl(unionTypeInference.ts, 0, 0)) ->a1 : Symbol(a1, Decl(unionTypeInference.ts, 7, 3), Decl(unionTypeInference.ts, 8, 3)) - -var a4: any; ->a4 : Symbol(a4, Decl(unionTypeInference.ts, 13, 3), Decl(unionTypeInference.ts, 14, 3)) - -var a4 = f(undefined, "abc"); ->a4 : Symbol(a4, Decl(unionTypeInference.ts, 13, 3), Decl(unionTypeInference.ts, 14, 3)) ->f : Symbol(f, Decl(unionTypeInference.ts, 0, 0)) +declare const b: boolean; +>b : Symbol(b, Decl(unionTypeInference.ts, 0, 13)) + +declare const s: string; +>s : Symbol(s, Decl(unionTypeInference.ts, 1, 13)) + +declare const sn: string | number; +>sn : Symbol(sn, Decl(unionTypeInference.ts, 2, 13)) + +declare function f1(x: T, y: string | T): T; +>f1 : Symbol(f1, Decl(unionTypeInference.ts, 2, 34)) +>T : Symbol(T, Decl(unionTypeInference.ts, 4, 20)) +>x : Symbol(x, Decl(unionTypeInference.ts, 4, 23)) +>T : Symbol(T, Decl(unionTypeInference.ts, 4, 20)) +>y : Symbol(y, Decl(unionTypeInference.ts, 4, 28)) +>T : Symbol(T, Decl(unionTypeInference.ts, 4, 20)) +>T : Symbol(T, Decl(unionTypeInference.ts, 4, 20)) + +const a1 = f1(1, 2); // 1 | 2 +>a1 : Symbol(a1, Decl(unionTypeInference.ts, 6, 5)) +>f1 : Symbol(f1, Decl(unionTypeInference.ts, 2, 34)) + +const a2 = f1(1, "hello"); // 1 +>a2 : Symbol(a2, Decl(unionTypeInference.ts, 7, 5)) +>f1 : Symbol(f1, Decl(unionTypeInference.ts, 2, 34)) + +const a3 = f1(1, sn); // number +>a3 : Symbol(a3, Decl(unionTypeInference.ts, 8, 5)) +>f1 : Symbol(f1, Decl(unionTypeInference.ts, 2, 34)) +>sn : Symbol(sn, Decl(unionTypeInference.ts, 2, 13)) + +const a4 = f1(undefined, "abc"); // undefined +>a4 : Symbol(a4, Decl(unionTypeInference.ts, 9, 5)) +>f1 : Symbol(f1, Decl(unionTypeInference.ts, 2, 34)) >undefined : Symbol(undefined) -function g(value: [string, T]): T { ->g : Symbol(g, Decl(unionTypeInference.ts, 14, 29)) ->T : Symbol(T, Decl(unionTypeInference.ts, 16, 11)) ->value : Symbol(value, Decl(unionTypeInference.ts, 16, 14)) ->T : Symbol(T, Decl(unionTypeInference.ts, 16, 11)) ->T : Symbol(T, Decl(unionTypeInference.ts, 16, 11)) - - return value[1]; ->value : Symbol(value, Decl(unionTypeInference.ts, 16, 14)) ->1 : Symbol(1) -} - -var b1: boolean; ->b1 : Symbol(b1, Decl(unionTypeInference.ts, 20, 3), Decl(unionTypeInference.ts, 21, 3)) - -var b1 = g(["string", true]); ->b1 : Symbol(b1, Decl(unionTypeInference.ts, 20, 3), Decl(unionTypeInference.ts, 21, 3)) ->g : Symbol(g, Decl(unionTypeInference.ts, 14, 29)) - -function h(x: string|boolean|T): T { ->h : Symbol(h, Decl(unionTypeInference.ts, 21, 29)) ->T : Symbol(T, Decl(unionTypeInference.ts, 23, 11)) ->x : Symbol(x, Decl(unionTypeInference.ts, 23, 14)) ->T : Symbol(T, Decl(unionTypeInference.ts, 23, 11)) ->T : Symbol(T, Decl(unionTypeInference.ts, 23, 11)) - - return typeof x === "string" || typeof x === "boolean" ? undefined : x; ->x : Symbol(x, Decl(unionTypeInference.ts, 23, 14)) ->x : Symbol(x, Decl(unionTypeInference.ts, 23, 14)) ->undefined : Symbol(undefined) ->x : Symbol(x, Decl(unionTypeInference.ts, 23, 14)) -} - -var c1: number; ->c1 : Symbol(c1, Decl(unionTypeInference.ts, 27, 3), Decl(unionTypeInference.ts, 28, 3)) - -var c1 = h(5); ->c1 : Symbol(c1, Decl(unionTypeInference.ts, 27, 3), Decl(unionTypeInference.ts, 28, 3)) ->h : Symbol(h, Decl(unionTypeInference.ts, 21, 29)) - -var c2: string; ->c2 : Symbol(c2, Decl(unionTypeInference.ts, 29, 3), Decl(unionTypeInference.ts, 30, 3)) - -var c2 = h("abc"); ->c2 : Symbol(c2, Decl(unionTypeInference.ts, 29, 3), Decl(unionTypeInference.ts, 30, 3)) ->h : Symbol(h, Decl(unionTypeInference.ts, 21, 29)) +const a5 = f1("foo", "bar"); // "foo" +>a5 : Symbol(a5, Decl(unionTypeInference.ts, 10, 5)) +>f1 : Symbol(f1, Decl(unionTypeInference.ts, 2, 34)) + +const a6 = f1(true, false); // boolean +>a6 : Symbol(a6, Decl(unionTypeInference.ts, 11, 5)) +>f1 : Symbol(f1, Decl(unionTypeInference.ts, 2, 34)) + +const a7 = f1("hello", 1); // Error +>a7 : Symbol(a7, Decl(unionTypeInference.ts, 12, 5)) +>f1 : Symbol(f1, Decl(unionTypeInference.ts, 2, 34)) + +declare function f2(value: [string, T]): T; +>f2 : Symbol(f2, Decl(unionTypeInference.ts, 12, 26)) +>T : Symbol(T, Decl(unionTypeInference.ts, 14, 20)) +>value : Symbol(value, Decl(unionTypeInference.ts, 14, 23)) +>T : Symbol(T, Decl(unionTypeInference.ts, 14, 20)) +>T : Symbol(T, Decl(unionTypeInference.ts, 14, 20)) + +var b1 = f2(["string", true]); // boolean +>b1 : Symbol(b1, Decl(unionTypeInference.ts, 16, 3)) +>f2 : Symbol(f2, Decl(unionTypeInference.ts, 12, 26)) + +declare function f3(x: string | false | T): T; +>f3 : Symbol(f3, Decl(unionTypeInference.ts, 16, 30)) +>T : Symbol(T, Decl(unionTypeInference.ts, 18, 20)) +>x : Symbol(x, Decl(unionTypeInference.ts, 18, 23)) +>T : Symbol(T, Decl(unionTypeInference.ts, 18, 20)) +>T : Symbol(T, Decl(unionTypeInference.ts, 18, 20)) + +const c1 = f3(5); // 5 +>c1 : Symbol(c1, Decl(unionTypeInference.ts, 20, 5)) +>f3 : Symbol(f3, Decl(unionTypeInference.ts, 16, 30)) + +const c2 = f3(sn); // number +>c2 : Symbol(c2, Decl(unionTypeInference.ts, 21, 5)) +>f3 : Symbol(f3, Decl(unionTypeInference.ts, 16, 30)) +>sn : Symbol(sn, Decl(unionTypeInference.ts, 2, 13)) + +const c3 = f3(true); // true +>c3 : Symbol(c3, Decl(unionTypeInference.ts, 22, 5)) +>f3 : Symbol(f3, Decl(unionTypeInference.ts, 16, 30)) + +const c4 = f3(b); // true +>c4 : Symbol(c4, Decl(unionTypeInference.ts, 23, 5)) +>f3 : Symbol(f3, Decl(unionTypeInference.ts, 16, 30)) +>b : Symbol(b, Decl(unionTypeInference.ts, 0, 13)) + +const c5 = f3("abc"); // never +>c5 : Symbol(c5, Decl(unionTypeInference.ts, 24, 5)) +>f3 : Symbol(f3, Decl(unionTypeInference.ts, 16, 30)) + +declare function f4(x: string & T): T; +>f4 : Symbol(f4, Decl(unionTypeInference.ts, 24, 21)) +>T : Symbol(T, Decl(unionTypeInference.ts, 26, 20)) +>x : Symbol(x, Decl(unionTypeInference.ts, 26, 23)) +>T : Symbol(T, Decl(unionTypeInference.ts, 26, 20)) +>T : Symbol(T, Decl(unionTypeInference.ts, 26, 20)) + +var d1 = f4("abc"); +>d1 : Symbol(d1, Decl(unionTypeInference.ts, 28, 3)) +>f4 : Symbol(f4, Decl(unionTypeInference.ts, 24, 21)) + +var d2 = f4(s); +>d2 : Symbol(d2, Decl(unionTypeInference.ts, 29, 3)) +>f4 : Symbol(f4, Decl(unionTypeInference.ts, 24, 21)) +>s : Symbol(s, Decl(unionTypeInference.ts, 1, 13)) + +var d3 = f4(42); // Error +>d3 : Symbol(d3, Decl(unionTypeInference.ts, 30, 3)) +>f4 : Symbol(f4, Decl(unionTypeInference.ts, 24, 21)) + +// Repros from #32434 + +declare function foo(x: T | Promise): void; +>foo : Symbol(foo, Decl(unionTypeInference.ts, 30, 16)) +>T : Symbol(T, Decl(unionTypeInference.ts, 34, 21)) +>x : Symbol(x, Decl(unionTypeInference.ts, 34, 24)) +>T : Symbol(T, Decl(unionTypeInference.ts, 34, 21)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(unionTypeInference.ts, 34, 21)) + +declare let x: false | Promise; +>x : Symbol(x, Decl(unionTypeInference.ts, 35, 11)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --)) + +foo(x); +>foo : Symbol(foo, Decl(unionTypeInference.ts, 30, 16)) +>x : Symbol(x, Decl(unionTypeInference.ts, 35, 11)) + +declare function bar(x: T, y: string | T): T; +>bar : Symbol(bar, Decl(unionTypeInference.ts, 36, 7)) +>T : Symbol(T, Decl(unionTypeInference.ts, 38, 21)) +>x : Symbol(x, Decl(unionTypeInference.ts, 38, 24)) +>T : Symbol(T, Decl(unionTypeInference.ts, 38, 21)) +>y : Symbol(y, Decl(unionTypeInference.ts, 38, 29)) +>T : Symbol(T, Decl(unionTypeInference.ts, 38, 21)) +>T : Symbol(T, Decl(unionTypeInference.ts, 38, 21)) + +const y = bar(1, 2); +>y : Symbol(y, Decl(unionTypeInference.ts, 39, 5)) +>bar : Symbol(bar, Decl(unionTypeInference.ts, 36, 7)) diff --git a/tests/baselines/reference/unionTypeInference.types b/tests/baselines/reference/unionTypeInference.types index fc5b777ff5a82..836c298bdbcce 100644 --- a/tests/baselines/reference/unionTypeInference.types +++ b/tests/baselines/reference/unionTypeInference.types @@ -1,113 +1,161 @@ === tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts === -// Verify that inferences made *to* a type parameter in a union type are secondary -// to inferences made directly to that type parameter +declare const b: boolean; +>b : boolean -function f(x: T, y: string|T): T { ->f : (x: T, y: string | T) => T ->x : T ->y : string | T +declare const s: string; +>s : string - return x; ->x : T -} +declare const sn: string | number; +>sn : string | number -var a1: number; ->a1 : number +declare function f1(x: T, y: string | T): T; +>f1 : (x: T, y: string | T) => T +>x : T +>y : string | T -var a1 = f(1, 2); ->a1 : number ->f(1, 2) : 1 | 2 ->f : (x: T, y: string | T) => T +const a1 = f1(1, 2); // 1 | 2 +>a1 : 1 | 2 +>f1(1, 2) : 1 | 2 +>f1 : (x: T, y: string | T) => T >1 : 1 >2 : 2 -var a2: number; ->a2 : number - -var a2 = f(1, "hello"); ->a2 : number ->f(1, "hello") : 1 ->f : (x: T, y: string | T) => T +const a2 = f1(1, "hello"); // 1 +>a2 : 1 +>f1(1, "hello") : 1 +>f1 : (x: T, y: string | T) => T >1 : 1 >"hello" : "hello" -var a3: number; ->a3 : number - -var a3 = f(1, a1 || "hello"); +const a3 = f1(1, sn); // number >a3 : number ->f(1, a1 || "hello") : number | "hello" ->f : (x: T, y: string | T) => T +>f1(1, sn) : number +>f1 : (x: T, y: string | T) => T >1 : 1 ->a1 || "hello" : number | "hello" ->a1 : number ->"hello" : "hello" - -var a4: any; ->a4 : any +>sn : string | number -var a4 = f(undefined, "abc"); ->a4 : any ->f(undefined, "abc") : any ->f : (x: T, y: string | T) => T +const a4 = f1(undefined, "abc"); // undefined +>a4 : undefined +>f1(undefined, "abc") : undefined +>f1 : (x: T, y: string | T) => T >undefined : undefined >"abc" : "abc" -function g(value: [string, T]): T { ->g : (value: [string, T]) => T ->value : [string, T] +const a5 = f1("foo", "bar"); // "foo" +>a5 : "foo" +>f1("foo", "bar") : "foo" +>f1 : (x: T, y: string | T) => T +>"foo" : "foo" +>"bar" : "bar" + +const a6 = f1(true, false); // boolean +>a6 : boolean +>f1(true, false) : boolean +>f1 : (x: T, y: string | T) => T +>true : true +>false : false - return value[1]; ->value[1] : T ->value : [string, T] +const a7 = f1("hello", 1); // Error +>a7 : any +>f1("hello", 1) : any +>f1 : (x: T, y: string | T) => T +>"hello" : "hello" >1 : 1 -} -var b1: boolean; ->b1 : boolean +declare function f2(value: [string, T]): T; +>f2 : (value: [string, T]) => T +>value : [string, T] -var b1 = g(["string", true]); +var b1 = f2(["string", true]); // boolean >b1 : boolean ->g(["string", true]) : boolean ->g : (value: [string, T]) => T +>f2(["string", true]) : boolean +>f2 : (value: [string, T]) => T >["string", true] : [string, true] >"string" : "string" >true : true -function h(x: string|boolean|T): T { ->h : (x: string | boolean | T) => T ->x : string | boolean | T +declare function f3(x: string | false | T): T; +>f3 : (x: string | false | T) => T +>x : string | false | T +>false : false - return typeof x === "string" || typeof x === "boolean" ? undefined : x; ->typeof x === "string" || typeof x === "boolean" ? undefined : x : T ->typeof x === "string" || typeof x === "boolean" : boolean ->typeof x === "string" : boolean ->typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" ->x : string | boolean | T ->"string" : "string" ->typeof x === "boolean" : boolean ->typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" ->x : boolean | T ->"boolean" : "boolean" ->undefined : undefined ->x : T -} +const c1 = f3(5); // 5 +>c1 : 5 +>f3(5) : 5 +>f3 : (x: string | false | T) => T +>5 : 5 -var c1: number; ->c1 : number +const c2 = f3(sn); // number +>c2 : number +>f3(sn) : number +>f3 : (x: string | false | T) => T +>sn : string | number -var c1 = h(5); ->c1 : number ->h(5) : 5 ->h : (x: string | boolean | T) => T ->5 : 5 +const c3 = f3(true); // true +>c3 : true +>f3(true) : true +>f3 : (x: string | false | T) => T +>true : true + +const c4 = f3(b); // true +>c4 : true +>f3(b) : true +>f3 : (x: string | false | T) => T +>b : boolean + +const c5 = f3("abc"); // never +>c5 : never +>f3("abc") : never +>f3 : (x: string | false | T) => T +>"abc" : "abc" -var c2: string; ->c2 : string +declare function f4(x: string & T): T; +>f4 : (x: string & T) => T +>x : string & T -var c2 = h("abc"); ->c2 : string ->h("abc") : never ->h : (x: string | boolean | T) => T +var d1 = f4("abc"); +>d1 : string +>f4("abc") : "abc" +>f4 : (x: string & T) => T >"abc" : "abc" +var d2 = f4(s); +>d2 : unknown +>f4(s) : unknown +>f4 : (x: string & T) => T +>s : string + +var d3 = f4(42); // Error +>d3 : any +>f4(42) : any +>f4 : (x: string & T) => T +>42 : 42 + +// Repros from #32434 + +declare function foo(x: T | Promise): void; +>foo : (x: T | Promise) => void +>x : T | Promise + +declare let x: false | Promise; +>x : false | Promise +>false : false +>true : true + +foo(x); +>foo(x) : void +>foo : (x: T | Promise) => void +>x : false | Promise + +declare function bar(x: T, y: string | T): T; +>bar : (x: T, y: string | T) => T +>x : T +>y : string | T + +const y = bar(1, 2); +>y : 1 | 2 +>bar(1, 2) : 1 | 2 +>bar : (x: T, y: string | T) => T +>1 : 1 +>2 : 2 + From de837ed51f8f6f329cee97932bf3a8e7ea4cc46e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 17 Jul 2019 15:07:36 -0700 Subject: [PATCH 07/18] Accept new baselines --- .../reference/unionTypeInference.errors.txt | 10 +++++----- tests/baselines/reference/unionTypeInference.js | 6 +++--- .../reference/unionTypeInference.symbols | 16 ++++++++-------- .../baselines/reference/unionTypeInference.types | 8 ++++---- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/baselines/reference/unionTypeInference.errors.txt b/tests/baselines/reference/unionTypeInference.errors.txt index 6993f4280a7e3..2fe480d0a079c 100644 --- a/tests/baselines/reference/unionTypeInference.errors.txt +++ b/tests/baselines/reference/unionTypeInference.errors.txt @@ -1,5 +1,5 @@ tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts(13,24): error TS2345: Argument of type '1' is not assignable to parameter of type 'string'. -tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts(31,13): error TS2345: Argument of type '42' is not assignable to parameter of type 'never'. +tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts(31,15): error TS2345: Argument of type '42' is not assignable to parameter of type 'never'. ==== tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts (2 errors) ==== @@ -33,10 +33,10 @@ tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference declare function f4(x: string & T): T; - var d1 = f4("abc"); - var d2 = f4(s); - var d3 = f4(42); // Error - ~~ + const d1 = f4("abc"); + const d2 = f4(s); + const d3 = f4(42); // Error + ~~ !!! error TS2345: Argument of type '42' is not assignable to parameter of type 'never'. // Repros from #32434 diff --git a/tests/baselines/reference/unionTypeInference.js b/tests/baselines/reference/unionTypeInference.js index fb94e1910f1f8..868140e83a041 100644 --- a/tests/baselines/reference/unionTypeInference.js +++ b/tests/baselines/reference/unionTypeInference.js @@ -27,9 +27,9 @@ const c5 = f3("abc"); // never declare function f4(x: string & T): T; -var d1 = f4("abc"); -var d2 = f4(s); -var d3 = f4(42); // Error +const d1 = f4("abc"); +const d2 = f4(s); +const d3 = f4(42); // Error // Repros from #32434 diff --git a/tests/baselines/reference/unionTypeInference.symbols b/tests/baselines/reference/unionTypeInference.symbols index a34be27983722..52b8809dee627 100644 --- a/tests/baselines/reference/unionTypeInference.symbols +++ b/tests/baselines/reference/unionTypeInference.symbols @@ -94,23 +94,23 @@ declare function f4(x: string & T): T; >T : Symbol(T, Decl(unionTypeInference.ts, 26, 20)) >T : Symbol(T, Decl(unionTypeInference.ts, 26, 20)) -var d1 = f4("abc"); ->d1 : Symbol(d1, Decl(unionTypeInference.ts, 28, 3)) +const d1 = f4("abc"); +>d1 : Symbol(d1, Decl(unionTypeInference.ts, 28, 5)) >f4 : Symbol(f4, Decl(unionTypeInference.ts, 24, 21)) -var d2 = f4(s); ->d2 : Symbol(d2, Decl(unionTypeInference.ts, 29, 3)) +const d2 = f4(s); +>d2 : Symbol(d2, Decl(unionTypeInference.ts, 29, 5)) >f4 : Symbol(f4, Decl(unionTypeInference.ts, 24, 21)) >s : Symbol(s, Decl(unionTypeInference.ts, 1, 13)) -var d3 = f4(42); // Error ->d3 : Symbol(d3, Decl(unionTypeInference.ts, 30, 3)) +const d3 = f4(42); // Error +>d3 : Symbol(d3, Decl(unionTypeInference.ts, 30, 5)) >f4 : Symbol(f4, Decl(unionTypeInference.ts, 24, 21)) // Repros from #32434 declare function foo(x: T | Promise): void; ->foo : Symbol(foo, Decl(unionTypeInference.ts, 30, 16)) +>foo : Symbol(foo, Decl(unionTypeInference.ts, 30, 18)) >T : Symbol(T, Decl(unionTypeInference.ts, 34, 21)) >x : Symbol(x, Decl(unionTypeInference.ts, 34, 24)) >T : Symbol(T, Decl(unionTypeInference.ts, 34, 21)) @@ -122,7 +122,7 @@ declare let x: false | Promise; >Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --)) foo(x); ->foo : Symbol(foo, Decl(unionTypeInference.ts, 30, 16)) +>foo : Symbol(foo, Decl(unionTypeInference.ts, 30, 18)) >x : Symbol(x, Decl(unionTypeInference.ts, 35, 11)) declare function bar(x: T, y: string | T): T; diff --git a/tests/baselines/reference/unionTypeInference.types b/tests/baselines/reference/unionTypeInference.types index 836c298bdbcce..e6d47fa4bdaa6 100644 --- a/tests/baselines/reference/unionTypeInference.types +++ b/tests/baselines/reference/unionTypeInference.types @@ -113,19 +113,19 @@ declare function f4(x: string & T): T; >f4 : (x: string & T) => T >x : string & T -var d1 = f4("abc"); ->d1 : string +const d1 = f4("abc"); +>d1 : "abc" >f4("abc") : "abc" >f4 : (x: string & T) => T >"abc" : "abc" -var d2 = f4(s); +const d2 = f4(s); >d2 : unknown >f4(s) : unknown >f4 : (x: string & T) => T >s : string -var d3 = f4(42); // Error +const d3 = f4(42); // Error >d3 : any >f4(42) : any >f4 : (x: string & T) => T From c6b77fa5dfe9a396c24035ed77a947c81a1ba42f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 17 Jul 2019 15:15:56 -0700 Subject: [PATCH 08/18] Fix lint error --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1a2fc79534e37..81c2a9590ba04 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15773,7 +15773,7 @@ namespace ts { // type variable. If there is more than one naked type variable, give lower priority to // the inferences as they are less specific. if (typeVariableCount > 0) { - const unmatched = flatMap(sources, (s, i) => matched![i] ? undefined : s); + const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); if (unmatched.length) { const s = getUnionType(unmatched); const savePriority = priority; From 8f020559fbde9d0c8252a3fde5dc4f2890461fb0 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 17 Jul 2019 18:49:56 -0700 Subject: [PATCH 09/18] Treat Array and ReadonlyArray as synonymous in inference --- src/compiler/checker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 81c2a9590ba04..f044ee8e3757f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15587,7 +15587,8 @@ namespace ts { } } } - if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target) { + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( + (source).target === (target).target || isArrayType(source) && isArrayType(target))) { // If source and target are references to the same generic type, infer from type arguments inferFromTypeArguments((source).typeArguments || emptyArray, (target).typeArguments || emptyArray, getVariances((source).target)); } From 2450c1947facf8101cb6cba2e030877ec28a0a88 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 20 Jul 2019 09:57:10 -0700 Subject: [PATCH 10/18] Make lower priority inferences when inference process is blocked --- src/compiler/checker.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f044ee8e3757f..c2510f84a1940 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15459,6 +15459,7 @@ namespace ts { let bivariant = false; let propagationType: Type; let inferenceCount = 0; + let inferenceBlocked = false; let allowComplexConstraintInference = true; inferFromTypes(originalSource, originalTarget); @@ -15655,6 +15656,7 @@ namespace ts { if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) { const key = source.id + "," + target.id; if (visited && visited.get(key)) { + inferenceBlocked = true; return; } (visited || (visited = createMap())).set(key, true); @@ -15667,6 +15669,7 @@ namespace ts { const symbol = isNonConstructorObject ? target.symbol : undefined; if (symbol) { if (contains(symbolStack, symbol)) { + inferenceBlocked = true; return; } (symbolStack || (symbolStack = [])).push(symbol); @@ -15755,6 +15758,8 @@ namespace ts { const sources = source.flags & TypeFlags.Union ? (source).types : [source]; const matched = new Array(sources.length); let typeVariableCount = 0; + const saveInferenceBlocked = inferenceBlocked; + inferenceBlocked = 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. for (const t of target.types) { @@ -15771,14 +15776,15 @@ namespace ts { } // If there are naked type variables in the target, create a union of the source types // from which no inferences have been made so far and infer from that union to each naked - // type variable. If there is more than one naked type variable, give lower priority to - // the inferences as they are less specific. + // type variable. If there is more than one naked type variable, or if inference was blocked + // (meaning we didn't explore the types fully), give lower priority to the inferences as + // they are less specific. if (typeVariableCount > 0) { const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); if (unmatched.length) { const s = getUnionType(unmatched); const savePriority = priority; - if (typeVariableCount > 1) { + if (typeVariableCount > 1 || inferenceBlocked) { priority |= InferencePriority.NakedTypeVariable; } for (const t of target.types) { @@ -15789,6 +15795,7 @@ namespace ts { priority = savePriority; } } + inferenceBlocked = saveInferenceBlocked; } function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { From d96d16e10b1c307719314886a2a740735593a2e8 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 20 Jul 2019 10:01:59 -0700 Subject: [PATCH 11/18] Add additional test --- .../typeInference/unionTypeInference.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts b/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts index ccedb753354ee..4d9a3eae21ede 100644 --- a/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts +++ b/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts @@ -32,6 +32,17 @@ const d1 = f4("abc"); const d2 = f4(s); const d3 = f4(42); // Error +export interface Foo { + then(f: (x: T) => U | Foo, g: U): Foo; +} +export interface Bar { + then(f: (x: T) => S | Bar, g: S): Bar; +} + +function qux(p1: Foo, p2: Bar) { + p1 = p2; +} + // Repros from #32434 declare function foo(x: T | Promise): void; From 623a1725c8f7ae779eb754a0d5d9f29a7e633688 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 20 Jul 2019 10:02:46 -0700 Subject: [PATCH 12/18] Accept new baselines --- .../reference/unionTypeInference.errors.txt | 11 +++ .../baselines/reference/unionTypeInference.js | 15 ++++ .../reference/unionTypeInference.symbols | 83 +++++++++++++++---- .../reference/unionTypeInference.types | 26 ++++++ 4 files changed, 118 insertions(+), 17 deletions(-) diff --git a/tests/baselines/reference/unionTypeInference.errors.txt b/tests/baselines/reference/unionTypeInference.errors.txt index 2fe480d0a079c..f28f32f337398 100644 --- a/tests/baselines/reference/unionTypeInference.errors.txt +++ b/tests/baselines/reference/unionTypeInference.errors.txt @@ -39,6 +39,17 @@ tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference ~~ !!! error TS2345: Argument of type '42' is not assignable to parameter of type 'never'. + export interface Foo { + then(f: (x: T) => U | Foo, g: U): Foo; + } + export interface Bar { + then(f: (x: T) => S | Bar, g: S): Bar; + } + + function qux(p1: Foo, p2: Bar) { + p1 = p2; + } + // Repros from #32434 declare function foo(x: T | Promise): void; diff --git a/tests/baselines/reference/unionTypeInference.js b/tests/baselines/reference/unionTypeInference.js index 868140e83a041..e41872eb4d4d8 100644 --- a/tests/baselines/reference/unionTypeInference.js +++ b/tests/baselines/reference/unionTypeInference.js @@ -31,6 +31,17 @@ const d1 = f4("abc"); const d2 = f4(s); const d3 = f4(42); // Error +export interface Foo { + then(f: (x: T) => U | Foo, g: U): Foo; +} +export interface Bar { + then(f: (x: T) => S | Bar, g: S): Bar; +} + +function qux(p1: Foo, p2: Bar) { + p1 = p2; +} + // Repros from #32434 declare function foo(x: T | Promise): void; @@ -43,6 +54,7 @@ const y = bar(1, 2); //// [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 @@ -59,5 +71,8 @@ var c5 = f3("abc"); // never var d1 = f4("abc"); var d2 = f4(s); var d3 = f4(42); // Error +function qux(p1, p2) { + p1 = p2; +} foo(x); var y = bar(1, 2); diff --git a/tests/baselines/reference/unionTypeInference.symbols b/tests/baselines/reference/unionTypeInference.symbols index 52b8809dee627..0d0bf6ae9be81 100644 --- a/tests/baselines/reference/unionTypeInference.symbols +++ b/tests/baselines/reference/unionTypeInference.symbols @@ -107,34 +107,83 @@ const d3 = f4(42); // Error >d3 : Symbol(d3, Decl(unionTypeInference.ts, 30, 5)) >f4 : Symbol(f4, Decl(unionTypeInference.ts, 24, 21)) +export interface Foo { +>Foo : Symbol(Foo, Decl(unionTypeInference.ts, 30, 18)) +>T : Symbol(T, Decl(unionTypeInference.ts, 32, 21)) + + then(f: (x: T) => U | Foo, g: U): Foo; +>then : Symbol(Foo.then, Decl(unionTypeInference.ts, 32, 25)) +>U : Symbol(U, Decl(unionTypeInference.ts, 33, 9)) +>f : Symbol(f, Decl(unionTypeInference.ts, 33, 12)) +>x : Symbol(x, Decl(unionTypeInference.ts, 33, 16)) +>T : Symbol(T, Decl(unionTypeInference.ts, 32, 21)) +>U : Symbol(U, Decl(unionTypeInference.ts, 33, 9)) +>Foo : Symbol(Foo, Decl(unionTypeInference.ts, 30, 18)) +>U : Symbol(U, Decl(unionTypeInference.ts, 33, 9)) +>g : Symbol(g, Decl(unionTypeInference.ts, 33, 36)) +>U : Symbol(U, Decl(unionTypeInference.ts, 33, 9)) +>Foo : Symbol(Foo, Decl(unionTypeInference.ts, 30, 18)) +>U : Symbol(U, Decl(unionTypeInference.ts, 33, 9)) +} +export interface Bar { +>Bar : Symbol(Bar, Decl(unionTypeInference.ts, 34, 1)) +>T : Symbol(T, Decl(unionTypeInference.ts, 35, 21)) + + then(f: (x: T) => S | Bar, g: S): Bar; +>then : Symbol(Bar.then, Decl(unionTypeInference.ts, 35, 25)) +>S : Symbol(S, Decl(unionTypeInference.ts, 36, 9)) +>f : Symbol(f, Decl(unionTypeInference.ts, 36, 12)) +>x : Symbol(x, Decl(unionTypeInference.ts, 36, 16)) +>T : Symbol(T, Decl(unionTypeInference.ts, 35, 21)) +>S : Symbol(S, Decl(unionTypeInference.ts, 36, 9)) +>Bar : Symbol(Bar, Decl(unionTypeInference.ts, 34, 1)) +>S : Symbol(S, Decl(unionTypeInference.ts, 36, 9)) +>g : Symbol(g, Decl(unionTypeInference.ts, 36, 36)) +>S : Symbol(S, Decl(unionTypeInference.ts, 36, 9)) +>Bar : Symbol(Bar, Decl(unionTypeInference.ts, 34, 1)) +>S : Symbol(S, Decl(unionTypeInference.ts, 36, 9)) +} + +function qux(p1: Foo, p2: Bar) { +>qux : Symbol(qux, Decl(unionTypeInference.ts, 37, 1)) +>p1 : Symbol(p1, Decl(unionTypeInference.ts, 39, 13)) +>Foo : Symbol(Foo, Decl(unionTypeInference.ts, 30, 18)) +>p2 : Symbol(p2, Decl(unionTypeInference.ts, 39, 27)) +>Bar : Symbol(Bar, Decl(unionTypeInference.ts, 34, 1)) + + p1 = p2; +>p1 : Symbol(p1, Decl(unionTypeInference.ts, 39, 13)) +>p2 : Symbol(p2, Decl(unionTypeInference.ts, 39, 27)) +} + // Repros from #32434 declare function foo(x: T | Promise): void; ->foo : Symbol(foo, Decl(unionTypeInference.ts, 30, 18)) ->T : Symbol(T, Decl(unionTypeInference.ts, 34, 21)) ->x : Symbol(x, Decl(unionTypeInference.ts, 34, 24)) ->T : Symbol(T, Decl(unionTypeInference.ts, 34, 21)) +>foo : Symbol(foo, Decl(unionTypeInference.ts, 41, 1)) +>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, --, --)) ->T : Symbol(T, Decl(unionTypeInference.ts, 34, 21)) +>T : Symbol(T, Decl(unionTypeInference.ts, 45, 21)) declare let x: false | Promise; ->x : Symbol(x, Decl(unionTypeInference.ts, 35, 11)) +>x : Symbol(x, Decl(unionTypeInference.ts, 46, 11)) >Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --)) foo(x); ->foo : Symbol(foo, Decl(unionTypeInference.ts, 30, 18)) ->x : Symbol(x, Decl(unionTypeInference.ts, 35, 11)) +>foo : Symbol(foo, Decl(unionTypeInference.ts, 41, 1)) +>x : Symbol(x, Decl(unionTypeInference.ts, 46, 11)) declare function bar(x: T, y: string | T): T; ->bar : Symbol(bar, Decl(unionTypeInference.ts, 36, 7)) ->T : Symbol(T, Decl(unionTypeInference.ts, 38, 21)) ->x : Symbol(x, Decl(unionTypeInference.ts, 38, 24)) ->T : Symbol(T, Decl(unionTypeInference.ts, 38, 21)) ->y : Symbol(y, Decl(unionTypeInference.ts, 38, 29)) ->T : Symbol(T, Decl(unionTypeInference.ts, 38, 21)) ->T : Symbol(T, Decl(unionTypeInference.ts, 38, 21)) +>bar : Symbol(bar, Decl(unionTypeInference.ts, 47, 7)) +>T : Symbol(T, Decl(unionTypeInference.ts, 49, 21)) +>x : Symbol(x, Decl(unionTypeInference.ts, 49, 24)) +>T : Symbol(T, Decl(unionTypeInference.ts, 49, 21)) +>y : Symbol(y, Decl(unionTypeInference.ts, 49, 29)) +>T : Symbol(T, Decl(unionTypeInference.ts, 49, 21)) +>T : Symbol(T, Decl(unionTypeInference.ts, 49, 21)) const y = bar(1, 2); ->y : Symbol(y, Decl(unionTypeInference.ts, 39, 5)) ->bar : Symbol(bar, Decl(unionTypeInference.ts, 36, 7)) +>y : Symbol(y, Decl(unionTypeInference.ts, 50, 5)) +>bar : Symbol(bar, Decl(unionTypeInference.ts, 47, 7)) diff --git a/tests/baselines/reference/unionTypeInference.types b/tests/baselines/reference/unionTypeInference.types index e6d47fa4bdaa6..6675e63f8c4b6 100644 --- a/tests/baselines/reference/unionTypeInference.types +++ b/tests/baselines/reference/unionTypeInference.types @@ -131,6 +131,32 @@ const d3 = f4(42); // Error >f4 : (x: string & T) => T >42 : 42 +export interface Foo { + then(f: (x: T) => U | Foo, g: U): Foo; +>then : (f: (x: T) => U | Foo, g: U) => Foo +>f : (x: T) => U | Foo +>x : T +>g : U +} +export interface Bar { + then(f: (x: T) => S | Bar, g: S): Bar; +>then : (f: (x: T) => S | Bar, g: S) => Bar +>f : (x: T) => S | Bar +>x : T +>g : S +} + +function qux(p1: Foo, p2: Bar) { +>qux : (p1: Foo, p2: Bar) => void +>p1 : Foo +>p2 : Bar + + p1 = p2; +>p1 = p2 : Bar +>p1 : Foo +>p2 : Bar +} + // Repros from #32434 declare function foo(x: T | Promise): void; From 2541a5d0fff2037854732253dfc74a096d4c1c04 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 20 Jul 2019 14:33:35 -0700 Subject: [PATCH 13/18] Always infer between distinct type references to same target --- src/compiler/checker.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0b4ae0d9d3305..7b39ce98b9302 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15958,9 +15958,14 @@ namespace ts { } } + function isTypeReferenceToSameTarget(source: Type, target: Type) { + return !!(getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && + (source).target === (target).target); + } + function typeIdenticalToSomeType(type: Type, types: Type[]): boolean { for (const t of types) { - if (isTypeIdenticalTo(t, type)) { + if (t === type || !isTypeReferenceToSameTarget(t, type) && isTypeIdenticalTo(t, type)) { return true; } } From 9b2d9cdffcc540d875b0cab533b9915337fa2097 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 21 Jul 2019 14:07:45 -0700 Subject: [PATCH 14/18] Fix issues uncovered by DT tests --- src/compiler/checker.ts | 50 +++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7b39ce98b9302..19517b6715cc2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13540,6 +13540,9 @@ namespace ts { if (relation !== identityRelation) { source = getApparentType(source); } + else if (isGenericMappedType(source)) { + return Ternary.False; + } if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target && !(getObjectFlags(source) & ObjectFlags.MarkerType || getObjectFlags(target) & ObjectFlags.MarkerType)) { // We have type references to the same generic type, and the type references are not marker @@ -15456,7 +15459,7 @@ namespace ts { function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, contravariant = false) { let symbolStack: Symbol[]; - let visited: Map; + let visited: Map; let bivariant = false; let propagationType: Type; let inferenceCount = 0; @@ -15656,15 +15659,17 @@ namespace ts { } if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) { const key = source.id + "," + target.id; - if (visited && visited.get(key)) { - inferenceBlocked = true; + const visitCount = visited && visited.get(key); + if (visitCount !== undefined) { + inferenceCount += visitCount; return; } - (visited || (visited = createMap())).set(key, true); + (visited || (visited = createMap())).set(key, 0); // If we are already processing another target type with the same associated symbol (such as // an instantiation of the same generic type), we do not explore this target as it would yield // no further inferences. We exclude the static side of classes from this check since it shares // its symbol with the instance side which would lead to false positives. + const startCount = inferenceCount; const isNonConstructorObject = target.flags & TypeFlags.Object && !(getObjectFlags(target) & ObjectFlags.Anonymous && target.symbol && target.symbol.flags & SymbolFlags.Class); const symbol = isNonConstructorObject ? target.symbol : undefined; @@ -15680,14 +15685,21 @@ namespace ts { else { inferFromObjectTypes(source, target); } + visited.set(key, inferenceCount - startCount); } } function inferFromTypesOnce(source: Type, target: Type) { const key = source.id + "," + target.id; - if (!visited || !visited.get(key)) { - (visited || (visited = createMap())).set(key, true); + const count = visited && visited.get(key); + if (count !== undefined) { + inferenceCount += count; + } + else { + (visited || (visited = createMap())).set(key, 0); + const startCount = inferenceCount; inferFromTypes(source, target); + visited.set(key, inferenceCount - startCount); } } } @@ -15780,23 +15792,28 @@ namespace ts { // type variable. If there is more than one naked type variable, or if inference was blocked // (meaning we didn't explore the types fully), give lower priority to the inferences as // they are less specific. - if (typeVariableCount > 0) { + if (typeVariableCount === 1 && !inferenceBlocked) { const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); if (unmatched.length) { const s = getUnionType(unmatched); - const savePriority = priority; - if (typeVariableCount > 1 || inferenceBlocked) { - priority |= InferencePriority.NakedTypeVariable; - } for (const t of target.types) { if (getInferenceInfoForType(t)) { inferFromTypes(s, t); } } - priority = savePriority; } } - inferenceBlocked = saveInferenceBlocked; + inferenceBlocked = inferenceBlocked || saveInferenceBlocked; + if (typeVariableCount > 0) { + const savePriority = priority; + priority |= InferencePriority.NakedTypeVariable; + for (const t of target.types) { + if (getInferenceInfoForType(t)) { + inferFromTypes(source, t); + } + } + priority = savePriority; + } } function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { @@ -15958,14 +15975,13 @@ namespace ts { } } - function isTypeReferenceToSameTarget(source: Type, target: Type) { - return !!(getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && - (source).target === (target).target); + function isMatchableType(type: Type) { + return !(type.flags & TypeFlags.Object) || !!(getObjectFlags(type) & ObjectFlags.Anonymous); } function typeIdenticalToSomeType(type: Type, types: Type[]): boolean { for (const t of types) { - if (t === type || !isTypeReferenceToSameTarget(t, type) && isTypeIdenticalTo(t, type)) { + if (t === type || isMatchableType(t) && isMatchableType(type) && isTypeIdenticalTo(t, type)) { return true; } } From 203fd9ff9e1a9923a7dc663cc738c4072b6431ee Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 22 Jul 2019 08:01:22 -0700 Subject: [PATCH 15/18] Combine multiple separate code paths --- src/compiler/checker.ts | 193 ++++++++++++++++++---------------------- 1 file changed, 86 insertions(+), 107 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 19517b6715cc2..9f64e13911ccc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15577,7 +15577,7 @@ namespace ts { // Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine const simplified = getSimplifiedType(target, /*writing*/ false); if (simplified !== target) { - inferFromTypesOnce(source, simplified); + invokeOnce(source, simplified, inferFromTypes); } else if (target.flags & TypeFlags.IndexedAccess) { const indexType = getSimplifiedType((target as IndexedAccessType).indexType, /*writing*/ false); @@ -15586,7 +15586,7 @@ namespace ts { if (indexType.flags & TypeFlags.Instantiable) { const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType, /*writing*/ false), indexType, /*writing*/ false); if (simplified && simplified !== target) { - inferFromTypesOnce(source, simplified); + invokeOnce(source, simplified, inferFromTypes); } } } @@ -15621,15 +15621,12 @@ namespace ts { inferFromTypes(getTrueTypeFromConditionalType(source), getTrueTypeFromConditionalType(target)); inferFromTypes(getFalseTypeFromConditionalType(source), getFalseTypeFromConditionalType(target)); } - else if (target.flags & TypeFlags.Union) { - inferToUnionType(source, target); - } - else if (target.flags & TypeFlags.Intersection) { - inferToMultipleTypes(source, (target).types, /*isIntersection*/ true); - } else if (target.flags & TypeFlags.Conditional && !contravariant) { const targetTypes = [getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)]; - inferToMultipleTypes(source, targetTypes, /*isIntersection*/ false); + inferToMultipleTypes(source, targetTypes, target.flags); + } + else if (target.flags & TypeFlags.UnionOrIntersection) { + inferToMultipleTypes(source, (target).types, target.flags); } else if (source.flags & TypeFlags.Union) { // Source is a union or intersection type, infer from each constituent type @@ -15658,50 +15655,22 @@ namespace ts { source = apparentSource; } if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) { - const key = source.id + "," + target.id; - const visitCount = visited && visited.get(key); - if (visitCount !== undefined) { - inferenceCount += visitCount; - return; - } - (visited || (visited = createMap())).set(key, 0); - // If we are already processing another target type with the same associated symbol (such as - // an instantiation of the same generic type), we do not explore this target as it would yield - // no further inferences. We exclude the static side of classes from this check since it shares - // its symbol with the instance side which would lead to false positives. - const startCount = inferenceCount; - const isNonConstructorObject = target.flags & TypeFlags.Object && - !(getObjectFlags(target) & ObjectFlags.Anonymous && target.symbol && target.symbol.flags & SymbolFlags.Class); - const symbol = isNonConstructorObject ? target.symbol : undefined; - if (symbol) { - if (contains(symbolStack, symbol)) { - inferenceBlocked = true; - return; - } - (symbolStack || (symbolStack = [])).push(symbol); - inferFromObjectTypes(source, target); - symbolStack.pop(); - } - else { - inferFromObjectTypes(source, target); - } - visited.set(key, inferenceCount - startCount); + invokeOnce(source, target, inferFromObjectTypes); } } + } - function inferFromTypesOnce(source: Type, target: Type) { - const key = source.id + "," + target.id; - const count = visited && visited.get(key); - if (count !== undefined) { - inferenceCount += count; - } - else { - (visited || (visited = createMap())).set(key, 0); - const startCount = inferenceCount; - inferFromTypes(source, target); - visited.set(key, inferenceCount - startCount); - } + function invokeOnce(source: Type, target: Type, action: (source: Type, target: Type) => void) { + const key = source.id + "," + target.id; + const count = visited && visited.get(key); + if (count !== undefined) { + inferenceCount += count; + return; } + (visited || (visited = createMap())).set(key, 0); + const startCount = inferenceCount; + action(source, target); + visited.set(key, inferenceCount - startCount); } function inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) { @@ -15738,76 +15707,64 @@ namespace ts { return undefined; } - function inferToMultipleTypes(source: Type, targets: Type[], isIntersection: boolean) { - // We infer from types that are not naked type variables first so that inferences we - // make from nested naked type variables and given slightly higher priority by virtue - // of being first in the candidates array. + function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) { let typeVariableCount = 0; - for (const t of targets) { - if (getInferenceInfoForType(t)) { - typeVariableCount++; + if (targetFlags & TypeFlags.Union) { + const sources = source.flags & TypeFlags.Union ? (source).types : [source]; + const matched = new Array(sources.length); + const saveInferenceBlocked = inferenceBlocked; + inferenceBlocked = 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. + for (const t of targets) { + if (getInferenceInfoForType(t)) { + typeVariableCount++; + } + else { + for (let i = 0; i < sources.length; i++) { + const count = inferenceCount; + inferFromTypes(sources[i], t); + if (count !== inferenceCount) matched[i] = true; + } + } } - else { - inferFromTypes(source, t); + // If the target has a single naked type variable and inference wasn't blocked (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 && !inferenceBlocked) { + const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); + if (unmatched.length) { + const s = getUnionType(unmatched); + for (const t of targets) { + if (getInferenceInfoForType(t)) { + inferFromTypes(s, t); + } + } + } } + inferenceBlocked = inferenceBlocked || saveInferenceBlocked; } - // Inferences directly to naked type variables are given lower priority as they are - // less specific. For example, when inferring from Promise to T | Promise, - // we want to infer string for T, not Promise | string. For intersection types - // we only infer to single naked type variables. - if (isIntersection ? typeVariableCount === 1 : typeVariableCount !== 0) { - const savePriority = priority; - priority |= InferencePriority.NakedTypeVariable; + else { + // We infer from types that are not naked type variables first so that inferences we + // make from nested naked type variables and given slightly higher priority by virtue + // of being first in the candidates array. for (const t of targets) { if (getInferenceInfoForType(t)) { - inferFromTypes(source, t); + typeVariableCount++; } - } - priority = savePriority; - } - } - - function inferToUnionType(source: Type, target: UnionType) { - const sources = source.flags & TypeFlags.Union ? (source).types : [source]; - const matched = new Array(sources.length); - let typeVariableCount = 0; - const saveInferenceBlocked = inferenceBlocked; - inferenceBlocked = 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. - for (const t of target.types) { - if (getInferenceInfoForType(t)) { - typeVariableCount++; - } - else { - for (let i = 0; i < sources.length; i++) { - const count = inferenceCount; - inferFromTypes(sources[i], t); - if (count !== inferenceCount) matched[i] = true; - } - } - } - // If there are naked type variables in the target, create a union of the source types - // from which no inferences have been made so far and infer from that union to each naked - // type variable. If there is more than one naked type variable, or if inference was blocked - // (meaning we didn't explore the types fully), give lower priority to the inferences as - // they are less specific. - if (typeVariableCount === 1 && !inferenceBlocked) { - const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); - if (unmatched.length) { - const s = getUnionType(unmatched); - for (const t of target.types) { - if (getInferenceInfoForType(t)) { - inferFromTypes(s, t); - } + else { + inferFromTypes(source, t); } } } - inferenceBlocked = inferenceBlocked || saveInferenceBlocked; - if (typeVariableCount > 0) { + // Inferences directly to naked type variables are given lower priority as they are + // less specific. For example, when inferring from Promise to T | Promise, + // we want to infer string for T, not Promise | string. For intersection types + // we only infer to single naked type variables. + if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) { const savePriority = priority; priority |= InferencePriority.NakedTypeVariable; - for (const t of target.types) { + for (const t of targets) { if (getInferenceInfoForType(t)) { inferFromTypes(source, t); } @@ -15873,6 +15830,28 @@ namespace ts { } function inferFromObjectTypes(source: Type, target: Type) { + // If we are already processing another target type with the same associated symbol (such as + // an instantiation of the same generic type), we do not explore this target as it would yield + // no further inferences. We exclude the static side of classes from this check since it shares + // its symbol with the instance side which would lead to false positives. + const isNonConstructorObject = target.flags & TypeFlags.Object && + !(getObjectFlags(target) & ObjectFlags.Anonymous && target.symbol && target.symbol.flags & SymbolFlags.Class); + const symbol = isNonConstructorObject ? target.symbol : undefined; + if (symbol) { + if (contains(symbolStack, symbol)) { + inferenceBlocked = true; + return; + } + (symbolStack || (symbolStack = [])).push(symbol); + inferFromObjectTypesWorker(source, target); + symbolStack.pop(); + } + else { + inferFromObjectTypesWorker(source, target); + } + } + + function inferFromObjectTypesWorker(source: Type, target: Type) { if (isGenericMappedType(source) && isGenericMappedType(target)) { // The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer // from S to T and from X to Y. From b822def6effe8521d14b218bf3287d83c761de21 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 22 Jul 2019 11:07:33 -0700 Subject: [PATCH 16/18] Minor cleanup plus more comments --- src/compiler/checker.ts | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9f64e13911ccc..0a64e0552e202 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15463,7 +15463,7 @@ namespace ts { let bivariant = false; let propagationType: Type; let inferenceCount = 0; - let inferenceBlocked = false; + let inferenceIncomplete = false; let allowComplexConstraintInference = true; inferFromTypes(originalSource, originalTarget); @@ -15710,14 +15710,16 @@ namespace ts { function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) { let typeVariableCount = 0; if (targetFlags & TypeFlags.Union) { + let nakedTypeVariable: Type | undefined; const sources = source.flags & TypeFlags.Union ? (source).types : [source]; const matched = new Array(sources.length); - const saveInferenceBlocked = inferenceBlocked; - inferenceBlocked = false; + const saveInferenceIncomplete = inferenceIncomplete; + inferenceIncomplete = 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. for (const t of targets) { if (getInferenceInfoForType(t)) { + nakedTypeVariable = t; typeVariableCount++; } else { @@ -15728,21 +15730,18 @@ namespace ts { } } } - // If the target has a single naked type variable and inference wasn't blocked (meaning - // we explored the types fully), create a union of the source types from which no inferences + 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 && !inferenceBlocked) { + if (typeVariableCount === 1 && inferenceComplete) { const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); if (unmatched.length) { - const s = getUnionType(unmatched); - for (const t of targets) { - if (getInferenceInfoForType(t)) { - inferFromTypes(s, t); - } - } + inferFromTypes(getUnionType(unmatched), nakedTypeVariable!); + return; } } - inferenceBlocked = inferenceBlocked || saveInferenceBlocked; } else { // We infer from types that are not naked type variables first so that inferences we @@ -15839,7 +15838,7 @@ namespace ts { const symbol = isNonConstructorObject ? target.symbol : undefined; if (symbol) { if (contains(symbolStack, symbol)) { - inferenceBlocked = true; + inferenceIncomplete = true; return; } (symbolStack || (symbolStack = [])).push(symbol); @@ -15955,10 +15954,13 @@ namespace ts { } function isMatchableType(type: Type) { + // We exclude non-anonymous object types because some frameworks (e.g. Ember) rely on the ability to + // infer between types that don't witness their type variables. Such types would otherwise be eliminated + // because they appear identical. return !(type.flags & TypeFlags.Object) || !!(getObjectFlags(type) & ObjectFlags.Anonymous); } - function typeIdenticalToSomeType(type: Type, types: Type[]): boolean { + function typeMatchedBySomeType(type: Type, types: Type[]): boolean { for (const t of types) { if (t === type || isMatchableType(t) && isMatchableType(type) && isTypeIdenticalTo(t, type)) { return true; @@ -15968,12 +15970,12 @@ namespace ts { } function findMatchedType(type: Type, target: UnionOrIntersectionType) { - if (typeIdenticalToSomeType(type, target.types)) { + if (typeMatchedBySomeType(type, target.types)) { return type; } if (type.flags & (TypeFlags.NumberLiteral | TypeFlags.StringLiteral) && target.flags & TypeFlags.Union) { const base = getBaseTypeOfLiteralType(type); - if (typeIdenticalToSomeType(base, target.types)) { + if (typeMatchedBySomeType(base, target.types)) { return base; } } @@ -15987,7 +15989,7 @@ namespace ts { function removeTypesFromUnionOrIntersection(type: UnionOrIntersectionType, typesToRemove: Type[]) { const reducedTypes: Type[] = []; for (const t of type.types) { - if (!typeIdenticalToSomeType(t, typesToRemove)) { + if (!typeMatchedBySomeType(t, typesToRemove)) { reducedTypes.push(t); } } From 3206f5fb94d7962c6f0588289a5c670bfc0b4b54 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 23 Jul 2019 06:38:49 -0700 Subject: [PATCH 17/18] When inferring from XXX to T | XXX make no inferece for T (instead of never) --- src/compiler/checker.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0a64e0552e202..72d91c184d7aa 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15515,16 +15515,18 @@ namespace ts { // removing the identically matched constituents. For example, when inferring from // 'string | string[]' to 'string | T' we reduce the types to 'string[]' and 'T'. if (matchingTypes) { - source = removeTypesFromUnionOrIntersection(source, matchingTypes); - target = removeTypesFromUnionOrIntersection(target, matchingTypes); + const s = removeTypesFromUnionOrIntersection(source, matchingTypes); + const t = removeTypesFromUnionOrIntersection(target, matchingTypes); + if (!(s && t)) return; + source = s; + target = t; } } else if (target.flags & TypeFlags.Union && !(target.flags & TypeFlags.EnumLiteral) || target.flags & TypeFlags.Intersection) { const matched = findMatchedType(source, target); if (matched) { inferFromTypes(matched, matched); - source = target.flags & TypeFlags.Union ? neverType : unknownType; - target = removeTypesFromUnionOrIntersection(target, [matched]); + return; } } else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { @@ -15993,7 +15995,7 @@ namespace ts { reducedTypes.push(t); } } - return type.flags & TypeFlags.Union ? getUnionType(reducedTypes) : getIntersectionType(reducedTypes); + return reducedTypes.length ? type.flags & TypeFlags.Union ? getUnionType(reducedTypes) : getIntersectionType(reducedTypes) : undefined; } function hasPrimitiveConstraint(type: TypeParameter): boolean { From 564685692f8103e6b8f90dd47283aa2363ef768f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 23 Jul 2019 06:38:58 -0700 Subject: [PATCH 18/18] Accept new baselines --- .../baselines/reference/unionAndIntersectionInference2.types | 2 +- tests/baselines/reference/unionTypeInference.types | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/baselines/reference/unionAndIntersectionInference2.types b/tests/baselines/reference/unionAndIntersectionInference2.types index 031354506b795..24f24220bd972 100644 --- a/tests/baselines/reference/unionAndIntersectionInference2.types +++ b/tests/baselines/reference/unionAndIntersectionInference2.types @@ -20,7 +20,7 @@ var e1: number | string | boolean; >e1 : string | number | boolean f1(a1); // string ->f1(a1) : never +>f1(a1) : unknown >f1 : (x: string | T) => T >a1 : string diff --git a/tests/baselines/reference/unionTypeInference.types b/tests/baselines/reference/unionTypeInference.types index 6675e63f8c4b6..304bbcd33ec51 100644 --- a/tests/baselines/reference/unionTypeInference.types +++ b/tests/baselines/reference/unionTypeInference.types @@ -104,8 +104,8 @@ const c4 = f3(b); // true >b : boolean const c5 = f3("abc"); // never ->c5 : never ->f3("abc") : never +>c5 : unknown +>f3("abc") : unknown >f3 : (x: string | false | T) => T >"abc" : "abc"