mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
BuildHIR for OptionalCallExpression
Implements lowering for a subset of optional calls - specifically, we don't (yet) support when the callee is a member expression or an optional member expression. So `foo?.()` works but we bailout on `object?.foo()` and `object.foo?.()`. For `<calleee>?.(<args>)` we lower as roughly: ``` bb0: t0 = <callee> OptionalCall test=bb1 fallthrough= bb1 (value): Branch t0 consequent=bb2 alternate=bb3 bb2 (value): ...lower <args> here... t1 = Call t0, args StoreLocal res, t1 Goto bb4 bb3 (value): t2 = undefined StoreLocal res, t2 Goto bb4 bb4: // result in `res` here ```
This commit is contained in:
@@ -11,7 +11,6 @@ import { Expression } from "@babel/types";
|
||||
import invariant from "invariant";
|
||||
import { CompilerError, ErrorSeverity } from "../CompilerError";
|
||||
import { Err, Ok, Result } from "../Utils/Result";
|
||||
import todo from "../Utils/todo";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
import { Environment, EnvironmentOptions } from "./Environment";
|
||||
import {
|
||||
@@ -942,9 +941,114 @@ function lowerExpression(
|
||||
}
|
||||
case "OptionalCallExpression": {
|
||||
const expr = exprPath as NodePath<t.OptionalCallExpression>;
|
||||
const _optional = expr.get("optional");
|
||||
const optional = expr.get("optional");
|
||||
const calleePath = expr.get("callee");
|
||||
if (
|
||||
calleePath.isMemberExpression() ||
|
||||
calleePath.isOptionalMemberExpression()
|
||||
) {
|
||||
// collect any errors from the arguments
|
||||
lowerArguments(builder, expr.get("arguments"));
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Support OptionalCallExpression with ${calleePath.type} callee`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
nodePath: calleePath,
|
||||
});
|
||||
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
|
||||
}
|
||||
const loc = expr.node.loc ?? GeneratedSource;
|
||||
const place = buildTemporaryPlace(builder, loc);
|
||||
const continuationBlock = builder.reserve(builder.currentBlockKind());
|
||||
|
||||
return todo("OptionalCallExpression");
|
||||
const callee = lowerExpressionToTemporary(builder, calleePath);
|
||||
|
||||
const consequent = builder.enter("value", () => {
|
||||
const args = lowerArguments(builder, expr.get("arguments"));
|
||||
const temp = buildTemporaryPlace(builder, loc);
|
||||
builder.push({
|
||||
id: makeInstructionId(0),
|
||||
lvalue: { ...temp },
|
||||
value: {
|
||||
kind: "CallExpression",
|
||||
callee: { ...callee },
|
||||
args,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
});
|
||||
builder.push({
|
||||
id: makeInstructionId(0),
|
||||
lvalue: buildTemporaryPlace(builder, loc),
|
||||
value: {
|
||||
kind: "StoreLocal",
|
||||
lvalue: { kind: InstructionKind.Const, place: { ...place } },
|
||||
value: { ...temp },
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
});
|
||||
return {
|
||||
kind: "goto",
|
||||
variant: GotoVariant.Break,
|
||||
block: continuationBlock.id,
|
||||
id: makeInstructionId(0),
|
||||
};
|
||||
});
|
||||
const alternate = builder.enter("value", () => {
|
||||
const temp = buildTemporaryPlace(builder, loc);
|
||||
builder.push({
|
||||
id: makeInstructionId(0),
|
||||
lvalue: { ...temp },
|
||||
value: {
|
||||
kind: "Primitive",
|
||||
value: undefined,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
});
|
||||
builder.push({
|
||||
id: makeInstructionId(0),
|
||||
lvalue: buildTemporaryPlace(builder, loc),
|
||||
value: {
|
||||
kind: "StoreLocal",
|
||||
lvalue: { kind: InstructionKind.Const, place: { ...place } },
|
||||
value: { ...temp },
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
});
|
||||
return {
|
||||
kind: "goto",
|
||||
variant: GotoVariant.Break,
|
||||
block: continuationBlock.id,
|
||||
id: makeInstructionId(0),
|
||||
};
|
||||
});
|
||||
|
||||
const testBlock = builder.enter("value", () => {
|
||||
return {
|
||||
kind: "branch",
|
||||
test: { ...callee },
|
||||
consequent,
|
||||
alternate,
|
||||
id: makeInstructionId(0),
|
||||
loc,
|
||||
};
|
||||
});
|
||||
|
||||
builder.terminateWithContinuation(
|
||||
{
|
||||
kind: "optional-call",
|
||||
optional,
|
||||
test: testBlock,
|
||||
fallthrough: continuationBlock.id,
|
||||
id: makeInstructionId(0),
|
||||
loc,
|
||||
},
|
||||
continuationBlock
|
||||
);
|
||||
|
||||
return { kind: "LoadLocal", place, loc: place.loc };
|
||||
}
|
||||
case "CallExpression": {
|
||||
const expr = exprPath as NodePath<t.CallExpression>;
|
||||
|
||||
@@ -355,6 +355,10 @@ export type TernaryTerminal = {
|
||||
|
||||
export type OptionalCallTerminal = {
|
||||
kind: "optional-call";
|
||||
// Whether the call itself is optional. If false, this means that the callee itself
|
||||
// is optionally evaluated (has some OptionalMemberExpresion) but the final call is
|
||||
// required (if the callee is not null/undefined).
|
||||
optional: boolean;
|
||||
test: BlockId;
|
||||
fallthrough: BlockId;
|
||||
id: InstructionId;
|
||||
|
||||
@@ -567,6 +567,7 @@ export function mapTerminalSuccessors(
|
||||
const fallthrough = fn(terminal.fallthrough);
|
||||
return {
|
||||
kind: "optional-call",
|
||||
optional: terminal.optional,
|
||||
test,
|
||||
fallthrough,
|
||||
id: makeInstructionId(0),
|
||||
|
||||
Reference in New Issue
Block a user