Fix retry in nested rescue blocks

Restore `rescue`-context from the outer context.
`retry` targets the next outer block except for between `rescue` and
`else` or `ensure`, otherwise, if there is no enclosing block, it
should be syntax error.
This commit is contained in:
Nobuyoshi Nakada 2023-09-29 01:58:07 +09:00
parent 5a376f0f71
commit eaa0fbf9b9
2 changed files with 44 additions and 10 deletions

33
parse.y
View file

@ -1571,6 +1571,12 @@ static NODE *allow_block_exit(struct parser_params *p);
static void restore_block_exit(struct parser_params *p, NODE *exits); static void restore_block_exit(struct parser_params *p, NODE *exits);
static void clear_block_exit(struct parser_params *p, bool error); static void clear_block_exit(struct parser_params *p, bool error);
static void
next_rescue_context(struct lex_context *next, const struct lex_context *outer, enum rescue_context def)
{
next->in_rescue = outer->in_rescue == after_rescue ? after_rescue : def;
}
static void static void
restore_defun(struct parser_params *p, NODE *name) restore_defun(struct parser_params *p, NODE *name)
{ {
@ -2116,29 +2122,37 @@ begin_block : block_open top_compstmt '}'
} }
; ;
bodystmt : compstmt bodystmt : compstmt[body]
lex_ctxt[ctxt]
opt_rescue opt_rescue
k_else k_else
{ {
if (!$2) yyerror1(&@3, "else without rescue is useless"); if (!$opt_rescue) yyerror1(&@k_else, "else without rescue is useless");
p->ctxt.in_rescue = after_else; next_rescue_context(&p->ctxt, &$ctxt, after_else);
}
compstmt[elsebody]
{
next_rescue_context(&p->ctxt, &$ctxt, after_ensure);
} }
compstmt
opt_ensure opt_ensure
{ {
/*%%%*/ /*%%%*/
$$ = new_bodystmt(p, $1, $2, $5, $6, &@$); $$ = new_bodystmt(p, $body, $opt_rescue, $elsebody, $opt_ensure, &@$);
/*% %*/ /*% %*/
/*% ripper: bodystmt!($1, $2, $5, $6) %*/ /*% ripper: bodystmt!($body, $opt_rescue, $elsebody, $opt_ensure) %*/
} }
| compstmt | compstmt[body]
lex_ctxt[ctxt]
opt_rescue opt_rescue
{
next_rescue_context(&p->ctxt, &$ctxt, after_ensure);
}
opt_ensure opt_ensure
{ {
/*%%%*/ /*%%%*/
$$ = new_bodystmt(p, $1, $2, 0, $3, &@$); $$ = new_bodystmt(p, $body, $opt_rescue, 0, $opt_ensure, &@$);
/*% %*/ /*% %*/
/*% ripper: bodystmt!($1, $2, Qnil, $3) %*/ /*% ripper: bodystmt!($body, $opt_rescue, Qnil, $opt_ensure) %*/
} }
; ;
@ -4242,7 +4256,6 @@ k_ensure : keyword_ensure
{ {
token_info_warn(p, "ensure", p->token_info, 1, &@$); token_info_warn(p, "ensure", p->token_info, 1, &@$);
$$ = p->ctxt; $$ = p->ctxt;
p->ctxt.in_rescue = after_ensure;
} }
; ;

View file

@ -283,6 +283,27 @@ class TestAst < Test::Unit::TestCase
assert_parse("begin rescue; ensure; defined? retry; end") assert_parse("begin rescue; ensure; defined? retry; end")
assert_parse("END {defined? retry}") assert_parse("END {defined? retry}")
assert_parse("begin rescue; END {defined? retry}; end") assert_parse("begin rescue; END {defined? retry}; end")
assert_parse("#{<<-"begin;"}\n#{<<-'end;'}")
begin;
def foo
begin
yield
rescue StandardError => e
begin
puts "hi"
retry
rescue
retry unless e
raise e
else
retry
ensure
retry
end
end
end
end;
end end
def test_invalid_yield def test_invalid_yield