@@ -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
@@ -31144,92 +31145,142 @@ namespace ts {
31144
31145
return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target);
31145
31146
}
31146
31147
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
+ }
31152
31160
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;
31162
31167
};
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);
31193
31220
}
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);
31203
31222
}
31204
- default: return Debug.fail(`Invalid state ${workStacks.state[stackIndex]} for checkBinaryExpression`);
31205
31223
}
31206
31224
}
31207
31225
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);
31209
31243
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;
31213
31252
}
31214
31253
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;
31221
31257
}
31222
31258
31223
- function maybeCheckExpression(node: Expression) {
31259
+ function maybeCheckExpression(state: WorkArea, node: Expression): BinaryExpression | undefined {
31224
31260
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;
31232
31262
}
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;
31233
31284
}
31234
31285
}
31235
31286
0 commit comments