@@ -5158,18 +5158,26 @@ namespace ts {
5158
5158
* to "this" in its body, if all base types are interfaces,
5159
5159
* and if none of the base interfaces have a "this" type.
5160
5160
*/
5161
- function interfaceReferencesThis(symbol: Symbol): boolean {
5162
- return some(symbol.declarations, declaration =>
5163
- isInterfaceDeclaration(declaration) && (
5164
- !!(declaration.flags & NodeFlags.ContainsThis)
5165
- || some(getInterfaceBaseTypeNodes(declaration), baseTypeReferencesThis)));
5166
- }
5167
- function baseTypeReferencesThis({ expression }: ExpressionWithTypeArguments): boolean {
5168
- if (!isEntityNameExpression(expression)) {
5169
- return false;
5161
+ function isThislessInterface(symbol: Symbol): boolean {
5162
+ for (const declaration of symbol.declarations) {
5163
+ if (declaration.kind === SyntaxKind.InterfaceDeclaration) {
5164
+ if (declaration.flags & NodeFlags.ContainsThis) {
5165
+ return false;
5166
+ }
5167
+ const baseTypeNodes = getInterfaceBaseTypeNodes(<InterfaceDeclaration>declaration);
5168
+ if (baseTypeNodes) {
5169
+ for (const node of baseTypeNodes) {
5170
+ if (isEntityNameExpression(node.expression)) {
5171
+ const baseSymbol = resolveEntityName(node.expression, SymbolFlags.Type, /*ignoreErrors*/ true);
5172
+ if (!baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || getDeclaredTypeOfClassOrInterface(baseSymbol).thisType) {
5173
+ return false;
5174
+ }
5175
+ }
5176
+ }
5177
+ }
5178
+ }
5170
5179
}
5171
- const baseSymbol = resolveEntityName(expression, SymbolFlags.Type, /*ignoreErrors*/ true);
5172
- return !baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || !!getDeclaredTypeOfClassOrInterface(baseSymbol).thisType;
5180
+ return true;
5173
5181
}
5174
5182
5175
5183
function getDeclaredTypeOfClassOrInterface(symbol: Symbol): InterfaceType {
@@ -5184,7 +5192,7 @@ namespace ts {
5184
5192
// property types inferred from initializers and method return types inferred from return statements are very hard
5185
5193
// to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of
5186
5194
// "this" references.
5187
- if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || interfaceReferencesThis (symbol)) {
5195
+ if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isThislessInterface (symbol)) {
5188
5196
type.objectFlags |= ObjectFlags.Reference;
5189
5197
type.typeParameters = concatenate(outerTypeParameters, localTypeParameters);
5190
5198
type.outerTypeParameters = outerTypeParameters;
@@ -5366,9 +5374,13 @@ namespace ts {
5366
5374
return undefined;
5367
5375
}
5368
5376
5369
- /** A type may reference `this` unless it's one of a few special types. */
5370
- function typeReferencesThis(node: TypeNode | undefined): boolean {
5371
- switch (node && node.kind) {
5377
+ /**
5378
+ * A type is free of this references if it's the any, string, number, boolean, symbol, or void keyword, a string
5379
+ * literal type, an array with an element type that is free of this references, or a type reference that is
5380
+ * free of this references.
5381
+ */
5382
+ function isThislessType(node: TypeNode): boolean {
5383
+ switch (node.kind) {
5372
5384
case SyntaxKind.AnyKeyword:
5373
5385
case SyntaxKind.StringKeyword:
5374
5386
case SyntaxKind.NumberKeyword:
@@ -5380,61 +5392,72 @@ namespace ts {
5380
5392
case SyntaxKind.NullKeyword:
5381
5393
case SyntaxKind.NeverKeyword:
5382
5394
case SyntaxKind.LiteralType:
5383
- return false ;
5395
+ return true ;
5384
5396
case SyntaxKind.ArrayType:
5385
- return typeReferencesThis ((<ArrayTypeNode>node).elementType);
5397
+ return isThislessType ((<ArrayTypeNode>node).elementType);
5386
5398
case SyntaxKind.TypeReference:
5387
- return some(( node as TypeReferenceNode).typeArguments, typeReferencesThis );
5399
+ return !( node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments.every(isThislessType );
5388
5400
}
5389
- return true; // TODO: GH#20034
5401
+ return false;
5390
5402
}
5391
5403
5392
- /** 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). */
5393
- function variableLikeDeclarationReferencesThis(node: VariableLikeDeclaration): boolean {
5404
+ /** A type parameter is thisless if its contraint is thisless, or if it has no constraint. */
5405
+ function isThislessTypeParameter(node: TypeParameterDeclaration) {
5406
+ return !node.constraint || isThislessType(node.constraint);
5407
+ }
5408
+
5409
+ /**
5410
+ * A variable-like declaration is free of this references if it has a type annotation
5411
+ * that is thisless, or if it has no type annotation and no initializer (and is thus of type any).
5412
+ */
5413
+ function isThislessVariableLikeDeclaration(node: VariableLikeDeclaration): boolean {
5394
5414
const typeNode = getEffectiveTypeAnnotationNode(node);
5395
- return typeNode ? typeReferencesThis (typeNode) : ! !node.initializer;
5415
+ return typeNode ? isThislessType (typeNode) : !node.initializer;
5396
5416
}
5397
5417
5398
5418
/**
5399
- * Returns true if the class/interface member may reference `this`.
5400
- * May return true for symbols that don't actually reference `this` because it would be slow to do a complete analysis.
5401
- * For example, property members with types inferred from initializers or function members with inferred return types are
5402
- * conservatively assumed to reference `this`.
5419
+ * A function-like declaration is considered free of `this` references if it has a return type
5420
+ * annotation that is free of this references and if each parameter is thisless and if
5421
+ * each type parameter (if present) is thisless.
5403
5422
*/
5404
- function symbolReferencesThis(symbol: Symbol): boolean {
5405
- const declaration = singleOrUndefined(symbol.declarations);
5406
- if (!declaration) return true;
5407
- switch (declaration.kind) {
5408
- case SyntaxKind.PropertyDeclaration:
5409
- case SyntaxKind.PropertySignature:
5410
- return variableLikeDeclarationReferencesThis(<PropertyDeclaration | PropertySignature>declaration);
5411
- case SyntaxKind.MethodDeclaration:
5412
- case SyntaxKind.MethodSignature:
5413
- case SyntaxKind.Constructor: {
5414
- // A function-like declaration references `this` if its return type does or some parameter / type parameter does.
5415
- const fn = declaration as MethodDeclaration | MethodSignature | ConstructorDeclaration;
5416
- return typeReferencesThis(getEffectiveReturnTypeNode(fn))
5417
- || fn.parameters.some(variableLikeDeclarationReferencesThis)
5418
- // A type parameter references `this` if its constraint does.
5419
- || some(fn.typeParameters, tp => typeReferencesThis(tp.constraint));
5423
+ function isThislessFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
5424
+ const returnType = getEffectiveReturnTypeNode(node);
5425
+ return (node.kind === SyntaxKind.Constructor || (returnType && isThislessType(returnType))) &&
5426
+ node.parameters.every(isThislessVariableLikeDeclaration) &&
5427
+ (!node.typeParameters || node.typeParameters.every(isThislessTypeParameter));
5428
+ }
5429
+
5430
+ /**
5431
+ * Returns true if the class or interface member given by the symbol is free of "this" references. The
5432
+ * function may return false for symbols that are actually free of "this" references because it is not
5433
+ * feasible to perform a complete analysis in all cases. In particular, property members with types
5434
+ * inferred from their initializers and function members with inferred return types are conservatively
5435
+ * assumed not to be free of "this" references.
5436
+ */
5437
+ function isThisless(symbol: Symbol): boolean {
5438
+ if (symbol.declarations && symbol.declarations.length === 1) {
5439
+ const declaration = symbol.declarations[0];
5440
+ if (declaration) {
5441
+ switch (declaration.kind) {
5442
+ case SyntaxKind.PropertyDeclaration:
5443
+ case SyntaxKind.PropertySignature:
5444
+ return isThislessVariableLikeDeclaration(<VariableLikeDeclaration>declaration);
5445
+ case SyntaxKind.MethodDeclaration:
5446
+ case SyntaxKind.MethodSignature:
5447
+ case SyntaxKind.Constructor:
5448
+ return isThislessFunctionLikeDeclaration(<FunctionLikeDeclaration>declaration);
5449
+ }
5420
5450
}
5421
- case SyntaxKind.Parameter:
5422
- case SyntaxKind.GetAccessor:
5423
- case SyntaxKind.SetAccessor:
5424
- case SyntaxKind.BinaryExpression:
5425
- case SyntaxKind.PropertyAccessExpression: // See `tests/cases/fourslash/renameJsThisProperty05` and 06
5426
- return true; // TODO: GH#20034
5427
- default:
5428
- throw Debug.failBadSyntaxKind(declaration);
5429
5451
}
5452
+ return false;
5430
5453
}
5431
5454
5432
5455
// The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true,
5433
5456
// we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation.
5434
5457
function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable {
5435
5458
const result = createSymbolTable();
5436
5459
for (const symbol of symbols) {
5437
- result.set(symbol.escapedName, mappingThisOnly && !symbolReferencesThis (symbol) ? symbol : instantiateSymbol(symbol, mapper));
5460
+ result.set(symbol.escapedName, mappingThisOnly && isThisless (symbol) ? symbol : instantiateSymbol(symbol, mapper));
5438
5461
}
5439
5462
return result;
5440
5463
}
0 commit comments