-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Changes from all commits
544b177
f5ca456
263c54e
d25bcea
a660d7b
dcbb2e5
d57e8d1
c7c774d
e0f9825
c3879bc
cf6cfed
6df0f3d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
|
@@ -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, | ||
|
@@ -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 | ||
|
@@ -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)); | ||
} | ||
} | ||
|
||
|
@@ -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) { | ||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why you have to check There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -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); | ||
|
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(); }); |
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)) | ||
|
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'. |
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(); }); |
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(); }); |
There was a problem hiding this comment.
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?There was a problem hiding this comment.
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.