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

Contextual generic function types #16305

Merged
merged 9 commits into from
Jun 7, 2017
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 22 additions & 12 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10256,7 +10256,7 @@ namespace ts {
const objectFlags = getObjectFlags(type);
return !!(type.flags & TypeFlags.TypeVariable ||
objectFlags & ObjectFlags.Reference && forEach((<TypeReference>type).typeArguments, couldContainTypeVariables) ||
objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Method | SymbolFlags.TypeLiteral | SymbolFlags.Class) ||
objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.TypeLiteral | SymbolFlags.Class) ||
objectFlags & ObjectFlags.Mapped ||
type.flags & TypeFlags.UnionOrIntersection && couldUnionOrIntersectionContainTypeVariables(<UnionOrIntersectionType>type));
}
Expand Down Expand Up @@ -13059,13 +13059,13 @@ namespace ts {
return node ? node.contextualMapper : identityMapper;
}

// If the given type is an object or union type, if that type has a single signature, and if
// that signature is non-generic, return the signature. Otherwise return undefined.
function getNonGenericSignature(type: Type, node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature {
// If the given type is an object or union type with a single signature, and if that signature has at
// least as many parameters as the given function, return the signature. Otherwise return undefined.
function getContextualCallSignature(type: Type, node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature {
const signatures = getSignaturesOfStructuredType(type, SignatureKind.Call);
if (signatures.length === 1) {
const signature = signatures[0];
if (!signature.typeParameters && !isAritySmaller(signature, node)) {
if (!isAritySmaller(signature, node)) {
return signature;
}
}
Expand Down Expand Up @@ -13116,12 +13116,12 @@ namespace ts {
return undefined;
}
if (!(type.flags & TypeFlags.Union)) {
return getNonGenericSignature(type, node);
return getContextualCallSignature(type, node);
}
let signatureList: Signature[];
const types = (<UnionType>type).types;
for (const current of types) {
const signature = getNonGenericSignature(current, node);
const signature = getContextualCallSignature(current, node);
if (signature) {
if (!signatureList) {
// This signature will contribute to contextual union signature
Expand Down Expand Up @@ -14981,11 +14981,21 @@ namespace ts {
// We clone the contextual mapper to avoid disturbing a resolution in progress for an
// outer call expression. Effectively we just want a snapshot of whatever has been
// inferred for any outer call expression so far.
const mapper = cloneTypeMapper(getContextualMapper(node));
const instantiatedType = instantiateType(contextualType, mapper);
const returnType = getReturnTypeOfSignature(signature);
// Inferences made from return types have lower priority than all other inferences.
inferTypes(context.inferences, instantiatedType, returnType, InferencePriority.ReturnType);
const instantiatedType = instantiateType(contextualType, cloneTypeMapper(getContextualMapper(node)));
// If the contextual type is a generic pure function type, we instantiate the type with
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is meant by "generic pure function type"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A type that has nothing but a single call signature.

// its own type parameters and type arguments. This ensures that the type parameters are
// not erased to type any during type inference such that they can be inferred as actual
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type 'any'

// types from the contextual type. For example:
// declare function arrayMap<T, U>(f: (x: T) => U): (a: T[]) => U[];
// const boxElements: <A>(a: A[]) => { value: A }[] = arrayMap(value => ({ value }));
// Above, the type of the 'value' parameter is inferred to be 'A'.
const contextualSignature = getSingleCallSignature(instantiatedType);
const inferenceSourceType = contextualSignature && contextualSignature.typeParameters ?
getOrCreateTypeFromSignature(getSignatureInstantiation(contextualSignature, contextualSignature.typeParameters)) :
instantiatedType;
const inferenceTargetType = getReturnTypeOfSignature(signature);
// Inferences made from return types have lower priority than all other inferences.
inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ var f2: {
};

f2 = (x, y) => { return x }
>f2 = (x, y) => { return x } : (x: any, y: any) => any
>f2 = (x, y) => { return x } : (x: T, y: U) => T
>f2 : <T, U>(x: T, y: U) => T
>(x, y) => { return x } : (x: any, y: any) => any
>x : any
>y : any
>x : any
>(x, y) => { return x } : (x: T, y: U) => T
>x : T
>y : U
>x : T

107 changes: 107 additions & 0 deletions tests/baselines/reference/genericContextualTypes1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//// [genericContextualTypes1.ts]
type Box<T> = { value: T };

declare function wrap<A, B>(f: (a: A) => B): (a: A) => B;

declare function compose<A, B, C>(f: (a: A) => B, g: (b: B) => C): (a: A) => C;

declare function list<T>(a: T): T[];

declare function unlist<T>(a: T[]): T;

declare function box<V>(x: V): Box<V>;

declare function unbox<W>(x: Box<W>): W;

declare function map<T, U>(a: T[], f: (x: T) => U): U[];

declare function identity<T>(x: T): T;

declare function zip<A, B>(a: A, b: B): [A, B];

declare function flip<X, Y, Z>(f: (x: X, y: Y) => Z): (y: Y, x: X) => Z;

const f00: <A>(x: A) => A[] = list;
const f01: <A>(x: A) => A[] = x => [x];
const f02: <A>(x: A) => A[] = wrap(list);
const f03: <A>(x: A) => A[] = wrap(x => [x]);

const f10: <T>(x: T) => Box<T[]> = compose(a => list(a), b => box(b));
const f11: <T>(x: T) => Box<T[]> = compose(list, box);
const f12: <T>(x: Box<T[]>) => T = compose(a => unbox(a), b => unlist(b));
const f13: <T>(x: Box<T[]>) => T = compose(unbox, unlist);

const arrayMap = <T, U>(f: (x: T) => U) => (a: T[]) => a.map(f);
const arrayFilter = <T>(f: (x: T) => boolean) => (a: T[]) => a.filter(f);

const f20: (a: string[]) => number[] = arrayMap(x => x.length);
const f21: <A>(a: A[]) => A[][] = arrayMap(x => [x]);
const f22: <A>(a: A[]) => A[] = arrayMap(identity);
const f23: <A>(a: A[]) => Box<A>[] = arrayMap(value => ({ value }));

const f30: (a: string[]) => string[] = arrayFilter(x => x.length > 10);
const f31: <T extends Box<number>>(a: T[]) => T[] = arrayFilter(x => x.value > 10);

const f40: <A, B>(b: B, a: A) => [A, B] = flip(zip);

// Repro from #16293

type fn = <A>(a: A) => A;
const fn: fn = a => a;


//// [genericContextualTypes1.js]
"use strict";
var f00 = list;
var f01 = function (x) { return [x]; };
var f02 = wrap(list);
var f03 = wrap(function (x) { return [x]; });
var f10 = compose(function (a) { return list(a); }, function (b) { return box(b); });
var f11 = compose(list, box);
var f12 = compose(function (a) { return unbox(a); }, function (b) { return unlist(b); });
var f13 = compose(unbox, unlist);
var arrayMap = function (f) { return function (a) { return a.map(f); }; };
var arrayFilter = function (f) { return function (a) { return a.filter(f); }; };
var f20 = arrayMap(function (x) { return x.length; });
var f21 = arrayMap(function (x) { return [x]; });
var f22 = arrayMap(identity);
var f23 = arrayMap(function (value) { return ({ value: value }); });
var f30 = arrayFilter(function (x) { return x.length > 10; });
var f31 = arrayFilter(function (x) { return x.value > 10; });
var f40 = flip(zip);
var fn = function (a) { return a; };


//// [genericContextualTypes1.d.ts]
declare type Box<T> = {
value: T;
};
declare function wrap<A, B>(f: (a: A) => B): (a: A) => B;
declare function compose<A, B, C>(f: (a: A) => B, g: (b: B) => C): (a: A) => C;
declare function list<T>(a: T): T[];
declare function unlist<T>(a: T[]): T;
declare function box<V>(x: V): Box<V>;
declare function unbox<W>(x: Box<W>): W;
declare function map<T, U>(a: T[], f: (x: T) => U): U[];
declare function identity<T>(x: T): T;
declare function zip<A, B>(a: A, b: B): [A, B];
declare function flip<X, Y, Z>(f: (x: X, y: Y) => Z): (y: Y, x: X) => Z;
declare const f00: <A>(x: A) => A[];
declare const f01: <A>(x: A) => A[];
declare const f02: <A>(x: A) => A[];
declare const f03: <A>(x: A) => A[];
declare const f10: <T>(x: T) => Box<T[]>;
declare const f11: <T>(x: T) => Box<T[]>;
declare const f12: <T>(x: Box<T[]>) => T;
declare const f13: <T>(x: Box<T[]>) => T;
declare const arrayMap: <T, U>(f: (x: T) => U) => (a: T[]) => U[];
declare const arrayFilter: <T>(f: (x: T) => boolean) => (a: T[]) => T[];
declare const f20: (a: string[]) => number[];
declare const f21: <A>(a: A[]) => A[][];
declare const f22: <A>(a: A[]) => A[];
declare const f23: <A>(a: A[]) => Box<A>[];
declare const f30: (a: string[]) => string[];
declare const f31: <T extends Box<number>>(a: T[]) => T[];
declare const f40: <A, B>(b: B, a: A) => [A, B];
declare type fn = <A>(a: A) => A;
declare const fn: fn;
Loading