Skip to content

Commit

Permalink
Merge pull request #16072 from Microsoft/improveTypeArgumentInference
Browse files Browse the repository at this point in the history
Infer from generic function return types
  • Loading branch information
ahejlsberg authored May 26, 2017
2 parents 3186fc4 + 7ca91f8 commit bcf84f4
Show file tree
Hide file tree
Showing 12 changed files with 1,801 additions and 172 deletions.
288 changes: 159 additions & 129 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

38 changes: 22 additions & 16 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3343,30 +3343,36 @@ namespace ts {
(t: TypeParameter): Type;
mappedTypes?: Type[]; // Types mapped by this mapper
instantiations?: Type[]; // Cache of instantiations created using this type mapper.
context?: InferenceContext; // The inference context this mapper was created from.
// Only inference mappers have this set (in createInferenceMapper).
// The identity mapper and regular instantiation mappers do not need it.
}

/* @internal */
export interface TypeInferences {
primary: Type[]; // Inferences made directly to a type parameter
secondary: Type[]; // Inferences made to a type parameter in a union type
topLevel: boolean; // True if all inferences were made from top-level (not nested in object type) locations
isFixed: boolean; // Whether the type parameter is fixed, as defined in section 4.12.2 of the TypeScript spec
// If a type parameter is fixed, no more inferences can be made for the type parameter
export const enum InferencePriority {
NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type
MappedType = 1 << 1, // Reverse inference for mapped type
ReturnType = 1 << 2, // Inference made from return type of generic function
}

export interface InferenceInfo {
typeParameter: TypeParameter;
candidates: Type[];
inferredType: Type;
priority: InferencePriority;
topLevel: boolean;
isFixed: boolean;
}

export const enum InferenceFlags {
InferUnionTypes = 1 << 0, // Infer union types for disjoint candidates (otherwise unknownType)
NoDefault = 1 << 1, // Infer unknownType for no inferences (otherwise anyType or emptyObjectType)
AnyDefault = 1 << 2, // Infer anyType for no inferences (otherwise emptyObjectType)
}

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

/* @internal */
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/implicitAnyGenerics.types
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ var c3 = new C<number>();
var c4: C<any> = new C();
>c4 : C<any>
>C : C<T>
>new C() : C<{}>
>new C() : C<any>
>C : typeof C

class D<T> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
tests/cases/compiler/inferFromGenericFunctionReturnTypes1.ts(68,16): error TS2339: Property 'toUpperCase' does not exist on type 'number'.


==== tests/cases/compiler/inferFromGenericFunctionReturnTypes1.ts (1 errors) ====
// Repro from #15680

// This is a contrived class. We could do the same thing with Observables, etc.
class SetOf<A> {
_store: A[];

add(a: A) {
this._store.push(a);
}

transform<B>(transformer: (a: SetOf<A>) => SetOf<B>): SetOf<B> {
return transformer(this);
}

forEach(fn: (a: A, index: number) => void) {
this._store.forEach((a, i) => fn(a, i));
}
}

function compose<A, B, C, D, E>(
fnA: (a: SetOf<A>) => SetOf<B>,
fnB: (b: SetOf<B>) => SetOf<C>,
fnC: (c: SetOf<C>) => SetOf<D>,
fnD: (c: SetOf<D>) => SetOf<E>,
):(x: SetOf<A>) => SetOf<E>;
/* ... etc ... */
function compose<T>(...fns: ((x: T) => T)[]): (x: T) => T {
return (x: T) => fns.reduce((prev, fn) => fn(prev), x);
}

function map<A, B>(fn: (a: A) => B): (s: SetOf<A>) => SetOf<B> {
return (a: SetOf<A>) => {
const b: SetOf<B> = new SetOf();
a.forEach(x => b.add(fn(x)));
return b;
}
}

function filter<A>(predicate: (a: A) => boolean): (s: SetOf<A>) => SetOf<A> {
return (a: SetOf<A>) => {
const result = new SetOf<A>();
a.forEach(x => {
if (predicate(x)) result.add(x);
});
return result;
}
}

const testSet = new SetOf<number>();
testSet.add(1);
testSet.add(2);
testSet.add(3);

testSet.transform(
compose(
filter(x => x % 1 === 0),
map(x => x + x),
map(x => x + '!!!'),
map(x => x.toUpperCase())
)
)

testSet.transform(
compose(
filter(x => x % 1 === 0),
map(x => x + x),
map(x => 123), // Whoops a bug
map(x => x.toUpperCase()) // causes an error!
~~~~~~~~~~~
!!! error TS2339: Property 'toUpperCase' does not exist on type 'number'.
)
)

123 changes: 123 additions & 0 deletions tests/baselines/reference/inferFromGenericFunctionReturnTypes1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//// [inferFromGenericFunctionReturnTypes1.ts]
// Repro from #15680

// This is a contrived class. We could do the same thing with Observables, etc.
class SetOf<A> {
_store: A[];

add(a: A) {
this._store.push(a);
}

transform<B>(transformer: (a: SetOf<A>) => SetOf<B>): SetOf<B> {
return transformer(this);
}

forEach(fn: (a: A, index: number) => void) {
this._store.forEach((a, i) => fn(a, i));
}
}

function compose<A, B, C, D, E>(
fnA: (a: SetOf<A>) => SetOf<B>,
fnB: (b: SetOf<B>) => SetOf<C>,
fnC: (c: SetOf<C>) => SetOf<D>,
fnD: (c: SetOf<D>) => SetOf<E>,
):(x: SetOf<A>) => SetOf<E>;
/* ... etc ... */
function compose<T>(...fns: ((x: T) => T)[]): (x: T) => T {
return (x: T) => fns.reduce((prev, fn) => fn(prev), x);
}

function map<A, B>(fn: (a: A) => B): (s: SetOf<A>) => SetOf<B> {
return (a: SetOf<A>) => {
const b: SetOf<B> = new SetOf();
a.forEach(x => b.add(fn(x)));
return b;
}
}

function filter<A>(predicate: (a: A) => boolean): (s: SetOf<A>) => SetOf<A> {
return (a: SetOf<A>) => {
const result = new SetOf<A>();
a.forEach(x => {
if (predicate(x)) result.add(x);
});
return result;
}
}

const testSet = new SetOf<number>();
testSet.add(1);
testSet.add(2);
testSet.add(3);

testSet.transform(
compose(
filter(x => x % 1 === 0),
map(x => x + x),
map(x => x + '!!!'),
map(x => x.toUpperCase())
)
)

testSet.transform(
compose(
filter(x => x % 1 === 0),
map(x => x + x),
map(x => 123), // Whoops a bug
map(x => x.toUpperCase()) // causes an error!
)
)


//// [inferFromGenericFunctionReturnTypes1.js]
// Repro from #15680
// This is a contrived class. We could do the same thing with Observables, etc.
var SetOf = (function () {
function SetOf() {
}
SetOf.prototype.add = function (a) {
this._store.push(a);
};
SetOf.prototype.transform = function (transformer) {
return transformer(this);
};
SetOf.prototype.forEach = function (fn) {
this._store.forEach(function (a, i) { return fn(a, i); });
};
return SetOf;
}());
/* ... etc ... */
function compose() {
var fns = [];
for (var _i = 0; _i < arguments.length; _i++) {
fns[_i] = arguments[_i];
}
return function (x) { return fns.reduce(function (prev, fn) { return fn(prev); }, x); };
}
function map(fn) {
return function (a) {
var b = new SetOf();
a.forEach(function (x) { return b.add(fn(x)); });
return b;
};
}
function filter(predicate) {
return function (a) {
var result = new SetOf();
a.forEach(function (x) {
if (predicate(x))
result.add(x);
});
return result;
};
}
var testSet = new SetOf();
testSet.add(1);
testSet.add(2);
testSet.add(3);
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(); })));
testSet.transform(compose(filter(function (x) { return x % 1 === 0; }), map(function (x) { return x + x; }), map(function (x) { return 123; }), // Whoops a bug
map(function (x) { return x.toUpperCase(); }) // causes an error!
));
Loading

0 comments on commit bcf84f4

Please sign in to comment.