From 207932a8fe33a366597d3f8e2657bccaf28021ec Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 18 Apr 2024 11:30:11 -0700 Subject: [PATCH] Move incremental state to WeakSets (#58087) --- src/compiler/parser.ts | 87 ++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 9ef46604879..a6b5937ab7b 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -318,7 +318,6 @@ import { QuestionToken, ReadonlyKeyword, ReadonlyPragmaMap, - ReadonlyTextRange, ResolutionMode, RestTypeNode, ReturnStatement, @@ -1950,7 +1949,7 @@ namespace Parser { function currentNode(position: number) { const node = baseSyntaxCursor.currentNode(position); if (topLevel && node && containsPossibleTopLevelAwait(node)) { - node.intersectsChange = true; + markAsIntersectingIncrementalChange(node); } return node; } @@ -3120,7 +3119,7 @@ namespace Parser { // Can't reuse a node that intersected the change range. // Can't reuse a node that contains a parse error. This is necessary so that we // produce the same set of errors again. - if (nodeIsMissing(node) || node.intersectsChange || containsParseError(node)) { + if (nodeIsMissing(node) || intersectsIncrementalChange(node) || containsParseError(node)) { return undefined; } @@ -9844,6 +9843,25 @@ namespace Parser { } } +const incrementallyParsedFiles = new WeakSet(); + +function markAsIncrementallyParsed(sourceFile: SourceFile) { + if (incrementallyParsedFiles.has(sourceFile)) { + Debug.fail("Source file has already been incrementally parsed"); + } + incrementallyParsedFiles.add(sourceFile); +} + +const intersectingChangeSet = new WeakSet>(); + +function intersectsIncrementalChange(node: Node | NodeArray): boolean { + return intersectingChangeSet.has(node); +} + +function markAsIntersectingIncrementalChange(node: Node | NodeArray) { + intersectingChangeSet.add(node); +} + namespace IncrementalParser { export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean): SourceFile { aggressiveChecks = aggressiveChecks || Debug.shouldAssert(AssertionLevel.Aggressive); @@ -9866,10 +9884,8 @@ namespace IncrementalParser { // This is because we do incremental parsing in-place. i.e. we take nodes from the old // tree and give them new positions and parents. From that point on, trusting the old // tree at all is not possible as far too much of it may violate invariants. - const incrementalSourceFile = sourceFile as Node as IncrementalNode; - Debug.assert(!incrementalSourceFile.hasBeenIncrementallyParsed); - incrementalSourceFile.hasBeenIncrementallyParsed = true; - Parser.fixupParentReferences(incrementalSourceFile); + markAsIncrementallyParsed(sourceFile); + Parser.fixupParentReferences(sourceFile); const oldText = sourceFile.text; const syntaxCursor = createSyntaxCursor(sourceFile); @@ -9908,7 +9924,7 @@ namespace IncrementalParser { // // Also, mark any syntax elements that intersect the changed span. We know, up front, // that we cannot reuse these elements. - updateTokenPositionsAndMarkElements(incrementalSourceFile, changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta, oldText, newText, aggressiveChecks); + updateTokenPositionsAndMarkElements(sourceFile, changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta, oldText, newText, aggressiveChecks); // Now that we've set up our internal incremental state just proceed and parse the // source file in the normal fashion. When possible the parser will retrieve and @@ -9984,18 +10000,18 @@ namespace IncrementalParser { } } - function moveElementEntirelyPastChangeRange(element: IncrementalNode, isArray: false, delta: number, oldText: string, newText: string, aggressiveChecks: boolean): void; - function moveElementEntirelyPastChangeRange(element: IncrementalNodeArray, isArray: true, delta: number, oldText: string, newText: string, aggressiveChecks: boolean): void; - function moveElementEntirelyPastChangeRange(element: IncrementalNode | IncrementalNodeArray, isArray: boolean, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) { + function moveElementEntirelyPastChangeRange(element: Node, isArray: false, delta: number, oldText: string, newText: string, aggressiveChecks: boolean): void; + function moveElementEntirelyPastChangeRange(element: NodeArray, isArray: true, delta: number, oldText: string, newText: string, aggressiveChecks: boolean): void; + function moveElementEntirelyPastChangeRange(element: Node | NodeArray, isArray: boolean, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) { if (isArray) { - visitArray(element as IncrementalNodeArray); + visitArray(element as NodeArray); } else { - visitNode(element as IncrementalNode); + visitNode(element as Node); } return; - function visitNode(node: IncrementalNode) { + function visitNode(node: Node) { let text = ""; if (aggressiveChecks && shouldCheckNode(node)) { text = oldText.substring(node.pos, node.end); @@ -10014,13 +10030,13 @@ namespace IncrementalParser { forEachChild(node, visitNode as (node: Node) => void, visitArray as (nodes: NodeArray) => void); if (hasJSDocNodes(node)) { for (const jsDocComment of node.jsDoc!) { - visitNode(jsDocComment as Node as IncrementalNode); + visitNode(jsDocComment); } } checkNodePositions(node, aggressiveChecks); } - function visitArray(array: IncrementalNodeArray) { + function visitArray(array: NodeArray) { setTextRangePosEnd(array, array.pos + delta, array.end + delta); for (const node of array) { @@ -10040,7 +10056,7 @@ namespace IncrementalParser { return false; } - function adjustIntersectingElement(element: IncrementalElement, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number) { + function adjustIntersectingElement(element: Node | NodeArray, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number) { Debug.assert(element.end >= changeStart, "Adjusting an element that was entirely before the change range"); Debug.assert(element.pos <= changeRangeOldEnd, "Adjusting an element that was entirely after the change range"); Debug.assert(element.pos <= element.end); @@ -10106,9 +10122,10 @@ namespace IncrementalParser { Math.min(element.end, changeRangeNewEnd); Debug.assert(pos <= end); - if (element.parent) { - Debug.assertGreaterThanOrEqual(pos, element.parent.pos); - Debug.assertLessThanOrEqual(end, element.parent.end); + if ((element as any).parent) { + const parent = (element as any).parent as Node; + Debug.assertGreaterThanOrEqual(pos, parent.pos); + Debug.assertLessThanOrEqual(end, parent.end); } setTextRangePosEnd(element, pos, end); @@ -10132,7 +10149,7 @@ namespace IncrementalParser { } function updateTokenPositionsAndMarkElements( - sourceFile: IncrementalNode, + sourceFile: SourceFile, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, @@ -10144,7 +10161,7 @@ namespace IncrementalParser { visitNode(sourceFile); return; - function visitNode(child: IncrementalNode) { + function visitNode(child: Node) { Debug.assert(child.pos <= child.end); if (child.pos > changeRangeOldEnd) { // Node is entirely past the change range. We need to move both its pos and @@ -10158,7 +10175,7 @@ namespace IncrementalParser { // be able to use. const fullEnd = child.end; if (fullEnd >= changeStart) { - child.intersectsChange = true; + markAsIntersectingIncrementalChange(child); unsetNodeChildren(child); // Adjust the pos or end (or both) of the intersecting element accordingly. @@ -10166,7 +10183,7 @@ namespace IncrementalParser { forEachChild(child, visitNode as (node: Node) => void, visitArray as (nodes: NodeArray) => void); if (hasJSDocNodes(child)) { for (const jsDocComment of child.jsDoc!) { - visitNode(jsDocComment as Node as IncrementalNode); + visitNode(jsDocComment); } } checkNodePositions(child, aggressiveChecks); @@ -10177,7 +10194,7 @@ namespace IncrementalParser { Debug.assert(fullEnd < changeStart); } - function visitArray(array: IncrementalNodeArray) { + function visitArray(array: NodeArray) { Debug.assert(array.pos <= array.end); if (array.pos > changeRangeOldEnd) { // Array is entirely after the change range. We need to move it, and move any of @@ -10191,7 +10208,7 @@ namespace IncrementalParser { // be able to use. const fullEnd = array.end; if (fullEnd >= changeStart) { - array.intersectsChange = true; + markAsIntersectingIncrementalChange(array); // Adjust the pos or end (or both) of the intersecting array accordingly. adjustIntersectingElement(array, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); @@ -10340,25 +10357,11 @@ namespace IncrementalParser { } } - interface IncrementalElement extends ReadonlyTextRange { - readonly parent: Node; - intersectsChange: boolean; - length?: number; - } - - export interface IncrementalNode extends Node, IncrementalElement { - hasBeenIncrementallyParsed: boolean; - } - - interface IncrementalNodeArray extends NodeArray, IncrementalElement { - length: number; - } - // Allows finding nodes in the source file at a certain position in an efficient manner. // The implementation takes advantage of the calling pattern it knows the parser will // make in order to optimize finding nodes as quickly as possible. export interface SyntaxCursor { - currentNode(position: number): IncrementalNode; + currentNode(position: number): Node; } export function createSyntaxCursor(sourceFile: SourceFile): SyntaxCursor { @@ -10400,7 +10403,7 @@ namespace IncrementalParser { // Either we don'd have a node, or we have a node at the position being asked for. Debug.assert(!current || current.pos === position); - return current as IncrementalNode; + return current; }, };