@@ -5111,26 +5111,18 @@ namespace ts {
5111
5111
* to "this" in its body, if all base types are interfaces,
5112
5112
* and if none of the base interfaces have a "this" type.
5113
5113
*/
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;
5132
5123
}
5133
- return true;
5124
+ const baseSymbol = resolveEntityName(expression, SymbolFlags.Type, /*ignoreErrors*/ true);
5125
+ return !baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || !!getDeclaredTypeOfClassOrInterface(baseSymbol).thisType;
5134
5126
}
5135
5127
5136
5128
function getDeclaredTypeOfClassOrInterface(symbol: Symbol): InterfaceType {
@@ -5145,7 +5137,7 @@ namespace ts {
5145
5137
// property types inferred from initializers and method return types inferred from return statements are very hard
5146
5138
// to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of
5147
5139
// "this" references.
5148
- if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isThislessInterface (symbol)) {
5140
+ if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || interfaceReferencesThis (symbol)) {
5149
5141
type.objectFlags |= ObjectFlags.Reference;
5150
5142
type.typeParameters = concatenate(outerTypeParameters, localTypeParameters);
5151
5143
type.outerTypeParameters = outerTypeParameters;
@@ -5327,13 +5319,9 @@ namespace ts {
5327
5319
return undefined;
5328
5320
}
5329
5321
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) {
5337
5325
case SyntaxKind.AnyKeyword:
5338
5326
case SyntaxKind.StringKeyword:
5339
5327
case SyntaxKind.NumberKeyword:
@@ -5345,72 +5333,61 @@ namespace ts {
5345
5333
case SyntaxKind.NullKeyword:
5346
5334
case SyntaxKind.NeverKeyword:
5347
5335
case SyntaxKind.LiteralType:
5348
- return true ;
5336
+ return false ;
5349
5337
case SyntaxKind.ArrayType:
5350
- return isThislessType ((<ArrayTypeNode>node).elementType);
5338
+ return typeReferencesThis ((<ArrayTypeNode>node).elementType);
5351
5339
case SyntaxKind.TypeReference:
5352
- return !(node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments.every(isThislessType );
5340
+ return some( (node as TypeReferenceNode).typeArguments, typeReferencesThis );
5353
5341
}
5354
- return false;
5342
+ return true; // TODO: GH#20034
5355
5343
}
5356
5344
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 {
5367
5347
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;
5381
5349
}
5382
5350
5383
5351
/**
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`.
5389
5356
*/
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));
5403
5373
}
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);
5404
5382
}
5405
- return false;
5406
5383
}
5407
5384
5408
5385
// The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true,
5409
5386
// we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation.
5410
5387
function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable {
5411
5388
const result = createSymbolTable();
5412
5389
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));
5414
5391
}
5415
5392
return result;
5416
5393
}
0 commit comments