diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 09354d957a3..ccb7c869137 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5108,10 +5108,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 isInterfaceFreeOfThisReference(symbol: Symbol): boolean { for (const declaration of symbol.declarations) { if (declaration.kind === SyntaxKind.InterfaceDeclaration) { if (declaration.flags & NodeFlags.ContainsThis) { @@ -5145,7 +5149,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 || !isInterfaceFreeOfThisReference(symbol)) { type.objectFlags |= ObjectFlags.Reference; type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); type.outerTypeParameters = outerTypeParameters; @@ -5327,22 +5331,17 @@ 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 reference is free of this references if each type argument is free of this references. */ + function isTypeReferenceFreeOfThisReference(node: TypeReferenceNode): boolean { + return !node.typeArguments || node.typeArguments.every(isTypeFreeOfThisReference); } - // 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 isTypeFreeOfThisReference(node: TypeNode): boolean { switch (node.kind) { case SyntaxKind.AnyKeyword: case SyntaxKind.StringKeyword: @@ -5357,54 +5356,58 @@ namespace ts { case SyntaxKind.LiteralType: return true; case SyntaxKind.ArrayType: - return isIndependentType((node).elementType); + return isTypeFreeOfThisReference((node).elementType); case SyntaxKind.TypeReference: - return isIndependentTypeReference(node); + return isTypeReferenceFreeOfThisReference(node); } 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 this-free if its contraint is this-free, or if it has no constraint. */ + function isTypeParameterFreeOfThisReference(node: TypeParameterDeclaration) { + return !node.constraint || isTypeFreeOfThisReference(node.constraint); + } + + /** + * A variable-like declaration is free of this references if it has a type annotation + * that is this-free, or if it has no type annotation and no initializer (and is thus of type any). + */ + function isVariableLikeDeclarationFreeOfThisReference(node: VariableLikeDeclaration): boolean { const typeNode = getEffectiveTypeAnnotationNode(node); - return typeNode ? isIndependentType(typeNode) : !node.initializer; + return typeNode ? isTypeFreeOfThisReference(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 this-free and if + * each type parameter (if present) is this-free. + */ + function isFunctionLikeDeclarationFreeOfThisReference(node: FunctionLikeDeclaration): boolean { + const returnType = getEffectiveReturnTypeNode(node); + return (node.kind === SyntaxKind.Constructor || (returnType && isTypeFreeOfThisReference(returnType))) && + node.parameters.every(isVariableLikeDeclarationFreeOfThisReference) && + (!node.typeParameters || node.typeParameters.every(isTypeParameterFreeOfThisReference)); } - // 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 isFreeOfThisReference(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 isVariableLikeDeclarationFreeOfThisReference(declaration); case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.Constructor: - return isIndependentFunctionLikeDeclaration(declaration); + return isFunctionLikeDeclarationFreeOfThisReference(declaration); } } } @@ -5416,7 +5419,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 && isFreeOfThisReference(symbol) ? symbol : instantiateSymbol(symbol, mapper)); } return result; }