diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5814b2dcfbf42..b2c1dca7f0f62 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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); } @@ -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(node); case SyntaxKind.ObjectLiteralExpression: return some((node).properties, isContextSensitive); @@ -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; @@ -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; @@ -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) : @@ -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 @@ -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); @@ -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); @@ -23088,8 +23115,6 @@ namespace ts { checkSignatureDeclaration(node); } } - - return type; } function getReturnOrPromisedType(node: FunctionLikeDeclaration | MethodSignature, functionFlags: FunctionFlags) { diff --git a/tests/baselines/reference/fallbackToBindingPatternForTypeInference.symbols b/tests/baselines/reference/fallbackToBindingPatternForTypeInference.symbols index f57b234f39fbc..4f450e21c24c0 100644 --- a/tests/baselines/reference/fallbackToBindingPatternForTypeInference.symbols +++ b/tests/baselines/reference/fallbackToBindingPatternForTypeInference.symbols @@ -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)) diff --git a/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.js b/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.js new file mode 100644 index 0000000000000..c3dd7d421129b --- /dev/null +++ b/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.js @@ -0,0 +1,67 @@ +//// [inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts] +class ClassA { + constructor(private entity?: TEntityClass, public settings?: SettingsInterface) { + + } +} +export interface ValueInterface { + func?: (row: TValueClass) => any; + value?: string; +} +export interface SettingsInterface { + values?: (row: TClass) => ValueInterface[], +} +class ConcreteClass { + theName = 'myClass'; +} + +var thisGetsTheFalseError = new ClassA(new ConcreteClass(), { + values: o => [ + { + value: o.theName, + func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' + } + ] +}); + +var thisIsOk = new ClassA(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'; } + } + ]; } +}); diff --git a/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.symbols b/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.symbols new file mode 100644 index 0000000000000..a47f3db5b5507 --- /dev/null +++ b/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.symbols @@ -0,0 +1,88 @@ +=== tests/cases/compiler/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts === +class ClassA { +>ClassA : Symbol(ClassA, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 0, 0)) +>TEntityClass : Symbol(TEntityClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 0, 13)) + + constructor(private entity?: TEntityClass, public settings?: SettingsInterface) { +>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 { +>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 { +>SettingsInterface : Symbol(SettingsInterface, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 8, 1)) +>TClass : Symbol(TClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 9, 35)) + + values?: (row: TClass) => ValueInterface[], +>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(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)) + } + ] +}); diff --git a/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.types b/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.types new file mode 100644 index 0000000000000..bda9aae9a4e01 --- /dev/null +++ b/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.types @@ -0,0 +1,92 @@ +=== tests/cases/compiler/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts === +class ClassA { +>ClassA : ClassA + + constructor(private entity?: TEntityClass, public settings?: SettingsInterface) { +>entity : TEntityClass +>settings : SettingsInterface + + } +} +export interface ValueInterface { + func?: (row: TValueClass) => any; +>func : (row: TValueClass) => any +>row : TValueClass + + value?: string; +>value : string +} +export interface SettingsInterface { + values?: (row: TClass) => ValueInterface[], +>values : (row: TClass) => ValueInterface[] +>row : TClass +} +class ConcreteClass { +>ConcreteClass : ConcreteClass + + theName = 'myClass'; +>theName : string +>'myClass' : "myClass" +} + +var thisGetsTheFalseError = new ClassA(new ConcreteClass(), { +>thisGetsTheFalseError : ClassA +>new ClassA(new ConcreteClass(), { values: o => [ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ]}) : ClassA +>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(new ConcreteClass(), { +>thisIsOk : ClassA +>new ClassA(new ConcreteClass(), { values: o => [ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ]}) : ClassA +>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" + } + ] +}); diff --git a/tests/cases/compiler/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts b/tests/cases/compiler/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts new file mode 100644 index 0000000000000..98c0f25ee7b24 --- /dev/null +++ b/tests/cases/compiler/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts @@ -0,0 +1,33 @@ +class ClassA { + constructor(private entity?: TEntityClass, public settings?: SettingsInterface) { + + } +} +export interface ValueInterface { + func?: (row: TValueClass) => any; + value?: string; +} +export interface SettingsInterface { + values?: (row: TClass) => ValueInterface[], +} +class ConcreteClass { + theName = 'myClass'; +} + +var thisGetsTheFalseError = new ClassA(new ConcreteClass(), { + values: o => [ + { + value: o.theName, + func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' + } + ] +}); + +var thisIsOk = new ClassA(new ConcreteClass(), { + values: o => [ + { + value: o.theName, + func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' + } + ] +}); \ No newline at end of file