Skip to content

Commit f84fd30

Browse files
authored
Merge pull request microsoft#34607 from microsoft/fix33490
Fix type inference regression
2 parents ff6626f + 82019d6 commit f84fd30

File tree

5 files changed

+275
-22
lines changed

5 files changed

+275
-22
lines changed

src/compiler/checker.ts

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17354,10 +17354,7 @@ namespace ts {
1735417354
// inferring a type parameter constraint. Instead, make a lower priority inference from
1735517355
// the full source to whatever remains in the target. For example, when inferring from
1735617356
// string to 'string | T', make a lower priority inference of string for T.
17357-
const savePriority = priority;
17358-
priority |= InferencePriority.NakedTypeVariable;
17359-
inferFromTypes(source, target);
17360-
priority = savePriority;
17357+
inferWithPriority(source, target, InferencePriority.NakedTypeVariable);
1736117358
return;
1736217359
}
1736317360
source = getUnionType(sources);
@@ -17459,10 +17456,7 @@ namespace ts {
1745917456
else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) {
1746017457
const empty = createEmptyObjectTypeFromStringLiteral(source);
1746117458
contravariant = !contravariant;
17462-
const savePriority = priority;
17463-
priority |= InferencePriority.LiteralKeyof;
17464-
inferFromTypes(empty, (target as IndexType).type);
17465-
priority = savePriority;
17459+
inferWithPriority(empty, (target as IndexType).type, InferencePriority.LiteralKeyof);
1746617460
contravariant = !contravariant;
1746717461
}
1746817462
else if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) {
@@ -17514,6 +17508,13 @@ namespace ts {
1751417508
}
1751517509
}
1751617510

17511+
function inferWithPriority(source: Type, target: Type, newPriority: InferencePriority) {
17512+
const savePriority = priority;
17513+
priority |= newPriority;
17514+
inferFromTypes(source, target);
17515+
priority = savePriority;
17516+
}
17517+
1751717518
function invokeOnce(source: Type, target: Type, action: (source: Type, target: Type) => void) {
1751817519
const key = source.id + "," + target.id;
1751917520
const status = visited && visited.get(key);
@@ -17581,6 +17582,18 @@ namespace ts {
1758117582
return undefined;
1758217583
}
1758317584

17585+
function getSingleTypeVariableFromIntersectionTypes(types: Type[]) {
17586+
let typeVariable: Type | undefined;
17587+
for (const type of types) {
17588+
const t = type.flags & TypeFlags.Intersection && find((<IntersectionType>type).types, t => !!getInferenceInfoForType(t));
17589+
if (!t || typeVariable && t !== typeVariable) {
17590+
return undefined;
17591+
}
17592+
typeVariable = t;
17593+
}
17594+
return typeVariable;
17595+
}
17596+
1758417597
function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) {
1758517598
let typeVariableCount = 0;
1758617599
if (targetFlags & TypeFlags.Union) {
@@ -17608,6 +17621,16 @@ namespace ts {
1760817621
}
1760917622
}
1761017623
}
17624+
if (typeVariableCount === 0) {
17625+
// If every target is an intersection of types containing a single naked type variable,
17626+
// make a lower priority inference to that type variable. This handles inferring from
17627+
// 'A | B' to 'T & (X | Y)' where we want to infer 'A | B' for T.
17628+
const intersectionTypeVariable = getSingleTypeVariableFromIntersectionTypes(targets);
17629+
if (intersectionTypeVariable) {
17630+
inferWithPriority(source, intersectionTypeVariable, InferencePriority.NakedTypeVariable);
17631+
}
17632+
return;
17633+
}
1761117634
// If the target has a single naked type variable and no inference circularities were
1761217635
// encountered above (meaning we explored the types fully), create a union of the source
1761317636
// types from which no inferences have been made so far and infer from that union to the
@@ -17638,14 +17661,11 @@ namespace ts {
1763817661
// we want to infer string for T, not Promise<string> | string. For intersection types
1763917662
// we only infer to single naked type variables.
1764017663
if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) {
17641-
const savePriority = priority;
17642-
priority |= InferencePriority.NakedTypeVariable;
1764317664
for (const t of targets) {
1764417665
if (getInferenceInfoForType(t)) {
17645-
inferFromTypes(source, t);
17666+
inferWithPriority(source, t, InferencePriority.NakedTypeVariable);
1764617667
}
1764717668
}
17648-
priority = savePriority;
1764917669
}
1765017670
}
1765117671

@@ -17666,25 +17686,21 @@ namespace ts {
1766617686
if (inference && !inference.isFixed) {
1766717687
const inferredType = inferTypeForHomomorphicMappedType(source, target, <IndexType>constraintType);
1766817688
if (inferredType) {
17669-
const savePriority = priority;
1767017689
// We assign a lower priority to inferences made from types containing non-inferrable
1767117690
// types because we may only have a partial result (i.e. we may have failed to make
1767217691
// reverse inferences for some properties).
17673-
priority |= getObjectFlags(source) & ObjectFlags.NonInferrableType ?
17674-
InferencePriority.PartialHomomorphicMappedType : InferencePriority.HomomorphicMappedType;
17675-
inferFromTypes(inferredType, inference.typeParameter);
17676-
priority = savePriority;
17692+
inferWithPriority(inferredType, inference.typeParameter,
17693+
getObjectFlags(source) & ObjectFlags.NonInferrableType ?
17694+
InferencePriority.PartialHomomorphicMappedType :
17695+
InferencePriority.HomomorphicMappedType);
1767717696
}
1767817697
}
1767917698
return true;
1768017699
}
1768117700
if (constraintType.flags & TypeFlags.TypeParameter) {
1768217701
// We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type
1768317702
// parameter. First infer from 'keyof S' to K.
17684-
const savePriority = priority;
17685-
priority |= InferencePriority.MappedTypeConstraint;
17686-
inferFromTypes(getIndexType(source), constraintType);
17687-
priority = savePriority;
17703+
inferWithPriority(getIndexType(source), constraintType, InferencePriority.MappedTypeConstraint);
1768817704
// If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X },
1768917705
// where K extends keyof T, we make the same inferences as for a homomorphic mapped type
1769017706
// { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a

tests/baselines/reference/unionAndIntersectionInference3.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,43 @@ let y1 = foo1(sx); // string
5252

5353
let x2 = foo2(sa); // unknown
5454
let y2 = foo2(sx); // { extra: number }
55+
56+
// Repro from #33490
57+
58+
declare class Component<P> { props: P }
59+
60+
export type ComponentClass<P> = new (props: P) => Component<P>;
61+
export type FunctionComponent<P> = (props: P) => null;
62+
63+
export type ComponentType<P> = FunctionComponent<P> | ComponentClass<P>;
64+
65+
export interface RouteComponentProps { route: string }
66+
67+
declare function withRouter<
68+
P extends RouteComponentProps,
69+
C extends ComponentType<P>
70+
>(
71+
component: C & ComponentType<P>
72+
): ComponentClass<Omit<P, keyof RouteComponentProps>>;
73+
74+
interface Props extends RouteComponentProps { username: string }
75+
76+
declare const MyComponent: ComponentType<Props>;
77+
78+
withRouter(MyComponent);
79+
80+
// Repro from #33490
81+
82+
type AB<T> = { a: T } | { b: T };
83+
84+
// T & AB<U> normalizes to T & { a: U } | T & { b: U } below
85+
declare function foo<T, U>(obj: T & AB<U>): [T, U];
86+
declare let ab: AB<string>;
87+
88+
let z = foo(ab); // [AB<string>, string]
5589

5690

5791
//// [unionAndIntersectionInference3.js]
58-
"use strict";
5992
// Repro from #30720
6093
concatMaybe([1, 2, 3], 4);
6194
// Repros from #32247
@@ -70,3 +103,5 @@ let x1 = foo1(sa); // string
70103
let y1 = foo1(sx); // string
71104
let x2 = foo2(sa); // unknown
72105
let y2 = foo2(sx); // { extra: number }
106+
withRouter(MyComponent);
107+
let z = foo(ab); // [AB<string>, string]

tests/baselines/reference/unionAndIntersectionInference3.symbols

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,107 @@ let y2 = foo2(sx); // { extra: number }
205205
>foo2 : Symbol(foo2, Decl(unionAndIntersectionInference3.ts, 42, 57))
206206
>sx : Symbol(sx, Decl(unionAndIntersectionInference3.ts, 46, 11))
207207

208+
// Repro from #33490
209+
210+
declare class Component<P> { props: P }
211+
>Component : Symbol(Component, Decl(unionAndIntersectionInference3.ts, 52, 18))
212+
>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 56, 24))
213+
>props : Symbol(Component.props, Decl(unionAndIntersectionInference3.ts, 56, 28))
214+
>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 56, 24))
215+
216+
export type ComponentClass<P> = new (props: P) => Component<P>;
217+
>ComponentClass : Symbol(ComponentClass, Decl(unionAndIntersectionInference3.ts, 56, 39))
218+
>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 58, 27))
219+
>props : Symbol(props, Decl(unionAndIntersectionInference3.ts, 58, 37))
220+
>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 58, 27))
221+
>Component : Symbol(Component, Decl(unionAndIntersectionInference3.ts, 52, 18))
222+
>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 58, 27))
223+
224+
export type FunctionComponent<P> = (props: P) => null;
225+
>FunctionComponent : Symbol(FunctionComponent, Decl(unionAndIntersectionInference3.ts, 58, 63))
226+
>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 59, 30))
227+
>props : Symbol(props, Decl(unionAndIntersectionInference3.ts, 59, 36))
228+
>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 59, 30))
229+
230+
export type ComponentType<P> = FunctionComponent<P> | ComponentClass<P>;
231+
>ComponentType : Symbol(ComponentType, Decl(unionAndIntersectionInference3.ts, 59, 54))
232+
>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 61, 26))
233+
>FunctionComponent : Symbol(FunctionComponent, Decl(unionAndIntersectionInference3.ts, 58, 63))
234+
>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 61, 26))
235+
>ComponentClass : Symbol(ComponentClass, Decl(unionAndIntersectionInference3.ts, 56, 39))
236+
>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 61, 26))
237+
238+
export interface RouteComponentProps { route: string }
239+
>RouteComponentProps : Symbol(RouteComponentProps, Decl(unionAndIntersectionInference3.ts, 61, 72))
240+
>route : Symbol(RouteComponentProps.route, Decl(unionAndIntersectionInference3.ts, 63, 38))
241+
242+
declare function withRouter<
243+
>withRouter : Symbol(withRouter, Decl(unionAndIntersectionInference3.ts, 63, 54))
244+
245+
P extends RouteComponentProps,
246+
>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 65, 28))
247+
>RouteComponentProps : Symbol(RouteComponentProps, Decl(unionAndIntersectionInference3.ts, 61, 72))
248+
249+
C extends ComponentType<P>
250+
>C : Symbol(C, Decl(unionAndIntersectionInference3.ts, 66, 32))
251+
>ComponentType : Symbol(ComponentType, Decl(unionAndIntersectionInference3.ts, 59, 54))
252+
>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 65, 28))
253+
254+
>(
255+
component: C & ComponentType<P>
256+
>component : Symbol(component, Decl(unionAndIntersectionInference3.ts, 68, 2))
257+
>C : Symbol(C, Decl(unionAndIntersectionInference3.ts, 66, 32))
258+
>ComponentType : Symbol(ComponentType, Decl(unionAndIntersectionInference3.ts, 59, 54))
259+
>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 65, 28))
260+
261+
): ComponentClass<Omit<P, keyof RouteComponentProps>>;
262+
>ComponentClass : Symbol(ComponentClass, Decl(unionAndIntersectionInference3.ts, 56, 39))
263+
>Omit : Symbol(Omit, Decl(lib.es5.d.ts, --, --))
264+
>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 65, 28))
265+
>RouteComponentProps : Symbol(RouteComponentProps, Decl(unionAndIntersectionInference3.ts, 61, 72))
266+
267+
interface Props extends RouteComponentProps { username: string }
268+
>Props : Symbol(Props, Decl(unionAndIntersectionInference3.ts, 70, 54))
269+
>RouteComponentProps : Symbol(RouteComponentProps, Decl(unionAndIntersectionInference3.ts, 61, 72))
270+
>username : Symbol(Props.username, Decl(unionAndIntersectionInference3.ts, 72, 45))
271+
272+
declare const MyComponent: ComponentType<Props>;
273+
>MyComponent : Symbol(MyComponent, Decl(unionAndIntersectionInference3.ts, 74, 13))
274+
>ComponentType : Symbol(ComponentType, Decl(unionAndIntersectionInference3.ts, 59, 54))
275+
>Props : Symbol(Props, Decl(unionAndIntersectionInference3.ts, 70, 54))
276+
277+
withRouter(MyComponent);
278+
>withRouter : Symbol(withRouter, Decl(unionAndIntersectionInference3.ts, 63, 54))
279+
>MyComponent : Symbol(MyComponent, Decl(unionAndIntersectionInference3.ts, 74, 13))
280+
281+
// Repro from #33490
282+
283+
type AB<T> = { a: T } | { b: T };
284+
>AB : Symbol(AB, Decl(unionAndIntersectionInference3.ts, 76, 24))
285+
>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 80, 8))
286+
>a : Symbol(a, Decl(unionAndIntersectionInference3.ts, 80, 14))
287+
>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 80, 8))
288+
>b : Symbol(b, Decl(unionAndIntersectionInference3.ts, 80, 25))
289+
>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 80, 8))
290+
291+
// T & AB<U> normalizes to T & { a: U } | T & { b: U } below
292+
declare function foo<T, U>(obj: T & AB<U>): [T, U];
293+
>foo : Symbol(foo, Decl(unionAndIntersectionInference3.ts, 80, 33))
294+
>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 83, 21))
295+
>U : Symbol(U, Decl(unionAndIntersectionInference3.ts, 83, 23))
296+
>obj : Symbol(obj, Decl(unionAndIntersectionInference3.ts, 83, 27))
297+
>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 83, 21))
298+
>AB : Symbol(AB, Decl(unionAndIntersectionInference3.ts, 76, 24))
299+
>U : Symbol(U, Decl(unionAndIntersectionInference3.ts, 83, 23))
300+
>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 83, 21))
301+
>U : Symbol(U, Decl(unionAndIntersectionInference3.ts, 83, 23))
302+
303+
declare let ab: AB<string>;
304+
>ab : Symbol(ab, Decl(unionAndIntersectionInference3.ts, 84, 11))
305+
>AB : Symbol(AB, Decl(unionAndIntersectionInference3.ts, 76, 24))
306+
307+
let z = foo(ab); // [AB<string>, string]
308+
>z : Symbol(z, Decl(unionAndIntersectionInference3.ts, 86, 3))
309+
>foo : Symbol(foo, Decl(unionAndIntersectionInference3.ts, 80, 33))
310+
>ab : Symbol(ab, Decl(unionAndIntersectionInference3.ts, 84, 11))
311+

tests/baselines/reference/unionAndIntersectionInference3.types

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,67 @@ let y2 = foo2(sx); // { extra: number }
135135
>foo2 : <T>(obj: string[] & T) => T
136136
>sx : string[] & { extra: number; }
137137

138+
// Repro from #33490
139+
140+
declare class Component<P> { props: P }
141+
>Component : Component<P>
142+
>props : P
143+
144+
export type ComponentClass<P> = new (props: P) => Component<P>;
145+
>ComponentClass : ComponentClass<P>
146+
>props : P
147+
148+
export type FunctionComponent<P> = (props: P) => null;
149+
>FunctionComponent : FunctionComponent<P>
150+
>props : P
151+
>null : null
152+
153+
export type ComponentType<P> = FunctionComponent<P> | ComponentClass<P>;
154+
>ComponentType : ComponentType<P>
155+
156+
export interface RouteComponentProps { route: string }
157+
>route : string
158+
159+
declare function withRouter<
160+
>withRouter : <P extends RouteComponentProps, C extends ComponentType<P>>(component: (C & FunctionComponent<P>) | (C & ComponentClass<P>)) => ComponentClass<Pick<P, Exclude<keyof P, "route">>>
161+
162+
P extends RouteComponentProps,
163+
C extends ComponentType<P>
164+
>(
165+
component: C & ComponentType<P>
166+
>component : (C & FunctionComponent<P>) | (C & ComponentClass<P>)
167+
168+
): ComponentClass<Omit<P, keyof RouteComponentProps>>;
169+
170+
interface Props extends RouteComponentProps { username: string }
171+
>username : string
172+
173+
declare const MyComponent: ComponentType<Props>;
174+
>MyComponent : ComponentType<Props>
175+
176+
withRouter(MyComponent);
177+
>withRouter(MyComponent) : ComponentClass<Pick<Props, "username">>
178+
>withRouter : <P extends RouteComponentProps, C extends ComponentType<P>>(component: (C & FunctionComponent<P>) | (C & ComponentClass<P>)) => ComponentClass<Pick<P, Exclude<keyof P, "route">>>
179+
>MyComponent : ComponentType<Props>
180+
181+
// Repro from #33490
182+
183+
type AB<T> = { a: T } | { b: T };
184+
>AB : AB<T>
185+
>a : T
186+
>b : T
187+
188+
// T & AB<U> normalizes to T & { a: U } | T & { b: U } below
189+
declare function foo<T, U>(obj: T & AB<U>): [T, U];
190+
>foo : <T, U>(obj: (T & { a: U; }) | (T & { b: U; })) => [T, U]
191+
>obj : (T & { a: U; }) | (T & { b: U; })
192+
193+
declare let ab: AB<string>;
194+
>ab : AB<string>
195+
196+
let z = foo(ab); // [AB<string>, string]
197+
>z : [AB<string>, string]
198+
>foo(ab) : [AB<string>, string]
199+
>foo : <T, U>(obj: (T & { a: U; }) | (T & { b: U; })) => [T, U]
200+
>ab : AB<string>
201+

tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference3.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,37 @@ let y1 = foo1(sx); // string
5454

5555
let x2 = foo2(sa); // unknown
5656
let y2 = foo2(sx); // { extra: number }
57+
58+
// Repro from #33490
59+
60+
declare class Component<P> { props: P }
61+
62+
export type ComponentClass<P> = new (props: P) => Component<P>;
63+
export type FunctionComponent<P> = (props: P) => null;
64+
65+
export type ComponentType<P> = FunctionComponent<P> | ComponentClass<P>;
66+
67+
export interface RouteComponentProps { route: string }
68+
69+
declare function withRouter<
70+
P extends RouteComponentProps,
71+
C extends ComponentType<P>
72+
>(
73+
component: C & ComponentType<P>
74+
): ComponentClass<Omit<P, keyof RouteComponentProps>>;
75+
76+
interface Props extends RouteComponentProps { username: string }
77+
78+
declare const MyComponent: ComponentType<Props>;
79+
80+
withRouter(MyComponent);
81+
82+
// Repro from #33490
83+
84+
type AB<T> = { a: T } | { b: T };
85+
86+
// T & AB<U> normalizes to T & { a: U } | T & { b: U } below
87+
declare function foo<T, U>(obj: T & AB<U>): [T, U];
88+
declare let ab: AB<string>;
89+
90+
let z = foo(ab); // [AB<string>, string]

0 commit comments

Comments
 (0)