Skip to content

Commit bcf84f4

Browse files
authored
Merge pull request #16072 from Microsoft/improveTypeArgumentInference
Infer from generic function return types
2 parents 3186fc4 + 7ca91f8 commit bcf84f4

12 files changed

+1801
-172
lines changed

src/compiler/checker.ts

+159-129
Large diffs are not rendered by default.

src/compiler/types.ts

+22-16
Original file line numberDiff line numberDiff line change
@@ -3343,30 +3343,36 @@ namespace ts {
33433343
(t: TypeParameter): Type;
33443344
mappedTypes?: Type[]; // Types mapped by this mapper
33453345
instantiations?: Type[]; // Cache of instantiations created using this type mapper.
3346-
context?: InferenceContext; // The inference context this mapper was created from.
3347-
// Only inference mappers have this set (in createInferenceMapper).
3348-
// The identity mapper and regular instantiation mappers do not need it.
33493346
}
33503347

3351-
/* @internal */
3352-
export interface TypeInferences {
3353-
primary: Type[]; // Inferences made directly to a type parameter
3354-
secondary: Type[]; // Inferences made to a type parameter in a union type
3355-
topLevel: boolean; // True if all inferences were made from top-level (not nested in object type) locations
3356-
isFixed: boolean; // Whether the type parameter is fixed, as defined in section 4.12.2 of the TypeScript spec
3357-
// If a type parameter is fixed, no more inferences can be made for the type parameter
3348+
export const enum InferencePriority {
3349+
NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type
3350+
MappedType = 1 << 1, // Reverse inference for mapped type
3351+
ReturnType = 1 << 2, // Inference made from return type of generic function
3352+
}
3353+
3354+
export interface InferenceInfo {
3355+
typeParameter: TypeParameter;
3356+
candidates: Type[];
3357+
inferredType: Type;
3358+
priority: InferencePriority;
3359+
topLevel: boolean;
3360+
isFixed: boolean;
3361+
}
3362+
3363+
export const enum InferenceFlags {
3364+
InferUnionTypes = 1 << 0, // Infer union types for disjoint candidates (otherwise unknownType)
3365+
NoDefault = 1 << 1, // Infer unknownType for no inferences (otherwise anyType or emptyObjectType)
3366+
AnyDefault = 1 << 2, // Infer anyType for no inferences (otherwise emptyObjectType)
33583367
}
33593368

33603369
/* @internal */
3361-
export interface InferenceContext {
3370+
export interface InferenceContext extends TypeMapper {
33623371
signature: Signature; // Generic signature for which inferences are made
3363-
inferUnionTypes: boolean; // Infer union types for disjoint candidates (otherwise undefinedType)
3364-
inferences: TypeInferences[]; // Inferences made for each type parameter
3365-
inferredTypes: Type[]; // Inferred type for each type parameter
3366-
mapper?: TypeMapper; // Type mapper for this inference context
3372+
inferences: InferenceInfo[]; // Inferences made for each type parameter
3373+
flags: InferenceFlags; // Inference flags
33673374
failedTypeParameterIndex?: number; // Index of type parameter for which inference failed
33683375
// It is optional because in contextual signature instantiation, nothing fails
3369-
useAnyForNoInferences?: boolean; // Use any instead of {} for no inferences
33703376
}
33713377

33723378
/* @internal */

tests/baselines/reference/implicitAnyGenerics.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ var c3 = new C<number>();
2626
var c4: C<any> = new C();
2727
>c4 : C<any>
2828
>C : C<T>
29-
>new C() : C<{}>
29+
>new C() : C<any>
3030
>C : typeof C
3131

3232
class D<T> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
tests/cases/compiler/inferFromGenericFunctionReturnTypes1.ts(68,16): error TS2339: Property 'toUpperCase' does not exist on type 'number'.
2+
3+
4+
==== tests/cases/compiler/inferFromGenericFunctionReturnTypes1.ts (1 errors) ====
5+
// Repro from #15680
6+
7+
// This is a contrived class. We could do the same thing with Observables, etc.
8+
class SetOf<A> {
9+
_store: A[];
10+
11+
add(a: A) {
12+
this._store.push(a);
13+
}
14+
15+
transform<B>(transformer: (a: SetOf<A>) => SetOf<B>): SetOf<B> {
16+
return transformer(this);
17+
}
18+
19+
forEach(fn: (a: A, index: number) => void) {
20+
this._store.forEach((a, i) => fn(a, i));
21+
}
22+
}
23+
24+
function compose<A, B, C, D, E>(
25+
fnA: (a: SetOf<A>) => SetOf<B>,
26+
fnB: (b: SetOf<B>) => SetOf<C>,
27+
fnC: (c: SetOf<C>) => SetOf<D>,
28+
fnD: (c: SetOf<D>) => SetOf<E>,
29+
):(x: SetOf<A>) => SetOf<E>;
30+
/* ... etc ... */
31+
function compose<T>(...fns: ((x: T) => T)[]): (x: T) => T {
32+
return (x: T) => fns.reduce((prev, fn) => fn(prev), x);
33+
}
34+
35+
function map<A, B>(fn: (a: A) => B): (s: SetOf<A>) => SetOf<B> {
36+
return (a: SetOf<A>) => {
37+
const b: SetOf<B> = new SetOf();
38+
a.forEach(x => b.add(fn(x)));
39+
return b;
40+
}
41+
}
42+
43+
function filter<A>(predicate: (a: A) => boolean): (s: SetOf<A>) => SetOf<A> {
44+
return (a: SetOf<A>) => {
45+
const result = new SetOf<A>();
46+
a.forEach(x => {
47+
if (predicate(x)) result.add(x);
48+
});
49+
return result;
50+
}
51+
}
52+
53+
const testSet = new SetOf<number>();
54+
testSet.add(1);
55+
testSet.add(2);
56+
testSet.add(3);
57+
58+
testSet.transform(
59+
compose(
60+
filter(x => x % 1 === 0),
61+
map(x => x + x),
62+
map(x => x + '!!!'),
63+
map(x => x.toUpperCase())
64+
)
65+
)
66+
67+
testSet.transform(
68+
compose(
69+
filter(x => x % 1 === 0),
70+
map(x => x + x),
71+
map(x => 123), // Whoops a bug
72+
map(x => x.toUpperCase()) // causes an error!
73+
~~~~~~~~~~~
74+
!!! error TS2339: Property 'toUpperCase' does not exist on type 'number'.
75+
)
76+
)
77+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//// [inferFromGenericFunctionReturnTypes1.ts]
2+
// Repro from #15680
3+
4+
// This is a contrived class. We could do the same thing with Observables, etc.
5+
class SetOf<A> {
6+
_store: A[];
7+
8+
add(a: A) {
9+
this._store.push(a);
10+
}
11+
12+
transform<B>(transformer: (a: SetOf<A>) => SetOf<B>): SetOf<B> {
13+
return transformer(this);
14+
}
15+
16+
forEach(fn: (a: A, index: number) => void) {
17+
this._store.forEach((a, i) => fn(a, i));
18+
}
19+
}
20+
21+
function compose<A, B, C, D, E>(
22+
fnA: (a: SetOf<A>) => SetOf<B>,
23+
fnB: (b: SetOf<B>) => SetOf<C>,
24+
fnC: (c: SetOf<C>) => SetOf<D>,
25+
fnD: (c: SetOf<D>) => SetOf<E>,
26+
):(x: SetOf<A>) => SetOf<E>;
27+
/* ... etc ... */
28+
function compose<T>(...fns: ((x: T) => T)[]): (x: T) => T {
29+
return (x: T) => fns.reduce((prev, fn) => fn(prev), x);
30+
}
31+
32+
function map<A, B>(fn: (a: A) => B): (s: SetOf<A>) => SetOf<B> {
33+
return (a: SetOf<A>) => {
34+
const b: SetOf<B> = new SetOf();
35+
a.forEach(x => b.add(fn(x)));
36+
return b;
37+
}
38+
}
39+
40+
function filter<A>(predicate: (a: A) => boolean): (s: SetOf<A>) => SetOf<A> {
41+
return (a: SetOf<A>) => {
42+
const result = new SetOf<A>();
43+
a.forEach(x => {
44+
if (predicate(x)) result.add(x);
45+
});
46+
return result;
47+
}
48+
}
49+
50+
const testSet = new SetOf<number>();
51+
testSet.add(1);
52+
testSet.add(2);
53+
testSet.add(3);
54+
55+
testSet.transform(
56+
compose(
57+
filter(x => x % 1 === 0),
58+
map(x => x + x),
59+
map(x => x + '!!!'),
60+
map(x => x.toUpperCase())
61+
)
62+
)
63+
64+
testSet.transform(
65+
compose(
66+
filter(x => x % 1 === 0),
67+
map(x => x + x),
68+
map(x => 123), // Whoops a bug
69+
map(x => x.toUpperCase()) // causes an error!
70+
)
71+
)
72+
73+
74+
//// [inferFromGenericFunctionReturnTypes1.js]
75+
// Repro from #15680
76+
// This is a contrived class. We could do the same thing with Observables, etc.
77+
var SetOf = (function () {
78+
function SetOf() {
79+
}
80+
SetOf.prototype.add = function (a) {
81+
this._store.push(a);
82+
};
83+
SetOf.prototype.transform = function (transformer) {
84+
return transformer(this);
85+
};
86+
SetOf.prototype.forEach = function (fn) {
87+
this._store.forEach(function (a, i) { return fn(a, i); });
88+
};
89+
return SetOf;
90+
}());
91+
/* ... etc ... */
92+
function compose() {
93+
var fns = [];
94+
for (var _i = 0; _i < arguments.length; _i++) {
95+
fns[_i] = arguments[_i];
96+
}
97+
return function (x) { return fns.reduce(function (prev, fn) { return fn(prev); }, x); };
98+
}
99+
function map(fn) {
100+
return function (a) {
101+
var b = new SetOf();
102+
a.forEach(function (x) { return b.add(fn(x)); });
103+
return b;
104+
};
105+
}
106+
function filter(predicate) {
107+
return function (a) {
108+
var result = new SetOf();
109+
a.forEach(function (x) {
110+
if (predicate(x))
111+
result.add(x);
112+
});
113+
return result;
114+
};
115+
}
116+
var testSet = new SetOf();
117+
testSet.add(1);
118+
testSet.add(2);
119+
testSet.add(3);
120+
testSet.transform(compose(filter(function (x) { return x % 1 === 0; }), map(function (x) { return x + x; }), map(function (x) { return x + '!!!'; }), map(function (x) { return x.toUpperCase(); })));
121+
testSet.transform(compose(filter(function (x) { return x % 1 === 0; }), map(function (x) { return x + x; }), map(function (x) { return 123; }), // Whoops a bug
122+
map(function (x) { return x.toUpperCase(); }) // causes an error!
123+
));

0 commit comments

Comments
 (0)