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" });