WIP: experiment with getting rid of bindLogicalLikeExpression

This commit is contained in:
Gabriela Araujo Britto 2021-11-23 18:09:46 -08:00
parent 1ade73df2b
commit 7684d66ba2
4 changed files with 1140 additions and 37 deletions

View file

@ -1433,15 +1433,15 @@ namespace ts {
} }
function bindLogicalLikeExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) { function bindLogicalLikeExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
const preRightLabel = createBranchLabel(); const preRightLabel = createBranchLabel(); // V
if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === SyntaxKind.AmpersandAmpersandEqualsToken) { if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === SyntaxKind.AmpersandAmpersandEqualsToken) {
bindCondition(node.left, preRightLabel, falseTarget); bindCondition(node.left, preRightLabel, falseTarget);
} }
else { else {
bindCondition(node.left, trueTarget, preRightLabel); bindCondition(node.left, trueTarget, preRightLabel);
} }
currentFlow = finishFlowLabel(preRightLabel); currentFlow = finishFlowLabel(preRightLabel); // V
bind(node.operatorToken); bind(node.operatorToken); // V
if (isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind)) { if (isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind)) {
doWithConditionalBranches(bind, node.right, trueTarget, falseTarget); doWithConditionalBranches(bind, node.right, trueTarget, falseTarget);
@ -1498,11 +1498,21 @@ namespace ts {
} }
function createBindBinaryExpressionFlow() { function createBindBinaryExpressionFlow() {
interface LogicalLikeState {
trueTarget: FlowLabel;
falseTarget: FlowLabel;
preRightLabel: FlowLabel;
postExpressionLabel?: FlowLabel;
}
interface WorkArea { interface WorkArea {
stackIndex: number; stackIndex: number;
skip: boolean; // skip: boolean;
inStrictModeStack: (boolean | undefined)[]; inStrictModeStack: (boolean | undefined)[];
parentStack: (Node | undefined)[]; parentStack: (Node | undefined)[];
currentBranchTargetStack: ([FlowLabel | undefined, FlowLabel | undefined])[];
logicalStack: (LogicalLikeState | undefined)[];
isLogicalLike: boolean;
} }
return createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, /*foldState*/ undefined); return createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, /*foldState*/ undefined);
@ -1517,70 +1527,139 @@ namespace ts {
bindWorker(node); bindWorker(node);
const saveParent = parent; const saveParent = parent;
parent = node; parent = node;
state.skip = false; // state.skip = false;
state.inStrictModeStack[state.stackIndex] = saveInStrictMode; state.inStrictModeStack[state.stackIndex] = saveInStrictMode;
state.parentStack[state.stackIndex] = saveParent; state.parentStack[state.stackIndex] = saveParent;
state.currentBranchTargetStack[state.stackIndex] = [currentTrueTarget, currentFalseTarget];
} }
else { else {
state = { state = {
stackIndex: 0, stackIndex: 0,
skip: false, // skip: false,
inStrictModeStack: [undefined], inStrictModeStack: [undefined],
parentStack: [undefined] parentStack: [undefined],
logicalStack: [undefined],
currentBranchTargetStack: [[currentTrueTarget, currentFalseTarget]],
isLogicalLike: false,
}; };
} }
// TODO: bindLogicalExpression is recursive - if we want to handle deeply nested `&&` expressions
// we'll need to handle the `bindLogicalExpression` scenarios in this state machine, too
// For now, though, since the common cases are chained `+`, leaving it recursive is fine
const operator = node.operatorToken.kind; const operator = node.operatorToken.kind;
if (operator === SyntaxKind.AmpersandAmpersandToken || if (operator === SyntaxKind.AmpersandAmpersandToken ||
operator === SyntaxKind.BarBarToken || operator === SyntaxKind.BarBarToken ||
operator === SyntaxKind.QuestionQuestionToken || operator === SyntaxKind.QuestionQuestionToken ||
isLogicalOrCoalescingAssignmentOperator(operator)) { isLogicalOrCoalescingAssignmentOperator(operator)) {
state.isLogicalLike = true;
let logicalState: LogicalLikeState;
const preRightLabel = createBranchLabel();
if (isTopLevelLogicalExpression(node)) { if (isTopLevelLogicalExpression(node)) {
const postExpressionLabel = createBranchLabel(); const postExpressionLabel = createBranchLabel(); // >> TODO: we need to set current flow afterwards, at the end of onexit
bindLogicalLikeExpression(node, postExpressionLabel, postExpressionLabel); logicalState = {
currentFlow = finishFlowLabel(postExpressionLabel); preRightLabel,
trueTarget: postExpressionLabel,
falseTarget: postExpressionLabel,
postExpressionLabel,
};
// bindLogicalLikeExpression(node, postExpressionLabel, postExpressionLabel);
// currentFlow = finishFlowLabel(postExpressionLabel); // >> TODO: on exit?
} }
else { else {
bindLogicalLikeExpression(node, currentTrueTarget!, currentFalseTarget!); logicalState = {
preRightLabel,
trueTarget: currentTrueTarget!,
falseTarget: currentFalseTarget!,
};
} }
state.skip = true; state.logicalStack[state.stackIndex] = logicalState;
} }
// TODO: bindLogicalExpression is recursive - if we want to handle deeply nested `&&` expressions
// we'll need to handle the `bindLogicalExpression` scenarios in this state machine, too
// For now, though, since the common cases are chained `+`, leaving it recursive is fine // >> TODO: suspiscious
// const operator = node.operatorToken.kind;
// if (operator === SyntaxKind.AmpersandAmpersandToken ||
// operator === SyntaxKind.BarBarToken ||
// operator === SyntaxKind.QuestionQuestionToken ||
// isLogicalOrCoalescingAssignmentOperator(operator)) {
// if (isTopLevelLogicalExpression(node)) {
// const postExpressionLabel = createBranchLabel();
// bindLogicalLikeExpression(node, postExpressionLabel, postExpressionLabel);
// currentFlow = finishFlowLabel(postExpressionLabel);
// }
// else {
// bindLogicalLikeExpression(node, currentTrueTarget!, currentFalseTarget!);
// }
// state.skip = true;
// }
return state; return state;
} }
function onLeft(left: Expression, state: WorkArea, _node: BinaryExpression) { function onLeft(left: Expression, state: WorkArea, node: BinaryExpression) {
if (!state.skip) { if (state.isLogicalLike) {
return maybeBind(left); if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken ||
node.operatorToken.kind === SyntaxKind.AmpersandAmpersandEqualsToken) {
currentTrueTarget = state.logicalStack[state.stackIndex]!.preRightLabel;
currentFalseTarget = state.logicalStack[state.stackIndex]!.falseTarget;
}
else {
currentTrueTarget = state.logicalStack[state.stackIndex]!.trueTarget;
currentFalseTarget = state.logicalStack[state.stackIndex]!.preRightLabel;
}
} }
return maybeBind(left);
// if (!state.skip) {
// return maybeBind(left);
// }
} }
function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, node: BinaryExpression) { function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, node: BinaryExpression) {
if (!state.skip) { if (state.isLogicalLike) {
if (operatorToken.kind === SyntaxKind.CommaToken) { if (!isLogicalAssignmentExpression(node) && !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) {
maybeBindExpressionFlowIfCall(node.left); addAntecedent(currentTrueTarget!, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node));
addAntecedent(currentFalseTarget!, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node));
} }
bind(operatorToken); // >> TODO: restore current true and current false right here, to emulate end of `bindCondition` call
currentFlow = finishFlowLabel(state.logicalStack[state.stackIndex]!.preRightLabel);
} }
else if (operatorToken.kind === SyntaxKind.CommaToken) {
maybeBindExpressionFlowIfCall(node.left);
}
bind(operatorToken);
// if (!state.skip) {
// if (operatorToken.kind === SyntaxKind.CommaToken) {
// maybeBindExpressionFlowIfCall(node.left);
// }
// bind(operatorToken);
// }
} }
function onRight(right: Expression, state: WorkArea, _node: BinaryExpression) { function onRight(right: Expression, state: WorkArea, node: BinaryExpression) {
if (!state.skip) { if (state.isLogicalLike) {
return maybeBind(right); // >> TODO: do special stuff if assignment operator
currentTrueTarget = state.logicalStack[state.stackIndex]!.trueTarget;
currentFalseTarget = state.logicalStack[state.stackIndex]!.falseTarget;
} }
return maybeBind(right);
// if (!state.skip) {
// return maybeBind(right);
// }
} }
function onExit(node: BinaryExpression, state: WorkArea) { function onExit(node: BinaryExpression, state: WorkArea) {
if (!state.skip) { const operator = node.operatorToken.kind;
const operator = node.operatorToken.kind; if (state.isLogicalLike) {
if (isAssignmentOperator(operator) && !isAssignmentTarget(node)) { const postExpressionLabel = state.logicalStack[state.stackIndex]?.postExpressionLabel;
bindAssignmentTargetFlow(node.left); if (postExpressionLabel) {
if (operator === SyntaxKind.EqualsToken && node.left.kind === SyntaxKind.ElementAccessExpression) { currentFlow = finishFlowLabel(postExpressionLabel);
const elementAccess = node.left as ElementAccessExpression; }
if (isNarrowableOperand(elementAccess.expression)) { }
currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node); else if (isAssignmentOperator(operator) && !isAssignmentTarget(node)) {
} bindAssignmentTargetFlow(node.left);
if (operator === SyntaxKind.EqualsToken && node.left.kind === SyntaxKind.ElementAccessExpression) {
const elementAccess = node.left as ElementAccessExpression;
if (isNarrowableOperand(elementAccess.expression)) {
currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node);
} }
} }
} }
@ -1592,7 +1671,9 @@ namespace ts {
if (savedParent !== undefined) { if (savedParent !== undefined) {
parent = savedParent; parent = savedParent;
} }
state.skip = false; currentTrueTarget = state.currentBranchTargetStack[state.stackIndex][0];
currentFalseTarget = state.currentBranchTargetStack[state.stackIndex][1];
state.isLogicalLike = false;
state.stackIndex--; state.stackIndex--;
} }

View file

@ -1016,7 +1016,15 @@ namespace ts {
* @param frame The current frame * @param frame The current frame
* @returns The new frame * @returns The new frame
*/ */
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 { 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; const prevUserState = stackIndex > 0 ? userStateStack[stackIndex - 1] : undefined;
Debug.assertEqual(stateStack[stackIndex], enter); Debug.assertEqual(stateStack[stackIndex], enter);
userStateStack[stackIndex] = machine.onEnter(nodeStack[stackIndex], prevUserState, outerState); userStateStack[stackIndex] = machine.onEnter(nodeStack[stackIndex], prevUserState, outerState);
@ -1030,7 +1038,15 @@ namespace ts {
* @param frame The current frame * @param frame The current frame
* @returns The new frame * @returns The new frame
*/ */
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 { 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.assertEqual(stateStack[stackIndex], left);
Debug.assertIsDefined(machine.onLeft); Debug.assertIsDefined(machine.onLeft);
stateStack[stackIndex] = nextState(machine, left); stateStack[stackIndex] = nextState(machine, left);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
const x = "a" + "b" + "c" + "d" + "e" + "f" + "g";