Skip to content

Commit

Permalink
Merge pull request #31116 from Microsoft/higherOrderConstructorTypes
Browse files Browse the repository at this point in the history
Support higher order inferences for constructor functions
  • Loading branch information
ahejlsberg authored Apr 30, 2019
2 parents 6e736c1 + 4051d73 commit 9005449
Show file tree
Hide file tree
Showing 7 changed files with 719 additions and 255 deletions.
43 changes: 30 additions & 13 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8565,7 +8565,7 @@ namespace ts {
function getSignatureInstantiation(signature: Signature, typeArguments: Type[] | undefined, isJavascript: boolean, inferredTypeParameters?: ReadonlyArray<TypeParameter>): Signature {
const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, fillMissingTypeArguments(typeArguments, signature.typeParameters, getMinTypeArgumentCount(signature.typeParameters), isJavascript));
if (inferredTypeParameters) {
const returnSignature = getSingleCallSignature(getReturnTypeOfSignature(instantiatedSignature));
const returnSignature = getSingleCallOrConstructSignature(getReturnTypeOfSignature(instantiatedSignature));
if (returnSignature) {
const newReturnSignature = cloneSignature(returnSignature);
newReturnSignature.typeParameters = inferredTypeParameters;
Expand Down Expand Up @@ -8641,7 +8641,8 @@ namespace ts {
// object type literal or interface (using the new keyword). Each way of declaring a constructor
// will result in a different declaration kind.
if (!signature.isolatedSignatureType) {
const isConstructor = signature.declaration!.kind === SyntaxKind.Constructor || signature.declaration!.kind === SyntaxKind.ConstructSignature; // TODO: GH#18217
const kind = signature.declaration ? signature.declaration.kind : SyntaxKind.Unknown;
const isConstructor = kind === SyntaxKind.Constructor || kind === SyntaxKind.ConstructSignature || kind === SyntaxKind.ConstructorType;
const type = createObjectType(ObjectFlags.Anonymous);
type.members = emptySymbols;
type.properties = emptyArray;
Expand Down Expand Up @@ -20630,11 +20631,24 @@ namespace ts {

// If type has a single call signature and no other members, return that signature. Otherwise, return undefined.
function getSingleCallSignature(type: Type): Signature | undefined {
return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false);
}

function getSingleCallOrConstructSignature(type: Type): Signature | undefined {
return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false) ||
getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ false);
}

function getSingleSignature(type: Type, kind: SignatureKind, allowMembers: boolean): Signature | undefined {
if (type.flags & TypeFlags.Object) {
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
if (resolved.callSignatures.length === 1 && resolved.constructSignatures.length === 0 &&
resolved.properties.length === 0 && !resolved.stringIndexInfo && !resolved.numberIndexInfo) {
return resolved.callSignatures[0];
if (allowMembers || resolved.properties.length === 0 && !resolved.stringIndexInfo && !resolved.numberIndexInfo) {
if (kind === SignatureKind.Call && resolved.callSignatures.length === 1 && resolved.constructSignatures.length === 0) {
return resolved.callSignatures[0];
}
if (kind === SignatureKind.Construct && resolved.constructSignatures.length === 1 && resolved.callSignatures.length === 0) {
return resolved.constructSignatures[0];
}
}
}
return undefined;
Expand Down Expand Up @@ -23976,24 +23990,27 @@ namespace ts {

function instantiateTypeWithSingleGenericCallSignature(node: Expression | MethodDeclaration | QualifiedName, type: Type, checkMode?: CheckMode) {
if (checkMode && checkMode & (CheckMode.Inferential | CheckMode.SkipGenericFunctions)) {
const signature = getSingleCallSignature(type);
const callSignature = getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ true);
const constructSignature = getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ true);
const signature = callSignature || constructSignature;
if (signature && signature.typeParameters) {
if (checkMode & CheckMode.SkipGenericFunctions) {
skippedGenericFunction(node, checkMode);
return anyFunctionType;
}
const contextualType = getApparentTypeOfContextualType(<Expression>node);
if (contextualType) {
const contextualSignature = getSingleCallSignature(getNonNullableType(contextualType));
if (contextualType && !isMixinConstructorType(contextualType)) {
const contextualSignature = getSingleSignature(getNonNullableType(contextualType), callSignature ? SignatureKind.Call : SignatureKind.Construct, /*allowMembers*/ false);
if (contextualSignature && !contextualSignature.typeParameters) {
if (checkMode & CheckMode.SkipGenericFunctions) {
skippedGenericFunction(node, checkMode);
return anyFunctionType;
}
const context = getInferenceContext(node)!;
// We have an expression that is an argument of a generic function for which we are performing
// type argument inference. The expression is of a function type with a single generic call
// signature and a contextual function type with a single non-generic call signature. Now check
// if the outer function returns a function type with a single non-generic call signature and
// if some of the outer function type parameters have no inferences so far. If so, we can
// potentially add inferred type parameters to the outer function return type.
const returnSignature = context.signature && getSingleCallSignature(getReturnTypeOfSignature(context.signature));
const returnType = context.signature && getReturnTypeOfSignature(context.signature);
const returnSignature = returnType && getSingleCallOrConstructSignature(returnType);
if (returnSignature && !returnSignature.typeParameters && !every(context.inferences, hasInferenceCandidates)) {
// Instantiate the signature with its own type parameters as type arguments, possibly
// renaming the type parameters to ensure they have unique names.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,15 @@ var r4 = foo2(1, i2); // error
>i2 : I2<string>

var r4b = foo2(1, a); // any
>r4b : unknown
>foo2(1, a) : unknown
>r4b : number
>foo2(1, a) : number
>foo2 : <T, U>(x: T, cb: new (a: T) => U) => U
>1 : 1
>a : new <T>(x: T) => T

var r5 = foo2(1, i); // any
>r5 : unknown
>foo2(1, i) : unknown
>r5 : number
>foo2(1, i) : number
>foo2 : <T, U>(x: T, cb: new (a: T) => U) => U
>1 : 1
>i : I
Expand All @@ -112,16 +112,16 @@ function foo3<T, U>(x: T, cb: new(a: T) => U, y: U) {
}

var r7 = foo3(null, i, ''); // any
>r7 : unknown
>foo3(null, i, '') : unknown
>r7 : any
>foo3(null, i, '') : any
>foo3 : <T, U>(x: T, cb: new (a: T) => U, y: U) => U
>null : null
>i : I
>'' : ""

var r7b = foo3(null, a, ''); // any
>r7b : unknown
>foo3(null, a, '') : unknown
>r7b : any
>foo3(null, a, '') : any
>foo3 : <T, U>(x: T, cb: new (a: T) => U, y: U) => U
>null : null
>a : new <T>(x: T) => T
Expand Down
49 changes: 48 additions & 1 deletion tests/baselines/reference/genericFunctionInference1.errors.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
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'.
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'.
Type 'number' is not assignable to type 'string'.


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

let f60 = wrap3(baz);

declare const list2: {
<T>(a: T): T[];
foo: string;
bar(): number;
}

let f70 = pipe(list2, box);
let f71 = pipe(box, list2);

declare class Point {
constructor(x: number, y: number);
readonly x: number;
readonly y: number;
}

declare class Bag<T> {
constructor(...args: T[]);
contains(value: T): boolean;
static foo: string;
}

function asFunction<A extends any[], B>(cf: new (...args: A) => B) {
return (...args: A) => new cf(...args);
}

const newPoint = asFunction(Point);
const newBag = asFunction(Bag);
const p1 = new Point(10, 20);
const p2 = newPoint(10, 20);
const bag1 = new Bag(1, 2, 3);
const bag2 = newBag('a', 'b', 'c');

declare class Comp<P> {
props: P;
constructor(props: P);
}

type CompClass<P> = new (props: P) => Comp<P>;

declare function myHoc<P>(C: CompClass<P>): CompClass<P>;

type GenericProps<T> = { foo: number, stuff: T };

declare class GenericComp<T> extends Comp<GenericProps<T>> {}

const GenericComp2 = myHoc(GenericComp);

// #417

function mirror<A, B>(f: (a: A) => B): (a: A) => B { return f; }
Expand Down
59 changes: 59 additions & 0 deletions tests/baselines/reference/genericFunctionInference1.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,53 @@ declare function baz<T, U extends T>(t1: T, t2: T, u: U): [T, U];

let f60 = wrap3(baz);

declare const list2: {
<T>(a: T): T[];
foo: string;
bar(): number;
}

let f70 = pipe(list2, box);
let f71 = pipe(box, list2);

declare class Point {
constructor(x: number, y: number);
readonly x: number;
readonly y: number;
}

declare class Bag<T> {
constructor(...args: T[]);
contains(value: T): boolean;
static foo: string;
}

function asFunction<A extends any[], B>(cf: new (...args: A) => B) {
return (...args: A) => new cf(...args);
}

const newPoint = asFunction(Point);
const newBag = asFunction(Bag);
const p1 = new Point(10, 20);
const p2 = newPoint(10, 20);
const bag1 = new Bag(1, 2, 3);
const bag2 = newBag('a', 'b', 'c');

declare class Comp<P> {
props: P;
constructor(props: P);
}

type CompClass<P> = new (props: P) => Comp<P>;

declare function myHoc<P>(C: CompClass<P>): CompClass<P>;

type GenericProps<T> = { foo: number, stuff: T };

declare class GenericComp<T> extends Comp<GenericProps<T>> {}

const GenericComp2 = myHoc(GenericComp);

// #417

function mirror<A, B>(f: (a: A) => B): (a: A) => B { return f; }
Expand Down Expand Up @@ -242,6 +289,18 @@ const f40 = pipe4([list, box]);
const f41 = pipe4([box, list]);
const f50 = pipe5(list); // No higher order inference
let f60 = wrap3(baz);
let f70 = pipe(list2, box);
let f71 = pipe(box, list2);
function asFunction(cf) {
return (...args) => new cf(...args);
}
const newPoint = asFunction(Point);
const newBag = asFunction(Bag);
const p1 = new Point(10, 20);
const p2 = newPoint(10, 20);
const bag1 = new Bag(1, 2, 3);
const bag2 = newBag('a', 'b', 'c');
const GenericComp2 = myHoc(GenericComp);
// #417
function mirror(f) { return f; }
var identityM = mirror(identity);
Expand Down
Loading

0 comments on commit 9005449

Please sign in to comment.