Skip to content

Commit acbcc0d

Browse files
ahejlsbergsandersn
authored andcommitted
Merge pull request #33252 from microsoft/fix33131
Make lower priority inference when source is completely matched in target
1 parent 6b72d5d commit acbcc0d

7 files changed

+215
-35
lines changed

src/compiler/checker.ts

+24-32
Original file line numberDiff line numberDiff line change
@@ -15544,24 +15544,30 @@ namespace ts {
1554415544
return;
1554515545
}
1554615546
if (target.flags & TypeFlags.Union) {
15547-
if (source.flags & TypeFlags.Union) {
15548-
// First, infer between identically matching source and target constituents and remove the
15549-
// matching types.
15550-
const [tempSources, tempTargets] = inferFromMatchingTypes((<UnionType>source).types, (<UnionType>target).types, isTypeOrBaseIdenticalTo);
15551-
// Next, infer between closely matching source and target constituents and remove
15552-
// the matching types. Types closely match when they are instantiations of the same
15553-
// object type or instantiations of the same type alias.
15554-
const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy);
15555-
if (sources.length === 0 || targets.length === 0) {
15556-
return;
15557-
}
15558-
source = getUnionType(sources);
15559-
target = getUnionType(targets);
15547+
// First, infer between identically matching source and target constituents and remove the
15548+
// matching types.
15549+
const [tempSources, tempTargets] = inferFromMatchingTypes(source.flags & TypeFlags.Union ? (<UnionType>source).types : [source], (<UnionType>target).types, isTypeOrBaseIdenticalTo);
15550+
// Next, infer between closely matching source and target constituents and remove
15551+
// the matching types. Types closely match when they are instantiations of the same
15552+
// object type or instantiations of the same type alias.
15553+
const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy);
15554+
if (targets.length === 0) {
15555+
return;
1556015556
}
15561-
else {
15562-
if (inferFromMatchingType(source, (<UnionType>target).types, isTypeOrBaseIdenticalTo)) return;
15563-
if (inferFromMatchingType(source, (<UnionType>target).types, isTypeCloselyMatchedBy)) return;
15557+
target = getUnionType(targets);
15558+
if (sources.length === 0) {
15559+
// All source constituents have been matched and there is nothing further to infer from.
15560+
// However, simply making no inferences is undesirable because it could ultimately mean
15561+
// inferring a type parameter constraint. Instead, make a lower priority inference from
15562+
// the full source to whatever remains in the target. For example, when inferring from
15563+
// string to 'string | T', make a lower priority inference of string for T.
15564+
const savePriority = priority;
15565+
priority |= InferencePriority.NakedTypeVariable;
15566+
inferFromTypes(source, target);
15567+
priority = savePriority;
15568+
return;
1556415569
}
15570+
source = getUnionType(sources);
1556515571
}
1556615572
else if (target.flags & TypeFlags.Intersection && some((<IntersectionType>target).types,
1556715573
t => !!getInferenceInfoForType(t) || (isGenericMappedType(t) && !!getInferenceInfoForType(getHomomorphicTypeVariable(t) || neverType)))) {
@@ -15571,18 +15577,15 @@ namespace ts {
1557115577
// string[] on the source side and infer string for T.
1557215578
// Likewise, we consider a homomorphic mapped type constrainted to the target type parameter as similar to a "naked type variable"
1557315579
// in such scenarios.
15574-
if (source.flags & TypeFlags.Intersection) {
15580+
if (!(source.flags & TypeFlags.Union)) {
1557515581
// Infer between identically matching source and target constituents and remove the matching types.
15576-
const [sources, targets] = inferFromMatchingTypes((<IntersectionType>source).types, (<IntersectionType>target).types, isTypeIdenticalTo);
15582+
const [sources, targets] = inferFromMatchingTypes(source.flags & TypeFlags.Intersection ? (<IntersectionType>source).types : [source], (<IntersectionType>target).types, isTypeIdenticalTo);
1557715583
if (sources.length === 0 || targets.length === 0) {
1557815584
return;
1557915585
}
1558015586
source = getIntersectionType(sources);
1558115587
target = getIntersectionType(targets);
1558215588
}
15583-
else if (!(source.flags & TypeFlags.Union)) {
15584-
if (inferFromMatchingType(source, (<IntersectionType>target).types, isTypeIdenticalTo)) return;
15585-
}
1558615589
}
1558715590
else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) {
1558815591
target = getActualTypeVariable(target);
@@ -15732,17 +15735,6 @@ namespace ts {
1573215735
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
1573315736
}
1573415737

15735-
function inferFromMatchingType(source: Type, targets: Type[], matches: (s: Type, t: Type) => boolean) {
15736-
let matched = false;
15737-
for (const t of targets) {
15738-
if (matches(source, t)) {
15739-
inferFromTypes(source, t);
15740-
matched = true;
15741-
}
15742-
}
15743-
return matched;
15744-
}
15745-
1574615738
function inferFromMatchingTypes(sources: Type[], targets: Type[], matches: (s: Type, t: Type) => boolean): [Type[], Type[]] {
1574715739
let matchedSources: Type[] | undefined;
1574815740
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)