diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9fc854515ee96..e27e8dab94cf2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15546,8 +15546,8 @@ namespace ts { return !node.typeParameters && !getEffectiveReturnTypeNode(node) && !!node.body && node.body.kind !== SyntaxKind.Block && isContextSensitive(node.body); } - function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration { - return (isInJSFile(func) && isFunctionDeclaration(func) || isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && + function isContextSensitiveFunctionOrObjectLiteralMethodOrClassMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration { + return (isInJSFile(func) && isFunctionDeclaration(func) || isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func) || (noImplicitAny && isClassMethodInSubClass(func))) && isContextSensitiveFunctionLikeDeclaration(func); } @@ -23549,7 +23549,7 @@ namespace ts { if (func.kind === SyntaxKind.ArrowFunction) { return undefined; } - if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) { + if (isContextSensitiveFunctionOrObjectLiteralMethodOrClassMethod(func)) { const contextualSignature = getContextualSignature(func); if (contextualSignature) { const thisParameter = contextualSignature.thisParameter; @@ -23609,7 +23609,7 @@ namespace ts { // Return contextual type of parameter or undefined if no contextual type is available function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type | undefined { const func = parameter.parent; - if (!isContextSensitiveFunctionOrObjectLiteralMethod(func)) { + if (!isContextSensitiveFunctionOrObjectLiteralMethodOrClassMethod(func)) { return undefined; } const iife = getImmediatelyInvokedFunctionExpression(func); @@ -23987,6 +23987,28 @@ namespace ts { return getContextualTypeForObjectLiteralElement(node, contextFlags); } + function getContextualTypeForClassMethod(node: MethodDeclaration): Type | undefined { + if (node.type || !isPropertyNameLiteral(node.name) || some(node.parameters, parameter => !!parameter.type)) { + return undefined; + } + + const container = cast(node.parent, isClassLike); + const heritageElement = getClassExtendsHeritageElement(container); + Debug.assertIsDefined(heritageElement); + + const baseType = getTypeOfNode(heritageElement); + if (baseType === errorType) { + return undefined; + } + + const basePropType = getTypeOfPropertyOfType(baseType, getTextOfPropertyName(node.name)); + if (!basePropType) { + return undefined; + } + + return basePropType; + } + function getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike, contextFlags?: ContextFlags) { const objectLiteral = element.parent; const type = getApparentTypeOfContextualType(objectLiteral, contextFlags); @@ -24122,6 +24144,7 @@ namespace ts { function getApparentTypeOfContextualType(node: Expression | MethodDeclaration, contextFlags?: ContextFlags): Type | undefined { const contextualType = isObjectLiteralMethod(node) ? getContextualTypeForObjectLiteralMethod(node, contextFlags) : + isClassMethodInSubClass(node) ? getContextualTypeForClassMethod(node) : getContextualType(node, contextFlags); const instantiatedType = instantiateContextualType(contextualType, node, contextFlags); if (instantiatedType && !(contextFlags && contextFlags & ContextFlags.NoConstraints && instantiatedType.flags & TypeFlags.TypeVariable)) { @@ -24453,7 +24476,7 @@ namespace ts { // all identical ignoring their return type, the result is same signature but with return type as // union type of return types from these signatures function getContextualSignature(node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature | undefined { - Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); + Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node) || isClassMethodInSubClass(node)); const typeTagSignature = getSignatureOfTypeTag(node); if (typeTagSignature) { return typeTagSignature; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 31293d2e81bb8..c586bae82c6bc 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1462,6 +1462,14 @@ namespace ts { return node && node.kind === SyntaxKind.MethodDeclaration && node.parent.kind === SyntaxKind.ObjectLiteralExpression; } + export function isClassMethodInSubClass(node: Node): node is MethodDeclaration { + if (!node || !isMethodDeclaration(node) || !isClassLike(node.parent)) { + return false; + } + + return !!getClassExtendsHeritageElement(node.parent); + } + export function isObjectLiteralOrClassExpressionMethod(node: Node): node is MethodDeclaration { return node.kind === SyntaxKind.MethodDeclaration && (node.parent.kind === SyntaxKind.ObjectLiteralExpression || diff --git a/tests/baselines/reference/parameterTypeOfMethodLike.js b/tests/baselines/reference/parameterTypeOfMethodLike.js new file mode 100644 index 0000000000000..97d1672f49210 --- /dev/null +++ b/tests/baselines/reference/parameterTypeOfMethodLike.js @@ -0,0 +1,42 @@ +//// [parameterTypeOfMethodLike.ts] +class Base { + method(x: number) { } +} + +class Derived extends Base { + method(x) { + x.toFixed(0); + } +} + +//// [parameterTypeOfMethodLike.js] +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var Base = /** @class */ (function () { + function Base() { + } + Base.prototype.method = function (x) { }; + return Base; +}()); +var Derived = /** @class */ (function (_super) { + __extends(Derived, _super); + function Derived() { + return _super !== null && _super.apply(this, arguments) || this; + } + Derived.prototype.method = function (x) { + x.toFixed(0); + }; + return Derived; +}(Base)); diff --git a/tests/baselines/reference/parameterTypeOfMethodLike.symbols b/tests/baselines/reference/parameterTypeOfMethodLike.symbols new file mode 100644 index 0000000000000..ed2ff6139e0f1 --- /dev/null +++ b/tests/baselines/reference/parameterTypeOfMethodLike.symbols @@ -0,0 +1,23 @@ +=== tests/cases/conformance/classes/methodDeclarations/parameterTypeOfMethodLike.ts === +class Base { +>Base : Symbol(Base, Decl(parameterTypeOfMethodLike.ts, 0, 0)) + + method(x: number) { } +>method : Symbol(Base.method, Decl(parameterTypeOfMethodLike.ts, 0, 12)) +>x : Symbol(x, Decl(parameterTypeOfMethodLike.ts, 1, 11)) +} + +class Derived extends Base { +>Derived : Symbol(Derived, Decl(parameterTypeOfMethodLike.ts, 2, 1)) +>Base : Symbol(Base, Decl(parameterTypeOfMethodLike.ts, 0, 0)) + + method(x) { +>method : Symbol(Derived.method, Decl(parameterTypeOfMethodLike.ts, 4, 28)) +>x : Symbol(x, Decl(parameterTypeOfMethodLike.ts, 5, 11)) + + x.toFixed(0); +>x.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>x : Symbol(x, Decl(parameterTypeOfMethodLike.ts, 5, 11)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + } +} diff --git a/tests/baselines/reference/parameterTypeOfMethodLike.types b/tests/baselines/reference/parameterTypeOfMethodLike.types new file mode 100644 index 0000000000000..82f04330d2ec6 --- /dev/null +++ b/tests/baselines/reference/parameterTypeOfMethodLike.types @@ -0,0 +1,25 @@ +=== tests/cases/conformance/classes/methodDeclarations/parameterTypeOfMethodLike.ts === +class Base { +>Base : Base + + method(x: number) { } +>method : (x: number) => void +>x : number +} + +class Derived extends Base { +>Derived : Derived +>Base : Base + + method(x) { +>method : (x: number) => void +>x : number + + x.toFixed(0); +>x.toFixed(0) : string +>x.toFixed : (fractionDigits?: number | undefined) => string +>x : number +>toFixed : (fractionDigits?: number | undefined) => string +>0 : 0 + } +} diff --git a/tests/cases/conformance/classes/methodDeclarations/parameterTypeOfMethodLike.ts b/tests/cases/conformance/classes/methodDeclarations/parameterTypeOfMethodLike.ts new file mode 100644 index 0000000000000..0726f07219e36 --- /dev/null +++ b/tests/cases/conformance/classes/methodDeclarations/parameterTypeOfMethodLike.ts @@ -0,0 +1,11 @@ +// @strict: true + +class Base { + method(x: number) { } +} + +class Derived extends Base { + method(x) { + x.toFixed(0); + } +} \ No newline at end of file