# frozen_string_literal: false require 'test/unit' class TestSyntax < Test::Unit::TestCase using Module.new { refine(Object) do def `(s) #` s end end } def assert_syntax_files(test) srcdir = File.expand_path("../../..", __FILE__) srcdir = File.join(srcdir, test) assert_separately(%W[--disable-gem - #{srcdir}], __FILE__, __LINE__, <<-'eom', timeout: Float::INFINITY) dir = ARGV.shift for script in Dir["#{dir}/**/*.rb"].sort assert_valid_syntax(IO::read(script), script) end eom end def test_syntax_lib; assert_syntax_files("lib"); end def test_syntax_sample; assert_syntax_files("sample"); end def test_syntax_ext; assert_syntax_files("ext"); end def test_syntax_test; assert_syntax_files("test"); end def test_defined_empty_argument bug8220 = '[ruby-core:53999] [Bug #8220]' assert_ruby_status(%w[--disable-gem], 'puts defined? ()', bug8220) end def test_must_ascii_compatible require 'tempfile' f = Tempfile.new("must_ac_") Encoding.list.each do |enc| next unless enc.ascii_compatible? make_tmpsrc(f, "# -*- coding: #{enc.name} -*-") assert_nothing_raised(ArgumentError, enc.name) {load(f.path)} end Encoding.list.each do |enc| next if enc.ascii_compatible? make_tmpsrc(f, "# -*- coding: #{enc.name} -*-") assert_raise(ArgumentError, enc.name) {load(f.path)} end ensure f&.close! end def test_script_lines require 'tempfile' f = Tempfile.new("bug4361_") bug4361 = '[ruby-dev:43168]' with_script_lines do |debug_lines| Encoding.list.each do |enc| next unless enc.ascii_compatible? make_tmpsrc(f, "# -*- coding: #{enc.name} -*-\n#----------------") load(f.path) assert_equal([f.path], debug_lines.keys) assert_equal([enc, enc], debug_lines[f.path].map(&:encoding), bug4361) end end ensure f&.close! end def test_newline_in_block_parameters bug = '[ruby-dev:45292]' ["", "a", "a, b"].product(["", ";x", [";", "x"]]) do |params| params = ["|", *params, "|"].join("\n") assert_valid_syntax("1.times{#{params}}", __FILE__, "#{bug} #{params.inspect}") end end tap do |_, bug6115 = '[ruby-dev:45308]', blockcall = '["elem"].each_with_object [] do end', methods = [['map', 'no'], ['inject([])', 'with']], blocks = [['do end', 'do'], ['{}', 'brace']], *| [%w'. dot', %w':: colon'].product(methods, blocks) do |(c, n1), (m, n2), (b, n3)| m = m.tr_s('()', ' ').strip if n2 == 'do' name = "test_#{n3}_block_after_blockcall_#{n1}_#{n2}_arg" code = "#{blockcall}#{c}#{m} #{b}" define_method(name) {assert_valid_syntax(code, bug6115)} end end def test_do_block_in_cmdarg bug9726 = '[ruby-core:61950] [Bug #9726]' assert_valid_syntax("tap (proc do end)", __FILE__, bug9726) end def test_array_kwsplat_hash kw = {} h = {a: 1} assert_equal([], [**{}]) assert_equal([], [**kw]) assert_equal([h], [**h]) assert_equal([{}], [{}]) assert_equal([kw], [kw]) assert_equal([h], [h]) assert_equal([1], [1, **{}]) assert_equal([1], [1, **kw]) assert_equal([1, h], [1, **h]) assert_equal([1, {}], [1, {}]) assert_equal([1, kw], [1, kw]) assert_equal([1, h], [1, h]) assert_equal([], [**kw, **kw]) assert_equal([], [**kw, **{}, **kw]) assert_equal([1], [1, **kw, **{}, **kw]) assert_equal([{}], [{}, **kw, **kw]) assert_equal([kw], [kw, **kw, **kw]) assert_equal([h], [h, **kw, **kw]) assert_equal([h, h], [h, **kw, **kw, **h]) assert_equal([h, {:a=>2}], [h, **{}, **h, a: 2]) assert_equal([h, h], [h, **{}, a: 2, **h]) assert_equal([h, h], [h, a: 2, **{}, **h]) assert_equal([h, h], [h, a: 2, **h, **{}]) assert_equal([h, {:a=>2}], [h, **h, a: 2, **{}]) assert_equal([h, {:a=>2}], [h, **h, **{}, a: 2]) end def test_normal_argument assert_valid_syntax('def foo(x) end') assert_syntax_error('def foo(X) end', /constant/) assert_syntax_error('def foo(@x) end', /instance variable/) assert_syntax_error('def foo(@@x) end', /class variable/) end def test_optional_argument assert_valid_syntax('def foo(x=nil) end') assert_syntax_error('def foo(X=nil) end', /constant/) assert_syntax_error('def foo(@x=nil) end', /instance variable/) assert_syntax_error('def foo(@@x=nil) end', /class variable/) end def test_keyword_rest bug5989 = '[ruby-core:42455]' assert_valid_syntax("def kwrest_test(**a) a; end", __FILE__, bug5989) assert_valid_syntax("def kwrest_test2(**a, &b) end", __FILE__, bug5989) o = Object.new def o.kw(**a) a end assert_equal({}, o.kw, bug5989) assert_equal({foo: 1}, o.kw(foo: 1), bug5989) assert_equal({foo: 1, bar: 2}, o.kw(foo: 1, bar: 2), bug5989) EnvUtil.under_gc_stress do eval("def o.m(k: 0) k end") end assert_equal(42, o.m(k: 42), '[ruby-core:45744]') bug7922 = '[ruby-core:52744] [Bug #7922]' def o.bug7922(**) end assert_nothing_raised(ArgumentError, bug7922) {o.bug7922(foo: 42)} end class KW2 def kw(k1: 1, k2: 2) [k1, k2] end end def test_keyword_splat assert_valid_syntax("foo(**h)", __FILE__) o = KW2.new h = {k1: 11, k2: 12} assert_equal([11, 12], o.kw(**h)) assert_equal([11, 12], o.kw(k2: 22, **h)) assert_equal([11, 22], o.kw(**h, **{k2: 22})) assert_equal([11, 12], o.kw(**{k2: 22}, **h)) end def test_keyword_duplicated_splat bug10315 = '[ruby-core:65368] [Bug #10315]' o = KW2.new assert_equal([23, 2], o.kw(**{k1: 22}, **{k1: 23}), bug10315) h = {k3: 31} assert_raise(ArgumentError) {o.kw(**h)} h = {"k1"=>11, k2: 12} assert_warn(/The last argument is split into positional and keyword parameters.* for `kw'/m) do assert_raise(ArgumentError) {o.kw(**h)} end end def test_keyword_duplicated bug10315 = '[ruby-core:65625] [Bug #10315]' a = [] def a.add(x) push(x); x; end def a.f(k:) k; end a.clear r = nil assert_warn(/duplicated/) {r = eval("a.f(k: a.add(1), k: a.add(2))")} assert_equal(2, r) assert_equal([1, 2], a, bug10315) a.clear r = nil assert_warn(/duplicated/) {r = eval("a.f({k: a.add(1), k: a.add(2)})")} assert_equal(2, r) assert_equal([1, 2], a, bug10315) end def test_keyword_empty_splat assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") begin; bug10719 = '[ruby-core:67446] [Bug #10719]' assert_valid_syntax("foo(a: 1, **{})", bug10719) end; assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") begin; bug13756 = '[ruby-core:82113] [Bug #13756]' assert_valid_syntax("defined? foo(**{})", bug13756) end; assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; bug15271 = '[ruby-core:89648] [Bug #15271]' assert_valid_syntax("a **{}", bug15271) end; end def test_keyword_self_reference message = /circular argument reference - var/ assert_syntax_error("def foo(var: defined?(var)) var end", message) assert_syntax_error("def foo(var: var) var end", message) assert_syntax_error("def foo(var: bar(var)) var end", message) assert_syntax_error("def foo(var: bar {var}) var end", message) o = Object.new assert_warn("") do o.instance_eval("def foo(var: bar {|var| var}) var end") end o = Object.new assert_warn("") do o.instance_eval("def foo(var: bar {| | var}) var end") end o = Object.new assert_warn("") do o.instance_eval("def foo(var: bar {|| var}) var end") end o = Object.new assert_warn("") do o.instance_eval("def foo(var: def bar(var) var; end) var end") end o = Object.new assert_warn("") do o.instance_eval("proc {|var: 1| var}") end end def test_keyword_invalid_name bug11663 = '[ruby-core:71356] [Bug #11663]' assert_syntax_error('def foo(arg1?:) end', /arg1\?/, bug11663) assert_syntax_error('def foo(arg1?:, arg2:) end', /arg1\?/, bug11663) assert_syntax_error('proc {|arg1?:|}', /arg1\?/, bug11663) assert_syntax_error('proc {|arg1?:, arg2:|}', /arg1\?/, bug11663) bug10545 = '[ruby-dev:48742] [Bug #10545]' assert_syntax_error('def foo(FOO: a) end', /constant/, bug10545) assert_syntax_error('def foo(@foo: a) end', /instance variable/) assert_syntax_error('def foo(@@foo: a) end', /class variable/) end def test_keywords_specified_and_not_accepted assert_syntax_error('def foo(a:, **nil) end', /unexpected/) assert_syntax_error('def foo(a:, **nil, &b) end', /unexpected/) assert_syntax_error('def foo(**a, **nil) end', /unexpected/) assert_syntax_error('def foo(**a, **nil, &b) end', /unexpected/) assert_syntax_error('def foo(**nil, **a) end', /unexpected/) assert_syntax_error('def foo(**nil, **a, &b) end', /unexpected/) assert_syntax_error('proc do |a:, **nil| end', /unexpected/) assert_syntax_error('proc do |a:, **nil, &b| end', /unexpected/) assert_syntax_error('proc do |**a, **nil| end', /unexpected/) assert_syntax_error('proc do |**a, **nil, &b| end', /unexpected/) assert_syntax_error('proc do |**nil, **a| end', /unexpected/) assert_syntax_error('proc do |**nil, **a, &b| end', /unexpected/) end def test_optional_self_reference message = /circular argument reference - var/ assert_syntax_error("def foo(var = defined?(var)) var end", message) assert_syntax_error("def foo(var = var) var end", message) assert_syntax_error("def foo(var = bar(var)) var end", message) assert_syntax_error("def foo(var = bar {var}) var end", message) assert_syntax_error("def foo(var = (def bar;end; var)) var end", message) assert_syntax_error("def foo(var = (def self.bar;end; var)) var end", message) o = Object.new assert_warn("") do o.instance_eval("def foo(var = bar {|var| var}) var end") end o = Object.new assert_warn("") do o.instance_eval("def foo(var = bar {| | var}) var end") end o = Object.new assert_warn("") do o.instance_eval("def foo(var = bar {|| var}) var end") end o = Object.new assert_warn("") do o.instance_eval("def foo(var = def bar(var) var; end) var end") end o = Object.new assert_warn("") do o.instance_eval("proc {|var = 1| var}") end end def test_warn_grouped_expression bug5214 = '[ruby-core:39050]' assert_warning("", bug5214) do assert_valid_syntax("foo \\\n(\n true)", "test", verbose: true) end end def test_warn_unreachable assert_warning("test:3: warning: statement not reached\n") do code = "loop do\n" "break\n" "foo\n" "end" assert_valid_syntax(code, "test", verbose: true) end end def test_warn_balanced warning = < (x, y) {}", __FILE__, feature6390) end def test_do_block_in_cmdarg_begin bug6419 = '[ruby-dev:45631]' assert_valid_syntax("p begin 1.times do 1 end end", __FILE__, bug6419) end def test_do_block_in_call_args bug9308 = '[ruby-core:59342] [Bug #9308]' assert_valid_syntax("bar def foo; self.each do end end", bug9308) end def test_do_block_in_lambda bug11107 = '[ruby-core:69017] [Bug #11107]' assert_valid_syntax('p ->() do a() do end end', bug11107) end def test_do_block_after_lambda bug11380 = '[ruby-core:70067] [Bug #11380]' assert_valid_syntax('p -> { :hello }, a: 1 do end', bug11380) end def test_reserved_method_no_args bug6403 = '[ruby-dev:45626]' assert_valid_syntax("def self; :foo; end", __FILE__, bug6403) end def test_unassignable gvar = global_variables %w[self nil true false __FILE__ __LINE__ __ENCODING__].each do |kwd| assert_syntax_error("#{kwd} = nil", /Can't .* #{kwd}$/) assert_equal(gvar, global_variables) end end Bug7559 = '[ruby-dev:46737]' def test_lineno_command_call_quote expected = __LINE__ + 1 actual = caller_lineno "a b c d e" assert_equal(expected, actual, "#{Bug7559}: ") end def assert_dedented_heredoc(expect, result, mesg = "") all_assertions(mesg) do |a| %w[eos "eos" 'eos' `eos`].each do |eos| a.for(eos) do assert_equal(eval("<<-#{eos}\n#{expect}eos\n"), eval("<<~#{eos}\n#{result}eos\n")) end end end end def test_dedented_heredoc_without_indentation result = " y\n" \ "z\n" expect = result assert_dedented_heredoc(expect, result) end def test_dedented_heredoc_with_indentation result = " a\n" \ " b\n" expect = " a\n" \ "b\n" assert_dedented_heredoc(expect, result) end def test_dedented_heredoc_with_blank_less_indented_line # the blank line has two leading spaces result = " a\n" \ " \n" \ " b\n" expect = "a\n" \ "\n" \ "b\n" assert_dedented_heredoc(expect, result) end def test_dedented_heredoc_with_blank_less_indented_line_escaped result = " a\n" \ "\\ \\ \n" \ " b\n" expect = result assert_dedented_heredoc(expect, result) end def test_dedented_heredoc_with_blank_more_indented_line # the blank line has six leading spaces result = " a\n" \ " \n" \ " b\n" expect = "a\n" \ " \n" \ "b\n" assert_dedented_heredoc(expect, result) end def test_dedented_heredoc_with_blank_more_indented_line_escaped result = " a\n" \ "\\ \\ \\ \\ \\ \\ \n" \ " b\n" expect = result assert_dedented_heredoc(expect, result) end def test_dedented_heredoc_with_empty_line result = " This would contain specially formatted text.\n" \ "\n" \ " That might span many lines\n" expect = 'This would contain specially formatted text.'"\n" \ ''"\n" \ 'That might span many lines'"\n" assert_dedented_heredoc(expect, result) end def test_dedented_heredoc_with_interpolated_expression result = ' #{1}a'"\n" \ " zy\n" expect = ' #{1}a'"\n" \ "zy\n" assert_dedented_heredoc(expect, result) end def test_dedented_heredoc_with_interpolated_string w = w = "" result = " \#{mesg} a\n" \ " zy\n" expect = '#{mesg} a'"\n" \ ' zy'"\n" assert_dedented_heredoc(expect, result) end def test_dedented_heredoc_with_newline bug11989 = '[ruby-core:72855] [Bug #11989] after escaped newline should not be dedented' result = ' x\n'" y\n" \ " z\n" expect = 'x\n'" y\n" \ "z\n" assert_dedented_heredoc(expect, result, bug11989) end def test_dedented_heredoc_with_concatenation bug11990 = '[ruby-core:72857] [Bug #11990] concatenated string should not be dedented' %w[eos "eos" 'eos'].each do |eos| assert_equal("x\n y", eval("<<~#{eos} ' y'\n x\neos\n"), "#{bug11990} with #{eos}") end %w[eos "eos" 'eos' `eos`].each do |eos| _, expect = eval("[<<~#{eos}, ' x']\n"" y\n""eos\n") assert_equal(' x', expect, bug11990) end end def test_dedented_heredoc_expr_at_beginning result = " a\n" \ '#{1}'"\n" expected = " a\n" \ '#{1}'"\n" assert_dedented_heredoc(expected, result) end def test_dedented_heredoc_expr_string result = ' one#{" two "}'"\n" expected = 'one#{" two "}'"\n" assert_dedented_heredoc(expected, result) end def test_dedented_heredoc_continued_line result = " 1\\\n" " 2\n" expected = "1\\\n" "2\n" assert_dedented_heredoc(expected, result) assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /can't find string "TEXT"/) begin; <<-TEXT \ TEXT end; assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /can't find string "TEXT"/) begin; <<~TEXT \ TEXT end; assert_equal(" TEXT\n", eval("<<~eos\n" " \\\n" "TEXT\n" "eos\n")) end def test_lineno_after_heredoc bug7559 = '[ruby-dev:46737]' expected, _, actual = __LINE__, < proc do end, b: proc do end}', bug13073 assert_valid_syntax 'p :foo, {"a": proc do end, b: proc do end}', bug13073 assert_valid_syntax 'p :foo, {** proc do end, b: proc do end}', bug13073 assert_valid_syntax 'p :foo, {proc do end => proc do end, b: proc do end}', bug13073 end def test_do_after_local_variable obj = Object.new def obj.m; yield; end result = assert_nothing_raised(SyntaxError) do obj.instance_eval("m = 1; m do :ok end") end assert_equal(:ok, result) end def test_brace_after_local_variable obj = Object.new def obj.m; yield; end result = assert_nothing_raised(SyntaxError) do obj.instance_eval("m = 1; m {:ok}") end assert_equal(:ok, result) end def test_brace_after_literal_argument bug = '[ruby-core:81037] [Bug #13547]' error = /unexpected '{'/ assert_syntax_error('m "x" {}', error) assert_syntax_error('m 1 {}', error, bug) assert_syntax_error('m 1.0 {}', error, bug) assert_syntax_error('m :m {}', error, bug) assert_syntax_error('m :"#{m}" {}', error, bug) assert_syntax_error('m ?x {}', error, bug) assert_syntax_error('m %[] {}', error, bug) assert_syntax_error('m 0..1 {}', error, bug) assert_syntax_error('m [] {}', error, bug) end def test_return_toplevel feature4840 = '[ruby-core:36785] [Feature #4840]' line = __LINE__+2 code = "#{<<~"begin;"}#{<<~'end;'}" begin; return; raise begin return; rescue SystemExit; exit false; end begin return; ensure puts "ensured"; end #=> ensured begin ensure return; end begin raise; ensure; return; end begin raise; rescue; return; end return false; raise return 1; raise "#{return}" raise((return; "should not raise")) begin raise; ensure return; end; self begin raise; ensure return; end and self nil&defined?0--begin e=no_method_error(); return; 0;end return puts('ignored') #=> ignored 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, [], /return/, proc {failed[n, s]}, success: false) else File.write(lib, s) assert_in_out_err(args, "", [], /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 def test_return_in_proc_in_class assert_in_out_err(['-e', 'class TestSyntax; proc{ return }.call; end'], "", [], /^-e:1:.*unexpected return \(LocalJumpError\)/) end def test_syntax_error_in_rescue bug12613 = '[ruby-core:76531] [Bug #12613]' assert_syntax_error("#{<<-"begin;"}\n#{<<-"end;"}", /Invalid retry/, bug12613) begin; while true begin p rescue retry else retry end break end end; end def test_syntax_error_at_newline expected = "\n ^" assert_syntax_error("%[abcdef", expected) assert_syntax_error("%[abcdef\n", expected) end def test_invalid_jump assert_in_out_err(%w[-e redo], "", [], /^-e:1: /) end def test_keyword_not_parens assert_valid_syntax("not()") end def test_rescue_do_end_raised result = [] assert_raise(RuntimeError) do eval("#{<<-"begin;"}\n#{<<-"end;"}") begin; tap do result << :begin raise "An exception occurred!" ensure result << :ensure end end; end assert_equal([:begin, :ensure], result) end def test_rescue_do_end_rescued result = [] assert_nothing_raised(RuntimeError) do eval("#{<<-"begin;"}\n#{<<-"end;"}") begin; tap do result << :begin raise "An exception occurred!" rescue result << :rescue else result << :else ensure result << :ensure end end; end assert_equal([:begin, :rescue, :ensure], result) end def test_rescue_do_end_no_raise result = [] assert_nothing_raised(RuntimeError) do eval("#{<<-"begin;"}\n#{<<-"end;"}") begin; tap do result << :begin rescue result << :rescue else result << :else ensure result << :ensure end end; end assert_equal([:begin, :else, :ensure], result) end def test_rescue_do_end_ensure_result result = eval("#{<<-"begin;"}\n#{<<-"end;"}") begin; proc do :begin ensure :ensure end.call end; assert_equal(:begin, result) end def test_rescue_do_end_ensure_in_lambda result = [] eval("#{<<-"begin;"}\n#{<<-"end;"}") begin; -> do result << :begin raise "An exception occurred!" rescue result << :rescue else result << :else ensure result << :ensure end.call end; assert_equal([:begin, :rescue, :ensure], result) end def test_return_in_loop obj = Object.new def obj.test x = nil return until x unless x end assert_nil obj.test end def test_method_call_location line = __LINE__+5 e = assert_raise(NoMethodError) do 1.upto(0) do end . foo( 1, 2, ) end assert_equal(line, e.backtrace_locations[0].lineno) line = __LINE__+5 e = assert_raise(NoMethodError) do 1.upto 0 do end . foo( 1, 2, ) end assert_equal(line, e.backtrace_locations[0].lineno) end def test_methoddef_in_cond assert_valid_syntax('while def foo; tap do end; end; break; end') assert_valid_syntax('while def foo a = tap do end; end; break; end') end def test_classdef_in_cond assert_valid_syntax('while class Foo; tap do end; end; break; end') assert_valid_syntax('while class Foo a = tap do end; end; break; end') end def test_command_with_cmd_brace_block assert_valid_syntax('obj.foo (1) {}') assert_valid_syntax('obj::foo (1) {}') end def test_numbered_parameter assert_valid_syntax('proc {_1}') assert_equal(3, eval('[1,2].then {_1+_2}')) assert_equal("12", eval('[1,2].then {"#{_1}#{_2}"}')) assert_equal([1, 2], eval('[1,2].then {_1}')) assert_equal(3, eval('->{_1+_2}.call(1,2)')) assert_equal(4, eval('->(a=->{_1}){a}.call.call(4)')) assert_equal(5, eval('-> a: ->{_1} {a}.call.call(5)')) assert_syntax_error('proc {|| _1}', /ordinary parameter is defined/) assert_syntax_error('proc {|;a| _1}', /ordinary parameter is defined/) assert_syntax_error("proc {|\n| _1}", /ordinary parameter is defined/) assert_syntax_error('proc {|x| _1}', /ordinary parameter is defined/) assert_syntax_error('proc {_1; proc {_2}}', /numbered parameter is already used/) assert_syntax_error('proc {proc {_1}; _2}', /numbered parameter is already used/) assert_syntax_error('->(){_1}', /ordinary parameter is defined/) assert_syntax_error('->(x){_1}', /ordinary parameter is defined/) assert_syntax_error('->x{_1}', /ordinary parameter is defined/) assert_syntax_error('->x:_2{}', /ordinary parameter is defined/) assert_syntax_error('->x=_1{}', /ordinary parameter is defined/) assert_syntax_error('-> {_1; -> {_2}}', /numbered parameter is already used/) assert_syntax_error('-> {-> {_1}; _2}', /numbered parameter is already used/) assert_syntax_error('proc {_1; _1 = nil}', /Can't assign to numbered parameter _1/) mesg = proc {|n| /`_#{n}' is reserved for numbered parameter/} assert_warn(mesg[1]) {eval('proc {_1 = nil}')} assert_warn(mesg[2]) {eval('_2=1')} assert_warn(mesg[3]) {eval('proc {|_3|}')} assert_warn(mesg[4]) {instance_eval('def x(_4) end')} assert_warn(mesg[5]) {instance_eval('def _5; end')} assert_warn(mesg[6]) {instance_eval('def self._6; end')} assert_raise_with_message(NameError, /undefined local variable or method `_1'/) { eval('_1') } ['class C', 'class << C', 'module M', 'def m', 'def o.m'].each do |c| assert_valid_syntax("->{#{c};->{_1};end;_1}\n") assert_valid_syntax("->{_1;#{c};->{_1};end}\n") end end def test_value_expr_in_condition mesg = /void value expression/ assert_syntax_error("tap {a = (true ? next : break)}", mesg) assert_valid_syntax("tap {a = (true ? true : break)}") assert_valid_syntax("tap {a = (break if false)}") assert_valid_syntax("tap {a = (break unless true)}") end def test_argument_forwarding assert_valid_syntax('def foo(...) bar(...) end') assert_valid_syntax('def foo(...) end') assert_syntax_error('iter do |...| end', /unexpected/) assert_syntax_error('iter {|...|}', /unexpected/) assert_syntax_error('->... {}', /unexpected/) assert_syntax_error('->(...) {}', /unexpected/) assert_syntax_error('def foo(x, y, z) bar(...); end', /unexpected/) assert_syntax_error('def foo(x, y, z) super(...); end', /unexpected/) assert_syntax_error('def foo(...) yield(...); end', /unexpected/) assert_syntax_error('def foo(...) return(...); end', /unexpected/) assert_syntax_error('def foo(...) a = (...); end', /unexpected/) assert_syntax_error('def foo(...) [...]; end', /unexpected/) assert_syntax_error('def foo(...) foo[...]; end', /unexpected/) assert_syntax_error('def foo(...) foo[...] = x; end', /unexpected/) assert_syntax_error('def foo(...) foo(...) { }; end', /both block arg and actual block given/) assert_syntax_error('def foo(...) defined?(...); end', /unexpected/) obj1 = Object.new def obj1.bar(*args, **kws, &block) if block block.call(args, kws) else [args, kws] end end obj1.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__) klass = Class.new { def foo(*args, **kws, &block) if block block.call(args, kws) else [args, kws] end end } obj2 = klass.new obj2.instance_eval('def foo(...) super(...) end', __FILE__, __LINE__) obj3 = Object.new def obj3.bar(*args, &block) if kws = Hash.try_convert(args.last) args.pop else kws = {} end if block block.call(args, kws) else [args, kws] end end obj3.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__) [obj1, obj2, obj3].each do |obj| assert_warning('') { assert_equal([[1, 2, 3], {k1: 4, k2: 5}], obj.foo(1, 2, 3, k1: 4, k2: 5) {|*x| x}) } assert_warning('') { assert_equal([[1, 2, 3], {k1: 4, k2: 5}], obj.foo(1, 2, 3, k1: 4, k2: 5)) } warning = "warning: The last argument is used as the keyword parameter" assert_warning(/\A\z|:(?!#{__LINE__+1})\d+: #{warning}/o) { assert_equal([[], {}], obj.foo({}) {|*x| x}) } assert_warning(/\A\z|:(?!#{__LINE__+1})\d+: #{warning}/o) { assert_equal([[], {}], obj.foo({})) } assert_equal(-1, obj.method(:foo).arity) parameters = obj.method(:foo).parameters assert_equal(:rest, parameters.dig(0, 0)) assert_equal(:block, parameters.dig(1, 0)) end end private def not_label(x) @result = x; @not_label ||= nil end def assert_not_label(expected, src, message = nil) @result = nil assert_nothing_raised(SyntaxError, message) {eval(src)} assert_equal(expected, @result, message) end def make_tmpsrc(f, src) f.open f.truncate(0) f.puts(src) f.close end def with_script_lines script_lines = nil debug_lines = {} Object.class_eval do if defined?(SCRIPT_LINES__) script_lines = SCRIPT_LINES__ remove_const :SCRIPT_LINES__ end const_set(:SCRIPT_LINES__, debug_lines) end yield debug_lines ensure Object.class_eval do remove_const :SCRIPT_LINES__ const_set(:SCRIPT_LINES__, script_lines) if script_lines end end def caller_lineno(*) caller_locations(1, 1)[0].lineno end end