From d45e422b4625ce58d744f58c082654d614da7488 Mon Sep 17 00:00:00 2001 From: Matt McCutchen Date: Wed, 1 Aug 2018 23:23:03 -0400 Subject: [PATCH] Have getAssignmentReducedType use the comparable relation instead of typeMaybeAssignableTo. typeMaybeAssignableTo decomposed unions at the top level of the assigned type but didn't properly handle other unions that arose during assignability checking, e.g., in the constraint of a generic lookup type. Fixes #26130. --- src/compiler/checker.ts | 14 +----- .../assignmentGenericLookupTypeNarrowing.js | 23 ++++++++++ ...signmentGenericLookupTypeNarrowing.symbols | 40 +++++++++++++++++ ...assignmentGenericLookupTypeNarrowing.types | 43 +++++++++++++++++++ .../reference/enumAssignmentCompat3.types | 4 +- .../assignmentGenericLookupTypeNarrowing.ts | 11 +++++ 6 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 tests/baselines/reference/assignmentGenericLookupTypeNarrowing.js create mode 100644 tests/baselines/reference/assignmentGenericLookupTypeNarrowing.symbols create mode 100644 tests/baselines/reference/assignmentGenericLookupTypeNarrowing.types create mode 100644 tests/cases/conformance/expressions/assignmentOperator/assignmentGenericLookupTypeNarrowing.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3c307585a9d5f..19012235d53c0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13791,18 +13791,6 @@ 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. @@ -13811,7 +13799,7 @@ namespace ts { if (assignedType.flags & TypeFlags.Never) { return assignedType; } - const reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); + const reducedType = filterType(declaredType, t => isTypeComparableTo(assignedType, t)); if (!(reducedType.flags & TypeFlags.Never)) { return reducedType; } diff --git a/tests/baselines/reference/assignmentGenericLookupTypeNarrowing.js b/tests/baselines/reference/assignmentGenericLookupTypeNarrowing.js new file mode 100644 index 0000000000000..7f757ef24c3c7 --- /dev/null +++ b/tests/baselines/reference/assignmentGenericLookupTypeNarrowing.js @@ -0,0 +1,23 @@ +//// [assignmentGenericLookupTypeNarrowing.ts] +// Repro from #26130 + +let mappedObject: {[K in "foo"]: null | {x: string}} = {foo: {x: "hello"}}; +declare function foo(x: T): null | T; + +function bar(key: K) { + const element = foo(mappedObject[key]); + if (element == null) + return; + const x = element.x; +} + + +//// [assignmentGenericLookupTypeNarrowing.js] +// Repro from #26130 +var mappedObject = { foo: { x: "hello" } }; +function bar(key) { + var element = foo(mappedObject[key]); + if (element == null) + return; + var x = element.x; +} diff --git a/tests/baselines/reference/assignmentGenericLookupTypeNarrowing.symbols b/tests/baselines/reference/assignmentGenericLookupTypeNarrowing.symbols new file mode 100644 index 0000000000000..f256ef8c1b68f --- /dev/null +++ b/tests/baselines/reference/assignmentGenericLookupTypeNarrowing.symbols @@ -0,0 +1,40 @@ +=== tests/cases/conformance/expressions/assignmentOperator/assignmentGenericLookupTypeNarrowing.ts === +// Repro from #26130 + +let mappedObject: {[K in "foo"]: null | {x: string}} = {foo: {x: "hello"}}; +>mappedObject : Symbol(mappedObject, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 3)) +>K : Symbol(K, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 20)) +>x : Symbol(x, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 41)) +>foo : Symbol(foo, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 56)) +>x : Symbol(x, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 62)) + +declare function foo(x: T): null | T; +>foo : Symbol(foo, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 75)) +>T : Symbol(T, Decl(assignmentGenericLookupTypeNarrowing.ts, 3, 21)) +>x : Symbol(x, Decl(assignmentGenericLookupTypeNarrowing.ts, 3, 24)) +>T : Symbol(T, Decl(assignmentGenericLookupTypeNarrowing.ts, 3, 21)) +>T : Symbol(T, Decl(assignmentGenericLookupTypeNarrowing.ts, 3, 21)) + +function bar(key: K) { +>bar : Symbol(bar, Decl(assignmentGenericLookupTypeNarrowing.ts, 3, 40)) +>K : Symbol(K, Decl(assignmentGenericLookupTypeNarrowing.ts, 5, 13)) +>key : Symbol(key, Decl(assignmentGenericLookupTypeNarrowing.ts, 5, 30)) +>K : Symbol(K, Decl(assignmentGenericLookupTypeNarrowing.ts, 5, 13)) + + const element = foo(mappedObject[key]); +>element : Symbol(element, Decl(assignmentGenericLookupTypeNarrowing.ts, 6, 7)) +>foo : Symbol(foo, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 75)) +>mappedObject : Symbol(mappedObject, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 3)) +>key : Symbol(key, Decl(assignmentGenericLookupTypeNarrowing.ts, 5, 30)) + + if (element == null) +>element : Symbol(element, Decl(assignmentGenericLookupTypeNarrowing.ts, 6, 7)) + + return; + const x = element.x; +>x : Symbol(x, Decl(assignmentGenericLookupTypeNarrowing.ts, 9, 7)) +>element.x : Symbol(x, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 41)) +>element : Symbol(element, Decl(assignmentGenericLookupTypeNarrowing.ts, 6, 7)) +>x : Symbol(x, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 41)) +} + diff --git a/tests/baselines/reference/assignmentGenericLookupTypeNarrowing.types b/tests/baselines/reference/assignmentGenericLookupTypeNarrowing.types new file mode 100644 index 0000000000000..5de987b283cdf --- /dev/null +++ b/tests/baselines/reference/assignmentGenericLookupTypeNarrowing.types @@ -0,0 +1,43 @@ +=== tests/cases/conformance/expressions/assignmentOperator/assignmentGenericLookupTypeNarrowing.ts === +// Repro from #26130 + +let mappedObject: {[K in "foo"]: null | {x: string}} = {foo: {x: "hello"}}; +>mappedObject : { foo: { x: string; }; } +>null : null +>x : string +>{foo: {x: "hello"}} : { foo: { x: string; }; } +>foo : { x: string; } +>{x: "hello"} : { x: string; } +>x : string +>"hello" : "hello" + +declare function foo(x: T): null | T; +>foo : (x: T) => T +>x : T +>null : null + +function bar(key: K) { +>bar : (key: K) => void +>key : K + + const element = foo(mappedObject[key]); +>element : { foo: { x: string; }; }[K] +>foo(mappedObject[key]) : { foo: { x: string; }; }[K] +>foo : (x: T) => T +>mappedObject[key] : { foo: { x: string; }; }[K] +>mappedObject : { foo: { x: string; }; } +>key : K + + if (element == null) +>element == null : boolean +>element : { foo: { x: string; }; }[K] +>null : null + + return; + const x = element.x; +>x : string +>element.x : string +>element : { foo: { x: string; }; }[K] +>x : string +} + diff --git a/tests/baselines/reference/enumAssignmentCompat3.types b/tests/baselines/reference/enumAssignmentCompat3.types index a152cd624dfc1..f2a693515e581 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 +>merged = abc : First.E.a | First.E.b >merged : Merged.E ->abc : First.E +>abc : First.E.a | First.E.b abc = merged2; // ok >abc = merged2 : Merged2.E diff --git a/tests/cases/conformance/expressions/assignmentOperator/assignmentGenericLookupTypeNarrowing.ts b/tests/cases/conformance/expressions/assignmentOperator/assignmentGenericLookupTypeNarrowing.ts new file mode 100644 index 0000000000000..c60ad06c1f7d8 --- /dev/null +++ b/tests/cases/conformance/expressions/assignmentOperator/assignmentGenericLookupTypeNarrowing.ts @@ -0,0 +1,11 @@ +// Repro from #26130 + +let mappedObject: {[K in "foo"]: null | {x: string}} = {foo: {x: "hello"}}; +declare function foo(x: T): null | T; + +function bar(key: K) { + const element = foo(mappedObject[key]); + if (element == null) + return; + const x = element.x; +}