Skip to content

Unify contextual signature type parameter assignment #31574

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
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
39 changes: 32 additions & 7 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5231,7 +5231,7 @@ namespace ts {
}
}
// Use contextual parameter type if one is available
const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration);
const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration, /*forCache*/ true);
if (type) {
return addOptionality(type, isOptional);
}
Expand Down Expand Up @@ -11515,6 +11515,7 @@ namespace ts {
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.FunctionDeclaration: // Function declarations can have context when annotated with a jsdoc @type
return isContextSensitiveFunctionLikeDeclaration(<FunctionExpression | ArrowFunction | MethodDeclaration>node);
case SyntaxKind.ObjectLiteralExpression:
return some((<ObjectLiteralExpression>node).properties, isContextSensitive);
Expand Down Expand Up @@ -11548,6 +11549,9 @@ namespace ts {
}

function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
if (isFunctionDeclaration(node) && (!isInJSFile(node) || !getTypeForDeclarationFromJSDocComment(node))) {
return false;
}
// Functions with type parameters are not context sensitive.
if (node.typeParameters) {
return false;
Expand Down Expand Up @@ -18241,7 +18245,7 @@ namespace ts {
}

// Return contextual type of parameter or undefined if no contextual type is available
function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type | undefined {
function getContextuallyTypedParameterType(parameter: ParameterDeclaration, forCache: boolean): Type | undefined {
const func = parameter.parent;
if (!isContextSensitiveFunctionOrObjectLiteralMethod(func)) {
return undefined;
Expand All @@ -18262,8 +18266,21 @@ namespace ts {
links.resolvedSignature = cached;
return type;
}
const contextualSignature = getContextualSignature(func);
let contextualSignature = getContextualSignature(func);
if (contextualSignature) {
if (forCache) {
// Calling the below guarantees the types are primed and assigned in the same way
// as when the parameter is reached via `checkFunctionExpressionOrObjectLiteralMethod`.
// This should prevent any uninstantiated inference variables in the contextual signature
// from leaking, and should lock in cached parameter types via `assignContextualParameterTypes`
// which we will then immediately use the results of below.
contextuallyCheckFunctionExpressionOrObjectLiteralMethod(func);
const type = getTypeOfSymbol(getMergedSymbol(func.symbol));
if (isTypeAny(type)) {
return type;
}
contextualSignature = getSignaturesOfType(type, SignatureKind.Call)[0];
}
const index = func.parameters.indexOf(parameter) - (getThisParameter(func) ? 1 : 0);
return parameter.dotDotDotToken && lastOrUndefined(func.parameters) === parameter ?
getRestTypeAtPosition(contextualSignature, index) :
Expand All @@ -18278,7 +18295,7 @@ namespace ts {
}
switch (declaration.kind) {
case SyntaxKind.Parameter:
return getContextuallyTypedParameterType(declaration);
return getContextuallyTypedParameterType(declaration, /*forCache*/ false);
case SyntaxKind.BindingElement:
return getContextualTypeForBindingElement(declaration);
// By default, do nothing and return undefined - only parameters and binding elements have context implied by a parent
Expand Down Expand Up @@ -23053,12 +23070,18 @@ namespace ts {
checkGrammarForGenerator(node);
}

const links = getNodeLinks(node);
const type = getTypeOfSymbol(getMergedSymbol(node.symbol));
if (isTypeAny(type)) {
return type;
}

contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node, checkMode);

return type;
}

function contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode) {
const links = getNodeLinks(node);
// Check if function expression is contextually typed and assign parameter types if so.
if (!(links.flags & NodeCheckFlags.ContextChecked)) {
const contextualSignature = getContextualSignature(node);
Expand All @@ -23068,6 +23091,10 @@ namespace ts {
if (!(links.flags & NodeCheckFlags.ContextChecked)) {
links.flags |= NodeCheckFlags.ContextChecked;
if (contextualSignature) {
const type = getTypeOfSymbol(getMergedSymbol(node.symbol));
if (isTypeAny(type)) {
return;
}
const signature = getSignaturesOfType(type, SignatureKind.Call)[0];
if (isContextSensitive(node)) {
const inferenceContext = getInferenceContext(node);
Expand All @@ -23088,8 +23115,6 @@ namespace ts {
checkSignatureDeclaration(node);
}
}

return type;
}

function getReturnOrPromisedType(node: FunctionLikeDeclaration | MethodSignature, functionFlags: FunctionFlags) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ trans(([b,c]) => 'foo');

trans(({d: [e,f]}) => 'foo');
>trans : Symbol(trans, Decl(fallbackToBindingPatternForTypeInference.ts, 0, 0))
>d : Symbol(d)
>e : Symbol(e, Decl(fallbackToBindingPatternForTypeInference.ts, 3, 12))
>f : Symbol(f, Decl(fallbackToBindingPatternForTypeInference.ts, 3, 14))

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//// [inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts]
class ClassA<TEntityClass> {
constructor(private entity?: TEntityClass, public settings?: SettingsInterface<TEntityClass>) {

}
}
export interface ValueInterface<TValueClass> {
func?: (row: TValueClass) => any;
value?: string;
}
export interface SettingsInterface<TClass> {
values?: (row: TClass) => ValueInterface<TClass>[],
}
class ConcreteClass {
theName = 'myClass';
}

var thisGetsTheFalseError = new ClassA(new ConcreteClass(), {
values: o => [
{
value: o.theName,
func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj'
}
]
});

var thisIsOk = new ClassA<ConcreteClass>(new ConcreteClass(), {
values: o => [
{
value: o.theName,
func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj'
}
]
});

//// [inferenceDoesntCompareAgainstUninstantiatedTypeParameter.js]
"use strict";
exports.__esModule = true;
var ClassA = /** @class */ (function () {
function ClassA(entity, settings) {
this.entity = entity;
this.settings = settings;
}
return ClassA;
}());
var ConcreteClass = /** @class */ (function () {
function ConcreteClass() {
this.theName = 'myClass';
}
return ConcreteClass;
}());
var thisGetsTheFalseError = new ClassA(new ConcreteClass(), {
values: function (o) { return [
{
value: o.theName,
func: function (x) { return 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj'; }
}
]; }
});
var thisIsOk = new ClassA(new ConcreteClass(), {
values: function (o) { return [
{
value: o.theName,
func: function (x) { return 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj'; }
}
]; }
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
=== tests/cases/compiler/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts ===
class ClassA<TEntityClass> {
>ClassA : Symbol(ClassA, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 0, 0))
>TEntityClass : Symbol(TEntityClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 0, 13))

constructor(private entity?: TEntityClass, public settings?: SettingsInterface<TEntityClass>) {
>entity : Symbol(ClassA.entity, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 1, 16))
>TEntityClass : Symbol(TEntityClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 0, 13))
>settings : Symbol(ClassA.settings, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 1, 46))
>SettingsInterface : Symbol(SettingsInterface, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 8, 1))
>TEntityClass : Symbol(TEntityClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 0, 13))

}
}
export interface ValueInterface<TValueClass> {
>ValueInterface : Symbol(ValueInterface, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 4, 1))
>TValueClass : Symbol(TValueClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 5, 32))

func?: (row: TValueClass) => any;
>func : Symbol(ValueInterface.func, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 5, 46))
>row : Symbol(row, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 6, 12))
>TValueClass : Symbol(TValueClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 5, 32))

value?: string;
>value : Symbol(ValueInterface.value, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 6, 37))
}
export interface SettingsInterface<TClass> {
>SettingsInterface : Symbol(SettingsInterface, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 8, 1))
>TClass : Symbol(TClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 9, 35))

values?: (row: TClass) => ValueInterface<TClass>[],
>values : Symbol(SettingsInterface.values, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 9, 44))
>row : Symbol(row, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 10, 14))
>TClass : Symbol(TClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 9, 35))
>ValueInterface : Symbol(ValueInterface, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 4, 1))
>TClass : Symbol(TClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 9, 35))
}
class ConcreteClass {
>ConcreteClass : Symbol(ConcreteClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 11, 1))

theName = 'myClass';
>theName : Symbol(ConcreteClass.theName, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 12, 21))
}

var thisGetsTheFalseError = new ClassA(new ConcreteClass(), {
>thisGetsTheFalseError : Symbol(thisGetsTheFalseError, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 16, 3))
>ClassA : Symbol(ClassA, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 0, 0))
>ConcreteClass : Symbol(ConcreteClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 11, 1))

values: o => [
>values : Symbol(values, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 16, 61))
>o : Symbol(o, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 17, 11))
{
value: o.theName,
>value : Symbol(value, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 18, 9))
>o.theName : Symbol(ConcreteClass.theName, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 12, 21))
>o : Symbol(o, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 17, 11))
>theName : Symbol(ConcreteClass.theName, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 12, 21))

func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj'
>func : Symbol(func, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 19, 29))
>x : Symbol(x, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 20, 17))
}
]
});

var thisIsOk = new ClassA<ConcreteClass>(new ConcreteClass(), {
>thisIsOk : Symbol(thisIsOk, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 25, 3))
>ClassA : Symbol(ClassA, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 0, 0))
>ConcreteClass : Symbol(ConcreteClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 11, 1))
>ConcreteClass : Symbol(ConcreteClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 11, 1))

values: o => [
>values : Symbol(values, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 25, 63))
>o : Symbol(o, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 26, 11))
{
value: o.theName,
>value : Symbol(value, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 27, 9))
>o.theName : Symbol(ConcreteClass.theName, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 12, 21))
>o : Symbol(o, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 26, 11))
>theName : Symbol(ConcreteClass.theName, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 12, 21))

func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj'
>func : Symbol(func, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 28, 29))
>x : Symbol(x, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 29, 17))
}
]
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
=== tests/cases/compiler/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts ===
class ClassA<TEntityClass> {
>ClassA : ClassA<TEntityClass>

constructor(private entity?: TEntityClass, public settings?: SettingsInterface<TEntityClass>) {
>entity : TEntityClass
>settings : SettingsInterface<TEntityClass>

}
}
export interface ValueInterface<TValueClass> {
func?: (row: TValueClass) => any;
>func : (row: TValueClass) => any
>row : TValueClass

value?: string;
>value : string
}
export interface SettingsInterface<TClass> {
values?: (row: TClass) => ValueInterface<TClass>[],
>values : (row: TClass) => ValueInterface<TClass>[]
>row : TClass
}
class ConcreteClass {
>ConcreteClass : ConcreteClass

theName = 'myClass';
>theName : string
>'myClass' : "myClass"
}

var thisGetsTheFalseError = new ClassA(new ConcreteClass(), {
>thisGetsTheFalseError : ClassA<ConcreteClass>
>new ClassA(new ConcreteClass(), { values: o => [ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ]}) : ClassA<ConcreteClass>
>ClassA : typeof ClassA
>new ConcreteClass() : ConcreteClass
>ConcreteClass : typeof ConcreteClass
>{ values: o => [ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ]} : { values: (o: ConcreteClass) => { value: string; func: (x: ConcreteClass) => string; }[]; }

values: o => [
>values : (o: ConcreteClass) => { value: string; func: (x: ConcreteClass) => string; }[]
>o => [ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ] : (o: ConcreteClass) => { value: string; func: (x: ConcreteClass) => string; }[]
>o : ConcreteClass
>[ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ] : { value: string; func: (x: ConcreteClass) => string; }[]
{
>{ value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } : { value: string; func: (x: ConcreteClass) => string; }

value: o.theName,
>value : string
>o.theName : string
>o : ConcreteClass
>theName : string

func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj'
>func : (x: ConcreteClass) => string
>x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' : (x: ConcreteClass) => string
>x : ConcreteClass
>'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' : "asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj"
}
]
});

var thisIsOk = new ClassA<ConcreteClass>(new ConcreteClass(), {
>thisIsOk : ClassA<ConcreteClass>
>new ClassA<ConcreteClass>(new ConcreteClass(), { values: o => [ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ]}) : ClassA<ConcreteClass>
>ClassA : typeof ClassA
>new ConcreteClass() : ConcreteClass
>ConcreteClass : typeof ConcreteClass
>{ values: o => [ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ]} : { values: (o: ConcreteClass) => { value: string; func: (x: ConcreteClass) => string; }[]; }

values: o => [
>values : (o: ConcreteClass) => { value: string; func: (x: ConcreteClass) => string; }[]
>o => [ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ] : (o: ConcreteClass) => { value: string; func: (x: ConcreteClass) => string; }[]
>o : ConcreteClass
>[ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ] : { value: string; func: (x: ConcreteClass) => string; }[]
{
>{ value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } : { value: string; func: (x: ConcreteClass) => string; }

value: o.theName,
>value : string
>o.theName : string
>o : ConcreteClass
>theName : string

func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj'
>func : (x: ConcreteClass) => string
>x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' : (x: ConcreteClass) => string
>x : ConcreteClass
>'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' : "asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj"
}
]
});
Loading