mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 08:11:30 -06:00
Use shared binary trampoline in checker (#43035)
This commit is contained in:
parent
15fae38b39
commit
9af313db77
@ -357,6 +357,7 @@ namespace ts {
|
||||
const keyofStringsOnly = !!compilerOptions.keyofStringsOnly;
|
||||
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : ObjectFlags.FreshLiteral;
|
||||
|
||||
const checkBinaryExpression = createCheckBinaryExpression();
|
||||
const emitResolver = createResolver();
|
||||
const nodeBuilder = createNodeBuilder();
|
||||
|
||||
@ -31144,92 +31145,142 @@ namespace ts {
|
||||
return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target);
|
||||
}
|
||||
|
||||
const enum CheckBinaryExpressionState {
|
||||
MaybeCheckLeft,
|
||||
CheckRight,
|
||||
FinishCheck
|
||||
}
|
||||
function createCheckBinaryExpression() {
|
||||
interface WorkArea {
|
||||
readonly checkMode: CheckMode | undefined;
|
||||
skip: boolean;
|
||||
stackIndex: number;
|
||||
/**
|
||||
* Holds the types from the left-side of an expression from [0..stackIndex].
|
||||
* Holds the type of the result at stackIndex+1. This allows us to reuse existing stack entries
|
||||
* and avoid storing an extra property on the object (i.e., `lastResult`).
|
||||
*/
|
||||
typeStack: (Type | undefined)[];
|
||||
}
|
||||
|
||||
function checkBinaryExpression(node: BinaryExpression, checkMode?: CheckMode) {
|
||||
const workStacks: {
|
||||
expr: BinaryExpression[],
|
||||
state: CheckBinaryExpressionState[],
|
||||
leftType: (Type | undefined)[]
|
||||
} = {
|
||||
expr: [node],
|
||||
state: [CheckBinaryExpressionState.MaybeCheckLeft],
|
||||
leftType: [undefined]
|
||||
const trampoline = createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, foldState);
|
||||
|
||||
return (node: BinaryExpression, checkMode: CheckMode | undefined) => {
|
||||
const result = trampoline(node, checkMode);
|
||||
Debug.assertIsDefined(result);
|
||||
return result;
|
||||
};
|
||||
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) {
|
||||
if (operator === SyntaxKind.AmpersandAmpersandToken) {
|
||||
const parent = walkUpParenthesizedExpressions(node.parent);
|
||||
checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined);
|
||||
}
|
||||
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;
|
||||
function onEnter(node: BinaryExpression, state: WorkArea | undefined, checkMode: CheckMode | undefined) {
|
||||
if (state) {
|
||||
state.stackIndex++;
|
||||
state.skip = false;
|
||||
setLeftType(state, /*type*/ undefined);
|
||||
setLastResult(state, /*type*/ undefined);
|
||||
}
|
||||
else {
|
||||
lastResult = checkExpression(node, checkMode);
|
||||
state = {
|
||||
checkMode,
|
||||
skip: false,
|
||||
stackIndex: 0,
|
||||
typeStack: [undefined, undefined],
|
||||
};
|
||||
}
|
||||
|
||||
if (isInJSFile(node) && getAssignedExpandoInitializer(node)) {
|
||||
state.skip = true;
|
||||
setLastResult(state, checkExpression(node.right, checkMode));
|
||||
return state;
|
||||
}
|
||||
|
||||
checkGrammarNullishCoalesceWithLogicalExpression(node);
|
||||
|
||||
const operator = node.operatorToken.kind;
|
||||
if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) {
|
||||
state.skip = true;
|
||||
setLastResult(state, checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword));
|
||||
return state;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function onLeft(left: Expression, state: WorkArea, _node: BinaryExpression) {
|
||||
if (!state.skip) {
|
||||
return maybeCheckExpression(state, left);
|
||||
}
|
||||
}
|
||||
|
||||
function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, node: BinaryExpression) {
|
||||
if (!state.skip) {
|
||||
const leftType = getLastResult(state);
|
||||
Debug.assertIsDefined(leftType);
|
||||
setLeftType(state, leftType);
|
||||
setLastResult(state, /*type*/ undefined);
|
||||
const operator = operatorToken.kind;
|
||||
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
|
||||
if (operator === SyntaxKind.AmpersandAmpersandToken) {
|
||||
const parent = walkUpParenthesizedExpressions(node.parent);
|
||||
checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined);
|
||||
}
|
||||
checkTruthinessOfType(leftType, node.left);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onRight(right: Expression, state: WorkArea, _node: BinaryExpression) {
|
||||
if (!state.skip) {
|
||||
return maybeCheckExpression(state, right);
|
||||
}
|
||||
}
|
||||
|
||||
function onExit(node: BinaryExpression, state: WorkArea): Type | undefined {
|
||||
let result: Type | undefined;
|
||||
if (state.skip) {
|
||||
result = getLastResult(state);
|
||||
}
|
||||
else {
|
||||
const leftType = getLeftType(state);
|
||||
Debug.assertIsDefined(leftType);
|
||||
|
||||
const rightType = getLastResult(state);
|
||||
Debug.assertIsDefined(rightType);
|
||||
|
||||
result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node);
|
||||
}
|
||||
|
||||
state.skip = false;
|
||||
setLeftType(state, /*type*/ undefined);
|
||||
setLastResult(state, /*type*/ undefined);
|
||||
state.stackIndex--;
|
||||
return result;
|
||||
}
|
||||
|
||||
function foldState(state: WorkArea, result: Type | undefined, _side: "left" | "right") {
|
||||
setLastResult(state, result);
|
||||
return state;
|
||||
}
|
||||
|
||||
function maybeCheckExpression(state: WorkArea, node: Expression): BinaryExpression | undefined {
|
||||
if (isBinaryExpression(node)) {
|
||||
return node;
|
||||
}
|
||||
setLastResult(state, checkExpression(node, state.checkMode));
|
||||
}
|
||||
|
||||
function getLeftType(state: WorkArea) {
|
||||
return state.typeStack[state.stackIndex];
|
||||
}
|
||||
|
||||
function setLeftType(state: WorkArea, type: Type | undefined) {
|
||||
state.typeStack[state.stackIndex] = type;
|
||||
}
|
||||
|
||||
function getLastResult(state: WorkArea) {
|
||||
return state.typeStack[state.stackIndex + 1];
|
||||
}
|
||||
|
||||
function setLastResult(state: WorkArea, type: Type | undefined) {
|
||||
// To reduce overhead, reuse the next stack entry to store the
|
||||
// last result. This avoids the overhead of an additional property
|
||||
// on `WorkArea` and reuses empty stack entries as we walk back up
|
||||
// the stack.
|
||||
state.typeStack[state.stackIndex + 1] = type;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -930,7 +930,7 @@ namespace ts {
|
||||
return isBinaryOperator(node.kind);
|
||||
}
|
||||
|
||||
type BinaryExpressionState = <TState, TResult>(machine: BinaryExpressionStateMachine<TState, TResult>, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], resultHolder: { value: TResult }) => number;
|
||||
type BinaryExpressionState = <TOuterState, TState, TResult>(machine: BinaryExpressionStateMachine<TOuterState, TState, TResult>, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], resultHolder: { value: TResult }, outerState: TOuterState) => number;
|
||||
|
||||
namespace BinaryExpressionState {
|
||||
/**
|
||||
@ -939,10 +939,10 @@ namespace ts {
|
||||
* @param frame The current frame
|
||||
* @returns The new frame
|
||||
*/
|
||||
export function enter<TState, TResult>(machine: BinaryExpressionStateMachine<TState, TResult>, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }): number {
|
||||
export function enter<TOuterState, TState, TResult>(machine: BinaryExpressionStateMachine<TOuterState, TState, TResult>, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, outerState: TOuterState): number {
|
||||
const prevUserState = stackIndex > 0 ? userStateStack[stackIndex - 1] : undefined;
|
||||
Debug.assertEqual(stateStack[stackIndex], enter);
|
||||
userStateStack[stackIndex] = machine.onEnter(nodeStack[stackIndex], prevUserState);
|
||||
userStateStack[stackIndex] = machine.onEnter(nodeStack[stackIndex], prevUserState, outerState);
|
||||
stateStack[stackIndex] = nextState(machine, enter);
|
||||
return stackIndex;
|
||||
}
|
||||
@ -953,7 +953,7 @@ namespace ts {
|
||||
* @param frame The current frame
|
||||
* @returns The new frame
|
||||
*/
|
||||
export function left<TState, TResult>(machine: BinaryExpressionStateMachine<TState, TResult>, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }): number {
|
||||
export function left<TOuterState, TState, TResult>(machine: BinaryExpressionStateMachine<TOuterState, TState, TResult>, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number {
|
||||
Debug.assertEqual(stateStack[stackIndex], left);
|
||||
Debug.assertIsDefined(machine.onLeft);
|
||||
stateStack[stackIndex] = nextState(machine, left);
|
||||
@ -971,7 +971,7 @@ namespace ts {
|
||||
* @param frame The current frame
|
||||
* @returns The new frame
|
||||
*/
|
||||
export function operator<TState, TResult>(machine: BinaryExpressionStateMachine<TState, TResult>, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }): number {
|
||||
export function operator<TOuterState, TState, TResult>(machine: BinaryExpressionStateMachine<TOuterState, TState, TResult>, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number {
|
||||
Debug.assertEqual(stateStack[stackIndex], operator);
|
||||
Debug.assertIsDefined(machine.onOperator);
|
||||
stateStack[stackIndex] = nextState(machine, operator);
|
||||
@ -985,7 +985,7 @@ namespace ts {
|
||||
* @param frame The current frame
|
||||
* @returns The new frame
|
||||
*/
|
||||
export function right<TState, TResult>(machine: BinaryExpressionStateMachine<TState, TResult>, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }): number {
|
||||
export function right<TOuterState, TState, TResult>(machine: BinaryExpressionStateMachine<TOuterState, TState, TResult>, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number {
|
||||
Debug.assertEqual(stateStack[stackIndex], right);
|
||||
Debug.assertIsDefined(machine.onRight);
|
||||
stateStack[stackIndex] = nextState(machine, right);
|
||||
@ -1003,7 +1003,7 @@ namespace ts {
|
||||
* @param frame The current frame
|
||||
* @returns The new frame
|
||||
*/
|
||||
export function exit<TState, TResult>(machine: BinaryExpressionStateMachine<TState, TResult>, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], resultHolder: { value: TResult }): number {
|
||||
export function exit<TOuterState, TState, TResult>(machine: BinaryExpressionStateMachine<TOuterState, TState, TResult>, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], resultHolder: { value: TResult }, _outerState: TOuterState): number {
|
||||
Debug.assertEqual(stateStack[stackIndex], exit);
|
||||
stateStack[stackIndex] = nextState(machine, exit);
|
||||
const result = machine.onExit(nodeStack[stackIndex], userStateStack[stackIndex]);
|
||||
@ -1024,12 +1024,12 @@ namespace ts {
|
||||
* Handles a frame that is already done.
|
||||
* @returns The `done` state.
|
||||
*/
|
||||
export function done<TState, TResult>(_machine: BinaryExpressionStateMachine<TState, TResult>, stackIndex: number, stateStack: BinaryExpressionState[], _nodeStack: BinaryExpression[], _userStateStack: TState[], _resultHolder: { value: TResult }): number {
|
||||
export function done<TOuterState, TState, TResult>(_machine: BinaryExpressionStateMachine<TOuterState, TState, TResult>, stackIndex: number, stateStack: BinaryExpressionState[], _nodeStack: BinaryExpression[], _userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number {
|
||||
Debug.assertEqual(stateStack[stackIndex], done);
|
||||
return stackIndex;
|
||||
}
|
||||
|
||||
export function nextState<TState, TResult>(machine: BinaryExpressionStateMachine<TState, TResult>, currentState: BinaryExpressionState) {
|
||||
export function nextState<TOuterState, TState, TResult>(machine: BinaryExpressionStateMachine<TOuterState, TState, TResult>, currentState: BinaryExpressionState) {
|
||||
switch (currentState) {
|
||||
case enter:
|
||||
if (machine.onLeft) return left;
|
||||
@ -1068,9 +1068,9 @@ namespace ts {
|
||||
/**
|
||||
* Holds state machine handler functions
|
||||
*/
|
||||
class BinaryExpressionStateMachine<TState, TResult> {
|
||||
class BinaryExpressionStateMachine<TOuterState, TState, TResult> {
|
||||
constructor(
|
||||
readonly onEnter: (node: BinaryExpression, prev: TState | undefined) => TState,
|
||||
readonly onEnter: (node: BinaryExpression, prev: TState | undefined, outerState: TOuterState) => TState,
|
||||
readonly onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined,
|
||||
readonly onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined,
|
||||
readonly onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined,
|
||||
@ -1089,26 +1089,53 @@ namespace ts {
|
||||
* @param foldState Callback evaluated when the result from a nested `onExit` should be folded into the state of that node's parent.
|
||||
* @returns A function that walks a `BinaryExpression` node using the above callbacks, returning the result of the call to `onExit` from the outermost `BinaryExpression` node.
|
||||
*/
|
||||
export function createBinaryExpressionTrampoline<TState, TResult>(
|
||||
export function createBinaryExpressionTrampoline<TState, TResult>(
|
||||
onEnter: (node: BinaryExpression, prev: TState | undefined) => TState,
|
||||
onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined,
|
||||
onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined,
|
||||
onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined,
|
||||
onExit: (node: BinaryExpression, userState: TState) => TResult,
|
||||
foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined,
|
||||
): (node: BinaryExpression) => TResult;
|
||||
/**
|
||||
* Creates a state machine that walks a `BinaryExpression` using the heap to reduce call-stack depth on a large tree.
|
||||
* @param onEnter Callback evaluated when entering a `BinaryExpression`. Returns new user-defined state to associate with the node while walking.
|
||||
* @param onLeft Callback evaluated when walking the left side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the right side.
|
||||
* @param onRight Callback evaluated when walking the right side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the end of the node.
|
||||
* @param onExit Callback evaluated when exiting a `BinaryExpression`. The result returned will either be folded into the parent's state, or returned from the walker if at the top frame.
|
||||
* @param foldState Callback evaluated when the result from a nested `onExit` should be folded into the state of that node's parent.
|
||||
* @returns A function that walks a `BinaryExpression` node using the above callbacks, returning the result of the call to `onExit` from the outermost `BinaryExpression` node.
|
||||
*/
|
||||
export function createBinaryExpressionTrampoline<TOuterState, TState, TResult>(
|
||||
onEnter: (node: BinaryExpression, prev: TState | undefined, outerState: TOuterState) => TState,
|
||||
onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined,
|
||||
onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined,
|
||||
onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined,
|
||||
onExit: (node: BinaryExpression, userState: TState) => TResult,
|
||||
foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined,
|
||||
): (node: BinaryExpression, outerState: TOuterState) => TResult;
|
||||
export function createBinaryExpressionTrampoline<TOuterState, TState, TResult>(
|
||||
onEnter: (node: BinaryExpression, prev: TState | undefined, outerState: TOuterState) => TState,
|
||||
onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined,
|
||||
onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined,
|
||||
onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined,
|
||||
onExit: (node: BinaryExpression, userState: TState) => TResult,
|
||||
foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined,
|
||||
) {
|
||||
const machine = new BinaryExpressionStateMachine(onEnter, onLeft, onOperator, onRight, onExit, foldState);
|
||||
return (node: BinaryExpression) => {
|
||||
return trampoline;
|
||||
|
||||
function trampoline(node: BinaryExpression, outerState?: TOuterState) {
|
||||
const resultHolder: { value: TResult } = { value: undefined! };
|
||||
const stateStack: BinaryExpressionState[] = [BinaryExpressionState.enter];
|
||||
const nodeStack: BinaryExpression[] = [node];
|
||||
const userStateStack: TState[] = [undefined!];
|
||||
let stackIndex = 0;
|
||||
while (stateStack[stackIndex] !== BinaryExpressionState.done) {
|
||||
stackIndex = stateStack[stackIndex](machine, stackIndex, stateStack, nodeStack, userStateStack, resultHolder);
|
||||
stackIndex = stateStack[stackIndex](machine, stackIndex, stateStack, nodeStack, userStateStack, resultHolder, outerState);
|
||||
}
|
||||
Debug.assertEqual(stackIndex, 0);
|
||||
return resultHolder.value;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user