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

Infer from generic function return types #16072

Merged
merged 12 commits into from
May 26, 2017
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 @@ -3336,30 +3336,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