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.
This commit is contained in:
Matt McCutchen 2018-08-01 23:23:03 -04:00
parent 76f7ee998a
commit d45e422b46
6 changed files with 120 additions and 15 deletions

View file

@ -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 (<UnionType>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;
}

View file

@ -0,0 +1,23 @@
//// [assignmentGenericLookupTypeNarrowing.ts]
// Repro from #26130
let mappedObject: {[K in "foo"]: null | {x: string}} = {foo: {x: "hello"}};
declare function foo<T>(x: T): null | T;
function bar<K extends "foo">(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;
}

View file

@ -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<T>(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<K extends "foo">(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))
}

View file

@ -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<T>(x: T): null | T;
>foo : <T>(x: T) => T
>x : T
>null : null
function bar<K extends "foo">(key: K) {
>bar : <K extends "foo">(key: K) => void
>key : K
const element = foo(mappedObject[key]);
>element : { foo: { x: string; }; }[K]
>foo(mappedObject[key]) : { foo: { x: string; }; }[K]
>foo : <T>(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
}

View file

@ -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

View file

@ -0,0 +1,11 @@
// Repro from #26130
let mappedObject: {[K in "foo"]: null | {x: string}} = {foo: {x: "hello"}};
declare function foo<T>(x: T): null | T;
function bar<K extends "foo">(key: K) {
const element = foo(mappedObject[key]);
if (element == null)
return;
const x = element.x;
}