Support eval "return" at toplevel
Since Ruby 2.4, `return` is supported at toplevel. This makes `eval "return"` also supported at toplevel. This mostly uses the same tests as direct `return` at toplevel, with a couple differences: `END {return if false}` is a SyntaxError, but `END {eval "return" if false}` is not an error since the eval is never executed. `END {return}` is a SyntaxError, but `END {eval "return"}` is a LocalJumpError. The following is a SyntaxError: ```ruby class X nil&defined?0--begin e=no_method_error(); return; 0;end end ``` However, the following is not, because the eval is never executed: ```ruby class X nil&defined?0--begin e=no_method_error(); eval "return"; 0;end end ``` Fixes [Bug #19779]
This commit is contained in:
parent
0c3593b657
commit
3a88de3ca7
@ -1405,6 +1405,54 @@ eom
|
||||
end
|
||||
end
|
||||
|
||||
def test_eval_return_toplevel
|
||||
feature4840 = '[ruby-core:36785] [Feature #4840]'
|
||||
line = __LINE__+2
|
||||
code = "#{<<~"begin;"}#{<<~'end;'}"
|
||||
begin;
|
||||
eval "return"; raise
|
||||
begin eval "return"; rescue SystemExit; exit false; end
|
||||
begin eval "return"; ensure puts "ensured"; end #=> ensured
|
||||
begin ensure eval "return"; end
|
||||
begin raise; ensure; eval "return"; end
|
||||
begin raise; rescue; eval "return"; end
|
||||
eval "return false"; raise
|
||||
eval "return 1"; raise
|
||||
"#{eval "return"}"
|
||||
raise((eval "return"; "should not raise"))
|
||||
begin raise; ensure eval "return"; end; self
|
||||
begin raise; ensure eval "return"; end and self
|
||||
eval "return puts('ignored')" #=> ignored
|
||||
BEGIN {eval "return"}
|
||||
end;
|
||||
.split(/\n/).map {|s|[(line+=1), *s.split(/#=> /, 2)]}
|
||||
failed = proc do |n, s|
|
||||
RubyVM::InstructionSequence.compile(s, __FILE__, nil, n).disasm
|
||||
end
|
||||
Tempfile.create(%w"test_return_ .rb") do |lib|
|
||||
lib.close
|
||||
args = %W[-W0 -r#{lib.path}]
|
||||
all_assertions_foreach(feature4840, *[:main, :lib].product([:class, :top], code)) do |main, klass, (n, s, *ex)|
|
||||
if klass == :class
|
||||
s = "class X; #{s}; end"
|
||||
if main == :main
|
||||
assert_in_out_err(%[-W0], s, ex, /return/, proc {failed[n, s]}, success: false)
|
||||
else
|
||||
File.write(lib, s)
|
||||
assert_in_out_err(args, "", ex, /return/, proc {failed[n, s]}, success: false)
|
||||
end
|
||||
else
|
||||
if main == :main
|
||||
assert_in_out_err(%[-W0], s, ex, [], proc {failed[n, s]}, success: true)
|
||||
else
|
||||
File.write(lib, s)
|
||||
assert_in_out_err(args, "", ex, [], proc {failed[n, s]}, success: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_return_toplevel_with_argument
|
||||
assert_warn(/argument of top-level return is ignored/) {eval("return 1")}
|
||||
end
|
||||
|
@ -1809,7 +1809,16 @@ vm_throw_start(const rb_execution_context_t *ec, rb_control_frame_t *const reg_c
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ISEQ_TYPE_EVAL:
|
||||
case ISEQ_TYPE_EVAL: {
|
||||
const rb_iseq_t *is = escape_cfp->iseq;
|
||||
enum rb_iseq_type t = ISEQ_BODY(is)->type;
|
||||
while (t == ISEQ_TYPE_RESCUE || t == ISEQ_TYPE_ENSURE || t == ISEQ_TYPE_EVAL) {
|
||||
if (!(is = ISEQ_BODY(is)->parent_iseq)) break;
|
||||
t = ISEQ_BODY(is)->type;
|
||||
}
|
||||
toplevel = t == ISEQ_TYPE_TOP || t == ISEQ_TYPE_MAIN;
|
||||
break;
|
||||
}
|
||||
case ISEQ_TYPE_CLASS:
|
||||
toplevel = 0;
|
||||
break;
|
||||
|
Loading…
x
Reference in New Issue
Block a user