diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b83fa543a899c..02718c6aa9ccc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17354,10 +17354,7 @@ namespace ts { // inferring a type parameter constraint. Instead, make a lower priority inference from // the full source to whatever remains in the target. For example, when inferring from // string to 'string | T', make a lower priority inference of string for T. - const savePriority = priority; - priority |= InferencePriority.NakedTypeVariable; - inferFromTypes(source, target); - priority = savePriority; + inferWithPriority(source, target, InferencePriority.NakedTypeVariable); return; } source = getUnionType(sources); @@ -17459,10 +17456,7 @@ namespace ts { else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) { const empty = createEmptyObjectTypeFromStringLiteral(source); contravariant = !contravariant; - const savePriority = priority; - priority |= InferencePriority.LiteralKeyof; - inferFromTypes(empty, (target as IndexType).type); - priority = savePriority; + inferWithPriority(empty, (target as IndexType).type, InferencePriority.LiteralKeyof); contravariant = !contravariant; } else if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) { @@ -17514,6 +17508,13 @@ namespace ts { } } + function inferWithPriority(source: Type, target: Type, newPriority: InferencePriority) { + const savePriority = priority; + priority |= newPriority; + inferFromTypes(source, target); + priority = savePriority; + } + function invokeOnce(source: Type, target: Type, action: (source: Type, target: Type) => void) { const key = source.id + "," + target.id; const status = visited && visited.get(key); @@ -17581,6 +17582,18 @@ namespace ts { return undefined; } + function getSingleTypeVariableFromIntersectionTypes(types: Type[]) { + let typeVariable: Type | undefined; + for (const type of types) { + const t = type.flags & TypeFlags.Intersection && find((type).types, t => !!getInferenceInfoForType(t)); + if (!t || typeVariable && t !== typeVariable) { + return undefined; + } + typeVariable = t; + } + return typeVariable; + } + function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) { let typeVariableCount = 0; if (targetFlags & TypeFlags.Union) { @@ -17608,6 +17621,16 @@ namespace ts { } } } + if (typeVariableCount === 0) { + // If every target is an intersection of types containing a single naked type variable, + // make a lower priority inference to that type variable. This handles inferring from + // 'A | B' to 'T & (X | Y)' where we want to infer 'A | B' for T. + const intersectionTypeVariable = getSingleTypeVariableFromIntersectionTypes(targets); + if (intersectionTypeVariable) { + inferWithPriority(source, intersectionTypeVariable, InferencePriority.NakedTypeVariable); + } + return; + } // If the target has a single naked type variable and no inference circularities were // encountered above (meaning we explored the types fully), create a union of the source // types from which no inferences have been made so far and infer from that union to the @@ -17638,14 +17661,11 @@ namespace ts { // we want to infer string for T, not Promise | string. For intersection types // we only infer to single naked type variables. if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) { - const savePriority = priority; - priority |= InferencePriority.NakedTypeVariable; for (const t of targets) { if (getInferenceInfoForType(t)) { - inferFromTypes(source, t); + inferWithPriority(source, t, InferencePriority.NakedTypeVariable); } } - priority = savePriority; } } @@ -17666,14 +17686,13 @@ namespace ts { if (inference && !inference.isFixed) { const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType); if (inferredType) { - const savePriority = priority; // We assign a lower priority to inferences made from types containing non-inferrable // types because we may only have a partial result (i.e. we may have failed to make // reverse inferences for some properties). - priority |= getObjectFlags(source) & ObjectFlags.NonInferrableType ? - InferencePriority.PartialHomomorphicMappedType : InferencePriority.HomomorphicMappedType; - inferFromTypes(inferredType, inference.typeParameter); - priority = savePriority; + inferWithPriority(inferredType, inference.typeParameter, + getObjectFlags(source) & ObjectFlags.NonInferrableType ? + InferencePriority.PartialHomomorphicMappedType : + InferencePriority.HomomorphicMappedType); } } return true; @@ -17681,10 +17700,7 @@ namespace ts { if (constraintType.flags & TypeFlags.TypeParameter) { // We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type // parameter. First infer from 'keyof S' to K. - const savePriority = priority; - priority |= InferencePriority.MappedTypeConstraint; - inferFromTypes(getIndexType(source), constraintType); - priority = savePriority; + inferWithPriority(getIndexType(source), constraintType, InferencePriority.MappedTypeConstraint); // If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X }, // where K extends keyof T, we make the same inferences as for a homomorphic mapped type // { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a diff --git a/tests/baselines/reference/unionAndIntersectionInference3.js b/tests/baselines/reference/unionAndIntersectionInference3.js index 415d98220a576..12ce5a9a19bdf 100644 --- a/tests/baselines/reference/unionAndIntersectionInference3.js +++ b/tests/baselines/reference/unionAndIntersectionInference3.js @@ -52,10 +52,43 @@ let y1 = foo1(sx); // string let x2 = foo2(sa); // unknown let y2 = foo2(sx); // { extra: number } + +// Repro from #33490 + +declare class Component

{ props: P } + +export type ComponentClass

= new (props: P) => Component

; +export type FunctionComponent

= (props: P) => null; + +export type ComponentType

= FunctionComponent

| ComponentClass

; + +export interface RouteComponentProps { route: string } + +declare function withRouter< + P extends RouteComponentProps, + C extends ComponentType

+>( + component: C & ComponentType

+): ComponentClass>; + +interface Props extends RouteComponentProps { username: string } + +declare const MyComponent: ComponentType; + +withRouter(MyComponent); + +// Repro from #33490 + +type AB = { a: T } | { b: T }; + +// T & AB normalizes to T & { a: U } | T & { b: U } below +declare function foo(obj: T & AB): [T, U]; +declare let ab: AB; + +let z = foo(ab); // [AB, string] //// [unionAndIntersectionInference3.js] -"use strict"; // Repro from #30720 concatMaybe([1, 2, 3], 4); // Repros from #32247 @@ -70,3 +103,5 @@ let x1 = foo1(sa); // string let y1 = foo1(sx); // string let x2 = foo2(sa); // unknown let y2 = foo2(sx); // { extra: number } +withRouter(MyComponent); +let z = foo(ab); // [AB, string] diff --git a/tests/baselines/reference/unionAndIntersectionInference3.symbols b/tests/baselines/reference/unionAndIntersectionInference3.symbols index 1d237951b4fd0..43079f9e8a958 100644 --- a/tests/baselines/reference/unionAndIntersectionInference3.symbols +++ b/tests/baselines/reference/unionAndIntersectionInference3.symbols @@ -205,3 +205,107 @@ let y2 = foo2(sx); // { extra: number } >foo2 : Symbol(foo2, Decl(unionAndIntersectionInference3.ts, 42, 57)) >sx : Symbol(sx, Decl(unionAndIntersectionInference3.ts, 46, 11)) +// Repro from #33490 + +declare class Component

{ props: P } +>Component : Symbol(Component, Decl(unionAndIntersectionInference3.ts, 52, 18)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 56, 24)) +>props : Symbol(Component.props, Decl(unionAndIntersectionInference3.ts, 56, 28)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 56, 24)) + +export type ComponentClass

= new (props: P) => Component

; +>ComponentClass : Symbol(ComponentClass, Decl(unionAndIntersectionInference3.ts, 56, 39)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 58, 27)) +>props : Symbol(props, Decl(unionAndIntersectionInference3.ts, 58, 37)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 58, 27)) +>Component : Symbol(Component, Decl(unionAndIntersectionInference3.ts, 52, 18)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 58, 27)) + +export type FunctionComponent

= (props: P) => null; +>FunctionComponent : Symbol(FunctionComponent, Decl(unionAndIntersectionInference3.ts, 58, 63)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 59, 30)) +>props : Symbol(props, Decl(unionAndIntersectionInference3.ts, 59, 36)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 59, 30)) + +export type ComponentType

= FunctionComponent

| ComponentClass

; +>ComponentType : Symbol(ComponentType, Decl(unionAndIntersectionInference3.ts, 59, 54)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 61, 26)) +>FunctionComponent : Symbol(FunctionComponent, Decl(unionAndIntersectionInference3.ts, 58, 63)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 61, 26)) +>ComponentClass : Symbol(ComponentClass, Decl(unionAndIntersectionInference3.ts, 56, 39)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 61, 26)) + +export interface RouteComponentProps { route: string } +>RouteComponentProps : Symbol(RouteComponentProps, Decl(unionAndIntersectionInference3.ts, 61, 72)) +>route : Symbol(RouteComponentProps.route, Decl(unionAndIntersectionInference3.ts, 63, 38)) + +declare function withRouter< +>withRouter : Symbol(withRouter, Decl(unionAndIntersectionInference3.ts, 63, 54)) + + P extends RouteComponentProps, +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 65, 28)) +>RouteComponentProps : Symbol(RouteComponentProps, Decl(unionAndIntersectionInference3.ts, 61, 72)) + + C extends ComponentType

+>C : Symbol(C, Decl(unionAndIntersectionInference3.ts, 66, 32)) +>ComponentType : Symbol(ComponentType, Decl(unionAndIntersectionInference3.ts, 59, 54)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 65, 28)) + +>( + component: C & ComponentType

+>component : Symbol(component, Decl(unionAndIntersectionInference3.ts, 68, 2)) +>C : Symbol(C, Decl(unionAndIntersectionInference3.ts, 66, 32)) +>ComponentType : Symbol(ComponentType, Decl(unionAndIntersectionInference3.ts, 59, 54)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 65, 28)) + +): ComponentClass>; +>ComponentClass : Symbol(ComponentClass, Decl(unionAndIntersectionInference3.ts, 56, 39)) +>Omit : Symbol(Omit, Decl(lib.es5.d.ts, --, --)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 65, 28)) +>RouteComponentProps : Symbol(RouteComponentProps, Decl(unionAndIntersectionInference3.ts, 61, 72)) + +interface Props extends RouteComponentProps { username: string } +>Props : Symbol(Props, Decl(unionAndIntersectionInference3.ts, 70, 54)) +>RouteComponentProps : Symbol(RouteComponentProps, Decl(unionAndIntersectionInference3.ts, 61, 72)) +>username : Symbol(Props.username, Decl(unionAndIntersectionInference3.ts, 72, 45)) + +declare const MyComponent: ComponentType; +>MyComponent : Symbol(MyComponent, Decl(unionAndIntersectionInference3.ts, 74, 13)) +>ComponentType : Symbol(ComponentType, Decl(unionAndIntersectionInference3.ts, 59, 54)) +>Props : Symbol(Props, Decl(unionAndIntersectionInference3.ts, 70, 54)) + +withRouter(MyComponent); +>withRouter : Symbol(withRouter, Decl(unionAndIntersectionInference3.ts, 63, 54)) +>MyComponent : Symbol(MyComponent, Decl(unionAndIntersectionInference3.ts, 74, 13)) + +// Repro from #33490 + +type AB = { a: T } | { b: T }; +>AB : Symbol(AB, Decl(unionAndIntersectionInference3.ts, 76, 24)) +>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 80, 8)) +>a : Symbol(a, Decl(unionAndIntersectionInference3.ts, 80, 14)) +>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 80, 8)) +>b : Symbol(b, Decl(unionAndIntersectionInference3.ts, 80, 25)) +>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 80, 8)) + +// T & AB normalizes to T & { a: U } | T & { b: U } below +declare function foo(obj: T & AB): [T, U]; +>foo : Symbol(foo, Decl(unionAndIntersectionInference3.ts, 80, 33)) +>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 83, 21)) +>U : Symbol(U, Decl(unionAndIntersectionInference3.ts, 83, 23)) +>obj : Symbol(obj, Decl(unionAndIntersectionInference3.ts, 83, 27)) +>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 83, 21)) +>AB : Symbol(AB, Decl(unionAndIntersectionInference3.ts, 76, 24)) +>U : Symbol(U, Decl(unionAndIntersectionInference3.ts, 83, 23)) +>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 83, 21)) +>U : Symbol(U, Decl(unionAndIntersectionInference3.ts, 83, 23)) + +declare let ab: AB; +>ab : Symbol(ab, Decl(unionAndIntersectionInference3.ts, 84, 11)) +>AB : Symbol(AB, Decl(unionAndIntersectionInference3.ts, 76, 24)) + +let z = foo(ab); // [AB, string] +>z : Symbol(z, Decl(unionAndIntersectionInference3.ts, 86, 3)) +>foo : Symbol(foo, Decl(unionAndIntersectionInference3.ts, 80, 33)) +>ab : Symbol(ab, Decl(unionAndIntersectionInference3.ts, 84, 11)) + diff --git a/tests/baselines/reference/unionAndIntersectionInference3.types b/tests/baselines/reference/unionAndIntersectionInference3.types index fb5074743219a..05cb9368f83f6 100644 --- a/tests/baselines/reference/unionAndIntersectionInference3.types +++ b/tests/baselines/reference/unionAndIntersectionInference3.types @@ -135,3 +135,67 @@ let y2 = foo2(sx); // { extra: number } >foo2 : (obj: string[] & T) => T >sx : string[] & { extra: number; } +// Repro from #33490 + +declare class Component

{ props: P } +>Component : Component

+>props : P + +export type ComponentClass

= new (props: P) => Component

; +>ComponentClass : ComponentClass

+>props : P + +export type FunctionComponent

= (props: P) => null; +>FunctionComponent : FunctionComponent

+>props : P +>null : null + +export type ComponentType

= FunctionComponent

| ComponentClass

; +>ComponentType : ComponentType

+ +export interface RouteComponentProps { route: string } +>route : string + +declare function withRouter< +>withRouter :

>(component: (C & FunctionComponent

) | (C & ComponentClass

)) => ComponentClass>> + + P extends RouteComponentProps, + C extends ComponentType

+>( + component: C & ComponentType

+>component : (C & FunctionComponent

) | (C & ComponentClass

) + +): ComponentClass>; + +interface Props extends RouteComponentProps { username: string } +>username : string + +declare const MyComponent: ComponentType; +>MyComponent : ComponentType + +withRouter(MyComponent); +>withRouter(MyComponent) : ComponentClass> +>withRouter :

>(component: (C & FunctionComponent

) | (C & ComponentClass

)) => ComponentClass>> +>MyComponent : ComponentType + +// Repro from #33490 + +type AB = { a: T } | { b: T }; +>AB : AB +>a : T +>b : T + +// T & AB normalizes to T & { a: U } | T & { b: U } below +declare function foo(obj: T & AB): [T, U]; +>foo : (obj: (T & { a: U; }) | (T & { b: U; })) => [T, U] +>obj : (T & { a: U; }) | (T & { b: U; }) + +declare let ab: AB; +>ab : AB + +let z = foo(ab); // [AB, string] +>z : [AB, string] +>foo(ab) : [AB, string] +>foo : (obj: (T & { a: U; }) | (T & { b: U; })) => [T, U] +>ab : AB + diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference3.ts b/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference3.ts index d64b19736952f..12a5e8b17cb1f 100644 --- a/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference3.ts +++ b/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference3.ts @@ -54,3 +54,37 @@ let y1 = foo1(sx); // string let x2 = foo2(sa); // unknown let y2 = foo2(sx); // { extra: number } + +// Repro from #33490 + +declare class Component

{ props: P } + +export type ComponentClass

= new (props: P) => Component

; +export type FunctionComponent

= (props: P) => null; + +export type ComponentType

= FunctionComponent

| ComponentClass

; + +export interface RouteComponentProps { route: string } + +declare function withRouter< + P extends RouteComponentProps, + C extends ComponentType

+>( + component: C & ComponentType

+): ComponentClass>; + +interface Props extends RouteComponentProps { username: string } + +declare const MyComponent: ComponentType; + +withRouter(MyComponent); + +// Repro from #33490 + +type AB = { a: T } | { b: T }; + +// T & AB normalizes to T & { a: U } | T & { b: U } below +declare function foo(obj: T & AB): [T, U]; +declare let ab: AB; + +let z = foo(ab); // [AB, string]