From cc1c2ab6b2c2165b393b8aa3397e3a7a11b8f33b Mon Sep 17 00:00:00 2001 From: Matt McCutchen Date: Fri, 17 Aug 2018 21:35:03 -0400 Subject: [PATCH] Go back to the old narrowing algorithm (pre #26143) and avoid #26130 by skipping narrowing if the old algorithm produces a type to which the assigned type is not assignable. This also means we'll no longer narrow for erroneous assignments where the assigned type is not assignable to the declared type. This is the reason for the numericLiteralTypes3 baseline change. Fixes #26405. --- src/compiler/checker.ts | 20 +++++++++++++++-- .../reference/assignmentTypeNarrowing.js | 8 +++++++ .../reference/assignmentTypeNarrowing.symbols | 20 +++++++++++++++++ .../reference/assignmentTypeNarrowing.types | 22 +++++++++++++++++++ .../reference/enumAssignmentCompat3.types | 4 ++-- .../reference/numericLiteralTypes3.types | 4 ++-- .../assignmentTypeNarrowing.ts | 6 +++++ 7 files changed, 78 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 26b93b19335..0b357bd771d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13900,6 +13900,18 @@ namespace ts { return flow.id; } + function typeMaybeAssignableTo(source: Type, target: Type) { + if (!(source.flags & TypeFlags.Union)) { + return isTypeAssignableTo(source, target); + } + for (const t of (source).types) { + if (isTypeAssignableTo(t, target)) { + return true; + } + } + return false; + } + // Remove those constituent types of declaredType to which no constituent type of assignedType is assignable. // For example, when a variable of type number | string | boolean is assigned a value of type number | boolean, // we remove type string. @@ -13908,8 +13920,12 @@ namespace ts { if (assignedType.flags & TypeFlags.Never) { return assignedType; } - const reducedType = filterType(declaredType, t => isTypeComparableTo(assignedType, t)); - if (!(reducedType.flags & TypeFlags.Never)) { + const reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); + // Our crude heuristic produces an invalid result in some cases: see GH#26130. + // For now, when that happens, we give up and don't narrow at all. (This also + // means we'll never narrow for erroneous assignments where the assigned type + // is not assignable to the declared type.) + if (isTypeAssignableTo(assignedType, reducedType)) { return reducedType; } } diff --git a/tests/baselines/reference/assignmentTypeNarrowing.js b/tests/baselines/reference/assignmentTypeNarrowing.js index 4d6b911b88b..24a08539eb7 100644 --- a/tests/baselines/reference/assignmentTypeNarrowing.js +++ b/tests/baselines/reference/assignmentTypeNarrowing.js @@ -27,6 +27,12 @@ let a: string[]; for (x of a) { x; // string } + +// Repro from #26405 + +type AOrArrA = T | T[]; +const arr: AOrArrA<{x?: "ok"}> = [{ x: "ok" }]; // weak type +arr.push({ x: "ok" }); //// [assignmentTypeNarrowing.js] @@ -51,3 +57,5 @@ for (var _i = 0, a_1 = a; _i < a_1.length; _i++) { x = a_1[_i]; x; // string } +var arr = [{ x: "ok" }]; // weak type +arr.push({ x: "ok" }); diff --git a/tests/baselines/reference/assignmentTypeNarrowing.symbols b/tests/baselines/reference/assignmentTypeNarrowing.symbols index b9e8f3f136d..36a2cf44dae 100644 --- a/tests/baselines/reference/assignmentTypeNarrowing.symbols +++ b/tests/baselines/reference/assignmentTypeNarrowing.symbols @@ -62,3 +62,23 @@ for (x of a) { >x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3)) } +// Repro from #26405 + +type AOrArrA = T | T[]; +>AOrArrA : Symbol(AOrArrA, Decl(assignmentTypeNarrowing.ts, 27, 1)) +>T : Symbol(T, Decl(assignmentTypeNarrowing.ts, 31, 13)) +>T : Symbol(T, Decl(assignmentTypeNarrowing.ts, 31, 13)) +>T : Symbol(T, Decl(assignmentTypeNarrowing.ts, 31, 13)) + +const arr: AOrArrA<{x?: "ok"}> = [{ x: "ok" }]; // weak type +>arr : Symbol(arr, Decl(assignmentTypeNarrowing.ts, 32, 5)) +>AOrArrA : Symbol(AOrArrA, Decl(assignmentTypeNarrowing.ts, 27, 1)) +>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 32, 20)) +>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 32, 35)) + +arr.push({ x: "ok" }); +>arr.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>arr : Symbol(arr, Decl(assignmentTypeNarrowing.ts, 32, 5)) +>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 33, 10)) + diff --git a/tests/baselines/reference/assignmentTypeNarrowing.types b/tests/baselines/reference/assignmentTypeNarrowing.types index 2e2e6ad44eb..15b71c259ae 100644 --- a/tests/baselines/reference/assignmentTypeNarrowing.types +++ b/tests/baselines/reference/assignmentTypeNarrowing.types @@ -96,3 +96,25 @@ for (x of a) { >x : string } +// Repro from #26405 + +type AOrArrA = T | T[]; +>AOrArrA : AOrArrA + +const arr: AOrArrA<{x?: "ok"}> = [{ x: "ok" }]; // weak type +>arr : AOrArrA<{ x?: "ok"; }> +>x : "ok" +>[{ x: "ok" }] : { x: "ok"; }[] +>{ x: "ok" } : { x: "ok"; } +>x : "ok" +>"ok" : "ok" + +arr.push({ x: "ok" }); +>arr.push({ x: "ok" }) : number +>arr.push : (...items: { x?: "ok"; }[]) => number +>arr : { x?: "ok"; }[] +>push : (...items: { x?: "ok"; }[]) => number +>{ x: "ok" } : { x: "ok"; } +>x : "ok" +>"ok" : "ok" + diff --git a/tests/baselines/reference/enumAssignmentCompat3.types b/tests/baselines/reference/enumAssignmentCompat3.types index f2a693515e5..a152cd624df 100644 --- a/tests/baselines/reference/enumAssignmentCompat3.types +++ b/tests/baselines/reference/enumAssignmentCompat3.types @@ -252,9 +252,9 @@ abc = merged; // missing 'd' >merged : Merged.E merged = abc; // ok ->merged = abc : First.E.a | First.E.b +>merged = abc : First.E >merged : Merged.E ->abc : First.E.a | First.E.b +>abc : First.E abc = merged2; // ok >abc = merged2 : Merged2.E diff --git a/tests/baselines/reference/numericLiteralTypes3.types b/tests/baselines/reference/numericLiteralTypes3.types index 3fede24475c..9748eaccfca 100644 --- a/tests/baselines/reference/numericLiteralTypes3.types +++ b/tests/baselines/reference/numericLiteralTypes3.types @@ -118,9 +118,9 @@ function f4(a: A, b: B, c: C, d: D) { >c : C d = d; ->d = d : 1 | 2 +>d = d : D +>d : D >d : D ->d : 1 | 2 } function f5(a: A, b: B, c: C, d: D) { diff --git a/tests/cases/conformance/expressions/assignmentOperator/assignmentTypeNarrowing.ts b/tests/cases/conformance/expressions/assignmentOperator/assignmentTypeNarrowing.ts index 0e5e257635a..19d12a1b81a 100644 --- a/tests/cases/conformance/expressions/assignmentOperator/assignmentTypeNarrowing.ts +++ b/tests/cases/conformance/expressions/assignmentOperator/assignmentTypeNarrowing.ts @@ -26,3 +26,9 @@ let a: string[]; for (x of a) { x; // string } + +// Repro from #26405 + +type AOrArrA = T | T[]; +const arr: AOrArrA<{x?: "ok"}> = [{ x: "ok" }]; // weak type +arr.push({ x: "ok" });