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 26b93b19335b4..0b357bd771da4 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 4d6b911b88b93..24a08539eb7d2 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 b9e8f3f136d21..36a2cf44dae8e 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 2e2e6ad44eb04..15b71c259aef8 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 f2a693515e581..a152cd624dfc1 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 3fede24475c6a..9748eaccfcad5 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 0e5e257635a57..19d12a1b81ae0 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" });