Reduce polymorphism resulting from unstable Node shapes (#51682)

* Move .symbol to Declaration

* simplify some factories

* Move localSymbol to Declaration

* Ensure JSDocContainer types are properly initialized

* Move contextualType from Node to NodeLinks

* Move 'locals' and 'nextContainer' out of Node

* Move 'flowNode' out of 'Node'

* Pre-define endFlowNode/returnFlowNode

* Pre-define some SourceFile properties and a more stable cloneNode

* Don't add excess properties to type nodes in typeToTypeNode

* Refactor wrapSymbolTrackerToReportForContext to improve perf
This commit is contained in:
Ron Buckton
2022-12-13 15:11:10 -05:00
committed by GitHub
parent 7267fcaeb9
commit 6d41964fd0
62 changed files with 2730 additions and 1622 deletions
@@ -6,6 +6,7 @@ import {
Block,
CallExpression,
canBeConvertedToAsync,
canHaveSymbol,
CodeFixContext,
concatenate,
createMultiMap,
@@ -867,7 +868,7 @@ function getArgBindingName(funcNode: Expression, transformer: Transformer): Synt
}
function getSymbol(node: Node): Symbol | undefined {
return node.symbol ? node.symbol : transformer.checker.getSymbolAtLocation(node);
return tryCast(node, canHaveSymbol)?.symbol ?? transformer.checker.getSymbolAtLocation(node);
}
function getOriginalNode(node: Node): Node {
+2 -2
View File
@@ -46,10 +46,10 @@ function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, po
const token = getTokenAtPosition(sourceFile, pos);
if (!isThis(token)) return undefined;
const fn = getThisContainer(token, /*includeArrowFunctions*/ false);
const fn = getThisContainer(token, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false);
if (!isFunctionDeclaration(fn) && !isFunctionExpression(fn)) return undefined;
if (!isSourceFile(getThisContainer(fn, /*includeArrowFunctions*/ false))) { // 'this' is defined outside, convert to arrow function
if (!isSourceFile(getThisContainer(fn, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false))) { // 'this' is defined outside, convert to arrow function
const fnKeyword = Debug.checkDefined(findChildOfKind(fn, SyntaxKind.FunctionKeyword, sourceFile));
const { name } = fn;
const body = Debug.checkDefined(fn.body); // Should be defined because the function contained a 'this' expression
@@ -1,5 +1,6 @@
import {
canHaveExportModifier,
canHaveLocals,
Declaration,
Diagnostics,
ExportDeclaration,
@@ -125,7 +126,7 @@ function getInfo(sourceFile: SourceFile, pos: number, program: Program): Info |
if (moduleSourceFile === undefined || isSourceFileFromLibrary(program, moduleSourceFile)) return undefined;
const moduleSymbol = moduleSourceFile.symbol;
const locals = moduleSymbol.valueDeclaration?.locals;
const locals = tryCast(moduleSymbol.valueDeclaration, canHaveLocals)?.locals;
if (locals === undefined) return undefined;
const localSymbol = locals.get(token.escapedText);
+4 -2
View File
@@ -294,6 +294,7 @@ import {
PropertyAccessExpression,
PropertyDeclaration,
PropertyName,
PropertySignature,
PseudoBigInt,
pseudoBigIntToString,
QualifiedName,
@@ -334,6 +335,7 @@ import {
textPart,
TextRange,
TextSpan,
ThisContainer,
timestamp,
Token,
TokenSyntaxKind,
@@ -3308,7 +3310,7 @@ function getCompletionData(
// Need to insert 'this.' before properties of `this` type, so only do that if `includeInsertTextCompletions`
if (preferences.includeCompletionsWithInsertText && scopeNode.kind !== SyntaxKind.SourceFile) {
const thisType = typeChecker.tryGetThisTypeAt(scopeNode, /*includeGlobalThis*/ false, isClassLike(scopeNode.parent) ? scopeNode : undefined);
const thisType = typeChecker.tryGetThisTypeAt(scopeNode, /*includeGlobalThis*/ false, isClassLike(scopeNode.parent) ? scopeNode as ThisContainer : undefined);
if (thisType && !isProbablyGlobalType(thisType, sourceFile, typeChecker)) {
for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) {
symbolToOriginInfoMap[symbols.length] = { kind: SymbolOriginInfoKind.ThisType };
@@ -4877,7 +4879,7 @@ function getConstraintOfTypeArgumentProperty(node: Node, checker: TypeChecker):
switch (node.kind) {
case SyntaxKind.PropertySignature:
return checker.getTypeOfPropertyOfContextualType(t, node.symbol.escapedName);
return checker.getTypeOfPropertyOfContextualType(t, (node as PropertySignature).symbol.escapedName);
case SyntaxKind.IntersectionType:
case SyntaxKind.TypeLiteral:
case SyntaxKind.UnionType:
+3 -1
View File
@@ -5,6 +5,7 @@ import {
Block,
BreakOrContinueStatement,
CancellationToken,
canHaveSymbol,
CaseClause,
cast,
concatenate,
@@ -78,6 +79,7 @@ import {
ThrowStatement,
toArray,
toPath,
tryCast,
TryStatement,
} from "./_namespaces/ts";
@@ -184,7 +186,7 @@ export namespace DocumentHighlights {
}
function getFromAllDeclarations<T extends Node>(nodeTest: (node: Node) => node is T, keywords: readonly SyntaxKind[]): HighlightSpan[] | undefined {
return useParent(node.parent, nodeTest, decl => mapDefined(decl.symbol.declarations, d =>
return useParent(node.parent, nodeTest, decl => mapDefined(tryCast(decl, canHaveSymbol)?.symbol.declarations, d =>
nodeTest(d) ? find(d.getChildren(sourceFile), c => contains(keywords, c.kind)) : undefined));
}
+13 -8
View File
@@ -8,6 +8,7 @@ import {
Block,
CallExpression,
CancellationToken,
canHaveSymbol,
cast,
CheckFlags,
ClassLikeDeclaration,
@@ -190,6 +191,7 @@ import {
NodeFlags,
nodeSeenTracker,
NumericLiteral,
ObjectLiteralExpression,
ParameterDeclaration,
ParenthesizedExpression,
Path,
@@ -218,6 +220,7 @@ import {
StringLiteral,
StringLiteralLike,
stripQuotes,
SuperContainer,
Symbol,
SymbolDisplay,
SymbolDisplayPart,
@@ -234,6 +237,7 @@ import {
tryGetClassExtendingExpressionWithTypeArguments,
tryGetImportFromModuleSpecifier,
TypeChecker,
TypeLiteralNode,
VariableDeclaration,
} from "./_namespaces/ts";
import {
@@ -1882,7 +1886,7 @@ export namespace Core {
// Use the parent symbol if the location is commonjs require syntax on javascript files only.
if (isInJSFile(referenceLocation)
&& referenceLocation.parent.kind === SyntaxKind.BindingElement
&& isBindingElement(referenceLocation.parent)
&& isVariableDeclarationInitializedToBareOrAccessedRequire(referenceLocation.parent.parent.parent)) {
referenceSymbol = referenceLocation.parent.symbol;
// The parent will not have a symbol if it's an ObjectBindingPattern (when destructuring is used). In
@@ -2253,7 +2257,7 @@ export namespace Core {
}
function getReferencesForSuperKeyword(superKeyword: Node): SymbolAndEntries[] | undefined {
let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false);
let searchSpaceNode: SuperContainer | ClassLikeDeclaration | TypeLiteralNode | InterfaceDeclaration | ObjectLiteralExpression | undefined = getSuperContainer(superKeyword, /*stopOnFunctions*/ false);
if (!searchSpaceNode) {
return undefined;
}
@@ -2286,7 +2290,7 @@ export namespace Core {
// If we have a 'super' container, we must have an enclosing class.
// Now make sure the owning class is the same as the search-space
// and has the same static qualifier as the original 'super's owner.
return container && isStatic(container) === !!staticFlag && container.parent.symbol === searchSpaceNode.symbol ? nodeEntry(node) : undefined;
return container && isStatic(container) === !!staticFlag && container.parent.symbol === searchSpaceNode!.symbol ? nodeEntry(node) : undefined;
});
return [{ definition: { type: DefinitionKind.Symbol, symbol: searchSpaceNode.symbol }, references }];
@@ -2297,7 +2301,7 @@ export namespace Core {
}
function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined {
let searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false);
let searchSpaceNode: Node = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false, /*includeClassComputedPropertyName*/ false);
// Whether 'this' occurs in a static context within a class.
let staticFlag = ModifierFlags.Static;
@@ -2339,11 +2343,12 @@ export namespace Core {
if (!isThis(node)) {
return false;
}
const container = getThisContainer(node, /* includeArrowFunctions */ false);
const container = getThisContainer(node, /* includeArrowFunctions */ false, /*includeClassComputedPropertyName*/ false);
if (!canHaveSymbol(container)) return false;
switch (searchSpaceNode.kind) {
case SyntaxKind.FunctionExpression:
case SyntaxKind.FunctionDeclaration:
return searchSpaceNode.symbol === container.symbol;
return (searchSpaceNode as FunctionExpression | FunctionDeclaration).symbol === container.symbol;
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
return isObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.symbol === container.symbol;
@@ -2352,9 +2357,9 @@ export namespace Core {
case SyntaxKind.ObjectLiteralExpression:
// Make sure the container belongs to the same class/object literals
// and has the appropriate static modifier from the original container.
return container.parent && searchSpaceNode.symbol === container.parent.symbol && isStatic(container) === !!staticFlag;
return container.parent && canHaveSymbol(container.parent) && (searchSpaceNode as ClassLikeDeclaration | ObjectLiteralExpression).symbol === container.parent.symbol && isStatic(container) === !!staticFlag;
case SyntaxKind.SourceFile:
return container.kind === SyntaxKind.SourceFile && !isExternalModule(container as SourceFile) && !isParameterName(node);
return container.kind === SyntaxKind.SourceFile && !isExternalModule(container) && !isParameterName(node);
}
});
}).map(n => nodeEntry(n));
+2 -2
View File
@@ -3,7 +3,7 @@ import {
AssignmentExpression,
AssignmentOperatorToken,
CallLikeExpression,
concatenate,
canHaveSymbol, concatenate,
createTextSpan,
createTextSpanFromBounds,
createTextSpanFromNode,
@@ -245,7 +245,7 @@ function symbolMatchesSignature(s: Symbol, calledDeclaration: SignatureDeclarati
return s === calledDeclaration.symbol
|| s === calledDeclaration.symbol.parent
|| isAssignmentExpression(calledDeclaration.parent)
|| (!isCallLikeExpression(calledDeclaration.parent) && s === calledDeclaration.parent.symbol);
|| (!isCallLikeExpression(calledDeclaration.parent) && s === tryCast(calledDeclaration.parent, canHaveSymbol)?.symbol);
}
// If the current location we want to find its definition is in an object literal, try to get the contextual type for the
+4 -2
View File
@@ -7,6 +7,7 @@ import {
CallExpression,
CancellationToken,
canHaveModifiers,
canHaveSymbol,
cast,
Debug,
ExportAssignment,
@@ -73,6 +74,7 @@ import {
SymbolFlags,
symbolName,
SyntaxKind,
tryCast,
TypeChecker,
ValidImportTypeNode,
VariableDeclaration,
@@ -683,10 +685,10 @@ function getExportEqualsLocalSymbol(importedSymbol: Symbol, checker: TypeChecker
const decl = Debug.checkDefined(importedSymbol.valueDeclaration);
if (isExportAssignment(decl)) { // `export = class {}`
return decl.expression.symbol;
return tryCast(decl.expression, canHaveSymbol)?.symbol;
}
else if (isBinaryExpression(decl)) { // `module.exports = class {}`
return decl.right.symbol;
return tryCast(decl.right, canHaveSymbol)?.symbol;
}
else if (isSourceFile(decl)) { // json module
return decl.symbol;
+1 -1
View File
@@ -387,7 +387,7 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
const printer = createPrinter(options);
return usingSingleLineStringWriter(writer => {
const typeNode = checker.typeToTypeNode(type, /*enclosingDeclaration*/ undefined, flags, writer);
const typeNode = checker.typeToTypeNode(type, /*enclosingDeclaration*/ undefined, flags);
Debug.assertIsDefined(typeNode, "should always get typenode");
printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ file, writer);
});
+3 -3
View File
@@ -33,6 +33,7 @@ import {
isStringLiteral,
makeImport,
ModifierFlags,
ModuleBlock,
NamespaceDeclaration,
Node,
NodeFlags,
@@ -122,7 +123,7 @@ function getInfo(context: RefactorContext, considerPartialSpans = true): ExportI
}
const checker = program.getTypeChecker();
const exportingModuleSymbol = getExportingModuleSymbol(exportNode, checker);
const exportingModuleSymbol = getExportingModuleSymbol(exportNode.parent, checker);
const flags = getSyntacticModifierFlags(exportNode) || ((isExportAssignment(exportNode) && !exportNode.isExportEquals) ? ModifierFlags.ExportDefault : ModifierFlags.None);
const wasDefault = !!(flags & ModifierFlags.Default);
@@ -319,8 +320,7 @@ function makeExportSpecifier(propertyName: string, name: string): ExportSpecifie
return factory.createExportSpecifier(/*isTypeOnly*/ false, propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name));
}
function getExportingModuleSymbol(node: Node, checker: TypeChecker) {
const parent = node.parent;
function getExportingModuleSymbol(parent: SourceFile | ModuleBlock, checker: TypeChecker) {
if (isSourceFile(parent)) {
return parent.symbol;
}
+1 -1
View File
@@ -633,7 +633,7 @@ export function getRangeToExtract(sourceFile: SourceFile, span: TextSpan, invoke
visit(nodeToCheck);
if (rangeFacts & RangeFacts.UsesThis) {
const container = getThisContainer(nodeToCheck, /** includeArrowFunctions */ false);
const container = getThisContainer(nodeToCheck, /** includeArrowFunctions */ false, /*includeClassComputedPropertyName*/ false);
if (
container.kind === SyntaxKind.FunctionDeclaration ||
(container.kind === SyntaxKind.MethodDeclaration && container.parent.kind === SyntaxKind.ObjectLiteralExpression) ||
+2 -2
View File
@@ -10,7 +10,7 @@ import {
CallExpression,
canHaveDecorators,
canHaveModifiers,
cast,
canHaveSymbol, cast,
ClassDeclaration,
codefix,
combinePaths,
@@ -505,7 +505,7 @@ function addExports(sourceFile: SourceFile, toMove: readonly Statement[], needEx
return flatMap(toMove, statement => {
if (isTopLevelDeclarationStatement(statement) &&
!isExported(sourceFile, statement, useEs6Exports) &&
forEachTopLevelDeclaration(statement, d => needExport.has(Debug.checkDefined(d.symbol)))) {
forEachTopLevelDeclaration(statement, d => needExport.has(Debug.checkDefined(tryCast(d, canHaveSymbol)?.symbol)))) {
const exports = addExport(statement, useEs6Exports);
if (exports) return exports;
}
+18 -15
View File
@@ -594,7 +594,7 @@ class TokenOrIdentifierObject implements Node {
}
public getChildren(): Node[] {
return this.kind === SyntaxKind.EndOfFileToken ? (this as EndOfFileToken).jsDoc || emptyArray : emptyArray;
return this.kind === SyntaxKind.EndOfFileToken ? (this as Node as EndOfFileToken).jsDoc || emptyArray : emptyArray;
}
public getFirstToken(): Node | undefined {
@@ -733,13 +733,15 @@ class IdentifierObject extends TokenOrIdentifierObject implements Identifier {
public kind: SyntaxKind.Identifier = SyntaxKind.Identifier;
public escapedText!: __String;
public autoGenerateFlags!: GeneratedIdentifierFlags;
_primaryExpressionBrand: any;
_memberExpressionBrand: any;
_leftHandSideExpressionBrand: any;
_updateExpressionBrand: any;
_unaryExpressionBrand: any;
_expressionBrand: any;
_declarationBrand: any;
declare _primaryExpressionBrand: any;
declare _memberExpressionBrand: any;
declare _leftHandSideExpressionBrand: any;
declare _updateExpressionBrand: any;
declare _unaryExpressionBrand: any;
declare _expressionBrand: any;
declare _declarationBrand: any;
declare _jsdocContainerBrand: any;
declare _flowContainerBrand: any;
/** @internal */typeArguments!: NodeArray<TypeNode>;
constructor(_kind: SyntaxKind.Identifier, pos: number, end: number) {
super(pos, end);
@@ -754,12 +756,12 @@ class PrivateIdentifierObject extends TokenOrIdentifierObject implements Private
public kind: SyntaxKind.PrivateIdentifier = SyntaxKind.PrivateIdentifier;
public escapedText!: __String;
// public symbol!: Symbol;
_primaryExpressionBrand: any;
_memberExpressionBrand: any;
_leftHandSideExpressionBrand: any;
_updateExpressionBrand: any;
_unaryExpressionBrand: any;
_expressionBrand: any;
declare _primaryExpressionBrand: any;
declare _memberExpressionBrand: any;
declare _leftHandSideExpressionBrand: any;
declare _updateExpressionBrand: any;
declare _unaryExpressionBrand: any;
declare _expressionBrand: any;
constructor(_kind: SyntaxKind.PrivateIdentifier, pos: number, end: number) {
super(pos, end);
}
@@ -992,7 +994,8 @@ function findBaseOfDeclaration<T>(checker: TypeChecker, declaration: Declaration
class SourceFileObject extends NodeObject implements SourceFile {
public kind: SyntaxKind.SourceFile = SyntaxKind.SourceFile;
public _declarationBrand: any;
declare _declarationBrand: any;
declare _localsContainerBrand: any;
public fileName!: string;
public path!: Path;
public resolvedPath!: Path;
+3 -1
View File
@@ -3,6 +3,7 @@ import {
BinaryExpression,
CallLikeExpression,
CancellationToken,
canHaveSymbol,
CheckFlags,
contains,
countWhere,
@@ -77,6 +78,7 @@ import {
TemplateExpression,
TextSpan,
TransientSymbol,
tryCast,
Type,
TypeChecker,
TypeParameter,
@@ -431,7 +433,7 @@ function getContextualSignatureLocationInfo(startingToken: Node, sourceFile: Sou
// The type of a function type node has a symbol at that node, but it's better to use the symbol for a parameter or type alias.
function chooseBetterSymbol(s: Symbol): Symbol {
return s.name === InternalSymbolName.Type
? firstDefined(s.declarations, d => isFunctionTypeNode(d) ? d.parent.symbol : undefined) || s
? firstDefined(s.declarations, d => isFunctionTypeNode(d) ? tryCast(d.parent, canHaveSymbol)?.symbol : undefined) || s
: s;
}
+4 -2
View File
@@ -34,6 +34,8 @@ import {
isBlock,
isCallExpression,
isExportAssignment,
isFunctionDeclaration,
isFunctionExpression,
isFunctionLike,
isIdentifier,
isPropertyAccessExpression,
@@ -276,7 +278,7 @@ function getKeyFromNode(exp: FunctionLikeDeclaration) {
}
function canBeConvertedToClass(node: Node, checker: TypeChecker): boolean {
if (node.kind === SyntaxKind.FunctionExpression) {
if (isFunctionExpression(node)) {
if (isVariableDeclaration(node.parent) && node.symbol.members?.size) {
return true;
}
@@ -285,7 +287,7 @@ function canBeConvertedToClass(node: Node, checker: TypeChecker): boolean {
return !!(symbol && (symbol.exports?.size || symbol.members?.size));
}
if (node.kind === SyntaxKind.FunctionDeclaration) {
if (isFunctionDeclaration(node)) {
return !!node.symbol.members?.size;
}
+5 -5
View File
@@ -48,7 +48,6 @@ import {
isFunctionBlock,
isFunctionExpression,
isFunctionLike,
isFunctionLikeKind,
isIdentifier,
isInExpressionContext,
isJsxOpeningLikeElement,
@@ -59,6 +58,7 @@ import {
isObjectBindingPattern,
isTaggedTemplateExpression,
isThisInTypeQuery,
isTypeAliasDeclaration,
isVarConst,
JSDocTagInfo,
JsxOpeningLikeElement,
@@ -493,19 +493,19 @@ export function getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker: Typ
const declaration = decl.parent;
if (declaration) {
if (isFunctionLikeKind(declaration.kind)) {
if (isFunctionLike(declaration)) {
addInPrefix();
const signature = typeChecker.getSignatureFromDeclaration(declaration as SignatureDeclaration)!; // TODO: GH#18217
const signature = typeChecker.getSignatureFromDeclaration(declaration)!; // TODO: GH#18217
if (declaration.kind === SyntaxKind.ConstructSignature) {
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
displayParts.push(spacePart());
}
else if (declaration.kind !== SyntaxKind.CallSignature && (declaration as SignatureDeclaration).name) {
else if (declaration.kind !== SyntaxKind.CallSignature && declaration.name) {
addFullSymbolName(declaration.symbol);
}
addRange(displayParts, signatureToDisplayParts(typeChecker, signature, sourceFile, TypeFormatFlags.WriteTypeArgumentsOfSignature));
}
else if (declaration.kind === SyntaxKind.TypeAliasDeclaration) {
else if (isTypeAliasDeclaration(declaration)) {
// Type alias type parameter
// For example
// type list<T> = T[]; // Both T will go through same code path
-5
View File
@@ -271,7 +271,6 @@ import {
nodeIsMissing,
nodeIsPresent,
nodeIsSynthesized,
noop,
normalizePath,
NoSubstitutionTemplateLiteral,
notImplemented,
@@ -2721,10 +2720,6 @@ function getDisplayPartWriter(): DisplayPartsSymbolWriter {
increaseIndent: () => { indent++; },
decreaseIndent: () => { indent--; },
clear: resetWriter,
trackSymbol: () => false,
reportInaccessibleThisError: noop,
reportInaccessibleUniqueSymbolError: noop,
reportPrivateInBaseOfClassExpression: noop,
};
function writeIndent() {