Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support higher order inferences for constructor functions #31116

Merged
merged 8 commits into from Apr 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 30 additions & 13 deletions src/compiler/checker.ts
Expand Up @@ -8563,7 +8563,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 @@ -8639,7 +8639,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 @@ -20421,11 +20422,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 @@ -23760,24 +23774,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
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
@@ -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
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