Skip to content

Commit 5888804

Browse files
authored
Merge pull request #16305 from Microsoft/contextualGenericTypes
Contextual generic function types
2 parents 7608196 + 1c967c3 commit 5888804

13 files changed

+953
-50
lines changed

src/compiler/checker.ts

+22-12
Original file line numberDiff line numberDiff line change
@@ -10261,7 +10261,7 @@ namespace ts {
1026110261
const objectFlags = getObjectFlags(type);
1026210262
return !!(type.flags & TypeFlags.TypeVariable ||
1026310263
objectFlags & ObjectFlags.Reference && forEach((<TypeReference>type).typeArguments, couldContainTypeVariables) ||
10264-
objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Method | SymbolFlags.TypeLiteral | SymbolFlags.Class) ||
10264+
objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.TypeLiteral | SymbolFlags.Class) ||
1026510265
objectFlags & ObjectFlags.Mapped ||
1026610266
type.flags & TypeFlags.UnionOrIntersection && couldUnionOrIntersectionContainTypeVariables(<UnionOrIntersectionType>type));
1026710267
}
@@ -13066,13 +13066,13 @@ namespace ts {
1306613066
return node ? node.contextualMapper : identityMapper;
1306713067
}
1306813068

13069-
// If the given type is an object or union type, if that type has a single signature, and if
13070-
// that signature is non-generic, return the signature. Otherwise return undefined.
13071-
function getNonGenericSignature(type: Type, node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature {
13069+
// If the given type is an object or union type with a single signature, and if that signature has at
13070+
// least as many parameters as the given function, return the signature. Otherwise return undefined.
13071+
function getContextualCallSignature(type: Type, node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature {
1307213072
const signatures = getSignaturesOfStructuredType(type, SignatureKind.Call);
1307313073
if (signatures.length === 1) {
1307413074
const signature = signatures[0];
13075-
if (!signature.typeParameters && !isAritySmaller(signature, node)) {
13075+
if (!isAritySmaller(signature, node)) {
1307613076
return signature;
1307713077
}
1307813078
}
@@ -13123,12 +13123,12 @@ namespace ts {
1312313123
return undefined;
1312413124
}
1312513125
if (!(type.flags & TypeFlags.Union)) {
13126-
return getNonGenericSignature(type, node);
13126+
return getContextualCallSignature(type, node);
1312713127
}
1312813128
let signatureList: Signature[];
1312913129
const types = (<UnionType>type).types;
1313013130
for (const current of types) {
13131-
const signature = getNonGenericSignature(current, node);
13131+
const signature = getContextualCallSignature(current, node);
1313213132
if (signature) {
1313313133
if (!signatureList) {
1313413134
// This signature will contribute to contextual union signature
@@ -14988,11 +14988,21 @@ namespace ts {
1498814988
// We clone the contextual mapper to avoid disturbing a resolution in progress for an
1498914989
// outer call expression. Effectively we just want a snapshot of whatever has been
1499014990
// inferred for any outer call expression so far.
14991-
const mapper = cloneTypeMapper(getContextualMapper(node));
14992-
const instantiatedType = instantiateType(contextualType, mapper);
14993-
const returnType = getReturnTypeOfSignature(signature);
14994-
// Inferences made from return types have lower priority than all other inferences.
14995-
inferTypes(context.inferences, instantiatedType, returnType, InferencePriority.ReturnType);
14991+
const instantiatedType = instantiateType(contextualType, cloneTypeMapper(getContextualMapper(node)));
14992+
// If the contextual type is a generic pure function type, we instantiate the type with
14993+
// its own type parameters and type arguments. This ensures that the type parameters are
14994+
// not erased to type any during type inference such that they can be inferred as actual
14995+
// types from the contextual type. For example:
14996+
// declare function arrayMap<T, U>(f: (x: T) => U): (a: T[]) => U[];
14997+
// const boxElements: <A>(a: A[]) => { value: A }[] = arrayMap(value => ({ value }));
14998+
// Above, the type of the 'value' parameter is inferred to be 'A'.
14999+
const contextualSignature = getSingleCallSignature(instantiatedType);
15000+
const inferenceSourceType = contextualSignature && contextualSignature.typeParameters ?
15001+
getOrCreateTypeFromSignature(getSignatureInstantiation(contextualSignature, contextualSignature.typeParameters)) :
15002+
instantiatedType;
15003+
const inferenceTargetType = getReturnTypeOfSignature(signature);
15004+
// Inferences made from return types have lower priority than all other inferences.
15005+
inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType);
1499615006
}
1499715007
}
1499815008

tests/baselines/reference/contextualTypingWithGenericSignature.types

+5-5
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ var f2: {
1616
};
1717

1818
f2 = (x, y) => { return x }
19-
>f2 = (x, y) => { return x } : (x: any, y: any) => any
19+
>f2 = (x, y) => { return x } : (x: T, y: U) => T
2020
>f2 : <T, U>(x: T, y: U) => T
21-
>(x, y) => { return x } : (x: any, y: any) => any
22-
>x : any
23-
>y : any
24-
>x : any
21+
>(x, y) => { return x } : (x: T, y: U) => T
22+
>x : T
23+
>y : U
24+
>x : T
2525

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//// [genericContextualTypes1.ts]
2+
type Box<T> = { value: T };
3+
4+
declare function wrap<A, B>(f: (a: A) => B): (a: A) => B;
5+
6+
declare function compose<A, B, C>(f: (a: A) => B, g: (b: B) => C): (a: A) => C;
7+
8+
declare function list<T>(a: T): T[];
9+
10+
declare function unlist<T>(a: T[]): T;
11+
12+
declare function box<V>(x: V): Box<V>;
13+
14+
declare function unbox<W>(x: Box<W>): W;
15+
16+
declare function map<T, U>(a: T[], f: (x: T) => U): U[];
17+
18+
declare function identity<T>(x: T): T;
19+
20+
declare function zip<A, B>(a: A, b: B): [A, B];
21+
22+
declare function flip<X, Y, Z>(f: (x: X, y: Y) => Z): (y: Y, x: X) => Z;
23+
24+
const f00: <A>(x: A) => A[] = list;
25+
const f01: <A>(x: A) => A[] = x => [x];
26+
const f02: <A>(x: A) => A[] = wrap(list);
27+
const f03: <A>(x: A) => A[] = wrap(x => [x]);
28+
29+
const f10: <T>(x: T) => Box<T[]> = compose(a => list(a), b => box(b));
30+
const f11: <T>(x: T) => Box<T[]> = compose(list, box);
31+
const f12: <T>(x: Box<T[]>) => T = compose(a => unbox(a), b => unlist(b));
32+
const f13: <T>(x: Box<T[]>) => T = compose(unbox, unlist);
33+
34+
const arrayMap = <T, U>(f: (x: T) => U) => (a: T[]) => a.map(f);
35+
const arrayFilter = <T>(f: (x: T) => boolean) => (a: T[]) => a.filter(f);
36+
37+
const f20: (a: string[]) => number[] = arrayMap(x => x.length);
38+
const f21: <A>(a: A[]) => A[][] = arrayMap(x => [x]);
39+
const f22: <A>(a: A[]) => A[] = arrayMap(identity);
40+
const f23: <A>(a: A[]) => Box<A>[] = arrayMap(value => ({ value }));
41+
42+
const f30: (a: string[]) => string[] = arrayFilter(x => x.length > 10);
43+
const f31: <T extends Box<number>>(a: T[]) => T[] = arrayFilter(x => x.value > 10);
44+
45+
const f40: <A, B>(b: B, a: A) => [A, B] = flip(zip);
46+
47+
// Repro from #16293
48+
49+
type fn = <A>(a: A) => A;
50+
const fn: fn = a => a;
51+
52+
53+
//// [genericContextualTypes1.js]
54+
"use strict";
55+
var f00 = list;
56+
var f01 = function (x) { return [x]; };
57+
var f02 = wrap(list);
58+
var f03 = wrap(function (x) { return [x]; });
59+
var f10 = compose(function (a) { return list(a); }, function (b) { return box(b); });
60+
var f11 = compose(list, box);
61+
var f12 = compose(function (a) { return unbox(a); }, function (b) { return unlist(b); });
62+
var f13 = compose(unbox, unlist);
63+
var arrayMap = function (f) { return function (a) { return a.map(f); }; };
64+
var arrayFilter = function (f) { return function (a) { return a.filter(f); }; };
65+
var f20 = arrayMap(function (x) { return x.length; });
66+
var f21 = arrayMap(function (x) { return [x]; });
67+
var f22 = arrayMap(identity);
68+
var f23 = arrayMap(function (value) { return ({ value: value }); });
69+
var f30 = arrayFilter(function (x) { return x.length > 10; });
70+
var f31 = arrayFilter(function (x) { return x.value > 10; });
71+
var f40 = flip(zip);
72+
var fn = function (a) { return a; };
73+
74+
75+
//// [genericContextualTypes1.d.ts]
76+
declare type Box<T> = {
77+
value: T;
78+
};
79+
declare function wrap<A, B>(f: (a: A) => B): (a: A) => B;
80+
declare function compose<A, B, C>(f: (a: A) => B, g: (b: B) => C): (a: A) => C;
81+
declare function list<T>(a: T): T[];
82+
declare function unlist<T>(a: T[]): T;
83+
declare function box<V>(x: V): Box<V>;
84+
declare function unbox<W>(x: Box<W>): W;
85+
declare function map<T, U>(a: T[], f: (x: T) => U): U[];
86+
declare function identity<T>(x: T): T;
87+
declare function zip<A, B>(a: A, b: B): [A, B];
88+
declare function flip<X, Y, Z>(f: (x: X, y: Y) => Z): (y: Y, x: X) => Z;
89+
declare const f00: <A>(x: A) => A[];
90+
declare const f01: <A>(x: A) => A[];
91+
declare const f02: <A>(x: A) => A[];
92+
declare const f03: <A>(x: A) => A[];
93+
declare const f10: <T>(x: T) => Box<T[]>;
94+
declare const f11: <T>(x: T) => Box<T[]>;
95+
declare const f12: <T>(x: Box<T[]>) => T;
96+
declare const f13: <T>(x: Box<T[]>) => T;
97+
declare const arrayMap: <T, U>(f: (x: T) => U) => (a: T[]) => U[];
98+
declare const arrayFilter: <T>(f: (x: T) => boolean) => (a: T[]) => T[];
99+
declare const f20: (a: string[]) => number[];
100+
declare const f21: <A>(a: A[]) => A[][];
101+
declare const f22: <A>(a: A[]) => A[];
102+
declare const f23: <A>(a: A[]) => Box<A>[];
103+
declare const f30: (a: string[]) => string[];
104+
declare const f31: <T extends Box<number>>(a: T[]) => T[];
105+
declare const f40: <A, B>(b: B, a: A) => [A, B];
106+
declare type fn = <A>(a: A) => A;
107+
declare const fn: fn;

0 commit comments

Comments
 (0)