mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 08:11:30 -06:00
Trampolines for large binary expressions (#36248)
* WIP * Test no longer crashes, but emit trampoline is incomplete and skips pipeline phases * Fix lints, use non-generator trampoline in emit (still skips pipeline) * Final version with emitBinaryExprssion work stack that is only used if possible * Fix lints * retarget to es2015 for testing * Use bespoke state machine trampolines in binder and checker * Remove now extraneous code in parser * Adjust fixupParentReferences to use a depth first preorder traversal rather than breadth first * Reintroduce incremental fast bail in fixupParentReferences * Revert target to es5 * PR feedback * Small edit for devops rebuild with updated definition * Fix comment nits, add internally extraneous check back into transformer
This commit is contained in:
parent
f3cc6f6d84
commit
08e6bc20bb
@ -1445,28 +1445,156 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
const enum BindBinaryExpressionFlowState {
|
||||
BindThenBindChildren,
|
||||
MaybeBindLeft,
|
||||
BindToken,
|
||||
BindRight,
|
||||
FinishBind
|
||||
}
|
||||
|
||||
function bindBinaryExpressionFlow(node: BinaryExpression) {
|
||||
const operator = node.operatorToken.kind;
|
||||
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
|
||||
if (isTopLevelLogicalExpression(node)) {
|
||||
const postExpressionLabel = createBranchLabel();
|
||||
bindLogicalExpression(node, postExpressionLabel, postExpressionLabel);
|
||||
currentFlow = finishFlowLabel(postExpressionLabel);
|
||||
}
|
||||
else {
|
||||
bindLogicalExpression(node, currentTrueTarget!, currentFalseTarget!);
|
||||
const workStacks: {
|
||||
expr: BinaryExpression[],
|
||||
state: BindBinaryExpressionFlowState[],
|
||||
inStrictMode: (boolean | undefined)[],
|
||||
parent: (Node | undefined)[],
|
||||
subtreeFlags: (number | undefined)[]
|
||||
} = {
|
||||
expr: [node],
|
||||
state: [BindBinaryExpressionFlowState.MaybeBindLeft],
|
||||
inStrictMode: [undefined],
|
||||
parent: [undefined],
|
||||
subtreeFlags: [undefined]
|
||||
};
|
||||
let stackIndex = 0;
|
||||
while (stackIndex >= 0) {
|
||||
node = workStacks.expr[stackIndex];
|
||||
switch (workStacks.state[stackIndex]) {
|
||||
case BindBinaryExpressionFlowState.BindThenBindChildren: {
|
||||
// This state is used only when recuring, to emulate the work that `bind` does before
|
||||
// reaching `bindChildren`. A normal call to `bindBinaryExpressionFlow` will already have done this work.
|
||||
node.parent = parent;
|
||||
const saveInStrictMode = inStrictMode;
|
||||
bindWorker(node);
|
||||
const saveParent = parent;
|
||||
parent = node;
|
||||
|
||||
let subtreeFlagsState: number | undefined;
|
||||
// While this next part does the work of `bindChildren` before it descends into `bindChildrenWorker`
|
||||
// and uses `subtreeFlagsState` to queue up the work that needs to be done once the node is bound.
|
||||
if (skipTransformFlagAggregation) {
|
||||
// do nothing extra
|
||||
}
|
||||
else if (node.transformFlags & TransformFlags.HasComputedFlags) {
|
||||
skipTransformFlagAggregation = true;
|
||||
subtreeFlagsState = -1;
|
||||
}
|
||||
else {
|
||||
const savedSubtreeTransformFlags = subtreeTransformFlags;
|
||||
subtreeTransformFlags = 0;
|
||||
subtreeFlagsState = savedSubtreeTransformFlags;
|
||||
}
|
||||
|
||||
advanceState(BindBinaryExpressionFlowState.MaybeBindLeft, saveInStrictMode, saveParent, subtreeFlagsState);
|
||||
break;
|
||||
}
|
||||
case BindBinaryExpressionFlowState.MaybeBindLeft: {
|
||||
const operator = node.operatorToken.kind;
|
||||
// TODO: bindLogicalExpression is recursive - if we want to handle deeply nested `&&` expressions
|
||||
// we'll need to handle the `bindLogicalExpression` scenarios in this state machine, too
|
||||
// For now, though, since the common cases are chained `+`, leaving it recursive is fine
|
||||
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
|
||||
if (isTopLevelLogicalExpression(node)) {
|
||||
const postExpressionLabel = createBranchLabel();
|
||||
bindLogicalExpression(node, postExpressionLabel, postExpressionLabel);
|
||||
currentFlow = finishFlowLabel(postExpressionLabel);
|
||||
}
|
||||
else {
|
||||
bindLogicalExpression(node, currentTrueTarget!, currentFalseTarget!);
|
||||
}
|
||||
completeNode();
|
||||
}
|
||||
else {
|
||||
advanceState(BindBinaryExpressionFlowState.BindToken);
|
||||
maybeBind(node.left);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BindBinaryExpressionFlowState.BindToken: {
|
||||
advanceState(BindBinaryExpressionFlowState.BindRight);
|
||||
maybeBind(node.operatorToken);
|
||||
break;
|
||||
}
|
||||
case BindBinaryExpressionFlowState.BindRight: {
|
||||
advanceState(BindBinaryExpressionFlowState.FinishBind);
|
||||
maybeBind(node.right);
|
||||
break;
|
||||
}
|
||||
case BindBinaryExpressionFlowState.FinishBind: {
|
||||
const operator = node.operatorToken.kind;
|
||||
if (isAssignmentOperator(operator) && !isAssignmentTarget(node)) {
|
||||
bindAssignmentTargetFlow(node.left);
|
||||
if (operator === SyntaxKind.EqualsToken && node.left.kind === SyntaxKind.ElementAccessExpression) {
|
||||
const elementAccess = <ElementAccessExpression>node.left;
|
||||
if (isNarrowableOperand(elementAccess.expression)) {
|
||||
currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
completeNode();
|
||||
break;
|
||||
}
|
||||
default: return Debug.fail(`Invalid state ${workStacks.state[stackIndex]} for bindBinaryExpressionFlow`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bindEachChild(node);
|
||||
if (isAssignmentOperator(operator) && !isAssignmentTarget(node)) {
|
||||
bindAssignmentTargetFlow(node.left);
|
||||
if (operator === SyntaxKind.EqualsToken && node.left.kind === SyntaxKind.ElementAccessExpression) {
|
||||
const elementAccess = <ElementAccessExpression>node.left;
|
||||
if (isNarrowableOperand(elementAccess.expression)) {
|
||||
currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that `advanceState` sets the _current_ head state, and that `maybeBind` potentially pushes on a new
|
||||
* head state; so `advanceState` must be called before any `maybeBind` during a state's execution.
|
||||
*/
|
||||
function advanceState(state: BindBinaryExpressionFlowState, isInStrictMode?: boolean, parent?: Node, subtreeFlags?: number) {
|
||||
workStacks.state[stackIndex] = state;
|
||||
if (isInStrictMode !== undefined) {
|
||||
workStacks.inStrictMode[stackIndex] = isInStrictMode;
|
||||
}
|
||||
if (parent !== undefined) {
|
||||
workStacks.parent[stackIndex] = parent;
|
||||
}
|
||||
if (subtreeFlags !== undefined) {
|
||||
workStacks.subtreeFlags[stackIndex] = subtreeFlags;
|
||||
}
|
||||
}
|
||||
|
||||
function completeNode() {
|
||||
if (workStacks.inStrictMode[stackIndex] !== undefined) {
|
||||
if (workStacks.subtreeFlags[stackIndex] === -1) {
|
||||
skipTransformFlagAggregation = false;
|
||||
subtreeTransformFlags |= node.transformFlags & ~getTransformFlagsSubtreeExclusions(node.kind);
|
||||
}
|
||||
else if (workStacks.subtreeFlags[stackIndex] !== undefined) {
|
||||
subtreeTransformFlags = workStacks.subtreeFlags[stackIndex]! | computeTransformFlagsForNode(node, subtreeTransformFlags);
|
||||
}
|
||||
inStrictMode = workStacks.inStrictMode[stackIndex]!;
|
||||
parent = workStacks.parent[stackIndex]!;
|
||||
}
|
||||
stackIndex--;
|
||||
}
|
||||
|
||||
/**
|
||||
* If `node` is a BinaryExpression, adds it to the local work stack, otherwise recursively binds it
|
||||
*/
|
||||
function maybeBind(node: Node) {
|
||||
if (node && isBinaryExpression(node)) {
|
||||
stackIndex++;
|
||||
workStacks.expr[stackIndex] = node;
|
||||
workStacks.state[stackIndex] = BindBinaryExpressionFlowState.BindThenBindChildren;
|
||||
workStacks.inStrictMode[stackIndex] = undefined;
|
||||
workStacks.parent[stackIndex] = undefined;
|
||||
workStacks.subtreeFlags[stackIndex] = undefined;
|
||||
}
|
||||
else {
|
||||
bind(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -27136,12 +27136,89 @@ namespace ts {
|
||||
return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target);
|
||||
}
|
||||
|
||||
const enum CheckBinaryExpressionState {
|
||||
MaybeCheckLeft,
|
||||
CheckRight,
|
||||
FinishCheck
|
||||
}
|
||||
|
||||
function checkBinaryExpression(node: BinaryExpression, checkMode?: CheckMode) {
|
||||
if (isInJSFile(node) && getAssignedExpandoInitializer(node)) {
|
||||
return checkExpression(node.right, checkMode);
|
||||
const workStacks: {
|
||||
expr: BinaryExpression[],
|
||||
state: CheckBinaryExpressionState[],
|
||||
leftType: (Type | undefined)[]
|
||||
} = {
|
||||
expr: [node],
|
||||
state: [CheckBinaryExpressionState.MaybeCheckLeft],
|
||||
leftType: [undefined]
|
||||
};
|
||||
let stackIndex = 0;
|
||||
let lastResult: Type | undefined;
|
||||
while (stackIndex >= 0) {
|
||||
node = workStacks.expr[stackIndex];
|
||||
switch (workStacks.state[stackIndex]) {
|
||||
case CheckBinaryExpressionState.MaybeCheckLeft: {
|
||||
if (isInJSFile(node) && getAssignedExpandoInitializer(node)) {
|
||||
finishInvocation(checkExpression(node.right, checkMode));
|
||||
break;
|
||||
}
|
||||
checkGrammarNullishCoalesceWithLogicalExpression(node);
|
||||
const operator = node.operatorToken.kind;
|
||||
if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) {
|
||||
finishInvocation(checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword));
|
||||
break;
|
||||
}
|
||||
advanceState(CheckBinaryExpressionState.CheckRight);
|
||||
maybeCheckExpression(node.left);
|
||||
break;
|
||||
}
|
||||
case CheckBinaryExpressionState.CheckRight: {
|
||||
const leftType = lastResult!;
|
||||
workStacks.leftType[stackIndex] = leftType;
|
||||
const operator = node.operatorToken.kind;
|
||||
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
|
||||
checkTruthinessOfType(leftType, node.left);
|
||||
}
|
||||
advanceState(CheckBinaryExpressionState.FinishCheck);
|
||||
maybeCheckExpression(node.right);
|
||||
break;
|
||||
}
|
||||
case CheckBinaryExpressionState.FinishCheck: {
|
||||
const leftType = workStacks.leftType[stackIndex]!;
|
||||
const rightType = lastResult!;
|
||||
finishInvocation(checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node));
|
||||
break;
|
||||
}
|
||||
default: return Debug.fail(`Invalid state ${workStacks.state[stackIndex]} for checkBinaryExpression`);
|
||||
}
|
||||
}
|
||||
|
||||
return lastResult!;
|
||||
|
||||
function finishInvocation(result: Type) {
|
||||
lastResult = result;
|
||||
stackIndex--;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that `advanceState` sets the _current_ head state, and that `maybeCheckExpression` potentially pushes on a new
|
||||
* head state; so `advanceState` must be called before any `maybeCheckExpression` during a state's execution.
|
||||
*/
|
||||
function advanceState(nextState: CheckBinaryExpressionState) {
|
||||
workStacks.state[stackIndex] = nextState;
|
||||
}
|
||||
|
||||
function maybeCheckExpression(node: Expression) {
|
||||
if (isBinaryExpression(node)) {
|
||||
stackIndex++;
|
||||
workStacks.expr[stackIndex] = node;
|
||||
workStacks.state[stackIndex] = CheckBinaryExpressionState.MaybeCheckLeft;
|
||||
workStacks.leftType[stackIndex] = undefined;
|
||||
}
|
||||
else {
|
||||
lastResult = checkExpression(node, checkMode);
|
||||
}
|
||||
}
|
||||
checkGrammarNullishCoalesceWithLogicalExpression(node);
|
||||
return checkBinaryLikeExpression(node.left, node.operatorToken, node.right, checkMode, node);
|
||||
}
|
||||
|
||||
function checkGrammarNullishCoalesceWithLogicalExpression(node: BinaryExpression) {
|
||||
@ -27156,6 +27233,8 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
// Note that this and `checkBinaryExpression` above should behave mostly the same, except this elides some
|
||||
// expression-wide checks and does not use a work stack to fold nested binary expressions into the same callstack frame
|
||||
function checkBinaryLikeExpression(left: Expression, operatorToken: Node, right: Expression, checkMode?: CheckMode, errorNode?: Node): Type {
|
||||
const operator = operatorToken.kind;
|
||||
if (operator === SyntaxKind.EqualsToken && (left.kind === SyntaxKind.ObjectLiteralExpression || left.kind === SyntaxKind.ArrayLiteralExpression)) {
|
||||
@ -27169,7 +27248,19 @@ namespace ts {
|
||||
leftType = checkExpression(left, checkMode);
|
||||
}
|
||||
|
||||
let rightType = checkExpression(right, checkMode);
|
||||
const rightType = checkExpression(right, checkMode);
|
||||
return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, errorNode);
|
||||
}
|
||||
|
||||
function checkBinaryLikeExpressionWorker(
|
||||
left: Expression,
|
||||
operatorToken: Node,
|
||||
right: Expression,
|
||||
leftType: Type,
|
||||
rightType: Type,
|
||||
errorNode?: Node
|
||||
): Type {
|
||||
const operator = operatorToken.kind;
|
||||
switch (operator) {
|
||||
case SyntaxKind.AsteriskToken:
|
||||
case SyntaxKind.AsteriskAsteriskToken:
|
||||
@ -30774,14 +30865,17 @@ namespace ts {
|
||||
checkSourceElement(node.statement);
|
||||
}
|
||||
|
||||
function checkTruthinessExpression(node: Expression, checkMode?: CheckMode) {
|
||||
const type = checkExpression(node, checkMode);
|
||||
function checkTruthinessOfType(type: Type, node: Node) {
|
||||
if (type.flags & TypeFlags.Void) {
|
||||
error(node, Diagnostics.An_expression_of_type_void_cannot_be_tested_for_truthiness);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
function checkTruthinessExpression(node: Expression, checkMode?: CheckMode) {
|
||||
return checkTruthinessOfType(checkExpression(node, checkMode), node);
|
||||
}
|
||||
|
||||
function checkForStatement(node: ForStatement) {
|
||||
// Grammar checking
|
||||
if (!checkGrammarStatementInAmbientContext(node)) {
|
||||
|
||||
@ -379,6 +379,7 @@ namespace ts {
|
||||
|
||||
// transform hooks
|
||||
onEmitNode: transform.emitNodeWithNotification,
|
||||
isEmitNotificationEnabled: transform.isEmitNotificationEnabled,
|
||||
substituteNode: transform.substituteNode,
|
||||
});
|
||||
|
||||
@ -437,6 +438,7 @@ namespace ts {
|
||||
|
||||
// transform hooks
|
||||
onEmitNode: declarationTransform.emitNodeWithNotification,
|
||||
isEmitNotificationEnabled: declarationTransform.isEmitNotificationEnabled,
|
||||
substituteNode: declarationTransform.substituteNode,
|
||||
});
|
||||
const declBlocked = (!!declarationTransform.diagnostics && !!declarationTransform.diagnostics.length) || !!host.isEmitBlocked(declarationFilePath) || !!compilerOptions.noEmit;
|
||||
@ -819,6 +821,7 @@ namespace ts {
|
||||
const {
|
||||
hasGlobalName,
|
||||
onEmitNode = noEmitNotification,
|
||||
isEmitNotificationEnabled,
|
||||
substituteNode = noEmitSubstitution,
|
||||
onBeforeEmitNodeArray,
|
||||
onAfterEmitNodeArray,
|
||||
@ -1160,7 +1163,7 @@ namespace ts {
|
||||
lastNode = node;
|
||||
lastSubstitution = undefined;
|
||||
|
||||
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node);
|
||||
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, emitHint, node);
|
||||
pipelinePhase(emitHint, node);
|
||||
|
||||
Debug.assert(lastNode === node);
|
||||
@ -1172,16 +1175,16 @@ namespace ts {
|
||||
return substitute || node;
|
||||
}
|
||||
|
||||
function getPipelinePhase(phase: PipelinePhase, node: Node) {
|
||||
function getPipelinePhase(phase: PipelinePhase, emitHint: EmitHint, node: Node) {
|
||||
switch (phase) {
|
||||
case PipelinePhase.Notification:
|
||||
if (onEmitNode !== noEmitNotification) {
|
||||
if (onEmitNode !== noEmitNotification && (!isEmitNotificationEnabled || isEmitNotificationEnabled(node))) {
|
||||
return pipelineEmitWithNotification;
|
||||
}
|
||||
// falls through
|
||||
|
||||
case PipelinePhase.Substitution:
|
||||
if (substituteNode !== noEmitSubstitution) {
|
||||
if (substituteNode !== noEmitSubstitution && (lastSubstitution = substituteNode(emitHint, node)) !== node) {
|
||||
return pipelineEmitWithSubstitution;
|
||||
}
|
||||
// falls through
|
||||
@ -1206,13 +1209,13 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function getNextPipelinePhase(currentPhase: PipelinePhase, node: Node) {
|
||||
return getPipelinePhase(currentPhase + 1, node);
|
||||
function getNextPipelinePhase(currentPhase: PipelinePhase, emitHint: EmitHint, node: Node) {
|
||||
return getPipelinePhase(currentPhase + 1, emitHint, node);
|
||||
}
|
||||
|
||||
function pipelineEmitWithNotification(hint: EmitHint, node: Node) {
|
||||
Debug.assert(lastNode === node);
|
||||
const pipelinePhase = getNextPipelinePhase(PipelinePhase.Notification, node);
|
||||
const pipelinePhase = getNextPipelinePhase(PipelinePhase.Notification, hint, node);
|
||||
onEmitNode(hint, node, pipelinePhase);
|
||||
Debug.assert(lastNode === node);
|
||||
}
|
||||
@ -1653,9 +1656,8 @@ namespace ts {
|
||||
|
||||
function pipelineEmitWithSubstitution(hint: EmitHint, node: Node) {
|
||||
Debug.assert(lastNode === node || lastSubstitution === node);
|
||||
const pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, node);
|
||||
lastSubstitution = substituteNode(hint, node);
|
||||
pipelinePhase(hint, lastSubstitution);
|
||||
const pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, hint, node);
|
||||
pipelinePhase(hint, lastSubstitution!);
|
||||
Debug.assert(lastNode === node || lastSubstitution === node);
|
||||
}
|
||||
|
||||
@ -2425,19 +2427,84 @@ namespace ts {
|
||||
writeTokenText(node.operator, writeOperator);
|
||||
}
|
||||
|
||||
function emitBinaryExpression(node: BinaryExpression) {
|
||||
const isCommaOperator = node.operatorToken.kind !== SyntaxKind.CommaToken;
|
||||
const indentBeforeOperator = needsIndentation(node, node.left, node.operatorToken);
|
||||
const indentAfterOperator = needsIndentation(node, node.operatorToken, node.right);
|
||||
const enum EmitBinaryExpressionState {
|
||||
EmitLeft,
|
||||
EmitRight,
|
||||
FinishEmit
|
||||
}
|
||||
|
||||
emitExpression(node.left);
|
||||
increaseIndentIf(indentBeforeOperator, isCommaOperator);
|
||||
emitLeadingCommentsOfPosition(node.operatorToken.pos);
|
||||
writeTokenNode(node.operatorToken, node.operatorToken.kind === SyntaxKind.InKeyword ? writeKeyword : writeOperator);
|
||||
emitTrailingCommentsOfPosition(node.operatorToken.end, /*prefixSpace*/ true); // Binary operators should have a space before the comment starts
|
||||
increaseIndentIf(indentAfterOperator, /*writeSpaceIfNotIndenting*/ true);
|
||||
emitExpression(node.right);
|
||||
decreaseIndentIf(indentBeforeOperator, indentAfterOperator);
|
||||
/**
|
||||
* emitBinaryExpression includes an embedded work stack to attempt to handle as many nested binary expressions
|
||||
* as possible without creating any additional stack frames. This can only be done when the emit pipeline does
|
||||
* not require notification/substitution/comment/sourcemap decorations.
|
||||
*/
|
||||
function emitBinaryExpression(node: BinaryExpression) {
|
||||
const nodeStack = [node];
|
||||
const stateStack = [EmitBinaryExpressionState.EmitLeft];
|
||||
let stackIndex = 0;
|
||||
while (stackIndex >= 0) {
|
||||
node = nodeStack[stackIndex];
|
||||
switch (stateStack[stackIndex]) {
|
||||
case EmitBinaryExpressionState.EmitLeft: {
|
||||
maybePipelineEmitExpression(node.left);
|
||||
break;
|
||||
}
|
||||
case EmitBinaryExpressionState.EmitRight: {
|
||||
const isCommaOperator = node.operatorToken.kind !== SyntaxKind.CommaToken;
|
||||
const indentBeforeOperator = needsIndentation(node, node.left, node.operatorToken);
|
||||
const indentAfterOperator = needsIndentation(node, node.operatorToken, node.right);
|
||||
increaseIndentIf(indentBeforeOperator, isCommaOperator);
|
||||
emitLeadingCommentsOfPosition(node.operatorToken.pos);
|
||||
writeTokenNode(node.operatorToken, node.operatorToken.kind === SyntaxKind.InKeyword ? writeKeyword : writeOperator);
|
||||
emitTrailingCommentsOfPosition(node.operatorToken.end, /*prefixSpace*/ true); // Binary operators should have a space before the comment starts
|
||||
increaseIndentIf(indentAfterOperator, /*writeSpaceIfNotIndenting*/ true);
|
||||
maybePipelineEmitExpression(node.right);
|
||||
break;
|
||||
}
|
||||
case EmitBinaryExpressionState.FinishEmit: {
|
||||
const indentBeforeOperator = needsIndentation(node, node.left, node.operatorToken);
|
||||
const indentAfterOperator = needsIndentation(node, node.operatorToken, node.right);
|
||||
decreaseIndentIf(indentBeforeOperator, indentAfterOperator);
|
||||
stackIndex--;
|
||||
break;
|
||||
}
|
||||
default: return Debug.fail(`Invalid state ${stateStack[stackIndex]} for emitBinaryExpressionWorker`);
|
||||
}
|
||||
}
|
||||
|
||||
function maybePipelineEmitExpression(next: Expression) {
|
||||
// Advance the state of this unit of work,
|
||||
stateStack[stackIndex]++;
|
||||
|
||||
// Then actually do the work of emitting the node `next` returned by the prior state
|
||||
|
||||
// The following section should be identical to `pipelineEmit` save it assumes EmitHint.Expression and offloads
|
||||
// binary expression handling, where possible, to the contained work queue
|
||||
|
||||
// #region trampolinePipelineEmit
|
||||
const savedLastNode = lastNode;
|
||||
const savedLastSubstitution = lastSubstitution;
|
||||
lastNode = next;
|
||||
lastSubstitution = undefined;
|
||||
|
||||
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, EmitHint.Expression, next);
|
||||
if (pipelinePhase === pipelineEmitWithHint && isBinaryExpression(next)) {
|
||||
// If the target pipeline phase is emit directly, and the next node's also a binary expression,
|
||||
// skip all the intermediate indirection and push the expression directly onto the work stack
|
||||
stackIndex++;
|
||||
stateStack[stackIndex] = EmitBinaryExpressionState.EmitLeft;
|
||||
nodeStack[stackIndex] = next;
|
||||
}
|
||||
else {
|
||||
pipelinePhase(EmitHint.Expression, next);
|
||||
}
|
||||
|
||||
Debug.assert(lastNode === next);
|
||||
|
||||
lastNode = savedLastNode;
|
||||
lastSubstitution = savedLastSubstitution;
|
||||
// #endregion trampolinePipelineEmit
|
||||
}
|
||||
}
|
||||
|
||||
function emitConditionalExpression(node: ConditionalExpression) {
|
||||
@ -4773,7 +4840,7 @@ namespace ts {
|
||||
forEach(getSyntheticLeadingComments(node), emitLeadingSynthesizedComment);
|
||||
exitComment();
|
||||
|
||||
const pipelinePhase = getNextPipelinePhase(PipelinePhase.Comments, node);
|
||||
const pipelinePhase = getNextPipelinePhase(PipelinePhase.Comments, hint, node);
|
||||
if (emitFlags & EmitFlags.NoNestedComments) {
|
||||
commentsDisabled = true;
|
||||
pipelinePhase(hint, node);
|
||||
@ -5046,7 +5113,7 @@ namespace ts {
|
||||
|
||||
function pipelineEmitWithSourceMap(hint: EmitHint, node: Node) {
|
||||
Debug.assert(lastNode === node || lastSubstitution === node);
|
||||
const pipelinePhase = getNextPipelinePhase(PipelinePhase.SourceMaps, node);
|
||||
const pipelinePhase = getNextPipelinePhase(PipelinePhase.SourceMaps, hint, node);
|
||||
if (isUnparsedSource(node) || isUnparsedPrepend(node)) {
|
||||
pipelinePhase(hint, node);
|
||||
}
|
||||
|
||||
@ -904,28 +904,31 @@ namespace ts {
|
||||
// overhead. This functions allows us to set all the parents, without all the expense of
|
||||
// binding.
|
||||
|
||||
let parent: Node = rootNode;
|
||||
forEachChild(rootNode, visitNode);
|
||||
const stack: Node[] = [rootNode];
|
||||
while (stack.length) {
|
||||
const parent = stack.pop()!;
|
||||
bindParentToChildren(parent, gatherChildren(parent));
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
function visitNode(n: Node): void {
|
||||
// walk down setting parents that differ from the parent we think it should be. This
|
||||
// allows us to quickly bail out of setting parents for subtrees during incremental
|
||||
// parsing
|
||||
if (n.parent !== parent) {
|
||||
n.parent = parent;
|
||||
function gatherChildren(node: Node) {
|
||||
const children: Node[] = [];
|
||||
forEachChild(node, n => { children.unshift(n); }); // By using a stack above and `unshift` here, we emulate a depth-first preorder traversal
|
||||
return children;
|
||||
}
|
||||
|
||||
const saveParent = parent;
|
||||
parent = n;
|
||||
forEachChild(n, visitNode);
|
||||
if (hasJSDocNodes(n)) {
|
||||
for (const jsDoc of n.jsDoc!) {
|
||||
jsDoc.parent = n;
|
||||
parent = jsDoc;
|
||||
forEachChild(jsDoc, visitNode);
|
||||
function bindParentToChildren(parent: Node, children: readonly Node[]) {
|
||||
for (const child of children) {
|
||||
if (child.parent === parent) continue; // already bound, assume subtree is bound
|
||||
child.parent = parent;
|
||||
stack.push(child);
|
||||
if (hasJSDocNodes(child)) {
|
||||
for (const jsDoc of child.jsDoc!) {
|
||||
jsDoc.parent = child;
|
||||
stack.push(jsDoc);
|
||||
}
|
||||
}
|
||||
parent = saveParent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,6 +225,7 @@ namespace ts {
|
||||
transformed,
|
||||
substituteNode,
|
||||
emitNodeWithNotification,
|
||||
isEmitNotificationEnabled,
|
||||
dispose,
|
||||
diagnostics
|
||||
};
|
||||
@ -288,6 +289,8 @@ namespace ts {
|
||||
function emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) {
|
||||
Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed.");
|
||||
if (node) {
|
||||
// TODO: Remove check and unconditionally use onEmitNode when API is breakingly changed
|
||||
// (see https://github.com/microsoft/TypeScript/pull/36248/files/5062623f39120171b98870c71344b3242eb03d23#r369766739)
|
||||
if (isEmitNotificationEnabled(node)) {
|
||||
onEmitNode(hint, node, emitCallback);
|
||||
}
|
||||
|
||||
@ -5972,6 +5972,13 @@ namespace ts {
|
||||
*/
|
||||
emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void;
|
||||
|
||||
/**
|
||||
* Indicates if a given node needs an emit notification
|
||||
*
|
||||
* @param node The node to emit.
|
||||
*/
|
||||
isEmitNotificationEnabled?(node: Node): boolean;
|
||||
|
||||
/**
|
||||
* Clean up EmitNode entries on any parse-tree nodes.
|
||||
*/
|
||||
@ -6167,6 +6174,12 @@ namespace ts {
|
||||
* ```
|
||||
*/
|
||||
onEmitNode?(hint: EmitHint, node: Node | undefined, emitCallback: (hint: EmitHint, node: Node | undefined) => void): void;
|
||||
|
||||
/**
|
||||
* A hook used to check if an emit notification is required for a node.
|
||||
* @param node The node to emit.
|
||||
*/
|
||||
isEmitNotificationEnabled?(node: Node | undefined): boolean;
|
||||
/**
|
||||
* A hook used by the Printer to perform just-in-time substitution of a node. This is
|
||||
* primarily used by node transformations that need to substitute one node for another,
|
||||
|
||||
@ -509,7 +509,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function isJSDocTypeExpressionOrChild(node: Node): boolean {
|
||||
return node.kind === SyntaxKind.JSDocTypeExpression || (node.parent && isJSDocTypeExpressionOrChild(node.parent));
|
||||
return !!findAncestor(node, isJSDocTypeExpression);
|
||||
}
|
||||
|
||||
export function getTextOfNodeFromSourceText(sourceText: string, node: Node, includeTrivia = false): string {
|
||||
|
||||
@ -69,56 +69,63 @@ namespace Utils {
|
||||
|
||||
export const canonicalizeForHarness = ts.createGetCanonicalFileName(/*caseSensitive*/ false); // This is done so tests work on windows _and_ linux
|
||||
|
||||
export function assertInvariants(node: ts.Node | undefined, parent: ts.Node | undefined): void {
|
||||
if (node) {
|
||||
assert.isFalse(node.pos < 0, "node.pos < 0");
|
||||
assert.isFalse(node.end < 0, "node.end < 0");
|
||||
assert.isFalse(node.end < node.pos, "node.end < node.pos");
|
||||
assert.equal(node.parent, parent, "node.parent !== parent");
|
||||
export function assertInvariants(node: ts.Node | undefined, parent: ts.Node | undefined) {
|
||||
const queue: [ts.Node | undefined, ts.Node | undefined][] = [[node, parent]];
|
||||
for (const [node, parent] of queue) {
|
||||
assertInvariantsWorker(node, parent);
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
// Make sure each child is contained within the parent.
|
||||
assert.isFalse(node.pos < parent.pos, "node.pos < parent.pos");
|
||||
assert.isFalse(node.end > parent.end, "node.end > parent.end");
|
||||
}
|
||||
function assertInvariantsWorker(node: ts.Node | undefined, parent: ts.Node | undefined): void {
|
||||
if (node) {
|
||||
assert.isFalse(node.pos < 0, "node.pos < 0");
|
||||
assert.isFalse(node.end < 0, "node.end < 0");
|
||||
assert.isFalse(node.end < node.pos, "node.end < node.pos");
|
||||
assert.equal(node.parent, parent, "node.parent !== parent");
|
||||
|
||||
ts.forEachChild(node, child => {
|
||||
assertInvariants(child, node);
|
||||
});
|
||||
if (parent) {
|
||||
// Make sure each child is contained within the parent.
|
||||
assert.isFalse(node.pos < parent.pos, "node.pos < parent.pos");
|
||||
assert.isFalse(node.end > parent.end, "node.end > parent.end");
|
||||
}
|
||||
|
||||
// Make sure each of the children is in order.
|
||||
let currentPos = 0;
|
||||
ts.forEachChild(node,
|
||||
child => {
|
||||
assert.isFalse(child.pos < currentPos, "child.pos < currentPos");
|
||||
currentPos = child.end;
|
||||
},
|
||||
array => {
|
||||
assert.isFalse(array.pos < node.pos, "array.pos < node.pos");
|
||||
assert.isFalse(array.end > node.end, "array.end > node.end");
|
||||
assert.isFalse(array.pos < currentPos, "array.pos < currentPos");
|
||||
|
||||
for (const item of array) {
|
||||
assert.isFalse(item.pos < currentPos, "array[i].pos < currentPos");
|
||||
currentPos = item.end;
|
||||
}
|
||||
|
||||
currentPos = array.end;
|
||||
ts.forEachChild(node, child => {
|
||||
queue.push([child, node]);
|
||||
});
|
||||
|
||||
const childNodesAndArrays: any[] = [];
|
||||
ts.forEachChild(node, child => { childNodesAndArrays.push(child); }, array => { childNodesAndArrays.push(array); });
|
||||
// Make sure each of the children is in order.
|
||||
let currentPos = 0;
|
||||
ts.forEachChild(node,
|
||||
child => {
|
||||
assert.isFalse(child.pos < currentPos, "child.pos < currentPos");
|
||||
currentPos = child.end;
|
||||
},
|
||||
array => {
|
||||
assert.isFalse(array.pos < node.pos, "array.pos < node.pos");
|
||||
assert.isFalse(array.end > node.end, "array.end > node.end");
|
||||
assert.isFalse(array.pos < currentPos, "array.pos < currentPos");
|
||||
|
||||
for (const childName in node) {
|
||||
if (childName === "parent" || childName === "nextContainer" || childName === "modifiers" || childName === "externalModuleIndicator" ||
|
||||
// for now ignore jsdoc comments
|
||||
childName === "jsDocComment" || childName === "checkJsDirective" || childName === "commonJsModuleIndicator") {
|
||||
continue;
|
||||
}
|
||||
const child = (<any>node)[childName];
|
||||
if (isNodeOrArray(child)) {
|
||||
assert.isFalse(childNodesAndArrays.indexOf(child) < 0,
|
||||
"Missing child when forEach'ing over node: " + (<any>ts).SyntaxKind[node.kind] + "-" + childName);
|
||||
for (const item of array) {
|
||||
assert.isFalse(item.pos < currentPos, "array[i].pos < currentPos");
|
||||
currentPos = item.end;
|
||||
}
|
||||
|
||||
currentPos = array.end;
|
||||
});
|
||||
|
||||
const childNodesAndArrays: any[] = [];
|
||||
ts.forEachChild(node, child => { childNodesAndArrays.push(child); }, array => { childNodesAndArrays.push(array); });
|
||||
|
||||
for (const childName in node) {
|
||||
if (childName === "parent" || childName === "nextContainer" || childName === "modifiers" || childName === "externalModuleIndicator" ||
|
||||
// for now ignore jsdoc comments
|
||||
childName === "jsDocComment" || childName === "checkJsDirective" || childName === "commonJsModuleIndicator") {
|
||||
continue;
|
||||
}
|
||||
const child = (<any>node)[childName];
|
||||
if (isNodeOrArray(child)) {
|
||||
assert.isFalse(childNodesAndArrays.indexOf(child) < 0,
|
||||
"Missing child when forEach'ing over node: " + (<any>ts).SyntaxKind[node.kind] + "-" + childName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,19 @@ namespace Harness {
|
||||
type?: string;
|
||||
}
|
||||
|
||||
function* forEachASTNode(node: ts.Node) {
|
||||
const work = [node];
|
||||
while (work.length) {
|
||||
const elem = work.pop()!;
|
||||
yield elem;
|
||||
|
||||
const resChildren: ts.Node[] = [];
|
||||
// push onto work queue in reverse order to maintain preorder traversal
|
||||
ts.forEachChild(elem, c => { resChildren.unshift(c); });
|
||||
work.push(...resChildren);
|
||||
}
|
||||
}
|
||||
|
||||
export class TypeWriterWalker {
|
||||
currentSourceFile!: ts.SourceFile;
|
||||
|
||||
@ -53,19 +66,15 @@ namespace Harness {
|
||||
}
|
||||
|
||||
private *visitNode(node: ts.Node, isSymbolWalk: boolean): IterableIterator<TypeWriterResult> {
|
||||
if (ts.isExpressionNode(node) || node.kind === ts.SyntaxKind.Identifier || ts.isDeclarationName(node)) {
|
||||
const result = this.writeTypeOrSymbol(node, isSymbolWalk);
|
||||
if (result) {
|
||||
yield result;
|
||||
}
|
||||
}
|
||||
|
||||
const children: ts.Node[] = [];
|
||||
ts.forEachChild(node, child => void children.push(child));
|
||||
for (const child of children) {
|
||||
const gen = this.visitNode(child, isSymbolWalk);
|
||||
for (let {done, value} = gen.next(); !done; { done, value } = gen.next()) {
|
||||
yield value;
|
||||
const gen = forEachASTNode(node);
|
||||
let res = gen.next();
|
||||
for (; !res.done; res = gen.next()) {
|
||||
const {value: node} = res;
|
||||
if (ts.isExpressionNode(node) || node.kind === ts.SyntaxKind.Identifier || ts.isDeclarationName(node)) {
|
||||
const result = this.writeTypeOrSymbol(node, isSymbolWalk);
|
||||
if (result) {
|
||||
yield result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -944,6 +944,11 @@ console.log(blabla);`
|
||||
path: `${tscWatch.projectRoot}/tsconfig.json`,
|
||||
content: "{}"
|
||||
};
|
||||
// Move things from staging to node_modules without triggering watch
|
||||
const moduleFile: File = {
|
||||
path: `${tscWatch.projectRoot}/node_modules/@angular/core/index.d.ts`,
|
||||
content: `export const y = 10;`
|
||||
};
|
||||
const projectFiles = [main, libFile, config];
|
||||
const host = createServerHost(projectFiles);
|
||||
const session = createSession(host, { canUseEvents: true });
|
||||
@ -983,11 +988,6 @@ console.log(blabla);`
|
||||
verifyWhileNpmInstall({ timeouts: 0, semantic: moduleNotFoundErr });
|
||||
|
||||
filesAndFoldersToAdd = [];
|
||||
// Move things from staging to node_modules without triggering watch
|
||||
const moduleFile: File = {
|
||||
path: `${tscWatch.projectRoot}/node_modules/@angular/core/index.d.ts`,
|
||||
content: `export const y = 10;`
|
||||
};
|
||||
host.ensureFileOrFolder(moduleFile, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true, /*ignoreParentWatch*/ true);
|
||||
// Since we added/removed in .staging no timeout
|
||||
verifyWhileNpmInstall({ timeouts: 0, semantic: moduleNotFoundErr });
|
||||
|
||||
@ -3054,6 +3054,12 @@ declare namespace ts {
|
||||
* @param emitCallback A callback used to emit the node.
|
||||
*/
|
||||
emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void;
|
||||
/**
|
||||
* Indicates if a given node needs an emit notification
|
||||
*
|
||||
* @param node The node to emit.
|
||||
*/
|
||||
isEmitNotificationEnabled?(node: Node): boolean;
|
||||
/**
|
||||
* Clean up EmitNode entries on any parse-tree nodes.
|
||||
*/
|
||||
@ -3126,6 +3132,11 @@ declare namespace ts {
|
||||
* ```
|
||||
*/
|
||||
onEmitNode?(hint: EmitHint, node: Node | undefined, emitCallback: (hint: EmitHint, node: Node | undefined) => void): void;
|
||||
/**
|
||||
* A hook used to check if an emit notification is required for a node.
|
||||
* @param node The node to emit.
|
||||
*/
|
||||
isEmitNotificationEnabled?(node: Node | undefined): boolean;
|
||||
/**
|
||||
* A hook used by the Printer to perform just-in-time substitution of a node. This is
|
||||
* primarily used by node transformations that need to substitute one node for another,
|
||||
|
||||
11
tests/baselines/reference/api/typescript.d.ts
vendored
11
tests/baselines/reference/api/typescript.d.ts
vendored
@ -3054,6 +3054,12 @@ declare namespace ts {
|
||||
* @param emitCallback A callback used to emit the node.
|
||||
*/
|
||||
emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void;
|
||||
/**
|
||||
* Indicates if a given node needs an emit notification
|
||||
*
|
||||
* @param node The node to emit.
|
||||
*/
|
||||
isEmitNotificationEnabled?(node: Node): boolean;
|
||||
/**
|
||||
* Clean up EmitNode entries on any parse-tree nodes.
|
||||
*/
|
||||
@ -3126,6 +3132,11 @@ declare namespace ts {
|
||||
* ```
|
||||
*/
|
||||
onEmitNode?(hint: EmitHint, node: Node | undefined, emitCallback: (hint: EmitHint, node: Node | undefined) => void): void;
|
||||
/**
|
||||
* A hook used to check if an emit notification is required for a node.
|
||||
* @param node The node to emit.
|
||||
*/
|
||||
isEmitNotificationEnabled?(node: Node | undefined): boolean;
|
||||
/**
|
||||
* A hook used by the Printer to perform just-in-time substitution of a node. This is
|
||||
* primarily used by node transformations that need to substitute one node for another,
|
||||
|
||||
9927
tests/baselines/reference/binderBinaryExpressionStress.js
Normal file
9927
tests/baselines/reference/binderBinaryExpressionStress.js
Normal file
File diff suppressed because one or more lines are too long
4971
tests/cases/compiler/binderBinaryExpressionStress.ts
Normal file
4971
tests/cases/compiler/binderBinaryExpressionStress.ts
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user