Skip to content

Commit 9005449

Browse files
authored
Merge pull request #31116 from Microsoft/higherOrderConstructorTypes
Support higher order inferences for constructor functions
2 parents 6e736c1 + 4051d73 commit 9005449

7 files changed

+719
-255
lines changed

src/compiler/checker.ts

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8565,7 +8565,7 @@ namespace ts {
85658565
function getSignatureInstantiation(signature: Signature, typeArguments: Type[] | undefined, isJavascript: boolean, inferredTypeParameters?: ReadonlyArray<TypeParameter>): Signature {
85668566
const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, fillMissingTypeArguments(typeArguments, signature.typeParameters, getMinTypeArgumentCount(signature.typeParameters), isJavascript));
85678567
if (inferredTypeParameters) {
8568-
const returnSignature = getSingleCallSignature(getReturnTypeOfSignature(instantiatedSignature));
8568+
const returnSignature = getSingleCallOrConstructSignature(getReturnTypeOfSignature(instantiatedSignature));
85698569
if (returnSignature) {
85708570
const newReturnSignature = cloneSignature(returnSignature);
85718571
newReturnSignature.typeParameters = inferredTypeParameters;
@@ -8641,7 +8641,8 @@ namespace ts {
86418641
// object type literal or interface (using the new keyword). Each way of declaring a constructor
86428642
// will result in a different declaration kind.
86438643
if (!signature.isolatedSignatureType) {
8644-
const isConstructor = signature.declaration!.kind === SyntaxKind.Constructor || signature.declaration!.kind === SyntaxKind.ConstructSignature; // TODO: GH#18217
8644+
const kind = signature.declaration ? signature.declaration.kind : SyntaxKind.Unknown;
8645+
const isConstructor = kind === SyntaxKind.Constructor || kind === SyntaxKind.ConstructSignature || kind === SyntaxKind.ConstructorType;
86458646
const type = createObjectType(ObjectFlags.Anonymous);
86468647
type.members = emptySymbols;
86478648
type.properties = emptyArray;
@@ -20630,11 +20631,24 @@ namespace ts {
2063020631

2063120632
// If type has a single call signature and no other members, return that signature. Otherwise, return undefined.
2063220633
function getSingleCallSignature(type: Type): Signature | undefined {
20634+
return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false);
20635+
}
20636+
20637+
function getSingleCallOrConstructSignature(type: Type): Signature | undefined {
20638+
return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false) ||
20639+
getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ false);
20640+
}
20641+
20642+
function getSingleSignature(type: Type, kind: SignatureKind, allowMembers: boolean): Signature | undefined {
2063320643
if (type.flags & TypeFlags.Object) {
2063420644
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
20635-
if (resolved.callSignatures.length === 1 && resolved.constructSignatures.length === 0 &&
20636-
resolved.properties.length === 0 && !resolved.stringIndexInfo && !resolved.numberIndexInfo) {
20637-
return resolved.callSignatures[0];
20645+
if (allowMembers || resolved.properties.length === 0 && !resolved.stringIndexInfo && !resolved.numberIndexInfo) {
20646+
if (kind === SignatureKind.Call && resolved.callSignatures.length === 1 && resolved.constructSignatures.length === 0) {
20647+
return resolved.callSignatures[0];
20648+
}
20649+
if (kind === SignatureKind.Construct && resolved.constructSignatures.length === 1 && resolved.callSignatures.length === 0) {
20650+
return resolved.constructSignatures[0];
20651+
}
2063820652
}
2063920653
}
2064020654
return undefined;
@@ -23976,24 +23990,27 @@ namespace ts {
2397623990

2397723991
function instantiateTypeWithSingleGenericCallSignature(node: Expression | MethodDeclaration | QualifiedName, type: Type, checkMode?: CheckMode) {
2397823992
if (checkMode && checkMode & (CheckMode.Inferential | CheckMode.SkipGenericFunctions)) {
23979-
const signature = getSingleCallSignature(type);
23993+
const callSignature = getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ true);
23994+
const constructSignature = getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ true);
23995+
const signature = callSignature || constructSignature;
2398023996
if (signature && signature.typeParameters) {
23981-
if (checkMode & CheckMode.SkipGenericFunctions) {
23982-
skippedGenericFunction(node, checkMode);
23983-
return anyFunctionType;
23984-
}
2398523997
const contextualType = getApparentTypeOfContextualType(<Expression>node);
23986-
if (contextualType) {
23987-
const contextualSignature = getSingleCallSignature(getNonNullableType(contextualType));
23998+
if (contextualType && !isMixinConstructorType(contextualType)) {
23999+
const contextualSignature = getSingleSignature(getNonNullableType(contextualType), callSignature ? SignatureKind.Call : SignatureKind.Construct, /*allowMembers*/ false);
2398824000
if (contextualSignature && !contextualSignature.typeParameters) {
24001+
if (checkMode & CheckMode.SkipGenericFunctions) {
24002+
skippedGenericFunction(node, checkMode);
24003+
return anyFunctionType;
24004+
}
2398924005
const context = getInferenceContext(node)!;
2399024006
// We have an expression that is an argument of a generic function for which we are performing
2399124007
// type argument inference. The expression is of a function type with a single generic call
2399224008
// signature and a contextual function type with a single non-generic call signature. Now check
2399324009
// if the outer function returns a function type with a single non-generic call signature and
2399424010
// if some of the outer function type parameters have no inferences so far. If so, we can
2399524011
// potentially add inferred type parameters to the outer function return type.
23996-
const returnSignature = context.signature && getSingleCallSignature(getReturnTypeOfSignature(context.signature));
24012+
const returnType = context.signature && getReturnTypeOfSignature(context.signature);
24013+
const returnSignature = returnType && getSingleCallOrConstructSignature(returnType);
2399724014
if (returnSignature && !returnSignature.typeParameters && !every(context.inferences, hasInferenceCandidates)) {
2399824015
// Instantiate the signature with its own type parameters as type arguments, possibly
2399924016
// renaming the type parameters to ensure they have unique names.

tests/baselines/reference/genericCallWithFunctionTypedArguments2.types

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,15 @@ var r4 = foo2(1, i2); // error
7878
>i2 : I2<string>
7979

8080
var r4b = foo2(1, a); // any
81-
>r4b : unknown
82-
>foo2(1, a) : unknown
81+
>r4b : number
82+
>foo2(1, a) : number
8383
>foo2 : <T, U>(x: T, cb: new (a: T) => U) => U
8484
>1 : 1
8585
>a : new <T>(x: T) => T
8686

8787
var r5 = foo2(1, i); // any
88-
>r5 : unknown
89-
>foo2(1, i) : unknown
88+
>r5 : number
89+
>foo2(1, i) : number
9090
>foo2 : <T, U>(x: T, cb: new (a: T) => U) => U
9191
>1 : 1
9292
>i : I
@@ -112,16 +112,16 @@ function foo3<T, U>(x: T, cb: new(a: T) => U, y: U) {
112112
}
113113

114114
var r7 = foo3(null, i, ''); // any
115-
>r7 : unknown
116-
>foo3(null, i, '') : unknown
115+
>r7 : any
116+
>foo3(null, i, '') : any
117117
>foo3 : <T, U>(x: T, cb: new (a: T) => U, y: U) => U
118118
>null : null
119119
>i : I
120120
>'' : ""
121121

122122
var r7b = foo3(null, a, ''); // any
123-
>r7b : unknown
124-
>foo3(null, a, '') : unknown
123+
>r7b : any
124+
>foo3(null, a, '') : any
125125
>foo3 : <T, U>(x: T, cb: new (a: T) => U, y: U) => U
126126
>null : null
127127
>a : new <T>(x: T) => T

tests/baselines/reference/genericFunctionInference1.errors.txt

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
tests/cases/compiler/genericFunctionInference1.ts(88,14): error TS2345: Argument of type '<a>(value: { key: a; }) => a' is not assignable to parameter of type '(value: Data) => string'.
1+
tests/cases/compiler/genericFunctionInference1.ts(135,14): error TS2345: Argument of type '<a>(value: { key: a; }) => a' is not assignable to parameter of type '(value: Data) => string'.
22
Type 'number' is not assignable to type 'string'.
33

44

@@ -67,6 +67,53 @@ tests/cases/compiler/genericFunctionInference1.ts(88,14): error TS2345: Argument
6767

6868
let f60 = wrap3(baz);
6969

70+
declare const list2: {
71+
<T>(a: T): T[];
72+
foo: string;
73+
bar(): number;
74+
}
75+
76+
let f70 = pipe(list2, box);
77+
let f71 = pipe(box, list2);
78+
79+
declare class Point {
80+
constructor(x: number, y: number);
81+
readonly x: number;
82+
readonly y: number;
83+
}
84+
85+
declare class Bag<T> {
86+
constructor(...args: T[]);
87+
contains(value: T): boolean;
88+
static foo: string;
89+
}
90+
91+
function asFunction<A extends any[], B>(cf: new (...args: A) => B) {
92+
return (...args: A) => new cf(...args);
93+
}
94+
95+
const newPoint = asFunction(Point);
96+
const newBag = asFunction(Bag);
97+
const p1 = new Point(10, 20);
98+
const p2 = newPoint(10, 20);
99+
const bag1 = new Bag(1, 2, 3);
100+
const bag2 = newBag('a', 'b', 'c');
101+
102+
declare class Comp<P> {
103+
props: P;
104+
constructor(props: P);
105+
}
106+
107+
type CompClass<P> = new (props: P) => Comp<P>;
108+
109+
declare function myHoc<P>(C: CompClass<P>): CompClass<P>;
110+
111+
type GenericProps<T> = { foo: number, stuff: T };
112+
113+
declare class GenericComp<T> extends Comp<GenericProps<T>> {}
114+
115+
const GenericComp2 = myHoc(GenericComp);
116+
70117
// #417
71118

72119
function mirror<A, B>(f: (a: A) => B): (a: A) => B { return f; }

tests/baselines/reference/genericFunctionInference1.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,53 @@ declare function baz<T, U extends T>(t1: T, t2: T, u: U): [T, U];
6363

6464
let f60 = wrap3(baz);
6565

66+
declare const list2: {
67+
<T>(a: T): T[];
68+
foo: string;
69+
bar(): number;
70+
}
71+
72+
let f70 = pipe(list2, box);
73+
let f71 = pipe(box, list2);
74+
75+
declare class Point {
76+
constructor(x: number, y: number);
77+
readonly x: number;
78+
readonly y: number;
79+
}
80+
81+
declare class Bag<T> {
82+
constructor(...args: T[]);
83+
contains(value: T): boolean;
84+
static foo: string;
85+
}
86+
87+
function asFunction<A extends any[], B>(cf: new (...args: A) => B) {
88+
return (...args: A) => new cf(...args);
89+
}
90+
91+
const newPoint = asFunction(Point);
92+
const newBag = asFunction(Bag);
93+
const p1 = new Point(10, 20);
94+
const p2 = newPoint(10, 20);
95+
const bag1 = new Bag(1, 2, 3);
96+
const bag2 = newBag('a', 'b', 'c');
97+
98+
declare class Comp<P> {
99+
props: P;
100+
constructor(props: P);
101+
}
102+
103+
type CompClass<P> = new (props: P) => Comp<P>;
104+
105+
declare function myHoc<P>(C: CompClass<P>): CompClass<P>;
106+
107+
type GenericProps<T> = { foo: number, stuff: T };
108+
109+
declare class GenericComp<T> extends Comp<GenericProps<T>> {}
110+
111+
const GenericComp2 = myHoc(GenericComp);
112+
66113
// #417
67114

68115
function mirror<A, B>(f: (a: A) => B): (a: A) => B { return f; }
@@ -242,6 +289,18 @@ const f40 = pipe4([list, box]);
242289
const f41 = pipe4([box, list]);
243290
const f50 = pipe5(list); // No higher order inference
244291
let f60 = wrap3(baz);
292+
let f70 = pipe(list2, box);
293+
let f71 = pipe(box, list2);
294+
function asFunction(cf) {
295+
return (...args) => new cf(...args);
296+
}
297+
const newPoint = asFunction(Point);
298+
const newBag = asFunction(Bag);
299+
const p1 = new Point(10, 20);
300+
const p2 = newPoint(10, 20);
301+
const bag1 = new Bag(1, 2, 3);
302+
const bag2 = newBag('a', 'b', 'c');
303+
const GenericComp2 = myHoc(GenericComp);
245304
// #417
246305
function mirror(f) { return f; }
247306
var identityM = mirror(identity);

0 commit comments

Comments
 (0)