Skip to content

Commit

Permalink
Simplify rescue node translation
Browse files Browse the repository at this point in the history
  • Loading branch information
st0012 committed Oct 15, 2024
1 parent 4d476d2 commit ec9f95e
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 56 deletions.
87 changes: 46 additions & 41 deletions parser/prism/Translator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -170,19 +170,17 @@ unique_ptr<parser::Node> Translator::translate(pm_node_t *node) {

NodeVec statements;

if (auto prismRescue = beginNode->rescue_clause; prismRescue != nullptr) {
auto sorbetBeginNode = translateStatements(beginNode->statements, true);

if (beginNode->rescue_clause != nullptr) {
// Handle `begin ... rescue ... end` and `begin ... rescue ... else ... end`
auto bodyNode = translateStatements(beginNode->statements, true);
auto elseNode = translate(reinterpret_cast<pm_node_t *>(beginNode->else_clause));

auto beginRescueNode = translateRescue(reinterpret_cast<pm_rescue_node *>(prismRescue),
move(sorbetBeginNode), move(elseNode));
auto beginRescueNode = translateRescue(reinterpret_cast<pm_rescue_node *>(beginNode->rescue_clause),
move(bodyNode), move(elseNode));

statements.emplace_back(move(beginRescueNode));
} else {
if (auto prismStatements = beginNode->statements; prismStatements != nullptr) {
statements = translateMulti(prismStatements->body);
}
} else if (beginNode->statements != nullptr) {
// Handle `begin ... end`
statements = translateMulti(beginNode->statements->body);
}

return make_unique<parser::Kwbegin>(location, move(statements));
Expand Down Expand Up @@ -1350,43 +1348,50 @@ unique_ptr<parser::Node> Translator::translateCallWithBlock(pm_block_node *prism
return make_unique<parser::Block>(sendNode->loc, move(sendNode), move(blockParametersNode), move(body));
}

// Prism models a rescue clause as a `pm_begin_node` that contains a `pm_rescue_node`.
// Sorbet's legacy parser models this the other way around, as a parent `Rescue` with a child `Begin`.
// Prism represents a `begin ... rescue ... end` construct using a `pm_begin_node` that may contain:
// - `statements`: the code before the `rescue` clauses (the main body).
// - `rescue_clause`: a `pm_rescue_node` representing the first `rescue` clause.
// - `else_clause`: an optional `pm_else_node` representing the `else` clause.
//
// This function translates between the two, creating a `Rescue` node for the given `pm_rescue_node *`,
// and wrapping it around the given `Begin` node.
unique_ptr<parser::Node> Translator::translateRescue(pm_rescue_node *prismRescueNode,
std::unique_ptr<parser::Node> beginNode,
std::unique_ptr<parser::Node> elseNode) {
auto var = translate(prismRescueNode->reference);
// The exceptions being rescued, e.g., `Foo, Bar` in `rescue Foo, Bar => e`
auto exceptions = translateMulti(prismRescueNode->exceptions);
auto rescueBody = translateStatements(prismRescueNode->statements, true);

auto exceptionsArray =
exceptions.empty() ? nullptr
: make_unique<parser::Array>(translateLoc(prismRescueNode->base.location), move(exceptions));

NodeVec rescueBodies{};
rescueBodies.emplace_back(make_unique<parser::Resbody>(translateLoc(prismRescueNode->base.location),
move(exceptionsArray), move(var), move(rescueBody)));

// Loop through subsequent rescue clauses
for (pm_rescue_node *subsequent = prismRescueNode->consequent; subsequent != nullptr;
subsequent = subsequent->consequent) {
auto var = translate(subsequent->reference);
auto exceptions = translateMulti(subsequent->exceptions);
auto rescueBody = translateStatements(subsequent->statements, true);

// Each `pm_rescue_node` represents a single `rescue` clause and is linked to subsequent `rescue` clauses via its
// `consequent` pointer. Each `pm_rescue_node` contains:
// - `exceptions`: the exceptions to rescue (e.g., `RuntimeError`).
// - `reference`: the exception variable (e.g., `=> e`).
// - `statements`: the body of the rescue clause.
//
// In contrast, Sorbet's legacy parser represents the same construct using a `Rescue` node that contains:
// - `body`: the code before the `rescue` clauses (the main body).
// - `rescue`: a list of `Resbody` nodes, each representing a `rescue` clause.
// - `else_`: an optional node representing the `else` clause.
//
// This function and the PM_BEGIN_NODE case translate between the two representations by processing the `pm_rescue_node`
// (and its linked `subsequent` nodes) and assembling the corresponding `Rescue` and `Resbody` nodes in Sorbet's AST.
unique_ptr<parser::Node> Translator::translateRescue(pm_rescue_node *prismRescueNode, unique_ptr<parser::Node> bodyNode,
unique_ptr<parser::Node> elseNode) {
NodeVec rescueBodies;

// Each `rescue` clause generates a `Resbody` node, which is a child of the `Rescue` node.
for (pm_rescue_node *currentRescueNode = prismRescueNode; currentRescueNode != nullptr;
currentRescueNode = currentRescueNode->consequent) {
// Translate the exception variable (e.g. the `=> e` in `rescue => e`)
auto var = translate(currentRescueNode->reference);

// Translate the body of the rescue clause
auto rescueBody = translateStatements(currentRescueNode->statements, true);

// Translate the exceptions being rescued (e.g., `RuntimeError` in `rescue RuntimeError`)
auto exceptions = translateMulti(currentRescueNode->exceptions);
auto exceptionsArray =
exceptions.empty() ? nullptr
: make_unique<parser::Array>(translateLoc(subsequent->base.location), move(exceptions));
exceptions.empty()
? nullptr
: make_unique<parser::Array>(translateLoc(currentRescueNode->base.location), move(exceptions));

rescueBodies.emplace_back(make_unique<parser::Resbody>(translateLoc(subsequent->base.location),
rescueBodies.emplace_back(make_unique<parser::Resbody>(translateLoc(currentRescueNode->base.location),
move(exceptionsArray), move(var), move(rescueBody)));
}

return make_unique<parser::Rescue>(beginNode->loc, move(beginNode), move(rescueBodies), move(elseNode));
// The `Rescue` node combines the main body, the rescue clauses, and the else clause.
return make_unique<parser::Rescue>(bodyNode->loc, move(bodyNode), move(rescueBodies), move(elseNode));
}

// Translates the given Prism Statements Node into a `parser::Begin` node or an inlined `parser::Node`.
Expand Down
158 changes: 148 additions & 10 deletions test/prism_regression/rescue.parse-tree.exp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ Begin {
Kwbegin {
stmts = [
Rescue {
body = Integer {
val = "123"
body = Send {
receiver = NULL
method = <U bar>
args = [
]
}
rescue = [
Resbody {
Expand All @@ -22,8 +25,43 @@ Begin {
Kwbegin {
stmts = [
Rescue {
body = Integer {
val = "123"
body = Begin {
stmts = [
Send {
receiver = NULL
method = <U foo>
args = [
]
}
Send {
receiver = NULL
method = <U bar>
args = [
]
}
]
}
rescue = [
Resbody {
exception = NULL
var = NULL
body = String {
val = <U rescued>
}
}
]
else_ = NULL
}
]
}
Kwbegin {
stmts = [
Rescue {
body = Send {
receiver = NULL
method = <U foo>
args = [
]
}
rescue = [
Resbody {
Expand All @@ -48,8 +86,11 @@ Begin {
Kwbegin {
stmts = [
Rescue {
body = Integer {
val = "123"
body = Send {
receiver = NULL
method = <U foo>
args = [
]
}
rescue = [
Resbody {
Expand Down Expand Up @@ -91,8 +132,11 @@ Begin {
Kwbegin {
stmts = [
Rescue {
body = Integer {
val = "123"
body = Send {
receiver = NULL
method = <U foo>
args = [
]
}
rescue = [
Resbody {
Expand All @@ -119,8 +163,11 @@ Begin {
Kwbegin {
stmts = [
Rescue {
body = Integer {
val = "123"
body = Send {
receiver = NULL
method = <U foo>
args = [
]
}
rescue = [
Resbody {
Expand Down Expand Up @@ -202,6 +249,97 @@ Begin {
}
]
}
Kwbegin {
stmts = [
Rescue {
body = Send {
receiver = NULL
method = <U foo>
args = [
]
}
rescue = [
Resbody {
exception = Array {
elts = [
Const {
scope = NULL
name = <C <U RuntimeError>>
}
]
}
var = LVarLhs {
name = <U e>
}
body = DString {
nodes = [
String {
val = <U rescued Foo >
}
Begin {
stmts = [
LVar {
name = <U e>
}
]
}
]
}
}
Resbody {
exception = Array {
elts = [
Const {
scope = NULL
name = <C <U NotImplementedError>>
}
]
}
var = LVarLhs {
name = <U e>
}
body = DString {
nodes = [
String {
val = <U rescued Bar >
}
Begin {
stmts = [
LVar {
name = <U e>
}
]
}
]
}
}
Resbody {
exception = NULL
var = LVarLhs {
name = <U e>
}
body = DString {
nodes = [
String {
val = <U rescued >
}
Begin {
stmts = [
LVar {
name = <U e>
}
]
}
]
}
}
]
else_ = String {
val = <U rescued else>
}
}
]
}
Rescue {
body = Send {
receiver = NULL
Expand Down
29 changes: 24 additions & 5 deletions test/prism_regression/rescue.rb
Original file line number Diff line number Diff line change
@@ -1,39 +1,58 @@
# typed: false

begin
123
bar
rescue
"rescued"
end

begin
123
foo
bar
rescue
"rescued"
end

begin
foo
rescue RuntimeError
"rescued Foo"
end

begin
123
foo
rescue RuntimeError, NotImplementedError => e
"rescued Foo #{e}"
end

begin
123
foo
rescue RuntimeError
"rescued Foo"
else
"rescued else"
end

begin
123
foo
rescue RuntimeError => e
"rescued Foo #{e}"
rescue NotImplementedError => e
"rescued Bar #{e}"
rescue => e
"rescued #{e}"
end

begin
foo
rescue RuntimeError => e
"rescued Foo #{e}"
rescue NotImplementedError => e
"rescued Bar #{e}"
rescue => e
"rescued #{e}"
else
"rescued else"
end

problematic_code rescue puts "rescued"
Expand Down

0 comments on commit ec9f95e

Please sign in to comment.