Skip to content

Commit f352e46

Browse files
author
Andy
authored
Revert "Change "isThisless" predicates to "mayReferenceThis" predicates (#20036)" (#20054)
This reverts commit 3d05952.
1 parent b4ea700 commit f352e46

File tree

1 file changed

+74
-51
lines changed

1 file changed

+74
-51
lines changed

src/compiler/checker.ts

Lines changed: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5158,18 +5158,26 @@ namespace ts {
51585158
* to "this" in its body, if all base types are interfaces,
51595159
* and if none of the base interfaces have a "this" type.
51605160
*/
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+
}
51705179
}
5171-
const baseSymbol = resolveEntityName(expression, SymbolFlags.Type, /*ignoreErrors*/ true);
5172-
return !baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || !!getDeclaredTypeOfClassOrInterface(baseSymbol).thisType;
5180+
return true;
51735181
}
51745182

51755183
function getDeclaredTypeOfClassOrInterface(symbol: Symbol): InterfaceType {
@@ -5184,7 +5192,7 @@ namespace ts {
51845192
// property types inferred from initializers and method return types inferred from return statements are very hard
51855193
// to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of
51865194
// "this" references.
5187-
if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || interfaceReferencesThis(symbol)) {
5195+
if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isThislessInterface(symbol)) {
51885196
type.objectFlags |= ObjectFlags.Reference;
51895197
type.typeParameters = concatenate(outerTypeParameters, localTypeParameters);
51905198
type.outerTypeParameters = outerTypeParameters;
@@ -5366,9 +5374,13 @@ namespace ts {
53665374
return undefined;
53675375
}
53685376

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) {
53725384
case SyntaxKind.AnyKeyword:
53735385
case SyntaxKind.StringKeyword:
53745386
case SyntaxKind.NumberKeyword:
@@ -5380,61 +5392,72 @@ namespace ts {
53805392
case SyntaxKind.NullKeyword:
53815393
case SyntaxKind.NeverKeyword:
53825394
case SyntaxKind.LiteralType:
5383-
return false;
5395+
return true;
53845396
case SyntaxKind.ArrayType:
5385-
return typeReferencesThis((<ArrayTypeNode>node).elementType);
5397+
return isThislessType((<ArrayTypeNode>node).elementType);
53865398
case SyntaxKind.TypeReference:
5387-
return some((node as TypeReferenceNode).typeArguments, typeReferencesThis);
5399+
return !(node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments.every(isThislessType);
53885400
}
5389-
return true; // TODO: GH#20034
5401+
return false;
53905402
}
53915403

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 {
53945414
const typeNode = getEffectiveTypeAnnotationNode(node);
5395-
return typeNode ? typeReferencesThis(typeNode) : !!node.initializer;
5415+
return typeNode ? isThislessType(typeNode) : !node.initializer;
53965416
}
53975417

53985418
/**
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.
54035422
*/
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+
}
54205450
}
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);
54295451
}
5452+
return false;
54305453
}
54315454

54325455
// The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true,
54335456
// we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation.
54345457
function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable {
54355458
const result = createSymbolTable();
54365459
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));
54385461
}
54395462
return result;
54405463
}

0 commit comments

Comments
 (0)