Skip to content

Commit d7ef49d

Browse files
committed
During inference discriminate the target type before falling back to close-matching types
1 parent 5e4fcfb commit d7ef49d

5 files changed

Lines changed: 224 additions & 1 deletion

File tree

‎src/compiler/checker.ts‎

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21039,7 +21039,7 @@ namespace ts {
2103921039
// Next, infer between closely matching source and target constituents and remove
2104021040
// the matching types. Types closely match when they are instantiations of the same
2104121041
// object type or instantiations of the same type alias.
21042-
const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy);
21042+
const [sources, targets] = inferFromDiscriminableOrCloselyMatchingTypes(tempSources, getUnionType(tempTargets));
2104321043
if (targets.length === 0) {
2104421044
return;
2104521045
}
@@ -21253,6 +21253,41 @@ namespace ts {
2125321253
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
2125421254
}
2125521255

21256+
function inferFromDiscriminableOrCloselyMatchingTypes(sources: Type[], target: Type): [Type[], Type[]] {
21257+
if (!(target.flags & TypeFlags.Union)) {
21258+
return inferFromMatchingTypes(sources, [target], isTypeCloselyMatchedBy);
21259+
}
21260+
let matchedSources: Type[] | undefined;
21261+
let matchedTargets: Type[] | undefined;
21262+
for (const s of sources) {
21263+
const discriminatedTarget = discriminateTypeByDiscriminableItems(
21264+
target as UnionType,
21265+
map(
21266+
filter(getPropertiesOfType(s), p => isDiscriminantProperty(target, p.escapedName) && isLiteralType(getTypeOfSymbol(p))),
21267+
p => ([() => getTypeOfSymbol(p), p.escapedName])
21268+
),
21269+
isTypeAssignableTo
21270+
);
21271+
if (discriminatedTarget) {
21272+
inferFromTypes(s, discriminatedTarget);
21273+
matchedSources = appendIfUnique(matchedSources, s);
21274+
matchedTargets = appendIfUnique(matchedTargets, discriminatedTarget);
21275+
continue;
21276+
}
21277+
for (const t of (target as UnionType).types) {
21278+
if (isTypeCloselyMatchedBy(s, t)) {
21279+
inferFromTypes(s, t);
21280+
matchedSources = appendIfUnique(matchedSources, s);
21281+
matchedTargets = appendIfUnique(matchedTargets, t);
21282+
}
21283+
}
21284+
}
21285+
return [
21286+
matchedSources ? filter(sources, t => !contains(matchedSources, t)) : sources,
21287+
matchedTargets ? filter((target as UnionType).types, t => !contains(matchedTargets, t)) : (target as UnionType).types,
21288+
];
21289+
}
21290+
2125621291
function inferFromMatchingTypes(sources: Type[], targets: Type[], matches: (s: Type, t: Type) => boolean): [Type[], Type[]] {
2125721292
let matchedSources: Type[] | undefined;
2125821293
let matchedTargets: Type[] | undefined;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//// [classExtendedDiscriminatedUnionInference.ts]
2+
declare class LocalController<T, U> {
3+
isLocal: true;
4+
getProps(a: T, b: U): void;
5+
}
6+
declare class GlobalController<T, U> {
7+
isLocal: false;
8+
// N.B.: Parameter type order is reversed
9+
getProps(a: U, b: T): void;
10+
}
11+
12+
class FailWithTS42 extends LocalController<{ foo: any }, { bar: any }> { }
13+
const p = new FailWithTS42();
14+
15+
declare function createEnhancer1<T, U>(Wrapper: GlobalController<T, U> | LocalController<T, U>): [T, U];
16+
const q1 = createEnhancer1(p);
17+
const q2 = createEnhancer1(p as LocalController<{ foo: any }, { bar: any }>); // structurally identical type to the above
18+
19+
20+
//// [classExtendedDiscriminatedUnionInference.js]
21+
var __extends = (this && this.__extends) || (function () {
22+
var extendStatics = function (d, b) {
23+
extendStatics = Object.setPrototypeOf ||
24+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
25+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
26+
return extendStatics(d, b);
27+
};
28+
return function (d, b) {
29+
if (typeof b !== "function" && b !== null)
30+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
31+
extendStatics(d, b);
32+
function __() { this.constructor = d; }
33+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
34+
};
35+
})();
36+
var FailWithTS42 = /** @class */ (function (_super) {
37+
__extends(FailWithTS42, _super);
38+
function FailWithTS42() {
39+
return _super !== null && _super.apply(this, arguments) || this;
40+
}
41+
return FailWithTS42;
42+
}(LocalController));
43+
var p = new FailWithTS42();
44+
var q1 = createEnhancer1(p);
45+
var q2 = createEnhancer1(p); // structurally identical type to the above
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
=== tests/cases/compiler/classExtendedDiscriminatedUnionInference.ts ===
2+
declare class LocalController<T, U> {
3+
>LocalController : Symbol(LocalController, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 0))
4+
>T : Symbol(T, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 30))
5+
>U : Symbol(U, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 32))
6+
7+
isLocal: true;
8+
>isLocal : Symbol(LocalController.isLocal, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 37))
9+
10+
getProps(a: T, b: U): void;
11+
>getProps : Symbol(LocalController.getProps, Decl(classExtendedDiscriminatedUnionInference.ts, 1, 18))
12+
>a : Symbol(a, Decl(classExtendedDiscriminatedUnionInference.ts, 2, 13))
13+
>T : Symbol(T, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 30))
14+
>b : Symbol(b, Decl(classExtendedDiscriminatedUnionInference.ts, 2, 18))
15+
>U : Symbol(U, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 32))
16+
}
17+
declare class GlobalController<T, U> {
18+
>GlobalController : Symbol(GlobalController, Decl(classExtendedDiscriminatedUnionInference.ts, 3, 1))
19+
>T : Symbol(T, Decl(classExtendedDiscriminatedUnionInference.ts, 4, 31))
20+
>U : Symbol(U, Decl(classExtendedDiscriminatedUnionInference.ts, 4, 33))
21+
22+
isLocal: false;
23+
>isLocal : Symbol(GlobalController.isLocal, Decl(classExtendedDiscriminatedUnionInference.ts, 4, 38))
24+
25+
// N.B.: Parameter type order is reversed
26+
getProps(a: U, b: T): void;
27+
>getProps : Symbol(GlobalController.getProps, Decl(classExtendedDiscriminatedUnionInference.ts, 5, 19))
28+
>a : Symbol(a, Decl(classExtendedDiscriminatedUnionInference.ts, 7, 13))
29+
>U : Symbol(U, Decl(classExtendedDiscriminatedUnionInference.ts, 4, 33))
30+
>b : Symbol(b, Decl(classExtendedDiscriminatedUnionInference.ts, 7, 18))
31+
>T : Symbol(T, Decl(classExtendedDiscriminatedUnionInference.ts, 4, 31))
32+
}
33+
34+
class FailWithTS42 extends LocalController<{ foo: any }, { bar: any }> { }
35+
>FailWithTS42 : Symbol(FailWithTS42, Decl(classExtendedDiscriminatedUnionInference.ts, 8, 1))
36+
>LocalController : Symbol(LocalController, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 0))
37+
>foo : Symbol(foo, Decl(classExtendedDiscriminatedUnionInference.ts, 10, 44))
38+
>bar : Symbol(bar, Decl(classExtendedDiscriminatedUnionInference.ts, 10, 58))
39+
40+
const p = new FailWithTS42();
41+
>p : Symbol(p, Decl(classExtendedDiscriminatedUnionInference.ts, 11, 5))
42+
>FailWithTS42 : Symbol(FailWithTS42, Decl(classExtendedDiscriminatedUnionInference.ts, 8, 1))
43+
44+
declare function createEnhancer1<T, U>(Wrapper: GlobalController<T, U> | LocalController<T, U>): [T, U];
45+
>createEnhancer1 : Symbol(createEnhancer1, Decl(classExtendedDiscriminatedUnionInference.ts, 11, 29))
46+
>T : Symbol(T, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 33))
47+
>U : Symbol(U, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 35))
48+
>Wrapper : Symbol(Wrapper, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 39))
49+
>GlobalController : Symbol(GlobalController, Decl(classExtendedDiscriminatedUnionInference.ts, 3, 1))
50+
>T : Symbol(T, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 33))
51+
>U : Symbol(U, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 35))
52+
>LocalController : Symbol(LocalController, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 0))
53+
>T : Symbol(T, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 33))
54+
>U : Symbol(U, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 35))
55+
>T : Symbol(T, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 33))
56+
>U : Symbol(U, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 35))
57+
58+
const q1 = createEnhancer1(p);
59+
>q1 : Symbol(q1, Decl(classExtendedDiscriminatedUnionInference.ts, 14, 5))
60+
>createEnhancer1 : Symbol(createEnhancer1, Decl(classExtendedDiscriminatedUnionInference.ts, 11, 29))
61+
>p : Symbol(p, Decl(classExtendedDiscriminatedUnionInference.ts, 11, 5))
62+
63+
const q2 = createEnhancer1(p as LocalController<{ foo: any }, { bar: any }>); // structurally identical type to the above
64+
>q2 : Symbol(q2, Decl(classExtendedDiscriminatedUnionInference.ts, 15, 5))
65+
>createEnhancer1 : Symbol(createEnhancer1, Decl(classExtendedDiscriminatedUnionInference.ts, 11, 29))
66+
>p : Symbol(p, Decl(classExtendedDiscriminatedUnionInference.ts, 11, 5))
67+
>LocalController : Symbol(LocalController, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 0))
68+
>foo : Symbol(foo, Decl(classExtendedDiscriminatedUnionInference.ts, 15, 49))
69+
>bar : Symbol(bar, Decl(classExtendedDiscriminatedUnionInference.ts, 15, 63))
70+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
=== tests/cases/compiler/classExtendedDiscriminatedUnionInference.ts ===
2+
declare class LocalController<T, U> {
3+
>LocalController : LocalController<T, U>
4+
5+
isLocal: true;
6+
>isLocal : true
7+
>true : true
8+
9+
getProps(a: T, b: U): void;
10+
>getProps : (a: T, b: U) => void
11+
>a : T
12+
>b : U
13+
}
14+
declare class GlobalController<T, U> {
15+
>GlobalController : GlobalController<T, U>
16+
17+
isLocal: false;
18+
>isLocal : false
19+
>false : false
20+
21+
// N.B.: Parameter type order is reversed
22+
getProps(a: U, b: T): void;
23+
>getProps : (a: U, b: T) => void
24+
>a : U
25+
>b : T
26+
}
27+
28+
class FailWithTS42 extends LocalController<{ foo: any }, { bar: any }> { }
29+
>FailWithTS42 : FailWithTS42
30+
>LocalController : LocalController<{ foo: any; }, { bar: any; }>
31+
>foo : any
32+
>bar : any
33+
34+
const p = new FailWithTS42();
35+
>p : FailWithTS42
36+
>new FailWithTS42() : FailWithTS42
37+
>FailWithTS42 : typeof FailWithTS42
38+
39+
declare function createEnhancer1<T, U>(Wrapper: GlobalController<T, U> | LocalController<T, U>): [T, U];
40+
>createEnhancer1 : <T, U>(Wrapper: GlobalController<T, U> | LocalController<T, U>) => [T, U]
41+
>Wrapper : GlobalController<T, U> | LocalController<T, U>
42+
43+
const q1 = createEnhancer1(p);
44+
>q1 : [{ foo: any; }, { bar: any; }]
45+
>createEnhancer1(p) : [{ foo: any; }, { bar: any; }]
46+
>createEnhancer1 : <T, U>(Wrapper: GlobalController<T, U> | LocalController<T, U>) => [T, U]
47+
>p : FailWithTS42
48+
49+
const q2 = createEnhancer1(p as LocalController<{ foo: any }, { bar: any }>); // structurally identical type to the above
50+
>q2 : [{ foo: any; }, { bar: any; }]
51+
>createEnhancer1(p as LocalController<{ foo: any }, { bar: any }>) : [{ foo: any; }, { bar: any; }]
52+
>createEnhancer1 : <T, U>(Wrapper: GlobalController<T, U> | LocalController<T, U>) => [T, U]
53+
>p as LocalController<{ foo: any }, { bar: any }> : LocalController<{ foo: any; }, { bar: any; }>
54+
>p : FailWithTS42
55+
>foo : any
56+
>bar : any
57+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
declare class LocalController<T, U> {
2+
isLocal: true;
3+
getProps(a: T, b: U): void;
4+
}
5+
declare class GlobalController<T, U> {
6+
isLocal: false;
7+
// N.B.: Parameter type order is reversed
8+
getProps(a: U, b: T): void;
9+
}
10+
11+
class FailWithTS42 extends LocalController<{ foo: any }, { bar: any }> { }
12+
const p = new FailWithTS42();
13+
14+
declare function createEnhancer1<T, U>(Wrapper: GlobalController<T, U> | LocalController<T, U>): [T, U];
15+
const q1 = createEnhancer1(p);
16+
const q2 = createEnhancer1(p as LocalController<{ foo: any }, { bar: any }>); // structurally identical type to the above

0 commit comments

Comments
 (0)