Skip to content

Fix type parameters upon subsequent visits to a parameter that caused fixing the first time #3787

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

Merged
merged 12 commits into from
Jul 17, 2015
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
100 changes: 83 additions & 17 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4253,15 +4253,18 @@ namespace ts {
}

function createInferenceMapper(context: InferenceContext): TypeMapper {
return t => {
let mapper: TypeMapper = t => {
for (let i = 0; i < context.typeParameters.length; i++) {
if (t === context.typeParameters[i]) {
context.inferences[i].isFixed = true;
return getInferredType(context, i);
}
}
return t;
return t;
};

mapper.context = context;
return mapper;
}

function identityMapper(type: Type): Type {
Expand Down Expand Up @@ -5472,7 +5475,9 @@ namespace ts {
function createInferenceContext(typeParameters: TypeParameter[], inferUnionTypes: boolean): InferenceContext {
let inferences: TypeInferences[] = [];
for (let unused of typeParameters) {
inferences.push({ primary: undefined, secondary: undefined, isFixed: false });
inferences.push({
primary: undefined, secondary: undefined, isFixed: false
});
}
return {
typeParameters,
Expand Down Expand Up @@ -6773,10 +6778,23 @@ namespace ts {
return result;
}

// Presence of a contextual type mapper indicates inferential typing, except the identityMapper object is
// used as a special marker for other purposes.
/**
* Detect if the mapper implies an inference context. Specifically, there are 4 possible values
* for a mapper. Let's go through each one of them:
*
* 1. undefined - this means we are not doing inferential typing, but we may do contextual typing,
* which could cause us to assign a parameter a type
* 2. identityMapper - means we want to avoid assigning a parameter a type, whether or not we are in
* inferential typing (context is undefined for the identityMapper)
* 3. a mapper created by createInferenceMapper - we are doing inferential typing, we want to assign
* types to parameters and fix type parameters (context is defined)
* 4. an instantiation mapper created by createTypeMapper or createTypeEraser - this should never be
* passed as the contextual mapper when checking an expression (context is undefined for these)
*
* isInferentialContext is detecting if we are in case 3
*/
function isInferentialContext(mapper: TypeMapper) {
return mapper && mapper !== identityMapper;
return mapper && mapper.context;
}

// A node is an assignment target if it is on the left hand side of an '=' token, if it is parented by a property
Expand Down Expand Up @@ -8865,13 +8883,52 @@ namespace ts {
let len = signature.parameters.length - (signature.hasRestParameter ? 1 : 0);
for (let i = 0; i < len; i++) {
let parameter = signature.parameters[i];
let links = getSymbolLinks(parameter);
links.type = instantiateType(getTypeAtPosition(context, i), mapper);
let contextualParameterType = getTypeAtPosition(context, i);
assignTypeToParameterAndFixTypeParameters(parameter, contextualParameterType, mapper);
}
if (signature.hasRestParameter && context.hasRestParameter && signature.parameters.length >= context.parameters.length) {
let parameter = lastOrUndefined(signature.parameters);
let links = getSymbolLinks(parameter);
links.type = instantiateType(getTypeOfSymbol(lastOrUndefined(context.parameters)), mapper);
let contextualParameterType = getTypeOfSymbol(lastOrUndefined(context.parameters));
assignTypeToParameterAndFixTypeParameters(parameter, contextualParameterType, mapper);
}
}

function assignTypeToParameterAndFixTypeParameters(parameter: Symbol, contextualType: Type, mapper: TypeMapper) {
let links = getSymbolLinks(parameter);
if (!links.type) {
links.type = instantiateType(contextualType, mapper);
}
else if (isInferentialContext(mapper)) {
// Even if the parameter already has a type, it might be because it was given a type while
// processing the function as an argument to a prior signature during overload resolution.
// If this was the case, it may have caused some type parameters to be fixed. So here,
// we need to ensure that type parameters at the same positions get fixed again. This is
// done by calling instantiateType to attach the mapper to the contextualType, and then
// calling inferTypes to force a walk of contextualType so that all the correct fixing
// happens. The choice to pass in links.type may seem kind of arbitrary, but it serves
// to make sure that all the correct positions in contextualType are reached by the walk.
// Here is an example:
//
// interface Base {
// baseProp;
// }
// interface Derived extends Base {
// toBase(): Base;
// }
//
// var derived: Derived;
//
// declare function foo<T>(x: T, func: (p: T) => T): T;
// declare function foo<T>(x: T, func: (p: T) => T): T;
//
// var result = foo(derived, d => d.toBase());
//
// We are typing d while checking the second overload. But we've already given d
// a type (Derived) from the first overload. However, we still want to fix the
// T in the second overload so that we do not infer Base as a candidate for T
// (inferring Base would make type argument inference inconsistent between the two
// overloads).
inferTypes(mapper.context, links.type, instantiateType(contextualType, mapper));
}
}

Expand Down Expand Up @@ -9091,27 +9148,36 @@ namespace ts {

let links = getNodeLinks(node);
let type = getTypeOfSymbol(node.symbol);
// Check if function expression is contextually typed and assign parameter types if so
if (!(links.flags & NodeCheckFlags.ContextChecked)) {
let contextSensitive = isContextSensitive(node);
let mightFixTypeParameters = contextSensitive && isInferentialContext(contextualMapper);

// Check if function expression is contextually typed and assign parameter types if so.
// See the comment in assignTypeToParameterAndFixTypeParameters to understand why we need to
// check mightFixTypeParameters.
if (mightFixTypeParameters || !(links.flags & NodeCheckFlags.ContextChecked)) {
let contextualSignature = getContextualSignature(node);
// If a type check is started at a function expression that is an argument of a function call, obtaining the
// contextual type may recursively get back to here during overload resolution of the call. If so, we will have
// already assigned contextual types.
if (!(links.flags & NodeCheckFlags.ContextChecked)) {
let contextChecked = !!(links.flags & NodeCheckFlags.ContextChecked);
if (mightFixTypeParameters || !contextChecked) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same- Why you have to check mightFixTypeParameters again?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because if I didn't check it, and we already checked this function expression, we would not go through this code path. We need to go through the code path in order to fix type parameters.

links.flags |= NodeCheckFlags.ContextChecked;
if (contextualSignature) {
let signature = getSignaturesOfType(type, SignatureKind.Call)[0];
if (isContextSensitive(node)) {
if (contextSensitive) {
assignContextualParameterTypes(signature, contextualSignature, contextualMapper || identityMapper);
}
if (!node.type && !signature.resolvedReturnType) {
if (mightFixTypeParameters || !node.type && !signature.resolvedReturnType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

why you have to check mightFixTypeParameters again?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question. In this case, I noticed that we are passing the contextualMapper into getReturnTypeFromBody. That indicates to me that we may fix some type parameters if the return expression contains a function expression. So I am ensuring that anywhere that it's possible to fix type parameters, we go through that code path.

let returnType = getReturnTypeFromBody(node, contextualMapper);
if (!signature.resolvedReturnType) {
signature.resolvedReturnType = returnType;
}
}
}
checkSignatureDeclaration(node);

if (!contextChecked) {
checkSignatureDeclaration(node);
}
}
}

Expand Down Expand Up @@ -9801,7 +9867,7 @@ namespace ts {
}

function instantiateTypeWithSingleGenericCallSignature(node: Expression | MethodDeclaration, type: Type, contextualMapper?: TypeMapper) {
if (contextualMapper && contextualMapper !== identityMapper) {
if (isInferentialContext(contextualMapper)) {
let signature = getSingleCallSignature(type);
if (signature && signature.typeParameters) {
let contextualType = getContextualType(<Expression>node);
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1912,6 +1912,9 @@ namespace ts {
/* @internal */
export interface TypeMapper {
(t: TypeParameter): Type;
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 */
Expand Down
12 changes: 12 additions & 0 deletions tests/baselines/reference/fixingTypeParametersRepeatedly1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//// [fixingTypeParametersRepeatedly1.ts]
declare function f<T>(x: T, y: (p: T) => T, z: (p: T) => T): T;
f("", x => null, x => x.toLowerCase());

// First overload of g should type check just like f
declare function g<T>(x: T, y: (p: T) => T, z: (p: T) => T): T;
declare function g();
g("", x => null, x => x.toLowerCase());

//// [fixingTypeParametersRepeatedly1.js]
f("", function (x) { return null; }, function (x) { return x.toLowerCase(); });
g("", function (x) { return null; }, function (x) { return x.toLowerCase(); });
51 changes: 51 additions & 0 deletions tests/baselines/reference/fixingTypeParametersRepeatedly1.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
=== tests/cases/compiler/fixingTypeParametersRepeatedly1.ts ===
declare function f<T>(x: T, y: (p: T) => T, z: (p: T) => T): T;
>f : Symbol(f, Decl(fixingTypeParametersRepeatedly1.ts, 0, 0))
>T : Symbol(T, Decl(fixingTypeParametersRepeatedly1.ts, 0, 19))
>x : Symbol(x, Decl(fixingTypeParametersRepeatedly1.ts, 0, 22))
>T : Symbol(T, Decl(fixingTypeParametersRepeatedly1.ts, 0, 19))
>y : Symbol(y, Decl(fixingTypeParametersRepeatedly1.ts, 0, 27))
>p : Symbol(p, Decl(fixingTypeParametersRepeatedly1.ts, 0, 32))
>T : Symbol(T, Decl(fixingTypeParametersRepeatedly1.ts, 0, 19))
>T : Symbol(T, Decl(fixingTypeParametersRepeatedly1.ts, 0, 19))
>z : Symbol(z, Decl(fixingTypeParametersRepeatedly1.ts, 0, 43))
>p : Symbol(p, Decl(fixingTypeParametersRepeatedly1.ts, 0, 48))
>T : Symbol(T, Decl(fixingTypeParametersRepeatedly1.ts, 0, 19))
>T : Symbol(T, Decl(fixingTypeParametersRepeatedly1.ts, 0, 19))
>T : Symbol(T, Decl(fixingTypeParametersRepeatedly1.ts, 0, 19))

f("", x => null, x => x.toLowerCase());
>f : Symbol(f, Decl(fixingTypeParametersRepeatedly1.ts, 0, 0))
>x : Symbol(x, Decl(fixingTypeParametersRepeatedly1.ts, 1, 5))
>x : Symbol(x, Decl(fixingTypeParametersRepeatedly1.ts, 1, 16))
>x.toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, 399, 51))
>x : Symbol(x, Decl(fixingTypeParametersRepeatedly1.ts, 1, 16))
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, 399, 51))

// First overload of g should type check just like f
declare function g<T>(x: T, y: (p: T) => T, z: (p: T) => T): T;
>g : Symbol(g, Decl(fixingTypeParametersRepeatedly1.ts, 1, 39), Decl(fixingTypeParametersRepeatedly1.ts, 4, 63))
>T : Symbol(T, Decl(fixingTypeParametersRepeatedly1.ts, 4, 19))
>x : Symbol(x, Decl(fixingTypeParametersRepeatedly1.ts, 4, 22))
>T : Symbol(T, Decl(fixingTypeParametersRepeatedly1.ts, 4, 19))
>y : Symbol(y, Decl(fixingTypeParametersRepeatedly1.ts, 4, 27))
>p : Symbol(p, Decl(fixingTypeParametersRepeatedly1.ts, 4, 32))
>T : Symbol(T, Decl(fixingTypeParametersRepeatedly1.ts, 4, 19))
>T : Symbol(T, Decl(fixingTypeParametersRepeatedly1.ts, 4, 19))
>z : Symbol(z, Decl(fixingTypeParametersRepeatedly1.ts, 4, 43))
>p : Symbol(p, Decl(fixingTypeParametersRepeatedly1.ts, 4, 48))
>T : Symbol(T, Decl(fixingTypeParametersRepeatedly1.ts, 4, 19))
>T : Symbol(T, Decl(fixingTypeParametersRepeatedly1.ts, 4, 19))
>T : Symbol(T, Decl(fixingTypeParametersRepeatedly1.ts, 4, 19))

declare function g();
>g : Symbol(g, Decl(fixingTypeParametersRepeatedly1.ts, 1, 39), Decl(fixingTypeParametersRepeatedly1.ts, 4, 63))

g("", x => null, x => x.toLowerCase());
>g : Symbol(g, Decl(fixingTypeParametersRepeatedly1.ts, 1, 39), Decl(fixingTypeParametersRepeatedly1.ts, 4, 63))
>x : Symbol(x, Decl(fixingTypeParametersRepeatedly1.ts, 6, 5))
>x : Symbol(x, Decl(fixingTypeParametersRepeatedly1.ts, 6, 16))
>x.toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, 399, 51))
>x : Symbol(x, Decl(fixingTypeParametersRepeatedly1.ts, 6, 16))
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, 399, 51))

63 changes: 63 additions & 0 deletions tests/baselines/reference/fixingTypeParametersRepeatedly1.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
=== tests/cases/compiler/fixingTypeParametersRepeatedly1.ts ===
declare function f<T>(x: T, y: (p: T) => T, z: (p: T) => T): T;
>f : <T>(x: T, y: (p: T) => T, z: (p: T) => T) => T
>T : T
>x : T
>T : T
>y : (p: T) => T
>p : T
>T : T
>T : T
>z : (p: T) => T
>p : T
>T : T
>T : T
>T : T

f("", x => null, x => x.toLowerCase());
>f("", x => null, x => x.toLowerCase()) : string
>f : <T>(x: T, y: (p: T) => T, z: (p: T) => T) => T
>"" : string
>x => null : (x: string) => any
>x : string
>null : null
>x => x.toLowerCase() : (x: string) => string
>x : string
>x.toLowerCase() : string
>x.toLowerCase : () => string
>x : string
>toLowerCase : () => string

// First overload of g should type check just like f
declare function g<T>(x: T, y: (p: T) => T, z: (p: T) => T): T;
>g : { <T>(x: T, y: (p: T) => T, z: (p: T) => T): T; (): any; }
>T : T
>x : T
>T : T
>y : (p: T) => T
>p : T
>T : T
>T : T
>z : (p: T) => T
>p : T
>T : T
>T : T
>T : T

declare function g();
>g : { <T>(x: T, y: (p: T) => T, z: (p: T) => T): T; (): any; }

g("", x => null, x => x.toLowerCase());
>g("", x => null, x => x.toLowerCase()) : string
>g : { <T>(x: T, y: (p: T) => T, z: (p: T) => T): T; (): any; }
>"" : string
>x => null : (x: string) => any
>x : string
>null : null
>x => x.toLowerCase() : (x: string) => string
>x : string
>x.toLowerCase() : string
>x.toLowerCase : () => string
>x : string
>toLowerCase : () => string

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
tests/cases/compiler/fixingTypeParametersRepeatedly2.ts(11,27): error TS2345: Argument of type '(d: Derived) => Base' is not assignable to parameter of type '(p: Derived) => Derived'.
Type 'Base' is not assignable to type 'Derived'.
Property 'toBase' is missing in type 'Base'.
tests/cases/compiler/fixingTypeParametersRepeatedly2.ts(17,27): error TS2345: Argument of type '(d: Derived) => Base' is not assignable to parameter of type '(p: Derived) => Derived'.
Type 'Base' is not assignable to type 'Derived'.


==== tests/cases/compiler/fixingTypeParametersRepeatedly2.ts (2 errors) ====
interface Base {
baseProp;
}
interface Derived extends Base {
toBase(): Base;
}

var derived: Derived;

declare function foo<T>(x: T, func: (p: T) => T): T;
var result = foo(derived, d => d.toBase());
~~~~~~~~~~~~~~~
!!! error TS2345: Argument of type '(d: Derived) => Base' is not assignable to parameter of type '(p: Derived) => Derived'.
!!! error TS2345: Type 'Base' is not assignable to type 'Derived'.
!!! error TS2345: Property 'toBase' is missing in type 'Base'.

// bar should type check just like foo.
// The same error should be observed in both cases.
declare function bar<T>(x: T, func: (p: T) => T): T;
declare function bar<T>(x: T, func: (p: T) => T): T;
var result = bar(derived, d => d.toBase());
~~~~~~~~~~~~~~~
!!! error TS2345: Argument of type '(d: Derived) => Base' is not assignable to parameter of type '(p: Derived) => Derived'.
!!! error TS2345: Type 'Base' is not assignable to type 'Derived'.
23 changes: 23 additions & 0 deletions tests/baselines/reference/fixingTypeParametersRepeatedly2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//// [fixingTypeParametersRepeatedly2.ts]
interface Base {
baseProp;
}
interface Derived extends Base {
toBase(): Base;
}

var derived: Derived;

declare function foo<T>(x: T, func: (p: T) => T): T;
var result = foo(derived, d => d.toBase());

// bar should type check just like foo.
// The same error should be observed in both cases.
declare function bar<T>(x: T, func: (p: T) => T): T;
declare function bar<T>(x: T, func: (p: T) => T): T;
var result = bar(derived, d => d.toBase());

//// [fixingTypeParametersRepeatedly2.js]
var derived;
var result = foo(derived, function (d) { return d.toBase(); });
var result = bar(derived, function (d) { return d.toBase(); });
23 changes: 23 additions & 0 deletions tests/baselines/reference/fixingTypeParametersRepeatedly3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//// [fixingTypeParametersRepeatedly3.ts]
interface Base {
baseProp;
}
interface Derived extends Base {
toBase?(): Base;
}

var derived: Derived;

declare function foo<T>(x: T, func: (p: T) => T): T;
var result = foo(derived, d => d.toBase());

// bar should type check just like foo.
// result2 should have the same type as result
declare function bar<T>(x: T, func: (p: T) => T): T;
declare function bar<T>(x: T, func: (p: T) => T): T;
var result2 = bar(derived, d => d.toBase());

//// [fixingTypeParametersRepeatedly3.js]
var derived;
var result = foo(derived, function (d) { return d.toBase(); });
var result2 = bar(derived, function (d) { return d.toBase(); });
Loading