Skip to content

Commit 3d05952

Browse files
author
Andy
authored
Change "isThisless" predicates to "mayReferenceThis" predicates (#20036)
* Change "isThisless" predicates to "mayReferenceThis" predicates * Fix name * Code review
1 parent d49491b commit 3d05952

File tree

1 file changed

+51
-74
lines changed

1 file changed

+51
-74
lines changed

src/compiler/checker.ts

Lines changed: 51 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)