diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 516a01ecf4482..6cedb78686d68 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5508,6 +5508,10 @@ namespace ts { return !node.typeParameters && areAllParametersUntyped && !isNullaryArrow; } + function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | MethodDeclaration { + return (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && isContextSensitiveFunctionLikeDeclaration(func); + } + function getTypeWithoutSignatures(type: Type): Type { if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); @@ -8179,6 +8183,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; @@ -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) { @@ -8447,12 +8450,13 @@ namespace ts { } function getContextuallyTypedThisType(func: FunctionLikeDeclaration): Type { - if ((isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && - isContextSensitive(func) && - func.kind !== SyntaxKind.ArrowFunction) { + if (isContextSensitiveFunctionOrObjectLiteralMethod(func) && func.kind !== SyntaxKind.ArrowFunction) { const contextualSignature = getContextualSignature(func); if (contextualSignature) { - return contextualSignature.thisType; + return contextualSignature.thisType || anyType; + } + else if (getContextualTypeForFunctionLikeDeclaration(func) === anyType) { + return anyType; } } @@ -8462,24 +8466,21 @@ namespace ts { // Return contextual type of parameter or undefined if no contextual type is available function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type { const func = parameter.parent; - if (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) { - if (isContextSensitive(func)) { - const contextualSignature = getContextualSignature(func); - if (contextualSignature) { - - const funcHasRestParameters = hasRestParameter(func); - const len = func.parameters.length - (funcHasRestParameters ? 1 : 0); - const indexOfParameter = indexOf(func.parameters, parameter); - if (indexOfParameter < len) { - return getTypeAtPosition(contextualSignature, indexOfParameter); - } + if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) { + const contextualSignature = getContextualSignature(func); + if (contextualSignature) { + const funcHasRestParameters = hasRestParameter(func); + const len = func.parameters.length - (funcHasRestParameters ? 1 : 0); + const indexOfParameter = indexOf(func.parameters, parameter); + if (indexOfParameter < len) { + return getTypeAtPosition(contextualSignature, indexOfParameter); + } - // If last parameter is contextually rest parameter get its type - if (funcHasRestParameters && - indexOfParameter === (func.parameters.length - 1) && - isRestParameterIndex(contextualSignature, func.parameters.length - 1)) { - return getTypeOfSymbol(lastOrUndefined(contextualSignature.parameters)); - } + // If last parameter is contextually rest parameter get its type + if (funcHasRestParameters && + indexOfParameter === (func.parameters.length - 1) && + isRestParameterIndex(contextualSignature, func.parameters.length - 1)) { + return getTypeOfSymbol(lastOrUndefined(contextualSignature.parameters)); } } } @@ -8487,9 +8488,9 @@ namespace ts { } // In a variable, parameter or property declaration with a type annotation, - // the contextual type of an initializer expression is the type of the variable, parameter or property. - // Otherwise, in a parameter declaration of a contextually typed function expression, - // the contextual type of an initializer expression is the contextual type of the parameter. + // the contextual type of an initializer expression is the type of the variable, parameter or property. + // Otherwise, in a parameter declaration of a contextually typed function expression, + // the contextual type of an initializer expression is the contextual type of the parameter. // Otherwise, in a variable or parameter declaration with a binding pattern name, // the contextual type of an initializer expression is the type implied by the binding pattern. // Otherwise, in a binding pattern inside a variable or parameter declaration, @@ -8843,6 +8844,12 @@ namespace ts { : undefined; } + function getContextualTypeForFunctionLikeDeclaration(node: FunctionExpression | MethodDeclaration) { + return isObjectLiteralMethod(node) + ? getContextualTypeForObjectLiteralMethod(node) + : getApparentTypeOfContextualType(node); + } + // Return the contextual signature for a given expression node. A contextual type provides a // contextual signature if it has a single call signature and if that call signature is non-generic. // If the contextual type is a union type, get the signature from each type possible and if they are @@ -8850,9 +8857,7 @@ namespace ts { // union type of return types from these signatures function getContextualSignature(node: FunctionExpression | MethodDeclaration): Signature { Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); - const type = isObjectLiteralMethod(node) - ? getContextualTypeForObjectLiteralMethod(node) - : getApparentTypeOfContextualType(node); + const type = getContextualTypeForFunctionLikeDeclaration(node); if (!type) { return undefined; } diff --git a/tests/baselines/reference/thisTypeInFunctions2.js b/tests/baselines/reference/thisTypeInFunctions2.js new file mode 100644 index 0000000000000..f52438a19ab5e --- /dev/null +++ b/tests/baselines/reference/thisTypeInFunctions2.js @@ -0,0 +1,91 @@ +//// [thisTypeInFunctions2.ts] +interface IndexedWithThis { + // this is a workaround for React + init?: (this: this) => void; + willDestroy?: (this: any) => void; + [propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any); +} +interface IndexedWithoutThis { + // this is what React would like to write (and what they write today) + init?: () => void; + willDestroy?: () => void; + [propName: string]: any; +} +interface SimpleInterface { + foo(n: string); + bar(): number; +} +declare function extend1(args: IndexedWithThis): void; +declare function extend2(args: IndexedWithoutThis): void; +declare function simple(arg: SimpleInterface): void; + +extend1({ + init() { + this // this: IndexedWithThis because of contextual typing. + // this.mine + this.willDestroy + }, + mine: 12, + foo() { + this.url; // this: any because 'foo' matches the string indexer + this.willDestroy; + } +}); +extend2({ + init() { + this // this: any because the contextual signature of init doesn't specify this' type + this.mine + this.willDestroy + }, + mine: 13, + foo() { + this // this: any because of the string indexer + this.mine + this.willDestroy + } +}); + +simple({ + foo(n) { + return n.length + this.bar(); + }, + bar() { + return 14; + } +}) + + +//// [thisTypeInFunctions2.js] +extend1({ + init: function () { + this; // this: IndexedWithThis because of contextual typing. + // this.mine + this.willDestroy; + }, + mine: 12, + foo: function () { + this.url; // this: any because 'foo' matches the string indexer + this.willDestroy; + } +}); +extend2({ + init: function () { + this; // this: any because the contextual signature of init doesn't specify this' type + this.mine; + this.willDestroy; + }, + mine: 13, + foo: function () { + this; // this: any because of the string indexer + this.mine; + this.willDestroy; + } +}); +simple({ + foo: function (n) { + return n.length + this.bar(); + }, + bar: function () { + return 14; + } +}); diff --git a/tests/baselines/reference/thisTypeInFunctions2.symbols b/tests/baselines/reference/thisTypeInFunctions2.symbols new file mode 100644 index 0000000000000..cdc7fb321d46f --- /dev/null +++ b/tests/baselines/reference/thisTypeInFunctions2.symbols @@ -0,0 +1,124 @@ +=== tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts === +interface IndexedWithThis { +>IndexedWithThis : Symbol(IndexedWithThis, Decl(thisTypeInFunctions2.ts, 0, 0)) + + // this is a workaround for React + init?: (this: this) => void; +>init : Symbol(IndexedWithThis.init, Decl(thisTypeInFunctions2.ts, 0, 27)) +>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 2, 12)) + + willDestroy?: (this: any) => void; +>willDestroy : Symbol(IndexedWithThis.willDestroy, Decl(thisTypeInFunctions2.ts, 2, 32)) +>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 3, 19)) + + [propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any); +>propName : Symbol(propName, Decl(thisTypeInFunctions2.ts, 4, 5)) +>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 4, 87)) +>args : Symbol(args, Decl(thisTypeInFunctions2.ts, 4, 97)) +} +interface IndexedWithoutThis { +>IndexedWithoutThis : Symbol(IndexedWithoutThis, Decl(thisTypeInFunctions2.ts, 5, 1)) + + // this is what React would like to write (and what they write today) + init?: () => void; +>init : Symbol(IndexedWithoutThis.init, Decl(thisTypeInFunctions2.ts, 6, 30)) + + willDestroy?: () => void; +>willDestroy : Symbol(IndexedWithoutThis.willDestroy, Decl(thisTypeInFunctions2.ts, 8, 22)) + + [propName: string]: any; +>propName : Symbol(propName, Decl(thisTypeInFunctions2.ts, 10, 5)) +} +interface SimpleInterface { +>SimpleInterface : Symbol(SimpleInterface, Decl(thisTypeInFunctions2.ts, 11, 1)) + + foo(n: string); +>foo : Symbol(SimpleInterface.foo, Decl(thisTypeInFunctions2.ts, 12, 27)) +>n : Symbol(n, Decl(thisTypeInFunctions2.ts, 13, 8)) + + bar(): number; +>bar : Symbol(SimpleInterface.bar, Decl(thisTypeInFunctions2.ts, 13, 19)) +} +declare function extend1(args: IndexedWithThis): void; +>extend1 : Symbol(extend1, Decl(thisTypeInFunctions2.ts, 15, 1)) +>args : Symbol(args, Decl(thisTypeInFunctions2.ts, 16, 25)) +>IndexedWithThis : Symbol(IndexedWithThis, Decl(thisTypeInFunctions2.ts, 0, 0)) + +declare function extend2(args: IndexedWithoutThis): void; +>extend2 : Symbol(extend2, Decl(thisTypeInFunctions2.ts, 16, 54)) +>args : Symbol(args, Decl(thisTypeInFunctions2.ts, 17, 25)) +>IndexedWithoutThis : Symbol(IndexedWithoutThis, Decl(thisTypeInFunctions2.ts, 5, 1)) + +declare function simple(arg: SimpleInterface): void; +>simple : Symbol(simple, Decl(thisTypeInFunctions2.ts, 17, 57)) +>arg : Symbol(arg, Decl(thisTypeInFunctions2.ts, 18, 24)) +>SimpleInterface : Symbol(SimpleInterface, Decl(thisTypeInFunctions2.ts, 11, 1)) + +extend1({ +>extend1 : Symbol(extend1, Decl(thisTypeInFunctions2.ts, 15, 1)) + + init() { +>init : Symbol(init, Decl(thisTypeInFunctions2.ts, 20, 9)) + + this // this: IndexedWithThis because of contextual typing. +>this : Symbol(IndexedWithThis, Decl(thisTypeInFunctions2.ts, 0, 0)) + + // this.mine + this.willDestroy +>this.willDestroy : Symbol(IndexedWithThis.willDestroy, Decl(thisTypeInFunctions2.ts, 2, 32)) +>this : Symbol(IndexedWithThis, Decl(thisTypeInFunctions2.ts, 0, 0)) +>willDestroy : Symbol(IndexedWithThis.willDestroy, Decl(thisTypeInFunctions2.ts, 2, 32)) + + }, + mine: 12, +>mine : Symbol(mine, Decl(thisTypeInFunctions2.ts, 25, 6)) + + foo() { +>foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 26, 13)) + + this.url; // this: any because 'foo' matches the string indexer + this.willDestroy; + } +}); +extend2({ +>extend2 : Symbol(extend2, Decl(thisTypeInFunctions2.ts, 16, 54)) + + init() { +>init : Symbol(init, Decl(thisTypeInFunctions2.ts, 32, 9)) + + this // this: any because the contextual signature of init doesn't specify this' type + this.mine + this.willDestroy + }, + mine: 13, +>mine : Symbol(mine, Decl(thisTypeInFunctions2.ts, 37, 6)) + + foo() { +>foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 38, 13)) + + this // this: any because of the string indexer + this.mine + this.willDestroy + } +}); + +simple({ +>simple : Symbol(simple, Decl(thisTypeInFunctions2.ts, 17, 57)) + + foo(n) { +>foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 46, 8)) +>n : Symbol(n, Decl(thisTypeInFunctions2.ts, 47, 8)) + + return n.length + this.bar(); +>n.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>n : Symbol(n, Decl(thisTypeInFunctions2.ts, 47, 8)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) + + }, + bar() { +>bar : Symbol(bar, Decl(thisTypeInFunctions2.ts, 49, 6)) + + return 14; + } +}) + diff --git a/tests/baselines/reference/thisTypeInFunctions2.types b/tests/baselines/reference/thisTypeInFunctions2.types new file mode 100644 index 0000000000000..371b5f5cafc17 --- /dev/null +++ b/tests/baselines/reference/thisTypeInFunctions2.types @@ -0,0 +1,165 @@ +=== tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts === +interface IndexedWithThis { +>IndexedWithThis : IndexedWithThis + + // this is a workaround for React + init?: (this: this) => void; +>init : (this: this) => void +>this : this + + 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[] +} +interface IndexedWithoutThis { +>IndexedWithoutThis : IndexedWithoutThis + + // this is what React would like to write (and what they write today) + init?: () => void; +>init : () => void + + willDestroy?: () => void; +>willDestroy : () => void + + [propName: string]: any; +>propName : string +} +interface SimpleInterface { +>SimpleInterface : SimpleInterface + + foo(n: string); +>foo : (n: string) => any +>n : string + + bar(): number; +>bar : () => number +} +declare function extend1(args: IndexedWithThis): void; +>extend1 : (args: IndexedWithThis) => void +>args : IndexedWithThis +>IndexedWithThis : IndexedWithThis + +declare function extend2(args: IndexedWithoutThis): void; +>extend2 : (args: IndexedWithoutThis) => void +>args : IndexedWithoutThis +>IndexedWithoutThis : IndexedWithoutThis + +declare function simple(arg: SimpleInterface): void; +>simple : (arg: SimpleInterface) => void +>arg : SimpleInterface +>SimpleInterface : SimpleInterface + +extend1({ +>extend1({ init() { this // this: IndexedWithThis because of contextual typing. // this.mine this.willDestroy }, mine: 12, foo() { this.url; // this: any because 'foo' matches the string indexer this.willDestroy; }}) : void +>extend1 : (args: IndexedWithThis) => void +>{ init() { this // this: IndexedWithThis because of contextual typing. // this.mine this.willDestroy }, mine: 12, foo() { this.url; // this: any because 'foo' matches the string indexer this.willDestroy; }} : { init(): void; mine: number; foo(): void; } + + init() { +>init : () => void + + this // this: IndexedWithThis because of contextual typing. +>this : IndexedWithThis + + // this.mine + this.willDestroy +>this.willDestroy : (this: any) => void +>this : IndexedWithThis +>willDestroy : (this: any) => void + + }, + mine: 12, +>mine : number +>12 : number + + foo() { +>foo : () => void + + this.url; // this: any because 'foo' matches the string indexer +>this.url : any +>this : any +>url : any + + this.willDestroy; +>this.willDestroy : any +>this : any +>willDestroy : any + } +}); +extend2({ +>extend2({ init() { this // this: any because the contextual signature of init doesn't specify this' type this.mine this.willDestroy }, mine: 13, foo() { this // this: any because of the string indexer this.mine this.willDestroy }}) : void +>extend2 : (args: IndexedWithoutThis) => void +>{ init() { this // this: any because the contextual signature of init doesn't specify this' type this.mine this.willDestroy }, mine: 13, foo() { this // this: any because of the string indexer this.mine this.willDestroy }} : { init(): void; mine: number; foo(): void; } + + init() { +>init : () => void + + this // this: any because the contextual signature of init doesn't specify this' type +>this : any + + this.mine +>this.mine : any +>this : any +>mine : any + + this.willDestroy +>this.willDestroy : any +>this : any +>willDestroy : any + + }, + mine: 13, +>mine : number +>13 : number + + foo() { +>foo : () => void + + this // this: any because of the string indexer +>this : any + + this.mine +>this.mine : any +>this : any +>mine : any + + this.willDestroy +>this.willDestroy : any +>this : any +>willDestroy : any + } +}); + +simple({ +>simple({ foo(n) { return n.length + this.bar(); }, bar() { return 14; }}) : void +>simple : (arg: SimpleInterface) => void +>{ foo(n) { return n.length + this.bar(); }, bar() { return 14; }} : { foo(n: string): any; bar(): number; } + + foo(n) { +>foo : (n: string) => any +>n : string + + return n.length + this.bar(); +>n.length + this.bar() : any +>n.length : number +>n : string +>length : number +>this.bar() : any +>this.bar : any +>this : any +>bar : any + + }, + bar() { +>bar : () => number + + return 14; +>14 : number + } +}) + diff --git a/tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts b/tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts new file mode 100644 index 0000000000000..a574c7a07e951 --- /dev/null +++ b/tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts @@ -0,0 +1,54 @@ +interface IndexedWithThis { + // this is a workaround for React + init?: (this: this) => void; + willDestroy?: (this: any) => void; + [propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any); +} +interface IndexedWithoutThis { + // this is what React would like to write (and what they write today) + init?: () => void; + willDestroy?: () => void; + [propName: string]: any; +} +interface SimpleInterface { + foo(n: string); + bar(): number; +} +declare function extend1(args: IndexedWithThis): void; +declare function extend2(args: IndexedWithoutThis): void; +declare function simple(arg: SimpleInterface): void; + +extend1({ + init() { + this // this: IndexedWithThis because of contextual typing. + // this.mine + this.willDestroy + }, + mine: 12, + foo() { + this.url; // this: any because 'foo' matches the string indexer + this.willDestroy; + } +}); +extend2({ + init() { + this // this: any because the contextual signature of init doesn't specify this' type + this.mine + this.willDestroy + }, + mine: 13, + foo() { + this // this: any because of the string indexer + this.mine + this.willDestroy + } +}); + +simple({ + foo(n) { + return n.length + this.bar(); + }, + bar() { + return 14; + } +})