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

Lines changed: 1 addition & 13 deletions
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
}
Lines changed: 23 additions & 0 deletions
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+
}
Lines changed: 40 additions & 0 deletions
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+
Lines changed: 43 additions & 0 deletions
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

Lines changed: 2 additions & 2 deletions
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
Lines changed: 11 additions & 0 deletions
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)