diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6edce1036cb68..022aa18a50ed7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5104,10 +5104,14 @@ namespace ts { } } - // Returns true if the interface given by the symbol is free of "this" references. Specifically, the result is - // true if the interface itself contains no references to "this" in its body, if all base types are interfaces, - // and if none of the base interfaces have a "this" type. - function isIndependentInterface(symbol: Symbol): boolean { + /** + * Returns true if the interface given by the symbol is free of "this" references. + * + * Specifically, the result is true if the interface itself contains no references + * to "this" in its body, if all base types are interfaces, + * and if none of the base interfaces have a "this" type. + */ + function isThislessInterface(symbol: Symbol): boolean { for (const declaration of symbol.declarations) { if (declaration.kind === SyntaxKind.InterfaceDeclaration) { if (declaration.flags & NodeFlags.ContainsThis) { @@ -5141,7 +5145,7 @@ namespace ts { // property types inferred from initializers and method return types inferred from return statements are very hard // to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of // "this" references. - if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isIndependentInterface(symbol)) { + if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isThislessInterface(symbol)) { type.objectFlags |= ObjectFlags.Reference; type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); type.outerTypeParameters = outerTypeParameters; @@ -5323,22 +5327,12 @@ namespace ts { return undefined; } - // A type reference is considered independent if each type argument is considered independent. - function isIndependentTypeReference(node: TypeReferenceNode): boolean { - if (node.typeArguments) { - for (const typeNode of node.typeArguments) { - if (!isIndependentType(typeNode)) { - return false; - } - } - } - return true; - } - - // A type is considered independent if it the any, string, number, boolean, symbol, or void keyword, a string - // literal type, an array with an element type that is considered independent, or a type reference that is - // considered independent. - function isIndependentType(node: TypeNode): boolean { + /** + * A type is free of this references if it's the any, string, number, boolean, symbol, or void keyword, a string + * literal type, an array with an element type that is free of this references, or a type reference that is + * free of this references. + */ + function isThislessType(node: TypeNode): boolean { switch (node.kind) { case SyntaxKind.AnyKeyword: case SyntaxKind.StringKeyword: @@ -5353,54 +5347,58 @@ namespace ts { case SyntaxKind.LiteralType: return true; case SyntaxKind.ArrayType: - return isIndependentType((node).elementType); + return isThislessType((node).elementType); case SyntaxKind.TypeReference: - return isIndependentTypeReference(node); + return !(node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments.every(isThislessType); } return false; } - // A variable-like declaration is considered independent (free of this references) if it has a type annotation - // that specifies an independent type, or if it has no type annotation and no initializer (and thus of type any). - function isIndependentVariableLikeDeclaration(node: VariableLikeDeclaration): boolean { + /** A type parameter is thisless if its contraint is thisless, or if it has no constraint. */ + function isThislessTypeParameter(node: TypeParameterDeclaration) { + return !node.constraint || isThislessType(node.constraint); + } + + /** + * A variable-like declaration is free of this references if it has a type annotation + * that is thisless, or if it has no type annotation and no initializer (and is thus of type any). + */ + function isThislessVariableLikeDeclaration(node: VariableLikeDeclaration): boolean { const typeNode = getEffectiveTypeAnnotationNode(node); - return typeNode ? isIndependentType(typeNode) : !node.initializer; + return typeNode ? isThislessType(typeNode) : !node.initializer; } - // A function-like declaration is considered independent (free of this references) if it has a return type - // annotation that is considered independent and if each parameter is considered independent. - function isIndependentFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { - if (node.kind !== SyntaxKind.Constructor) { - const typeNode = getEffectiveReturnTypeNode(node); - if (!typeNode || !isIndependentType(typeNode)) { - return false; - } - } - for (const parameter of node.parameters) { - if (!isIndependentVariableLikeDeclaration(parameter)) { - return false; - } - } - return true; + /** + * A function-like declaration is considered free of `this` references if it has a return type + * annotation that is free of this references and if each parameter is thisless and if + * each type parameter (if present) is thisless. + */ + function isThislessFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { + const returnType = getEffectiveReturnTypeNode(node); + return (node.kind === SyntaxKind.Constructor || (returnType && isThislessType(returnType))) && + node.parameters.every(isThislessVariableLikeDeclaration) && + (!node.typeParameters || node.typeParameters.every(isThislessTypeParameter)); } - // Returns true if the class or interface member given by the symbol is free of "this" references. The - // function may return false for symbols that are actually free of "this" references because it is not - // feasible to perform a complete analysis in all cases. In particular, property members with types - // inferred from their initializers and function members with inferred return types are conservatively - // assumed not to be free of "this" references. - function isIndependentMember(symbol: Symbol): boolean { + /** + * Returns true if the class or interface member given by the symbol is free of "this" references. The + * function may return false for symbols that are actually free of "this" references because it is not + * feasible to perform a complete analysis in all cases. In particular, property members with types + * inferred from their initializers and function members with inferred return types are conservatively + * assumed not to be free of "this" references. + */ + function isThisless(symbol: Symbol): boolean { if (symbol.declarations && symbol.declarations.length === 1) { const declaration = symbol.declarations[0]; if (declaration) { switch (declaration.kind) { case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: - return isIndependentVariableLikeDeclaration(declaration); + return isThislessVariableLikeDeclaration(declaration); case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.Constructor: - return isIndependentFunctionLikeDeclaration(declaration); + return isThislessFunctionLikeDeclaration(declaration); } } } @@ -5412,7 +5410,7 @@ namespace ts { function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable { const result = createSymbolTable(); for (const symbol of symbols) { - result.set(symbol.escapedName, mappingThisOnly && isIndependentMember(symbol) ? symbol : instantiateSymbol(symbol, mapper)); + result.set(symbol.escapedName, mappingThisOnly && isThisless(symbol) ? symbol : instantiateSymbol(symbol, mapper)); } return result; } diff --git a/tests/baselines/reference/thisTypeInFunctions3.js b/tests/baselines/reference/thisTypeInFunctions3.js new file mode 100644 index 0000000000000..68af387c25118 --- /dev/null +++ b/tests/baselines/reference/thisTypeInFunctions3.js @@ -0,0 +1,33 @@ +//// [thisTypeInFunctions3.ts] +declare class Base { + check(prop: TProp): boolean; +} + +class Test extends Base { + m() { + this.check(this); + } +} + + +//// [thisTypeInFunctions3.js] +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var Test = /** @class */ (function (_super) { + __extends(Test, _super); + function Test() { + return _super !== null && _super.apply(this, arguments) || this; + } + Test.prototype.m = function () { + this.check(this); + }; + return Test; +}(Base)); diff --git a/tests/baselines/reference/thisTypeInFunctions3.symbols b/tests/baselines/reference/thisTypeInFunctions3.symbols new file mode 100644 index 0000000000000..061a33fd63d1b --- /dev/null +++ b/tests/baselines/reference/thisTypeInFunctions3.symbols @@ -0,0 +1,26 @@ +=== tests/cases/conformance/types/thisType/thisTypeInFunctions3.ts === +declare class Base { +>Base : Symbol(Base, Decl(thisTypeInFunctions3.ts, 0, 0)) + + check(prop: TProp): boolean; +>check : Symbol(Base.check, Decl(thisTypeInFunctions3.ts, 0, 20)) +>TProp : Symbol(TProp, Decl(thisTypeInFunctions3.ts, 1, 10)) +>prop : Symbol(prop, Decl(thisTypeInFunctions3.ts, 1, 30)) +>TProp : Symbol(TProp, Decl(thisTypeInFunctions3.ts, 1, 10)) +} + +class Test extends Base { +>Test : Symbol(Test, Decl(thisTypeInFunctions3.ts, 2, 1)) +>Base : Symbol(Base, Decl(thisTypeInFunctions3.ts, 0, 0)) + + m() { +>m : Symbol(Test.m, Decl(thisTypeInFunctions3.ts, 4, 25)) + + this.check(this); +>this.check : Symbol(Base.check, Decl(thisTypeInFunctions3.ts, 0, 20)) +>this : Symbol(Test, Decl(thisTypeInFunctions3.ts, 2, 1)) +>check : Symbol(Base.check, Decl(thisTypeInFunctions3.ts, 0, 20)) +>this : Symbol(Test, Decl(thisTypeInFunctions3.ts, 2, 1)) + } +} + diff --git a/tests/baselines/reference/thisTypeInFunctions3.types b/tests/baselines/reference/thisTypeInFunctions3.types new file mode 100644 index 0000000000000..563d6488704b5 --- /dev/null +++ b/tests/baselines/reference/thisTypeInFunctions3.types @@ -0,0 +1,27 @@ +=== tests/cases/conformance/types/thisType/thisTypeInFunctions3.ts === +declare class Base { +>Base : Base + + check(prop: TProp): boolean; +>check : (prop: TProp) => boolean +>TProp : TProp +>prop : TProp +>TProp : TProp +} + +class Test extends Base { +>Test : Test +>Base : Base + + m() { +>m : () => void + + this.check(this); +>this.check(this) : boolean +>this.check : (prop: TProp) => boolean +>this : this +>check : (prop: TProp) => boolean +>this : this + } +} + diff --git a/tests/cases/conformance/types/thisType/thisTypeInFunctions3.ts b/tests/cases/conformance/types/thisType/thisTypeInFunctions3.ts new file mode 100644 index 0000000000000..01d7fd0430bfa --- /dev/null +++ b/tests/cases/conformance/types/thisType/thisTypeInFunctions3.ts @@ -0,0 +1,9 @@ +declare class Base { + check(prop: TProp): boolean; +} + +class Test extends Base { + m() { + this.check(this); + } +}