Skip to content

Commit c450425

Browse files
committed
Use shared binary trampoline in checker
1 parent 4b67b4a commit c450425

File tree

2 files changed

+165
-87
lines changed

2 files changed

+165
-87
lines changed

src/compiler/checker.ts

Lines changed: 123 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ namespace ts {
357357
const keyofStringsOnly = !!compilerOptions.keyofStringsOnly;
358358
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : ObjectFlags.FreshLiteral;
359359

360+
const checkBinaryExpression = createCheckBinaryExpression();
360361
const emitResolver = createResolver();
361362
const nodeBuilder = createNodeBuilder();
362363

@@ -30981,92 +30982,142 @@ namespace ts {
3098130982
return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target);
3098230983
}
3098330984

30984-
const enum CheckBinaryExpressionState {
30985-
MaybeCheckLeft,
30986-
CheckRight,
30987-
FinishCheck
30988-
}
30985+
function createCheckBinaryExpression() {
30986+
interface WorkArea {
30987+
readonly checkMode: CheckMode | undefined;
30988+
skip: boolean;
30989+
stackIndex: number;
30990+
/**
30991+
* Holds the types from the left-side of an expression from [0..stackIndex].
30992+
* Holds the type of the result at stackIndex+1. This allows us to reuse existing stack entries
30993+
* and avoid storing an extra property on the object (i.e., `lastResult`).
30994+
*/
30995+
typeStack: (Type | undefined)[];
30996+
}
3098930997

30990-
function checkBinaryExpression(node: BinaryExpression, checkMode?: CheckMode) {
30991-
const workStacks: {
30992-
expr: BinaryExpression[],
30993-
state: CheckBinaryExpressionState[],
30994-
leftType: (Type | undefined)[]
30995-
} = {
30996-
expr: [node],
30997-
state: [CheckBinaryExpressionState.MaybeCheckLeft],
30998-
leftType: [undefined]
30998+
const trampoline = createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, foldState);
30999+
31000+
return (node: BinaryExpression, checkMode: CheckMode | undefined) => {
31001+
const result = trampoline(node, checkMode);
31002+
Debug.assertIsDefined(result);
31003+
return result;
3099931004
};
31000-
let stackIndex = 0;
31001-
let lastResult: Type | undefined;
31002-
while (stackIndex >= 0) {
31003-
node = workStacks.expr[stackIndex];
31004-
switch (workStacks.state[stackIndex]) {
31005-
case CheckBinaryExpressionState.MaybeCheckLeft: {
31006-
if (isInJSFile(node) && getAssignedExpandoInitializer(node)) {
31007-
finishInvocation(checkExpression(node.right, checkMode));
31008-
break;
31009-
}
31010-
checkGrammarNullishCoalesceWithLogicalExpression(node);
31011-
const operator = node.operatorToken.kind;
31012-
if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) {
31013-
finishInvocation(checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword));
31014-
break;
31015-
}
31016-
advanceState(CheckBinaryExpressionState.CheckRight);
31017-
maybeCheckExpression(node.left);
31018-
break;
31019-
}
31020-
case CheckBinaryExpressionState.CheckRight: {
31021-
const leftType = lastResult!;
31022-
workStacks.leftType[stackIndex] = leftType;
31023-
const operator = node.operatorToken.kind;
31024-
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
31025-
if (operator === SyntaxKind.AmpersandAmpersandToken) {
31026-
const parent = walkUpParenthesizedExpressions(node.parent);
31027-
checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined);
31028-
}
31029-
checkTruthinessOfType(leftType, node.left);
31005+
31006+
function onEnter(node: BinaryExpression, state: WorkArea | undefined, checkMode: CheckMode | undefined) {
31007+
if (state) {
31008+
state.stackIndex++;
31009+
state.skip = false;
31010+
setLeftType(state, /*type*/ undefined);
31011+
setLastResult(state, /*type*/ undefined);
31012+
}
31013+
else {
31014+
state = {
31015+
checkMode,
31016+
skip: false,
31017+
stackIndex: 0,
31018+
typeStack: [undefined, undefined],
31019+
};
31020+
}
31021+
31022+
if (isInJSFile(node) && getAssignedExpandoInitializer(node)) {
31023+
state.skip = true;
31024+
setLastResult(state, checkExpression(node.right, checkMode));
31025+
return state;
31026+
}
31027+
31028+
checkGrammarNullishCoalesceWithLogicalExpression(node);
31029+
31030+
const operator = node.operatorToken.kind;
31031+
if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) {
31032+
state.skip = true;
31033+
setLastResult(state, checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword));
31034+
return state;
31035+
}
31036+
31037+
return state;
31038+
}
31039+
31040+
function onLeft(left: Expression, state: WorkArea, _node: BinaryExpression) {
31041+
if (!state.skip) {
31042+
return maybeCheckExpression(state, left);
31043+
}
31044+
}
31045+
31046+
function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, node: BinaryExpression) {
31047+
if (!state.skip) {
31048+
const leftType = getLastResult(state);
31049+
Debug.assertIsDefined(leftType);
31050+
setLeftType(state, leftType);
31051+
setLastResult(state, /*type*/ undefined);
31052+
const operator = operatorToken.kind;
31053+
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
31054+
if (operator === SyntaxKind.AmpersandAmpersandToken) {
31055+
const parent = walkUpParenthesizedExpressions(node.parent);
31056+
checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined);
3103031057
}
31031-
advanceState(CheckBinaryExpressionState.FinishCheck);
31032-
maybeCheckExpression(node.right);
31033-
break;
31034-
}
31035-
case CheckBinaryExpressionState.FinishCheck: {
31036-
const leftType = workStacks.leftType[stackIndex]!;
31037-
const rightType = lastResult!;
31038-
finishInvocation(checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node));
31039-
break;
31058+
checkTruthinessOfType(leftType, node.left);
3104031059
}
31041-
default: return Debug.fail(`Invalid state ${workStacks.state[stackIndex]} for checkBinaryExpression`);
3104231060
}
3104331061
}
3104431062

31045-
return lastResult!;
31063+
function onRight(right: Expression, state: WorkArea, _node: BinaryExpression) {
31064+
if (!state.skip) {
31065+
return maybeCheckExpression(state, right);
31066+
}
31067+
}
31068+
31069+
function onExit(node: BinaryExpression, state: WorkArea): Type | undefined {
31070+
let result: Type | undefined;
31071+
if (state.skip) {
31072+
result = getLastResult(state);
31073+
}
31074+
else {
31075+
const leftType = getLeftType(state);
31076+
Debug.assertIsDefined(leftType);
31077+
31078+
const rightType = getLastResult(state);
31079+
Debug.assertIsDefined(rightType);
3104631080

31047-
function finishInvocation(result: Type) {
31048-
lastResult = result;
31049-
stackIndex--;
31081+
result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node);
31082+
}
31083+
31084+
state.skip = false;
31085+
setLeftType(state, /*type*/ undefined);
31086+
setLastResult(state, /*type*/ undefined);
31087+
state.stackIndex--;
31088+
return result;
3105031089
}
3105131090

31052-
/**
31053-
* Note that `advanceState` sets the _current_ head state, and that `maybeCheckExpression` potentially pushes on a new
31054-
* head state; so `advanceState` must be called before any `maybeCheckExpression` during a state's execution.
31055-
*/
31056-
function advanceState(nextState: CheckBinaryExpressionState) {
31057-
workStacks.state[stackIndex] = nextState;
31091+
function foldState(state: WorkArea, result: Type | undefined, _side: "left" | "right") {
31092+
setLastResult(state, result);
31093+
return state;
3105831094
}
3105931095

31060-
function maybeCheckExpression(node: Expression) {
31096+
function maybeCheckExpression(state: WorkArea, node: Expression): BinaryExpression | undefined {
3106131097
if (isBinaryExpression(node)) {
31062-
stackIndex++;
31063-
workStacks.expr[stackIndex] = node;
31064-
workStacks.state[stackIndex] = CheckBinaryExpressionState.MaybeCheckLeft;
31065-
workStacks.leftType[stackIndex] = undefined;
31066-
}
31067-
else {
31068-
lastResult = checkExpression(node, checkMode);
31098+
return node;
3106931099
}
31100+
setLastResult(state, checkExpression(node, state.checkMode));
31101+
}
31102+
31103+
function getLeftType(state: WorkArea) {
31104+
return state.typeStack[state.stackIndex];
31105+
}
31106+
31107+
function setLeftType(state: WorkArea, type: Type | undefined) {
31108+
state.typeStack[state.stackIndex] = type;
31109+
}
31110+
31111+
function getLastResult(state: WorkArea) {
31112+
return state.typeStack[state.stackIndex + 1];
31113+
}
31114+
31115+
function setLastResult(state: WorkArea, type: Type | undefined) {
31116+
// To reduce overhead, reuse the next stack entry to store the
31117+
// last result. This avoids the overhead of an additional property
31118+
// on `WorkArea` and reuses empty stack entries as we walk back up
31119+
// the stack.
31120+
state.typeStack[state.stackIndex + 1] = type;
3107031121
}
3107131122
}
3107231123

0 commit comments

Comments
 (0)