diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 516a01ecf44..2f3bd4b285f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8179,6 +8179,21 @@ namespace ts { captureLexicalThis(node, container); } if (isFunctionLike(container)) { + // If this is a function in a JS file, it might be a class method. Check if it's the RHS + // of a x.prototype.y = function [name]() { .... } + if (container.kind === SyntaxKind.FunctionExpression && + isInJavaScriptFile(container.parent) && + getSpecialPropertyAssignmentKind(container.parent) === SpecialPropertyAssignmentKind.PrototypeProperty) { + // Get the 'x' of 'x.prototype.y = f' (here, 'f' is 'container') + const className = (((container.parent as BinaryExpression) // x.prototype.y = f + .left as PropertyAccessExpression) // x.prototype.y + .expression as PropertyAccessExpression) // x.prototype + .expression; // x + const classSymbol = checkExpression(className).symbol; + if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) { + return getInferredClassType(classSymbol); + } + } const type = getContextuallyTypedThisType(container); if (type) { return type; @@ -8190,9 +8205,13 @@ namespace ts { if (container.parent && container.parent.kind === SyntaxKind.ObjectLiteralExpression) { // Note: this works because object literal methods are deferred, // which means that the type of the containing object literal is already known. - const type = checkExpressionCached(container.parent); - if (type) { - return type; + const contextualType = getContextualType(container.parent as ObjectLiteralExpression); + const literalType = checkExpressionCached(container.parent); + if (contextualType && literalType) { + return getIntersectionType([contextualType, literalType]); + } + else if (contextualType || literalType) { + return contextualType || literalType; } } } @@ -8207,22 +8226,6 @@ namespace ts { if (type && type !== unknownType) { return type; } - - // If this is a function in a JS file, it might be a class method. Check if it's the RHS - // of a x.prototype.y = function [name]() { .... } - if (container.kind === SyntaxKind.FunctionExpression) { - if (getSpecialPropertyAssignmentKind(container.parent) === SpecialPropertyAssignmentKind.PrototypeProperty) { - // Get the 'x' of 'x.prototype.y = f' (here, 'f' is 'container') - const className = (((container.parent as BinaryExpression) // x.prototype.y = f - .left as PropertyAccessExpression) // x.prototype.y - .expression as PropertyAccessExpression) // x.prototype - .expression; // x - const classSymbol = checkExpression(className).symbol; - if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) { - return getInferredClassType(classSymbol); - } - } - } } if (compilerOptions.noImplicitThis) { @@ -8450,6 +8453,12 @@ namespace ts { if ((isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && isContextSensitive(func) && func.kind !== SyntaxKind.ArrowFunction) { + const type = isObjectLiteralMethod(func) + ? getContextualTypeForObjectLiteralMethod(func) + : getApparentTypeOfContextualType(func); + if (type === anyType) { + return anyType; + } const contextualSignature = getContextualSignature(func); if (contextualSignature) { return contextualSignature.thisType; diff --git a/tests/baselines/reference/thisTypeInFunctions2.js b/tests/baselines/reference/thisTypeInFunctions2.js new file mode 100644 index 00000000000..7fc52877191 --- /dev/null +++ b/tests/baselines/reference/thisTypeInFunctions2.js @@ -0,0 +1,51 @@ +//// [thisTypeInFunctions2.ts] +interface Arguments { + init?: (this: void) => void; + willDestroy?: (this: any) => void; + [propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any); +} +declare function extend(arguments: Arguments): void; +class Mixin { + stuff: number; +} + +extend({ + init() { + this + }, + mine: 12, + bar() { + this.init(); + }, + foo() { + this.bar; + this.url + this.handler() + this.baz + this.willDestroy + } +}) + + +//// [thisTypeInFunctions2.js] +var Mixin = (function () { + function Mixin() { + } + return Mixin; +}()); +extend({ + init: function () { + this; + }, + mine: 12, + bar: function () { + this.init(); + }, + foo: function () { + this.bar; + this.url; + this.handler(); + this.baz; + this.willDestroy; + } +}); diff --git a/tests/baselines/reference/thisTypeInFunctions2.symbols b/tests/baselines/reference/thisTypeInFunctions2.symbols new file mode 100644 index 00000000000..5e186e9485d --- /dev/null +++ b/tests/baselines/reference/thisTypeInFunctions2.symbols @@ -0,0 +1,56 @@ +=== tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts === +interface Arguments { +>Arguments : Symbol(Arguments, Decl(thisTypeInFunctions2.ts, 0, 0)) + + init?: (this: void) => void; +>init : Symbol(Arguments.init, Decl(thisTypeInFunctions2.ts, 0, 21)) +>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 1, 12)) + + willDestroy?: (this: any) => void; +>willDestroy : Symbol(Arguments.willDestroy, Decl(thisTypeInFunctions2.ts, 1, 32)) +>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 2, 19)) + + [propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any); +>propName : Symbol(propName, Decl(thisTypeInFunctions2.ts, 3, 5)) +>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 3, 87)) +>args : Symbol(args, Decl(thisTypeInFunctions2.ts, 3, 97)) +} +declare function extend(arguments: Arguments): void; +>extend : Symbol(extend, Decl(thisTypeInFunctions2.ts, 4, 1)) +>arguments : Symbol(arguments, Decl(thisTypeInFunctions2.ts, 5, 24)) +>Arguments : Symbol(Arguments, Decl(thisTypeInFunctions2.ts, 0, 0)) + +class Mixin { +>Mixin : Symbol(Mixin, Decl(thisTypeInFunctions2.ts, 5, 52)) + + stuff: number; +>stuff : Symbol(Mixin.stuff, Decl(thisTypeInFunctions2.ts, 6, 13)) +} + +extend({ +>extend : Symbol(extend, Decl(thisTypeInFunctions2.ts, 4, 1)) + + init() { +>init : Symbol(init, Decl(thisTypeInFunctions2.ts, 10, 8)) + + this + }, + mine: 12, +>mine : Symbol(mine, Decl(thisTypeInFunctions2.ts, 13, 6)) + + bar() { +>bar : Symbol(bar, Decl(thisTypeInFunctions2.ts, 14, 13)) + + this.init(); + }, + foo() { +>foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 17, 6)) + + this.bar; + this.url + this.handler() + this.baz + this.willDestroy + } +}) + diff --git a/tests/baselines/reference/thisTypeInFunctions2.types b/tests/baselines/reference/thisTypeInFunctions2.types new file mode 100644 index 00000000000..3e3e9123549 --- /dev/null +++ b/tests/baselines/reference/thisTypeInFunctions2.types @@ -0,0 +1,87 @@ +=== tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts === +interface Arguments { +>Arguments : Arguments + + init?: (this: void) => void; +>init : (this: void) => void +>this : void + + willDestroy?: (this: any) => void; +>willDestroy : (this: any) => void +>this : any + + [propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any); +>propName : string +>null : null +>this : any +>args : any[] +} +declare function extend(arguments: Arguments): void; +>extend : (arguments: Arguments) => void +>arguments : Arguments +>Arguments : Arguments + +class Mixin { +>Mixin : Mixin + + stuff: number; +>stuff : number +} + +extend({ +>extend({ init() { this }, mine: 12, bar() { this.init(); }, foo() { this.bar; this.url this.handler() this.baz this.willDestroy }}) : void +>extend : (arguments: Arguments) => void +>{ init() { this }, mine: 12, bar() { this.init(); }, foo() { this.bar; this.url this.handler() this.baz this.willDestroy }} : { init(): void; mine: number; bar(): void; foo(): void; } + + init() { +>init : () => void + + this +>this : void + + }, + mine: 12, +>mine : number +>12 : number + + bar() { +>bar : () => void + + this.init(); +>this.init() : any +>this.init : any +>this : any +>init : any + + }, + foo() { +>foo : () => void + + this.bar; +>this.bar : any +>this : any +>bar : any + + this.url +>this.url : any +>this : any +>url : any + + this.handler() +>this.handler() : any +>this.handler : any +>this : any +>handler : any + + this.baz +>this.baz : any +>this : any +>baz : any + + this.willDestroy +>this.willDestroy : any +>this : any +>willDestroy : any + } +}) + diff --git a/tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts b/tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts new file mode 100644 index 00000000000..c33c5062249 --- /dev/null +++ b/tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts @@ -0,0 +1,26 @@ +interface Arguments { + init?: (this: void) => void; + willDestroy?: (this: any) => void; + [propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any); +} +declare function extend(arguments: Arguments): void; +class Mixin { + stuff: number; +} + +extend({ + init() { + this + }, + mine: 12, + bar() { + this.init(); + }, + foo() { + this.bar; + this.url + this.handler() + this.baz + this.willDestroy + } +})