diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000000..19b2c91d45f --- /dev/null +++ b/Gemfile @@ -0,0 +1 @@ +source "https://rubygems.org" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000000..a4da68e423a --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,12 @@ +GEM + remote: https://rubygems.org/ + specs: + +PLATFORMS + arm64-darwin-23 + ruby + +DEPENDENCIES + +BUNDLED WITH + 2.5.17 diff --git a/parser/prism/Translator.cc b/parser/prism/Translator.cc index 914ea777b7c..d8a7f8ae0a9 100644 --- a/parser/prism/Translator.cc +++ b/parser/prism/Translator.cc @@ -170,8 +170,17 @@ unique_ptr Translator::translate(pm_node_t *node) { NodeVec statements; - if (auto prismStatements = beginNode->statements; prismStatements != nullptr) { - statements = translateMulti(prismStatements->body); + 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(beginNode->else_clause)); + auto beginRescueNode = translateRescue(reinterpret_cast(beginNode->rescue_clause), + move(bodyNode), move(elseNode)); + + statements.emplace_back(move(beginRescueNode)); + } else if (beginNode->statements != nullptr) { + // Handle `begin ... end` + statements = translateMulti(beginNode->statements->body); } return make_unique(location, move(statements)); @@ -850,6 +859,9 @@ unique_ptr Translator::translate(pm_node_t *node) { return make_unique(location, move(body), move(cases), nullptr); } + case PM_RESCUE_NODE: { + unreachable("PM_RESCUE_NODE is handled separately in translateRescue, see its docs for details."); + } case PM_REST_PARAMETER_NODE: { // A rest parameter, like `def foo(*rest)` auto restParamNode = reinterpret_cast(node); core::LocOffsets nameLoc; @@ -1041,7 +1053,6 @@ unique_ptr Translator::translate(pm_node_t *node) { case PM_NUMBERED_REFERENCE_READ_NODE: case PM_POST_EXECUTION_NODE: case PM_PRE_EXECUTION_NODE: - case PM_RESCUE_NODE: case PM_SHAREABLE_CONSTANT_NODE: case PM_SCOPE_NODE: auto type_id = PM_NODE_TYPE(node); @@ -1337,6 +1348,52 @@ unique_ptr Translator::translateCallWithBlock(pm_block_node *prism return make_unique(sendNode->loc, move(sendNode), move(blockParametersNode), move(body)); } +// 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. +// +// 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 Translator::translateRescue(pm_rescue_node *prismRescueNode, unique_ptr bodyNode, + unique_ptr 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(translateLoc(currentRescueNode->base.location), move(exceptions)); + + rescueBodies.emplace_back(make_unique(translateLoc(currentRescueNode->base.location), + move(exceptionsArray), move(var), move(rescueBody))); + } + + // The `Rescue` node combines the main body, the rescue clauses, and the else clause. + return make_unique(bodyNode->loc, move(bodyNode), move(rescueBodies), move(elseNode)); +} + // Translates the given Prism Statements Node into a `parser::Begin` node or an inlined `parser::Node`. // @param inlineIfSingle If enabled and there's 1 child node, we skip the `Begin` and just return the one `parser::Node` unique_ptr Translator::translateStatements(pm_statements_node *stmtsNode, bool inlineIfSingle) { diff --git a/parser/prism/Translator.h b/parser/prism/Translator.h index 68597b4b480..687ce115535 100644 --- a/parser/prism/Translator.h +++ b/parser/prism/Translator.h @@ -43,6 +43,9 @@ class Translator final { bool isUsedForKeywordArguments); std::unique_ptr translateCallWithBlock(pm_block_node *prismBlockNode, std::unique_ptr sendNode); + std::unique_ptr translateRescue(pm_rescue_node *prismRescueNode, + std::unique_ptr beginNode, + std::unique_ptr elseNode); std::unique_ptr translateStatements(pm_statements_node *stmtsNode, bool inlineIfSingle); template std::unique_ptr translateSimpleKeyword(pm_node_t *untypedNode); diff --git a/test/prism_regression/rescue.parse-tree.exp b/test/prism_regression/rescue.parse-tree.exp index 7fa4d81eac6..d10c58c5438 100644 --- a/test/prism_regression/rescue.parse-tree.exp +++ b/test/prism_regression/rescue.parse-tree.exp @@ -1,5 +1,345 @@ Begin { stmts = [ + Kwbegin { + stmts = [ + Rescue { + body = Send { + receiver = NULL + method = + args = [ + ] + } + rescue = [ + Resbody { + exception = NULL + var = NULL + body = String { + val = + } + } + ] + else_ = NULL + } + ] + } + Kwbegin { + stmts = [ + Rescue { + body = Begin { + stmts = [ + Send { + receiver = NULL + method = + args = [ + ] + } + Send { + receiver = NULL + method = + args = [ + ] + } + ] + } + rescue = [ + Resbody { + exception = NULL + var = NULL + body = String { + val = + } + } + ] + else_ = NULL + } + ] + } + Kwbegin { + stmts = [ + Rescue { + body = Send { + receiver = NULL + method = + args = [ + ] + } + rescue = [ + Resbody { + exception = Array { + elts = [ + Const { + scope = NULL + name = > + } + ] + } + var = NULL + body = String { + val = + } + } + ] + else_ = NULL + } + ] + } + Kwbegin { + stmts = [ + Rescue { + body = Send { + receiver = NULL + method = + args = [ + ] + } + rescue = [ + Resbody { + exception = Array { + elts = [ + Const { + scope = NULL + name = > + } + Const { + scope = NULL + name = > + } + ] + } + var = LVarLhs { + name = + } + body = DString { + nodes = [ + String { + val = + } + Begin { + stmts = [ + LVar { + name = + } + ] + } + ] + } + } + ] + else_ = NULL + } + ] + } + Kwbegin { + stmts = [ + Rescue { + body = Send { + receiver = NULL + method = + args = [ + ] + } + rescue = [ + Resbody { + exception = Array { + elts = [ + Const { + scope = NULL + name = > + } + ] + } + var = NULL + body = String { + val = + } + } + ] + else_ = String { + val = + } + } + ] + } + Kwbegin { + stmts = [ + Rescue { + body = Send { + receiver = NULL + method = + args = [ + ] + } + rescue = [ + Resbody { + exception = Array { + elts = [ + Const { + scope = NULL + name = > + } + ] + } + var = LVarLhs { + name = + } + body = DString { + nodes = [ + String { + val = + } + Begin { + stmts = [ + LVar { + name = + } + ] + } + ] + } + } + Resbody { + exception = Array { + elts = [ + Const { + scope = NULL + name = > + } + ] + } + var = LVarLhs { + name = + } + body = DString { + nodes = [ + String { + val = + } + Begin { + stmts = [ + LVar { + name = + } + ] + } + ] + } + } + Resbody { + exception = NULL + var = LVarLhs { + name = + } + body = DString { + nodes = [ + String { + val = + } + Begin { + stmts = [ + LVar { + name = + } + ] + } + ] + } + } + ] + else_ = NULL + } + ] + } + Kwbegin { + stmts = [ + Rescue { + body = Send { + receiver = NULL + method = + args = [ + ] + } + rescue = [ + Resbody { + exception = Array { + elts = [ + Const { + scope = NULL + name = > + } + ] + } + var = LVarLhs { + name = + } + body = DString { + nodes = [ + String { + val = + } + Begin { + stmts = [ + LVar { + name = + } + ] + } + ] + } + } + Resbody { + exception = Array { + elts = [ + Const { + scope = NULL + name = > + } + ] + } + var = LVarLhs { + name = + } + body = DString { + nodes = [ + String { + val = + } + Begin { + stmts = [ + LVar { + name = + } + ] + } + ] + } + } + Resbody { + exception = NULL + var = LVarLhs { + name = + } + body = DString { + nodes = [ + String { + val = + } + Begin { + stmts = [ + LVar { + name = + } + ] + } + ] + } + } + ] + else_ = String { + val = + } + } + ] + } Rescue { body = Send { receiver = NULL diff --git a/test/prism_regression/rescue.rb b/test/prism_regression/rescue.rb index 37d427fd807..79715e8dac8 100644 --- a/test/prism_regression/rescue.rb +++ b/test/prism_regression/rescue.rb @@ -1,5 +1,68 @@ # typed: false +# Testing a simple rescue clause +begin + bar +rescue + "rescued" +end + +# Testing a rescue clause with multiple body statements +begin + foo + bar +rescue + "rescued" +end + +# Testing a rescue clause with a specific exception +begin + foo +rescue RuntimeError + "rescued Foo" +end + +# Testing a rescue clause with multiple exceptions and a variable assignment +begin + foo +rescue RuntimeError, NotImplementedError => e + "rescued Foo #{e}" +end + +# Testing a rescue clause with an else clause +begin + foo +rescue RuntimeError + "rescued Foo" +else + "rescued else" +end + +# Testing multiple rescue clauses with different exceptions +begin + foo +rescue RuntimeError => e + "rescued Foo #{e}" +rescue NotImplementedError => e + "rescued Bar #{e}" +rescue => e + "rescued #{e}" +end + +# Testing multiple rescue clauses with different exceptions and a final else clause +begin + foo +rescue RuntimeError => e + "rescued Foo #{e}" +rescue NotImplementedError => e + "rescued Bar #{e}" +rescue => e + "rescued #{e}" +else + "rescued else" +end + +# Testing rescue modifiers problematic_code rescue puts "rescued" problematic_code rescue nil problematic_code rescue raise rescue puts "rescued again"