@@ -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
@@ -31144,92 +31145,142 @@ namespace ts {
3114431145 return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target);
3114531146 }
3114631147
31147- const enum CheckBinaryExpressionState {
31148- MaybeCheckLeft,
31149- CheckRight,
31150- FinishCheck
31151- }
31148+ function createCheckBinaryExpression() {
31149+ interface WorkArea {
31150+ readonly checkMode: CheckMode | undefined;
31151+ skip: boolean;
31152+ stackIndex: number;
31153+ /**
31154+ * Holds the types from the left-side of an expression from [0..stackIndex].
31155+ * Holds the type of the result at stackIndex+1. This allows us to reuse existing stack entries
31156+ * and avoid storing an extra property on the object (i.e., `lastResult`).
31157+ */
31158+ typeStack: (Type | undefined)[];
31159+ }
3115231160
31153- function checkBinaryExpression(node: BinaryExpression, checkMode?: CheckMode) {
31154- const workStacks: {
31155- expr: BinaryExpression[],
31156- state: CheckBinaryExpressionState[],
31157- leftType: (Type | undefined)[]
31158- } = {
31159- expr: [node],
31160- state: [CheckBinaryExpressionState.MaybeCheckLeft],
31161- leftType: [undefined]
31161+ const trampoline = createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, foldState);
31162+
31163+ return (node: BinaryExpression, checkMode: CheckMode | undefined) => {
31164+ const result = trampoline(node, checkMode);
31165+ Debug.assertIsDefined(result);
31166+ return result;
3116231167 };
31163- let stackIndex = 0;
31164- let lastResult: Type | undefined;
31165- while (stackIndex >= 0) {
31166- node = workStacks.expr[stackIndex];
31167- switch (workStacks.state[stackIndex]) {
31168- case CheckBinaryExpressionState.MaybeCheckLeft: {
31169- if (isInJSFile(node) && getAssignedExpandoInitializer(node)) {
31170- finishInvocation(checkExpression(node.right, checkMode));
31171- break;
31172- }
31173- checkGrammarNullishCoalesceWithLogicalExpression(node);
31174- const operator = node.operatorToken.kind;
31175- if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) {
31176- finishInvocation(checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword));
31177- break;
31178- }
31179- advanceState(CheckBinaryExpressionState.CheckRight);
31180- maybeCheckExpression(node.left);
31181- break;
31182- }
31183- case CheckBinaryExpressionState.CheckRight: {
31184- const leftType = lastResult!;
31185- workStacks.leftType[stackIndex] = leftType;
31186- const operator = node.operatorToken.kind;
31187- if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
31188- if (operator === SyntaxKind.AmpersandAmpersandToken) {
31189- const parent = walkUpParenthesizedExpressions(node.parent);
31190- checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined);
31191- }
31192- checkTruthinessOfType(leftType, node.left);
31168+
31169+ function onEnter(node: BinaryExpression, state: WorkArea | undefined, checkMode: CheckMode | undefined) {
31170+ if (state) {
31171+ state.stackIndex++;
31172+ state.skip = false;
31173+ setLeftType(state, /*type*/ undefined);
31174+ setLastResult(state, /*type*/ undefined);
31175+ }
31176+ else {
31177+ state = {
31178+ checkMode,
31179+ skip: false,
31180+ stackIndex: 0,
31181+ typeStack: [undefined, undefined],
31182+ };
31183+ }
31184+
31185+ if (isInJSFile(node) && getAssignedExpandoInitializer(node)) {
31186+ state.skip = true;
31187+ setLastResult(state, checkExpression(node.right, checkMode));
31188+ return state;
31189+ }
31190+
31191+ checkGrammarNullishCoalesceWithLogicalExpression(node);
31192+
31193+ const operator = node.operatorToken.kind;
31194+ if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) {
31195+ state.skip = true;
31196+ setLastResult(state, checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword));
31197+ return state;
31198+ }
31199+
31200+ return state;
31201+ }
31202+
31203+ function onLeft(left: Expression, state: WorkArea, _node: BinaryExpression) {
31204+ if (!state.skip) {
31205+ return maybeCheckExpression(state, left);
31206+ }
31207+ }
31208+
31209+ function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, node: BinaryExpression) {
31210+ if (!state.skip) {
31211+ const leftType = getLastResult(state);
31212+ Debug.assertIsDefined(leftType);
31213+ setLeftType(state, leftType);
31214+ setLastResult(state, /*type*/ undefined);
31215+ const operator = operatorToken.kind;
31216+ if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
31217+ if (operator === SyntaxKind.AmpersandAmpersandToken) {
31218+ const parent = walkUpParenthesizedExpressions(node.parent);
31219+ checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined);
3119331220 }
31194- advanceState(CheckBinaryExpressionState.FinishCheck);
31195- maybeCheckExpression(node.right);
31196- break;
31197- }
31198- case CheckBinaryExpressionState.FinishCheck: {
31199- const leftType = workStacks.leftType[stackIndex]!;
31200- const rightType = lastResult!;
31201- finishInvocation(checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node));
31202- break;
31221+ checkTruthinessOfType(leftType, node.left);
3120331222 }
31204- default: return Debug.fail(`Invalid state ${workStacks.state[stackIndex]} for checkBinaryExpression`);
3120531223 }
3120631224 }
3120731225
31208- return lastResult!;
31226+ function onRight(right: Expression, state: WorkArea, _node: BinaryExpression) {
31227+ if (!state.skip) {
31228+ return maybeCheckExpression(state, right);
31229+ }
31230+ }
31231+
31232+ function onExit(node: BinaryExpression, state: WorkArea): Type | undefined {
31233+ let result: Type | undefined;
31234+ if (state.skip) {
31235+ result = getLastResult(state);
31236+ }
31237+ else {
31238+ const leftType = getLeftType(state);
31239+ Debug.assertIsDefined(leftType);
31240+
31241+ const rightType = getLastResult(state);
31242+ Debug.assertIsDefined(rightType);
3120931243
31210- function finishInvocation(result: Type) {
31211- lastResult = result;
31212- stackIndex--;
31244+ result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node);
31245+ }
31246+
31247+ state.skip = false;
31248+ setLeftType(state, /*type*/ undefined);
31249+ setLastResult(state, /*type*/ undefined);
31250+ state.stackIndex--;
31251+ return result;
3121331252 }
3121431253
31215- /**
31216- * Note that `advanceState` sets the _current_ head state, and that `maybeCheckExpression` potentially pushes on a new
31217- * head state; so `advanceState` must be called before any `maybeCheckExpression` during a state's execution.
31218- */
31219- function advanceState(nextState: CheckBinaryExpressionState) {
31220- workStacks.state[stackIndex] = nextState;
31254+ function foldState(state: WorkArea, result: Type | undefined, _side: "left" | "right") {
31255+ setLastResult(state, result);
31256+ return state;
3122131257 }
3122231258
31223- function maybeCheckExpression(node: Expression) {
31259+ function maybeCheckExpression(state: WorkArea, node: Expression): BinaryExpression | undefined {
3122431260 if (isBinaryExpression(node)) {
31225- stackIndex++;
31226- workStacks.expr[stackIndex] = node;
31227- workStacks.state[stackIndex] = CheckBinaryExpressionState.MaybeCheckLeft;
31228- workStacks.leftType[stackIndex] = undefined;
31229- }
31230- else {
31231- lastResult = checkExpression(node, checkMode);
31261+ return node;
3123231262 }
31263+ setLastResult(state, checkExpression(node, state.checkMode));
31264+ }
31265+
31266+ function getLeftType(state: WorkArea) {
31267+ return state.typeStack[state.stackIndex];
31268+ }
31269+
31270+ function setLeftType(state: WorkArea, type: Type | undefined) {
31271+ state.typeStack[state.stackIndex] = type;
31272+ }
31273+
31274+ function getLastResult(state: WorkArea) {
31275+ return state.typeStack[state.stackIndex + 1];
31276+ }
31277+
31278+ function setLastResult(state: WorkArea, type: Type | undefined) {
31279+ // To reduce overhead, reuse the next stack entry to store the
31280+ // last result. This avoids the overhead of an additional property
31281+ // on `WorkArea` and reuses empty stack entries as we walk back up
31282+ // the stack.
31283+ state.typeStack[state.stackIndex + 1] = type;
3123331284 }
3123431285 }
3123531286
0 commit comments