Skip to content

Commit d45e422

Browse files
committed
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 microsoft#26130.
1 parent 76f7ee9 commit d45e422

File tree

6 files changed

+120
-15
lines changed

6 files changed

+120
-15
lines changed

src/compiler/checker.ts

+1-13
Original file line numberDiff line numberDiff line change
@@ -13791,18 +13791,6 @@ namespace ts {
1379113791
return flow.id;
1379213792
}
1379313793

13794-
function typeMaybeAssignableTo(source: Type, target: Type) {
13795-
if (!(source.flags & TypeFlags.Union)) {
13796-
return isTypeAssignableTo(source, target);
13797-
}
13798-
for (const t of (<UnionType>source).types) {
13799-
if (isTypeAssignableTo(t, target)) {
13800-
return true;
13801-
}
13802-
}
13803-
return false;
13804-
}
13805-
1380613794
// Remove those constituent types of declaredType to which no constituent type of assignedType is assignable.
1380713795
// For example, when a variable of type number | string | boolean is assigned a value of type number | boolean,
1380813796
// we remove type string.
@@ -13811,7 +13799,7 @@ namespace ts {
1381113799
if (assignedType.flags & TypeFlags.Never) {
1381213800
return assignedType;
1381313801
}
13814-
const reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t));
13802+
const reducedType = filterType(declaredType, t => isTypeComparableTo(assignedType, t));
1381513803
if (!(reducedType.flags & TypeFlags.Never)) {
1381613804
return reducedType;
1381713805
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//// [assignmentGenericLookupTypeNarrowing.ts]
2+
// Repro from #26130
3+
4+
let mappedObject: {[K in "foo"]: null | {x: string}} = {foo: {x: "hello"}};
5+
declare function foo<T>(x: T): null | T;
6+
7+
function bar<K extends "foo">(key: K) {
8+
const element = foo(mappedObject[key]);
9+
if (element == null)
10+
return;
11+
const x = element.x;
12+
}
13+
14+
15+
//// [assignmentGenericLookupTypeNarrowing.js]
16+
// Repro from #26130
17+
var mappedObject = { foo: { x: "hello" } };
18+
function bar(key) {
19+
var element = foo(mappedObject[key]);
20+
if (element == null)
21+
return;
22+
var x = element.x;
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
=== tests/cases/conformance/expressions/assignmentOperator/assignmentGenericLookupTypeNarrowing.ts ===
2+
// Repro from #26130
3+
4+
let mappedObject: {[K in "foo"]: null | {x: string}} = {foo: {x: "hello"}};
5+
>mappedObject : Symbol(mappedObject, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 3))
6+
>K : Symbol(K, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 20))
7+
>x : Symbol(x, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 41))
8+
>foo : Symbol(foo, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 56))
9+
>x : Symbol(x, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 62))
10+
11+
declare function foo<T>(x: T): null | T;
12+
>foo : Symbol(foo, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 75))
13+
>T : Symbol(T, Decl(assignmentGenericLookupTypeNarrowing.ts, 3, 21))
14+
>x : Symbol(x, Decl(assignmentGenericLookupTypeNarrowing.ts, 3, 24))
15+
>T : Symbol(T, Decl(assignmentGenericLookupTypeNarrowing.ts, 3, 21))
16+
>T : Symbol(T, Decl(assignmentGenericLookupTypeNarrowing.ts, 3, 21))
17+
18+
function bar<K extends "foo">(key: K) {
19+
>bar : Symbol(bar, Decl(assignmentGenericLookupTypeNarrowing.ts, 3, 40))
20+
>K : Symbol(K, Decl(assignmentGenericLookupTypeNarrowing.ts, 5, 13))
21+
>key : Symbol(key, Decl(assignmentGenericLookupTypeNarrowing.ts, 5, 30))
22+
>K : Symbol(K, Decl(assignmentGenericLookupTypeNarrowing.ts, 5, 13))
23+
24+
const element = foo(mappedObject[key]);
25+
>element : Symbol(element, Decl(assignmentGenericLookupTypeNarrowing.ts, 6, 7))
26+
>foo : Symbol(foo, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 75))
27+
>mappedObject : Symbol(mappedObject, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 3))
28+
>key : Symbol(key, Decl(assignmentGenericLookupTypeNarrowing.ts, 5, 30))
29+
30+
if (element == null)
31+
>element : Symbol(element, Decl(assignmentGenericLookupTypeNarrowing.ts, 6, 7))
32+
33+
return;
34+
const x = element.x;
35+
>x : Symbol(x, Decl(assignmentGenericLookupTypeNarrowing.ts, 9, 7))
36+
>element.x : Symbol(x, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 41))
37+
>element : Symbol(element, Decl(assignmentGenericLookupTypeNarrowing.ts, 6, 7))
38+
>x : Symbol(x, Decl(assignmentGenericLookupTypeNarrowing.ts, 2, 41))
39+
}
40+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
=== tests/cases/conformance/expressions/assignmentOperator/assignmentGenericLookupTypeNarrowing.ts ===
2+
// Repro from #26130
3+
4+
let mappedObject: {[K in "foo"]: null | {x: string}} = {foo: {x: "hello"}};
5+
>mappedObject : { foo: { x: string; }; }
6+
>null : null
7+
>x : string
8+
>{foo: {x: "hello"}} : { foo: { x: string; }; }
9+
>foo : { x: string; }
10+
>{x: "hello"} : { x: string; }
11+
>x : string
12+
>"hello" : "hello"
13+
14+
declare function foo<T>(x: T): null | T;
15+
>foo : <T>(x: T) => T
16+
>x : T
17+
>null : null
18+
19+
function bar<K extends "foo">(key: K) {
20+
>bar : <K extends "foo">(key: K) => void
21+
>key : K
22+
23+
const element = foo(mappedObject[key]);
24+
>element : { foo: { x: string; }; }[K]
25+
>foo(mappedObject[key]) : { foo: { x: string; }; }[K]
26+
>foo : <T>(x: T) => T
27+
>mappedObject[key] : { foo: { x: string; }; }[K]
28+
>mappedObject : { foo: { x: string; }; }
29+
>key : K
30+
31+
if (element == null)
32+
>element == null : boolean
33+
>element : { foo: { x: string; }; }[K]
34+
>null : null
35+
36+
return;
37+
const x = element.x;
38+
>x : string
39+
>element.x : string
40+
>element : { foo: { x: string; }; }[K]
41+
>x : string
42+
}
43+

tests/baselines/reference/enumAssignmentCompat3.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,9 @@ abc = merged; // missing 'd'
252252
>merged : Merged.E
253253

254254
merged = abc; // ok
255-
>merged = abc : First.E
255+
>merged = abc : First.E.a | First.E.b
256256
>merged : Merged.E
257-
>abc : First.E
257+
>abc : First.E.a | First.E.b
258258

259259
abc = merged2; // ok
260260
>abc = merged2 : Merged2.E
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Repro from #26130
2+
3+
let mappedObject: {[K in "foo"]: null | {x: string}} = {foo: {x: "hello"}};
4+
declare function foo<T>(x: T): null | T;
5+
6+
function bar<K extends "foo">(key: K) {
7+
const element = foo(mappedObject[key]);
8+
if (element == null)
9+
return;
10+
const x = element.x;
11+
}

0 commit comments

Comments
 (0)