Use shared binary trampoline in checker (#43035)

This commit is contained in:
Ron Buckton 2021-03-19 18:54:51 -07:00 committed by GitHub
parent 15fae38b39
commit 9af313db77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 173 additions and 95 deletions

View File

@ -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;
}
}

View File

@ -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;
};
}
}
}