@@ -5103,18 +5103,26 @@ namespace ts {
5103
5103
* to "this" in its body, if all base types are interfaces,
5104
5104
* and if none of the base interfaces have a "this" type.
5105
5105
*/
5106
- function interfaceReferencesThis(symbol: Symbol): boolean {
5107
- return some(symbol.declarations, declaration =>
5108
- isInterfaceDeclaration(declaration) && (
5109
- !!(declaration.flags & NodeFlags.ContainsThis)
5110
- || some(getInterfaceBaseTypeNodes(declaration), baseTypeReferencesThis)));
5111
- }
5112
- function baseTypeReferencesThis({ expression }: ExpressionWithTypeArguments): boolean {
5113
- if (!isEntityNameExpression(expression)) {
5114
- return false;
5106
+ function isThislessInterface(symbol: Symbol): boolean {
5107
+ for (const declaration of symbol.declarations) {
5108
+ if (declaration.kind === SyntaxKind.InterfaceDeclaration) {
5109
+ if (declaration.flags & NodeFlags.ContainsThis) {
5110
+ return false;
5111
+ }
5112
+ const baseTypeNodes = getInterfaceBaseTypeNodes(<InterfaceDeclaration>declaration);
5113
+ if (baseTypeNodes) {
5114
+ for (const node of baseTypeNodes) {
5115
+ if (isEntityNameExpression(node.expression)) {
5116
+ const baseSymbol = resolveEntityName(node.expression, SymbolFlags.Type, /*ignoreErrors*/ true);
5117
+ if (!baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || getDeclaredTypeOfClassOrInterface(baseSymbol).thisType) {
5118
+ return false;
5119
+ }
5120
+ }
5121
+ }
5122
+ }
5123
+ }
5115
5124
}
5116
- const baseSymbol = resolveEntityName(expression, SymbolFlags.Type, /*ignoreErrors*/ true);
5117
- return !baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || !!getDeclaredTypeOfClassOrInterface(baseSymbol).thisType;
5125
+ return true;
5118
5126
}
5119
5127
5120
5128
function getDeclaredTypeOfClassOrInterface(symbol: Symbol): InterfaceType {
@@ -5129,7 +5137,7 @@ namespace ts {
5129
5137
// property types inferred from initializers and method return types inferred from return statements are very hard
5130
5138
// to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of
5131
5139
// "this" references.
5132
- if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || interfaceReferencesThis (symbol)) {
5140
+ if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isThislessInterface (symbol)) {
5133
5141
type.objectFlags |= ObjectFlags.Reference;
5134
5142
type.typeParameters = concatenate(outerTypeParameters, localTypeParameters);
5135
5143
type.outerTypeParameters = outerTypeParameters;
@@ -5311,9 +5319,13 @@ namespace ts {
5311
5319
return undefined;
5312
5320
}
5313
5321
5314
- /** A type may reference `this` unless it's one of a few special types. */
5315
- function typeReferencesThis(node: TypeNode | undefined): boolean {
5316
- switch (node && node.kind) {
5322
+ /**
5323
+ * A type is free of this references if it's the any, string, number, boolean, symbol, or void keyword, a string
5324
+ * literal type, an array with an element type that is free of this references, or a type reference that is
5325
+ * free of this references.
5326
+ */
5327
+ function isThislessType(node: TypeNode): boolean {
5328
+ switch (node.kind) {
5317
5329
case SyntaxKind.AnyKeyword:
5318
5330
case SyntaxKind.StringKeyword:
5319
5331
case SyntaxKind.NumberKeyword:
@@ -5325,61 +5337,72 @@ namespace ts {
5325
5337
case SyntaxKind.NullKeyword:
5326
5338
case SyntaxKind.NeverKeyword:
5327
5339
case SyntaxKind.LiteralType:
5328
- return false ;
5340
+ return true ;
5329
5341
case SyntaxKind.ArrayType:
5330
- return typeReferencesThis ((<ArrayTypeNode>node).elementType);
5342
+ return isThislessType ((<ArrayTypeNode>node).elementType);
5331
5343
case SyntaxKind.TypeReference:
5332
- return some(( node as TypeReferenceNode).typeArguments, typeReferencesThis );
5344
+ return !( node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments.every(isThislessType );
5333
5345
}
5334
- return true; // TODO: GH#20034
5346
+ return false;
5335
5347
}
5336
5348
5337
- /** 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). */
5338
- function variableLikeDeclarationReferencesThis(node: VariableLikeDeclaration): boolean {
5349
+ /** A type parameter is thisless if its contraint is thisless, or if it has no constraint. */
5350
+ function isThislessTypeParameter(node: TypeParameterDeclaration) {
5351
+ return !node.constraint || isThislessType(node.constraint);
5352
+ }
5353
+
5354
+ /**
5355
+ * A variable-like declaration is free of this references if it has a type annotation
5356
+ * that is thisless, or if it has no type annotation and no initializer (and is thus of type any).
5357
+ */
5358
+ function isThislessVariableLikeDeclaration(node: VariableLikeDeclaration): boolean {
5339
5359
const typeNode = getEffectiveTypeAnnotationNode(node);
5340
- return typeNode ? typeReferencesThis (typeNode) : ! !node.initializer;
5360
+ return typeNode ? isThislessType (typeNode) : !node.initializer;
5341
5361
}
5342
5362
5343
5363
/**
5344
- * Returns true if the class/interface member may reference `this`.
5345
- * May return true for symbols that don't actually reference `this` because it would be slow to do a complete analysis.
5346
- * For example, property members with types inferred from initializers or function members with inferred return types are
5347
- * conservatively assumed to reference `this`.
5364
+ * A function-like declaration is considered free of `this` references if it has a return type
5365
+ * annotation that is free of this references and if each parameter is thisless and if
5366
+ * each type parameter (if present) is thisless.
5348
5367
*/
5349
- function symbolReferencesThis(symbol: Symbol): boolean {
5350
- const declaration = singleOrUndefined(symbol.declarations);
5351
- if (!declaration) return true;
5352
- switch (declaration.kind) {
5353
- case SyntaxKind.PropertyDeclaration:
5354
- case SyntaxKind.PropertySignature:
5355
- return variableLikeDeclarationReferencesThis(<PropertyDeclaration | PropertySignature>declaration);
5356
- case SyntaxKind.MethodDeclaration:
5357
- case SyntaxKind.MethodSignature:
5358
- case SyntaxKind.Constructor: {
5359
- // A function-like declaration references `this` if its return type does or some parameter / type parameter does.
5360
- const fn = declaration as MethodDeclaration | MethodSignature | ConstructorDeclaration;
5361
- return typeReferencesThis(getEffectiveReturnTypeNode(fn))
5362
- || fn.parameters.some(variableLikeDeclarationReferencesThis)
5363
- // A type parameter references `this` if its constraint does.
5364
- || some(fn.typeParameters, tp => typeReferencesThis(tp.constraint));
5368
+ function isThislessFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
5369
+ const returnType = getEffectiveReturnTypeNode(node);
5370
+ return (node.kind === SyntaxKind.Constructor || (returnType && isThislessType(returnType))) &&
5371
+ node.parameters.every(isThislessVariableLikeDeclaration) &&
5372
+ (!node.typeParameters || node.typeParameters.every(isThislessTypeParameter));
5373
+ }
5374
+
5375
+ /**
5376
+ * Returns true if the class or interface member given by the symbol is free of "this" references. The
5377
+ * function may return false for symbols that are actually free of "this" references because it is not
5378
+ * feasible to perform a complete analysis in all cases. In particular, property members with types
5379
+ * inferred from their initializers and function members with inferred return types are conservatively
5380
+ * assumed not to be free of "this" references.
5381
+ */
5382
+ function isThisless(symbol: Symbol): boolean {
5383
+ if (symbol.declarations && symbol.declarations.length === 1) {
5384
+ const declaration = symbol.declarations[0];
5385
+ if (declaration) {
5386
+ switch (declaration.kind) {
5387
+ case SyntaxKind.PropertyDeclaration:
5388
+ case SyntaxKind.PropertySignature:
5389
+ return isThislessVariableLikeDeclaration(<VariableLikeDeclaration>declaration);
5390
+ case SyntaxKind.MethodDeclaration:
5391
+ case SyntaxKind.MethodSignature:
5392
+ case SyntaxKind.Constructor:
5393
+ return isThislessFunctionLikeDeclaration(<FunctionLikeDeclaration>declaration);
5394
+ }
5365
5395
}
5366
- case SyntaxKind.Parameter:
5367
- case SyntaxKind.GetAccessor:
5368
- case SyntaxKind.SetAccessor:
5369
- case SyntaxKind.BinaryExpression:
5370
- case SyntaxKind.PropertyAccessExpression: // See `tests/cases/fourslash/renameJsThisProperty05` and 06
5371
- return true; // TODO: GH#20034
5372
- default:
5373
- throw Debug.failBadSyntaxKind(declaration);
5374
5396
}
5397
+ return false;
5375
5398
}
5376
5399
5377
5400
// The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true,
5378
5401
// we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation.
5379
5402
function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable {
5380
5403
const result = createSymbolTable();
5381
5404
for (const symbol of symbols) {
5382
- result.set(symbol.escapedName, mappingThisOnly && !symbolReferencesThis (symbol) ? symbol : instantiateSymbol(symbol, mapper));
5405
+ result.set(symbol.escapedName, mappingThisOnly && isThisless (symbol) ? symbol : instantiateSymbol(symbol, mapper));
5383
5406
}
5384
5407
return result;
5385
5408
}
0 commit comments