Skip to content

Commit c5e6d95

Browse files
authored
Merge pull request #33252 from microsoft/fix33131
Make lower priority inference when source is completely matched in target
2 parents 72bb4c2 + aaa064b commit c5e6d95

7 files changed

+215
-35
lines changed

src/compiler/checker.ts

+24-32
Original file line numberDiff line numberDiff line change
@@ -15593,24 +15593,30 @@ namespace ts {
1559315593
return;
1559415594
}
1559515595
if (target.flags & TypeFlags.Union) {
15596-
if (source.flags & TypeFlags.Union) {
15597-
// First, infer between identically matching source and target constituents and remove the
15598-
// matching types.
15599-
const [tempSources, tempTargets] = inferFromMatchingTypes((<UnionType>source).types, (<UnionType>target).types, isTypeOrBaseIdenticalTo);
15600-
// Next, infer between closely matching source and target constituents and remove
15601-
// the matching types. Types closely match when they are instantiations of the same
15602-
// object type or instantiations of the same type alias.
15603-
const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy);
15604-
if (sources.length === 0 || targets.length === 0) {
15605-
return;
15606-
}
15607-
source = getUnionType(sources);
15608-
target = getUnionType(targets);
15596+
// First, infer between identically matching source and target constituents and remove the
15597+
// matching types.
15598+
const [tempSources, tempTargets] = inferFromMatchingTypes(source.flags & TypeFlags.Union ? (<UnionType>source).types : [source], (<UnionType>target).types, isTypeOrBaseIdenticalTo);
15599+
// Next, infer between closely matching source and target constituents and remove
15600+
// the matching types. Types closely match when they are instantiations of the same
15601+
// object type or instantiations of the same type alias.
15602+
const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy);
15603+
if (targets.length === 0) {
15604+
return;
1560915605
}
15610-
else {
15611-
if (inferFromMatchingType(source, (<UnionType>target).types, isTypeOrBaseIdenticalTo)) return;
15612-
if (inferFromMatchingType(source, (<UnionType>target).types, isTypeCloselyMatchedBy)) return;
15606+
target = getUnionType(targets);
15607+
if (sources.length === 0) {
15608+
// All source constituents have been matched and there is nothing further to infer from.
15609+
// However, simply making no inferences is undesirable because it could ultimately mean
15610+
// inferring a type parameter constraint. Instead, make a lower priority inference from
15611+
// the full source to whatever remains in the target. For example, when inferring from
15612+
// string to 'string | T', make a lower priority inference of string for T.
15613+
const savePriority = priority;
15614+
priority |= InferencePriority.NakedTypeVariable;
15615+
inferFromTypes(source, target);
15616+
priority = savePriority;
15617+
return;
1561315618
}
15619+
source = getUnionType(sources);
1561415620
}
1561515621
else if (target.flags & TypeFlags.Intersection && some((<IntersectionType>target).types,
1561615622
t => !!getInferenceInfoForType(t) || (isGenericMappedType(t) && !!getInferenceInfoForType(getHomomorphicTypeVariable(t) || neverType)))) {
@@ -15620,18 +15626,15 @@ namespace ts {
1562015626
// string[] on the source side and infer string for T.
1562115627
// Likewise, we consider a homomorphic mapped type constrainted to the target type parameter as similar to a "naked type variable"
1562215628
// in such scenarios.
15623-
if (source.flags & TypeFlags.Intersection) {
15629+
if (!(source.flags & TypeFlags.Union)) {
1562415630
// Infer between identically matching source and target constituents and remove the matching types.
15625-
const [sources, targets] = inferFromMatchingTypes((<IntersectionType>source).types, (<IntersectionType>target).types, isTypeIdenticalTo);
15631+
const [sources, targets] = inferFromMatchingTypes(source.flags & TypeFlags.Intersection ? (<IntersectionType>source).types : [source], (<IntersectionType>target).types, isTypeIdenticalTo);
1562615632
if (sources.length === 0 || targets.length === 0) {
1562715633
return;
1562815634
}
1562915635
source = getIntersectionType(sources);
1563015636
target = getIntersectionType(targets);
1563115637
}
15632-
else if (!(source.flags & TypeFlags.Union)) {
15633-
if (inferFromMatchingType(source, (<IntersectionType>target).types, isTypeIdenticalTo)) return;
15634-
}
1563515638
}
1563615639
else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) {
1563715640
target = getActualTypeVariable(target);
@@ -15781,17 +15784,6 @@ namespace ts {
1578115784
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
1578215785
}
1578315786

15784-
function inferFromMatchingType(source: Type, targets: Type[], matches: (s: Type, t: Type) => boolean) {
15785-
let matched = false;
15786-
for (const t of targets) {
15787-
if (matches(source, t)) {
15788-
inferFromTypes(source, t);
15789-
matched = true;
15790-
}
15791-
}
15792-
return matched;
15793-
}
15794-
1579515787
function inferFromMatchingTypes(sources: Type[], targets: Type[], matches: (s: Type, t: Type) => boolean): [Type[], Type[]] {
1579615788
let matchedSources: Type[] | undefined;
1579715789
let matchedTargets: Type[] | undefined;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//// [observableInferenceCanBeMade.ts]
2+
// Repro from #33131
3+
4+
declare function of<T>(a: T): Observable<T>;
5+
declare function from<O extends ObservableInput<any>>(input: O): Observable<ObservedValueOf<O>>;
6+
7+
type ObservedValueOf<O> = O extends ObservableInput<infer T> ? T : never;
8+
9+
interface Subscribable<T> {
10+
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void;
11+
}
12+
type ObservableInput<T> = Subscribable<T> | Subscribable<never>;
13+
14+
15+
declare class Observable<T> implements Subscribable<T> {
16+
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void;
17+
}
18+
19+
function asObservable(input: string | ObservableInput<string>): Observable<string> {
20+
return typeof input === 'string' ? of(input) : from(input)
21+
}
22+
23+
24+
//// [observableInferenceCanBeMade.js]
25+
"use strict";
26+
// Repro from #33131
27+
function asObservable(input) {
28+
return typeof input === 'string' ? of(input) : from(input);
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
=== tests/cases/compiler/observableInferenceCanBeMade.ts ===
2+
// Repro from #33131
3+
4+
declare function of<T>(a: T): Observable<T>;
5+
>of : Symbol(of, Decl(observableInferenceCanBeMade.ts, 0, 0))
6+
>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 2, 20))
7+
>a : Symbol(a, Decl(observableInferenceCanBeMade.ts, 2, 23))
8+
>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 2, 20))
9+
>Observable : Symbol(Observable, Decl(observableInferenceCanBeMade.ts, 10, 64))
10+
>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 2, 20))
11+
12+
declare function from<O extends ObservableInput<any>>(input: O): Observable<ObservedValueOf<O>>;
13+
>from : Symbol(from, Decl(observableInferenceCanBeMade.ts, 2, 44))
14+
>O : Symbol(O, Decl(observableInferenceCanBeMade.ts, 3, 22))
15+
>ObservableInput : Symbol(ObservableInput, Decl(observableInferenceCanBeMade.ts, 9, 1))
16+
>input : Symbol(input, Decl(observableInferenceCanBeMade.ts, 3, 54))
17+
>O : Symbol(O, Decl(observableInferenceCanBeMade.ts, 3, 22))
18+
>Observable : Symbol(Observable, Decl(observableInferenceCanBeMade.ts, 10, 64))
19+
>ObservedValueOf : Symbol(ObservedValueOf, Decl(observableInferenceCanBeMade.ts, 3, 96))
20+
>O : Symbol(O, Decl(observableInferenceCanBeMade.ts, 3, 22))
21+
22+
type ObservedValueOf<O> = O extends ObservableInput<infer T> ? T : never;
23+
>ObservedValueOf : Symbol(ObservedValueOf, Decl(observableInferenceCanBeMade.ts, 3, 96))
24+
>O : Symbol(O, Decl(observableInferenceCanBeMade.ts, 5, 21))
25+
>O : Symbol(O, Decl(observableInferenceCanBeMade.ts, 5, 21))
26+
>ObservableInput : Symbol(ObservableInput, Decl(observableInferenceCanBeMade.ts, 9, 1))
27+
>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 5, 57))
28+
>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 5, 57))
29+
30+
interface Subscribable<T> {
31+
>Subscribable : Symbol(Subscribable, Decl(observableInferenceCanBeMade.ts, 5, 73))
32+
>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 7, 23))
33+
34+
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void;
35+
>subscribe : Symbol(Subscribable.subscribe, Decl(observableInferenceCanBeMade.ts, 7, 27))
36+
>next : Symbol(next, Decl(observableInferenceCanBeMade.ts, 8, 14))
37+
>value : Symbol(value, Decl(observableInferenceCanBeMade.ts, 8, 22))
38+
>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 7, 23))
39+
>error : Symbol(error, Decl(observableInferenceCanBeMade.ts, 8, 40))
40+
>error : Symbol(error, Decl(observableInferenceCanBeMade.ts, 8, 50))
41+
>complete : Symbol(complete, Decl(observableInferenceCanBeMade.ts, 8, 70))
42+
}
43+
type ObservableInput<T> = Subscribable<T> | Subscribable<never>;
44+
>ObservableInput : Symbol(ObservableInput, Decl(observableInferenceCanBeMade.ts, 9, 1))
45+
>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 10, 21))
46+
>Subscribable : Symbol(Subscribable, Decl(observableInferenceCanBeMade.ts, 5, 73))
47+
>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 10, 21))
48+
>Subscribable : Symbol(Subscribable, Decl(observableInferenceCanBeMade.ts, 5, 73))
49+
50+
51+
declare class Observable<T> implements Subscribable<T> {
52+
>Observable : Symbol(Observable, Decl(observableInferenceCanBeMade.ts, 10, 64))
53+
>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 13, 25))
54+
>Subscribable : Symbol(Subscribable, Decl(observableInferenceCanBeMade.ts, 5, 73))
55+
>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 13, 25))
56+
57+
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void;
58+
>subscribe : Symbol(Observable.subscribe, Decl(observableInferenceCanBeMade.ts, 13, 56))
59+
>next : Symbol(next, Decl(observableInferenceCanBeMade.ts, 14, 14))
60+
>value : Symbol(value, Decl(observableInferenceCanBeMade.ts, 14, 22))
61+
>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 13, 25))
62+
>error : Symbol(error, Decl(observableInferenceCanBeMade.ts, 14, 40))
63+
>error : Symbol(error, Decl(observableInferenceCanBeMade.ts, 14, 50))
64+
>complete : Symbol(complete, Decl(observableInferenceCanBeMade.ts, 14, 70))
65+
}
66+
67+
function asObservable(input: string | ObservableInput<string>): Observable<string> {
68+
>asObservable : Symbol(asObservable, Decl(observableInferenceCanBeMade.ts, 15, 1))
69+
>input : Symbol(input, Decl(observableInferenceCanBeMade.ts, 17, 22))
70+
>ObservableInput : Symbol(ObservableInput, Decl(observableInferenceCanBeMade.ts, 9, 1))
71+
>Observable : Symbol(Observable, Decl(observableInferenceCanBeMade.ts, 10, 64))
72+
73+
return typeof input === 'string' ? of(input) : from(input)
74+
>input : Symbol(input, Decl(observableInferenceCanBeMade.ts, 17, 22))
75+
>of : Symbol(of, Decl(observableInferenceCanBeMade.ts, 0, 0))
76+
>input : Symbol(input, Decl(observableInferenceCanBeMade.ts, 17, 22))
77+
>from : Symbol(from, Decl(observableInferenceCanBeMade.ts, 2, 44))
78+
>input : Symbol(input, Decl(observableInferenceCanBeMade.ts, 17, 22))
79+
}
80+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
=== tests/cases/compiler/observableInferenceCanBeMade.ts ===
2+
// Repro from #33131
3+
4+
declare function of<T>(a: T): Observable<T>;
5+
>of : <T>(a: T) => Observable<T>
6+
>a : T
7+
8+
declare function from<O extends ObservableInput<any>>(input: O): Observable<ObservedValueOf<O>>;
9+
>from : <O extends ObservableInput<any>>(input: O) => Observable<ObservedValueOf<O>>
10+
>input : O
11+
12+
type ObservedValueOf<O> = O extends ObservableInput<infer T> ? T : never;
13+
>ObservedValueOf : ObservedValueOf<O>
14+
15+
interface Subscribable<T> {
16+
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void;
17+
>subscribe : (next?: ((value: T) => void) | undefined, error?: ((error: any) => void) | undefined, complete?: (() => void) | undefined) => void
18+
>next : ((value: T) => void) | undefined
19+
>value : T
20+
>error : ((error: any) => void) | undefined
21+
>error : any
22+
>complete : (() => void) | undefined
23+
}
24+
type ObservableInput<T> = Subscribable<T> | Subscribable<never>;
25+
>ObservableInput : ObservableInput<T>
26+
27+
28+
declare class Observable<T> implements Subscribable<T> {
29+
>Observable : Observable<T>
30+
31+
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void;
32+
>subscribe : (next?: ((value: T) => void) | undefined, error?: ((error: any) => void) | undefined, complete?: (() => void) | undefined) => void
33+
>next : ((value: T) => void) | undefined
34+
>value : T
35+
>error : ((error: any) => void) | undefined
36+
>error : any
37+
>complete : (() => void) | undefined
38+
}
39+
40+
function asObservable(input: string | ObservableInput<string>): Observable<string> {
41+
>asObservable : (input: string | Subscribable<never> | Subscribable<string>) => Observable<string>
42+
>input : string | Subscribable<never> | Subscribable<string>
43+
44+
return typeof input === 'string' ? of(input) : from(input)
45+
>typeof input === 'string' ? of(input) : from(input) : Observable<string>
46+
>typeof input === 'string' : boolean
47+
>typeof input : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
48+
>input : string | Subscribable<never> | Subscribable<string>
49+
>'string' : "string"
50+
>of(input) : Observable<string>
51+
>of : <T>(a: T) => Observable<T>
52+
>input : string
53+
>from(input) : Observable<string>
54+
>from : <O extends ObservableInput<any>>(input: O) => Observable<ObservedValueOf<O>>
55+
>input : ObservableInput<string>
56+
}
57+

tests/baselines/reference/unionAndIntersectionInference2.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ var e1: number | string | boolean;
2020
>e1 : string | number | boolean
2121

2222
f1(a1); // string
23-
>f1(a1) : unknown
23+
>f1(a1) : string
2424
>f1 : <T>(x: string | T) => T
2525
>a1 : string
2626

tests/baselines/reference/unionTypeInference.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ const c4 = f3(b); // true
104104
>b : boolean
105105

106106
const c5 = f3("abc"); // never
107-
>c5 : unknown
108-
>f3("abc") : unknown
107+
>c5 : "abc"
108+
>f3("abc") : "abc"
109109
>f3 : <T>(x: string | false | T) => T
110110
>"abc" : "abc"
111111

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// @strict: true
2+
3+
// Repro from #33131
4+
5+
declare function of<T>(a: T): Observable<T>;
6+
declare function from<O extends ObservableInput<any>>(input: O): Observable<ObservedValueOf<O>>;
7+
8+
type ObservedValueOf<O> = O extends ObservableInput<infer T> ? T : never;
9+
10+
interface Subscribable<T> {
11+
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void;
12+
}
13+
type ObservableInput<T> = Subscribable<T> | Subscribable<never>;
14+
15+
16+
declare class Observable<T> implements Subscribable<T> {
17+
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void;
18+
}
19+
20+
function asObservable(input: string | ObservableInput<string>): Observable<string> {
21+
return typeof input === 'string' ? of(input) : from(input)
22+
}

0 commit comments

Comments
 (0)