@@ -5111,26 +5111,18 @@ namespace ts {
51115111 * to "this" in its body, if all base types are interfaces,
51125112 * and if none of the base interfaces have a "this" type.
51135113 */
5114- function isThislessInterface(symbol: Symbol): boolean {
5115- for (const declaration of symbol.declarations) {
5116- if (declaration.kind === SyntaxKind.InterfaceDeclaration) {
5117- if (declaration.flags & NodeFlags.ContainsThis) {
5118- return false;
5119- }
5120- const baseTypeNodes = getInterfaceBaseTypeNodes(<InterfaceDeclaration>declaration);
5121- if (baseTypeNodes) {
5122- for (const node of baseTypeNodes) {
5123- if (isEntityNameExpression(node.expression)) {
5124- const baseSymbol = resolveEntityName(node.expression, SymbolFlags.Type, /*ignoreErrors*/ true);
5125- if (!baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || getDeclaredTypeOfClassOrInterface(baseSymbol).thisType) {
5126- return false;
5127- }
5128- }
5129- }
5130- }
5131- }
5114+ function interfaceReferencesThis(symbol: Symbol): boolean {
5115+ return some(symbol.declarations, declaration =>
5116+ isInterfaceDeclaration(declaration) && (
5117+ !!(declaration.flags & NodeFlags.ContainsThis)
5118+ || some(getInterfaceBaseTypeNodes(declaration), baseTypeReferencesThis)));
5119+ }
5120+ function baseTypeReferencesThis({ expression }: ExpressionWithTypeArguments): boolean {
5121+ if (!isEntityNameExpression(expression)) {
5122+ return false;
51325123 }
5133- return true;
5124+ const baseSymbol = resolveEntityName(expression, SymbolFlags.Type, /*ignoreErrors*/ true);
5125+ return !baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || !!getDeclaredTypeOfClassOrInterface(baseSymbol).thisType;
51345126 }
51355127
51365128 function getDeclaredTypeOfClassOrInterface(symbol: Symbol): InterfaceType {
@@ -5145,7 +5137,7 @@ namespace ts {
51455137 // property types inferred from initializers and method return types inferred from return statements are very hard
51465138 // to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of
51475139 // "this" references.
5148- if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isThislessInterface (symbol)) {
5140+ if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || interfaceReferencesThis (symbol)) {
51495141 type.objectFlags |= ObjectFlags.Reference;
51505142 type.typeParameters = concatenate(outerTypeParameters, localTypeParameters);
51515143 type.outerTypeParameters = outerTypeParameters;
@@ -5327,13 +5319,9 @@ namespace ts {
53275319 return undefined;
53285320 }
53295321
5330- /**
5331- * A type is free of this references if it's the any, string, number, boolean, symbol, or void keyword, a string
5332- * literal type, an array with an element type that is free of this references, or a type reference that is
5333- * free of this references.
5334- */
5335- function isThislessType(node: TypeNode): boolean {
5336- switch (node.kind) {
5322+ /** A type may reference `this` unless it's one of a few special types. */
5323+ function typeReferencesThis(node: TypeNode | undefined): boolean {
5324+ switch (node && node.kind) {
53375325 case SyntaxKind.AnyKeyword:
53385326 case SyntaxKind.StringKeyword:
53395327 case SyntaxKind.NumberKeyword:
@@ -5345,72 +5333,61 @@ namespace ts {
53455333 case SyntaxKind.NullKeyword:
53465334 case SyntaxKind.NeverKeyword:
53475335 case SyntaxKind.LiteralType:
5348- return true ;
5336+ return false ;
53495337 case SyntaxKind.ArrayType:
5350- return isThislessType ((<ArrayTypeNode>node).elementType);
5338+ return typeReferencesThis ((<ArrayTypeNode>node).elementType);
53515339 case SyntaxKind.TypeReference:
5352- return !(node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments.every(isThislessType );
5340+ return some( (node as TypeReferenceNode).typeArguments, typeReferencesThis );
53535341 }
5354- return false;
5342+ return true; // TODO: GH#20034
53555343 }
53565344
5357- /** A type parameter is thisless if its contraint is thisless, or if it has no constraint. */
5358- function isThislessTypeParameter(node: TypeParameterDeclaration) {
5359- return !node.constraint || isThislessType(node.constraint);
5360- }
5361-
5362- /**
5363- * A variable-like declaration is free of this references if it has a type annotation
5364- * that is thisless, or if it has no type annotation and no initializer (and is thus of type any).
5365- */
5366- function isThislessVariableLikeDeclaration(node: VariableLikeDeclaration): boolean {
5345+ /** A variable-like declaration may reference `this` if its type does or if it has no declared type and an initializer (which may infer a `this` type). */
5346+ function variableLikeDeclarationReferencesThis(node: VariableLikeDeclaration): boolean {
53675347 const typeNode = getEffectiveTypeAnnotationNode(node);
5368- return typeNode ? isThislessType(typeNode) : !node.initializer;
5369- }
5370-
5371- /**
5372- * A function-like declaration is considered free of `this` references if it has a return type
5373- * annotation that is free of this references and if each parameter is thisless and if
5374- * each type parameter (if present) is thisless.
5375- */
5376- function isThislessFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
5377- const returnType = getEffectiveReturnTypeNode(node);
5378- return (node.kind === SyntaxKind.Constructor || (returnType && isThislessType(returnType))) &&
5379- node.parameters.every(isThislessVariableLikeDeclaration) &&
5380- (!node.typeParameters || node.typeParameters.every(isThislessTypeParameter));
5348+ return typeNode ? typeReferencesThis(typeNode) : !!node.initializer;
53815349 }
53825350
53835351 /**
5384- * Returns true if the class or interface member given by the symbol is free of "this" references. The
5385- * function may return false for symbols that are actually free of "this" references because it is not
5386- * feasible to perform a complete analysis in all cases. In particular, property members with types
5387- * inferred from their initializers and function members with inferred return types are conservatively
5388- * assumed not to be free of "this" references.
5352+ * Returns true if the class/interface member may reference `this`.
5353+ * May return true for symbols that don't actually reference `this` because it would be slow to do a complete analysis.
5354+ * For example, property members with types inferred from initializers or function members with inferred return types are
5355+ * conservatively assumed to reference `this`.
53895356 */
5390- function isThisless(symbol: Symbol): boolean {
5391- if (symbol.declarations && symbol.declarations.length === 1) {
5392- const declaration = symbol.declarations[0];
5393- if (declaration) {
5394- switch (declaration.kind) {
5395- case SyntaxKind.PropertyDeclaration:
5396- case SyntaxKind.PropertySignature:
5397- return isThislessVariableLikeDeclaration(<VariableLikeDeclaration>declaration);
5398- case SyntaxKind.MethodDeclaration:
5399- case SyntaxKind.MethodSignature:
5400- case SyntaxKind.Constructor:
5401- return isThislessFunctionLikeDeclaration(<FunctionLikeDeclaration>declaration);
5402- }
5357+ function symbolReferencesThis(symbol: Symbol): boolean {
5358+ const declaration = singleOrUndefined(symbol.declarations);
5359+ if (!declaration) return true;
5360+ switch (declaration.kind) {
5361+ case SyntaxKind.PropertyDeclaration:
5362+ case SyntaxKind.PropertySignature:
5363+ return variableLikeDeclarationReferencesThis(<PropertyDeclaration | PropertySignature>declaration);
5364+ case SyntaxKind.MethodDeclaration:
5365+ case SyntaxKind.MethodSignature:
5366+ case SyntaxKind.Constructor: {
5367+ // A function-like declaration references `this` if its return type does or some parameter / type parameter does.
5368+ const fn = declaration as MethodDeclaration | MethodSignature | ConstructorDeclaration;
5369+ return typeReferencesThis(getEffectiveReturnTypeNode(fn))
5370+ || fn.parameters.some(variableLikeDeclarationReferencesThis)
5371+ // A type parameter references `this` if its constraint does.
5372+ || some(fn.typeParameters, tp => typeReferencesThis(tp.constraint));
54035373 }
5374+ case SyntaxKind.Parameter:
5375+ case SyntaxKind.GetAccessor:
5376+ case SyntaxKind.SetAccessor:
5377+ case SyntaxKind.BinaryExpression:
5378+ case SyntaxKind.PropertyAccessExpression: // See `tests/cases/fourslash/renameJsThisProperty05` and 06
5379+ return true; // TODO: GH#20034
5380+ default:
5381+ throw Debug.failBadSyntaxKind(declaration);
54045382 }
5405- return false;
54065383 }
54075384
54085385 // The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true,
54095386 // we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation.
54105387 function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable {
54115388 const result = createSymbolTable();
54125389 for (const symbol of symbols) {
5413- result.set(symbol.escapedName, mappingThisOnly && isThisless (symbol) ? symbol : instantiateSymbol(symbol, mapper));
5390+ result.set(symbol.escapedName, mappingThisOnly && !symbolReferencesThis (symbol) ? symbol : instantiateSymbol(symbol, mapper));
54145391 }
54155392 return result;
54165393 }
0 commit comments