@@ -357,6 +357,7 @@ namespace ts {
357
357
const keyofStringsOnly = !!compilerOptions.keyofStringsOnly;
358
358
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : ObjectFlags.FreshLiteral;
359
359
360
+ const checkBinaryExpression = createCheckBinaryExpression();
360
361
const emitResolver = createResolver();
361
362
const nodeBuilder = createNodeBuilder();
362
363
@@ -30981,92 +30982,142 @@ namespace ts {
30981
30982
return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target);
30982
30983
}
30983
30984
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
+ }
30989
30997
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;
30999
31004
};
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);
31030
31057
}
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);
31040
31059
}
31041
- default: return Debug.fail(`Invalid state ${workStacks.state[stackIndex]} for checkBinaryExpression`);
31042
31060
}
31043
31061
}
31044
31062
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);
31046
31080
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;
31050
31089
}
31051
31090
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;
31058
31094
}
31059
31095
31060
- function maybeCheckExpression(node: Expression) {
31096
+ function maybeCheckExpression(state: WorkArea, node: Expression): BinaryExpression | undefined {
31061
31097
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;
31069
31099
}
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;
31070
31121
}
31071
31122
}
31072
31123
0 commit comments