diff options
Diffstat (limited to 'test/ruby/test_syntax.rb')
| -rw-r--r-- | test/ruby/test_syntax.rb | 2048 |
1 files changed, 2026 insertions, 22 deletions
diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index bb13a4ed8c..b355128a73 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -1,12 +1,19 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' 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 -r#{__dir__}/envutil - #{srcdir}], - __FILE__, __LINE__, <<-'eom', timeout: Float::INFINITY) + assert_separately(%W[- #{srcdir}], __FILE__, __LINE__, <<-'eom', timeout: Float::INFINITY) dir = ARGV.shift for script in Dir["#{dir}/**/*.rb"].sort assert_valid_syntax(IO::read(script), script) @@ -37,7 +44,8 @@ class TestSyntax < Test::Unit::TestCase make_tmpsrc(f, "# -*- coding: #{enc.name} -*-") assert_raise(ArgumentError, enc.name) {load(f.path)} end - f.close! + ensure + f&.close! end def test_script_lines @@ -53,7 +61,145 @@ class TestSyntax < Test::Unit::TestCase assert_equal([enc, enc], debug_lines[f.path].map(&:encoding), bug4361) end end - f.close! + ensure + f&.close! + end + + def test_script_lines_encoding + require 'tmpdir' + Dir.mktmpdir do |dir| + File.write(File.join(dir, "script_lines.rb"), "SCRIPT_LINES__ = {}\n") + assert_in_out_err(%w"-r./script_lines -w -Ke", "puts __ENCODING__.name", + %w"EUC-JP", /-K is specified/, chdir: dir) + end + end + + def test_anonymous_block_forwarding + assert_syntax_error("def b; c(&); end", /no anonymous block parameter/) + assert_syntax_error("def b(&) ->(&) {c(&)} end", /anonymous block parameter is also used/) + assert_valid_syntax("def b(&) ->() {c(&)} end") + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + def b(&); c(&) end + def c(&); yield 1 end + a = nil + b{|c| a = c} + assert_equal(1, a) + + def inner + yield + end + + def block_only(&) + inner(&) + end + assert_equal(1, block_only{1}) + + def pos(arg1, &) + inner(&) + end + assert_equal(2, pos(nil){2}) + + def pos_kwrest(arg1, **kw, &) + inner(&) + end + assert_equal(3, pos_kwrest(nil){3}) + + def no_kw(arg1, **nil, &) + inner(&) + end + assert_equal(4, no_kw(nil){4}) + + def rest_kw(*a, kwarg: 1, &) + inner(&) + end + assert_equal(5, rest_kw{5}) + + def kw(kwarg:1, &) + inner(&) + end + assert_equal(6, kw{6}) + + def pos_kw_kwrest(arg1, kwarg:1, **kw, &) + inner(&) + end + assert_equal(7, pos_kw_kwrest(nil){7}) + + def pos_rkw(arg1, kwarg1:, &) + inner(&) + end + assert_equal(8, pos_rkw(nil, kwarg1: nil){8}) + + def all(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, &) + inner(&) + end + assert_equal(9, all(nil, nil, nil, nil, okw1: nil, okw2: nil){9}) + + def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, **kw, &) + inner(&) + end + assert_equal(10, all_kwrest(nil, nil, nil, nil, okw1: nil, okw2: nil){10}) + + def evaled(&) + eval("inner(&)") + end + assert_equal(1, evaled{1}) + end; + end + + def test_anonymous_rest_forwarding + assert_syntax_error("def b; c(*); end", /no anonymous rest parameter/) + assert_syntax_error("def b; c(1, *); end", /no anonymous rest parameter/) + assert_syntax_error("def b(*) ->(*) {c(*)} end", /anonymous rest parameter is also used/) + assert_syntax_error("def b(a, *) ->(*) {c(1, *)} end", /anonymous rest parameter is also used/) + assert_syntax_error("def b(*) ->(a, *) {c(*)} end", /anonymous rest parameter is also used/) + assert_valid_syntax("def b(*) ->() {c(*)} end") + assert_valid_syntax("def b(a, *) ->() {c(1, *)} end") + assert_valid_syntax("def b(*) ->(a) {c(*)} end") + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + def b(*); c(*) end + def c(*a); a end + def d(*); b(*, *) end + def e(*); eval("b(*)") end + assert_equal([1, 2], b(1, 2)) + assert_equal([1, 2, 1, 2], d(1, 2)) + assert_equal([1, 2], e(1, 2)) + end; + end + + def test_anonymous_keyword_rest_forwarding + assert_syntax_error("def b; c(**); end", /no anonymous keyword rest parameter/) + assert_syntax_error("def b; c(k: 1, **); end", /no anonymous keyword rest parameter/) + assert_syntax_error("def b(**) ->(**) {c(**)} end", /anonymous keyword rest parameter is also used/) + assert_syntax_error("def b(k:, **) ->(**) {c(k: 1, **)} end", /anonymous keyword rest parameter is also used/) + assert_syntax_error("def b(**) ->(k:, **) {c(**)} end", /anonymous keyword rest parameter is also used/) + assert_valid_syntax("def b(**) ->() {c(**)} end") + assert_valid_syntax("def b(k:, **) ->() {c(k: 1, **)} end") + assert_valid_syntax("def b(**) ->(k:) {c(**)} end") + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + def b(**); c(**) end + def c(**kw); kw end + def d(**); b(k: 1, **) end + def e(**); b(**, k: 1) end + def f(a: nil, **); b(**) end + def g(**); eval("b(**)") end + assert_equal({a: 1, k: 3}, b(a: 1, k: 3)) + assert_equal({a: 1, k: 3}, d(a: 1, k: 3)) + assert_equal({a: 1, k: 1}, e(a: 1, k: 3)) + assert_equal({k: 3}, f(a: 1, k: 3)) + assert_equal({a: 1, k: 3}, g(a: 1, k: 3)) + end; + end + + def test_argument_forwarding_with_anon_rest_kwrest_and_block + assert_syntax_error("def f(*, **, &); g(...); end", /unexpected \.\.\./) + assert_syntax_error("def f(...); g(*); end", /no anonymous rest parameter/) + assert_syntax_error("def f(...); g(0, *); end", /no anonymous rest parameter/) + assert_syntax_error("def f(...); g(**); end", /no anonymous keyword rest parameter/) + assert_syntax_error("def f(...); g(x: 1, **); end", /no anonymous keyword rest parameter/) + assert_syntax_error("def f(...); g(&); end", /no anonymous block parameter/) end def test_newline_in_block_parameters @@ -71,7 +217,7 @@ class TestSyntax < Test::Unit::TestCase 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' + m = m.tr_s('()', ' ').strip if n3 == 'do' name = "test_#{n3}_block_after_blockcall_#{n1}_#{n2}_arg" code = "#{blockcall}#{c}#{m} #{b}" define_method(name) {assert_valid_syntax(code, bug6115)} @@ -83,6 +229,80 @@ class TestSyntax < Test::Unit::TestCase assert_valid_syntax("tap (proc do end)", __FILE__, bug9726) end + def test_hash_kwsplat_hash + kw = {} + h = {a: 1} + assert_equal({}, {**{}}) + assert_equal({}, {**kw}) + assert_equal(h, {**h}) + assert_equal(false, {**{}}.frozen?) + assert_equal(false, {**kw}.equal?(kw)) + assert_equal(false, {**h}.equal?(h)) + end + + def test_array_kwsplat_hash + kw = {} + h = {a: 1} + a = [] + 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([], [*a, **{}]) + assert_equal([], [*a, **kw]) + assert_equal([h], [*a, **h]) + assert_equal([{}], [*a, {}]) + assert_equal([kw], [*a, kw]) + assert_equal([h], [*a, h]) + + assert_equal([1], [1, *a, **{}]) + assert_equal([1], [1, *a, **kw]) + assert_equal([1, h], [1, *a, **h]) + assert_equal([1, {}], [1, *a, {}]) + assert_equal([1, kw], [1, *a, kw]) + assert_equal([1, h], [1, *a, 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) @@ -101,32 +321,230 @@ class TestSyntax < Test::Unit::TestCase 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 = Object.new - def o.kw(k1: 1, k2: 2) [k1, k2] end + o = KW2.new h = {k1: 11, k2: 12} assert_equal([11, 12], o.kw(**h)) - assert_equal([11, 22], o.kw(k2: 22, **h)) - assert_equal([11, 12], o.kw(**h, **{k2: 22})) - assert_equal([11, 22], o.kw(**{k2: 22}, **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 + begin + verbose_bak, $VERBOSE = $VERBOSE, nil + assert_equal([23, 2], eval("o.kw(**{k1: 22}, **{k1: 23})"), bug10315) + ensure + $VERBOSE = verbose_bak + end + h = {k3: 31} assert_raise(ArgumentError) {o.kw(**h)} h = {"k1"=>11, k2: 12} - assert_raise(TypeError) {o.kw(**h)} + assert_raise(ArgumentError) {o.kw(**h)} + end + + def test_keyword_duplicated + bug10315 = '[ruby-core:65625] [Bug #10315]' + a = [] + def a.add(x) push(x); x; end + b = a.clone + def a.f(k:, **) k; end + def b.f(k:) k; end + a.clear + r = nil + assert_warn(/duplicated/) {r = eval("b.f(k: b.add(1), k: b.add(2))")} + assert_equal(2, r) + assert_equal([1, 2], b, bug10315) + b.clear + r = nil + assert_warn(/duplicated/) {r = eval("a.f(k: a.add(1), j: a.add(2), k: a.add(3), k: a.add(4))")} + assert_equal(4, r) + assert_equal([1, 2, 3, 4], a) + a.clear + r = nil + assert_warn(/duplicated/) {r = eval("b.f(**{k: b.add(1), k: b.add(2)})")} + assert_equal(2, r) + assert_equal([1, 2], b, bug10315) + b.clear + r = nil + assert_warn(/duplicated/) {r = eval("a.f(**{k: a.add(1), j: a.add(2), k: a.add(3), k: a.add(4)})")} + assert_equal(4, r) + assert_equal([1, 2, 3, 4], a) + a.clear + r = nil + _z = {} + assert_warn(/duplicated/) {r = eval("a.f(k: a.add(1), **_z, k: a.add(2))")} + assert_equal(2, r) + assert_equal([1, 2], a) + 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 + assert_valid_syntax("def foo(var: defined?(var)) var end") + assert_valid_syntax("def foo(var: var) var end") + assert_valid_syntax("def foo(var: bar(var)) var end") + assert_valid_syntax("def foo(var: bar {var}) var end") + assert_valid_syntax("def foo(var: (1 in ^var)); end") + + 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 + + o = Object.new + assert_nil(o.instance_eval("def foo(bar: bar) = bar; foo")) + 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 + assert_valid_syntax("def foo(var = defined?(var)) var end") + assert_valid_syntax("def foo(var = var) var end") + assert_valid_syntax("def foo(var = bar(var)) var end") + assert_valid_syntax("def foo(var = bar {var}) var end") + assert_valid_syntax("def foo(var = (def bar;end; var)) var end") + assert_valid_syntax("def foo(var = (def self.bar;end; var)) var end") + assert_valid_syntax("def foo(var = (1 in ^var)); end") + + 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 + + o = Object.new + assert_nil(o.instance_eval("def foo(bar: bar) = bar; foo")) end def test_warn_grouped_expression bug5214 = '[ruby-core:39050]' assert_warning("", bug5214) do - assert_valid_syntax("foo \\\n(\n true)", "test") {$VERBOSE = true} + 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} + assert_valid_syntax(code, "test", verbose: true) + end + end + + def test_warn_balanced + [ + [:**, "argument prefix"], + [:*, "argument prefix"], + [:<<, "here document"], + [:&, "argument prefix"], + [:+, "unary operator"], + [:-, "unary operator"], + [:/, "regexp literal"], + [:%, "string literal"], + ].each do |op, syn| + all_assertions do |a| + ["puts 1 #{op}0", "puts :a #{op}0", "m = 1; puts m #{op}0"].each do |src| + a.for(src) do + warning = /'#{Regexp.escape(op)}' after local variable or literal is interpreted as binary operator.+?even though it seems like #{syn}/m + + assert_warning(warning, src) do + assert_valid_syntax(src, "test", verbose: true) + end + end + end + end end end @@ -155,20 +573,77 @@ class TestSyntax < Test::Unit::TestCase assert_not_label(:foo, 'class Foo < not_label:foo; end', bug6347) end + def test_no_label_with_percent + assert_syntax_error('{%"a": 1}', /unexpected ':'/) + assert_syntax_error("{%'a': 1}", /unexpected ':'/) + assert_syntax_error('{%Q"a": 1}', /unexpected ':'/) + assert_syntax_error("{%Q'a': 1}", /unexpected ':'/) + assert_syntax_error('{%q"a": 1}', /unexpected ':'/) + assert_syntax_error("{%q'a': 1}", /unexpected ':'/) + end + + def test_block_after_cond + bug10653 = '[ruby-dev:48790] [Bug #10653]' + assert_valid_syntax("false ? raise {} : tap {}", bug10653) + assert_valid_syntax("false ? raise do end : tap do end", bug10653) + end + + def test_paren_after_label + bug11456 = '[ruby-dev:49221] [Bug #11456]' + assert_valid_syntax("{foo: (1 rescue 0)}", bug11456) + assert_valid_syntax("{foo: /=/}", bug11456) + end + + def test_percent_string_after_label + bug11812 = '[ruby-core:72084]' + assert_valid_syntax('{label:%w(*)}', bug11812) + assert_valid_syntax('{label: %w(*)}', bug11812) + end + + def test_heredoc_after_label + bug11849 = '[ruby-core:72396] [Bug #11849]' + assert_valid_syntax("{label:<<DOC\n""DOC\n""}", bug11849) + assert_valid_syntax("{label:<<-DOC\n""DOC\n""}", bug11849) + assert_valid_syntax("{label:<<~DOC\n""DOC\n""}", bug11849) + assert_valid_syntax("{label: <<DOC\n""DOC\n""}", bug11849) + assert_valid_syntax("{label: <<-DOC\n""DOC\n""}", bug11849) + assert_valid_syntax("{label: <<~DOC\n""DOC\n""}", bug11849) + end + + def test_cmdarg_kwarg_lvar_clashing_method + bug12073 = '[ruby-core:73816] [Bug#12073]' + a = a = 1 + assert_valid_syntax("a b: 1") + assert_valid_syntax("a = 1; a b: 1", bug12073) + end + def test_duplicated_arg assert_syntax_error("def foo(a, a) end", /duplicated argument name/) + assert_valid_syntax("def foo(_, _) end") + (obj = Object.new).instance_eval("def foo(_, x, _) x end") + assert_equal(2, obj.foo(1, 2, 3)) end def test_duplicated_rest assert_syntax_error("def foo(a, *a) end", /duplicated argument name/) + assert_valid_syntax("def foo(_, *_) end") + (obj = Object.new).instance_eval("def foo(_, x, *_) x end") + assert_equal(2, obj.foo(1, 2, 3)) end def test_duplicated_opt assert_syntax_error("def foo(a, a=1) end", /duplicated argument name/) + assert_valid_syntax("def foo(_, _=1) end") + (obj = Object.new).instance_eval("def foo(_, x, _=42) x end") + assert_equal(2, obj.foo(1, 2)) end def test_duplicated_opt_rest assert_syntax_error("def foo(a=1, *a) end", /duplicated argument name/) + assert_valid_syntax("def foo(_=1, *_) end") + (obj = Object.new).instance_eval("def foo(_, x=42, *_) x end") + assert_equal(42, obj.foo(1)) + assert_equal(2, obj.foo(1, 2)) end def test_duplicated_rest_opt @@ -177,39 +652,87 @@ class TestSyntax < Test::Unit::TestCase def test_duplicated_rest_post assert_syntax_error("def foo(*a, a) end", /duplicated argument name/) + assert_valid_syntax("def foo(*_, _) end") + (obj = Object.new).instance_eval("def foo(*_, x, _) x end") + assert_equal(2, obj.foo(1, 2, 3)) + assert_equal(2, obj.foo(2, 3)) + (obj = Object.new).instance_eval("def foo(*_, _, x) x end") + assert_equal(3, obj.foo(1, 2, 3)) + assert_equal(3, obj.foo(2, 3)) end def test_duplicated_opt_post assert_syntax_error("def foo(a=1, a) end", /duplicated argument name/) + assert_valid_syntax("def foo(_=1, _) end") + (obj = Object.new).instance_eval("def foo(_=1, x, _) x end") + assert_equal(2, obj.foo(1, 2, 3)) + assert_equal(2, obj.foo(2, 3)) + (obj = Object.new).instance_eval("def foo(_=1, _, x) x end") + assert_equal(3, obj.foo(1, 2, 3)) + assert_equal(3, obj.foo(2, 3)) end def test_duplicated_kw assert_syntax_error("def foo(a, a: 1) end", /duplicated argument name/) + assert_valid_syntax("def foo(_, _: 1) end") + (obj = Object.new).instance_eval("def foo(_, x, _: 1) x end") + assert_equal(3, obj.foo(2, 3)) + assert_equal(3, obj.foo(2, 3, _: 42)) + (obj = Object.new).instance_eval("def foo(x, _, _: 1) x end") + assert_equal(2, obj.foo(2, 3)) + assert_equal(2, obj.foo(2, 3, _: 42)) end def test_duplicated_rest_kw assert_syntax_error("def foo(*a, a: 1) end", /duplicated argument name/) + assert_nothing_raised {def foo(*_, _: 1) end} + (obj = Object.new).instance_eval("def foo(*_, x: 42, _: 1) x end") + assert_equal(42, obj.foo(42)) + assert_equal(42, obj.foo(2, _: 0)) + assert_equal(2, obj.foo(x: 2, _: 0)) + ensure + self.class.remove_method(:foo) end def test_duplicated_opt_kw assert_syntax_error("def foo(a=1, a: 1) end", /duplicated argument name/) + assert_valid_syntax("def foo(_=1, _: 1) end") + (obj = Object.new).instance_eval("def foo(_=42, x, _: 1) x end") + assert_equal(0, obj.foo(0)) + assert_equal(0, obj.foo(0, _: 3)) end def test_duplicated_kw_kwrest assert_syntax_error("def foo(a: 1, **a) end", /duplicated argument name/) + assert_valid_syntax("def foo(_: 1, **_) end") + (obj = Object.new).instance_eval("def foo(_: 1, x: 42, **_) x end") + assert_equal(42, obj.foo()) + assert_equal(42, obj.foo(a: 0)) + assert_equal(42, obj.foo(_: 0, a: 0)) + assert_equal(1, obj.foo(_: 0, x: 1, a: 0)) end def test_duplicated_rest_kwrest assert_syntax_error("def foo(*a, **a) end", /duplicated argument name/) + assert_valid_syntax("def foo(*_, **_) end") + (obj = Object.new).instance_eval("def foo(*_, x, **_) x end") + assert_equal(1, obj.foo(1)) + assert_equal(1, obj.foo(1, a: 0)) + assert_equal(2, obj.foo(1, 2, a: 0)) end def test_duplicated_opt_kwrest assert_syntax_error("def foo(a=1, **a) end", /duplicated argument name/) + assert_valid_syntax("def foo(_=1, **_) end") + (obj = Object.new).instance_eval("def foo(_=42, x, **_) x end") + assert_equal(1, obj.foo(1)) + assert_equal(1, obj.foo(1, a: 0)) + assert_equal(1, obj.foo(0, 1, a: 0)) end def test_duplicated_when - w = 'warning: duplicated when clause is ignored' - assert_warning(/3: #{w}.+4: #{w}.+4: #{w}.+5: #{w}.+5: #{w}/m){ + w = ->(line) { "warning: 'when' clause on line #{line} duplicates 'when' clause on line 3 and is ignored" } + assert_warning(/#{w[3]}.+#{w[4]}.+#{w[4]}.+#{w[5]}.+#{w[5]}/m) { eval %q{ case 1 when 1, 1 @@ -218,8 +741,8 @@ class TestSyntax < Test::Unit::TestCase end } } - assert_warning(/#{w}/){#/3: #{w}.+4: #{w}.+5: #{w}.+5: #{w}/m){ - a = 1 + assert_warning(/#{w[3]}.+#{w[4]}.+#{w[5]}.+#{w[5]}/m) { + a = a = 1 eval %q{ case 1 when 1, 1 @@ -228,6 +751,47 @@ class TestSyntax < Test::Unit::TestCase end } } + assert_warning(/#{w[3]}/) { + eval %q{ + case 1 + when __LINE__, __LINE__ + when 3, 3 + when 3, 3 + end + } + } + assert_warning(/#{w[3]}/) { + eval %q{ + case 1 + when __FILE__, __FILE__ + when "filename", "filename" + when "filename", "filename" + end + }, binding, "filename" + } + end + + def test_duplicated_when_check_option + w = /'when' clause on line 4 duplicates 'when' clause on line 3 and is ignored/ + assert_in_out_err(%[-wc], "#{<<~"begin;"}\n#{<<~'end;'}", ["Syntax OK"], w) + begin; + case 1 + when 1 + when 1 + end + end; + end + + def test_invalid_break + assert_syntax_error("def m; break; end", /Invalid break/) + assert_syntax_error('/#{break}/', /Invalid break/) + assert_syntax_error('/#{break}/o', /Invalid break/) + end + + def test_invalid_next + assert_syntax_error("def m; next; end", /Invalid next/) + assert_syntax_error('/#{next}/', /Invalid next/) + assert_syntax_error('/#{next}/o', /Invalid next/) end def test_lambda_with_space @@ -245,6 +809,21 @@ class TestSyntax < Test::Unit::TestCase 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) + + assert_valid_syntax('->(opt = (foo.[] bar)) {}') + assert_valid_syntax('->(opt = (foo.[]= bar)) {}') + assert_valid_syntax('->(opt = (foo.[] bar)) do end') + assert_valid_syntax('->(opt = (foo.[]= bar)) do end') + end + def test_reserved_method_no_args bug6403 = '[ruby-dev:45626]' assert_valid_syntax("def self; :foo; end", __FILE__, bug6403) @@ -253,7 +832,7 @@ class TestSyntax < Test::Unit::TestCase def test_unassignable gvar = global_variables %w[self nil true false __FILE__ __LINE__ __ENCODING__].each do |kwd| - assert_raise(SyntaxError) {eval("#{kwd} = nil")} + assert_syntax_error("#{kwd} = nil", /Can't .* #{kwd}$/) assert_equal(gvar, global_variables) end end @@ -270,6 +849,163 @@ 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_leading_blank_line + # the blank line has six leading spaces + result = " \n" \ + " b\n" + expect = " \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__, <<eom, __LINE__ @@ -281,6 +1017,58 @@ eom assert_equal(expected, actual, bug7559) end + def test_dedented_heredoc_invalid_identifer + assert_syntax_error('<<~ "#{}"', /unexpected <</) + end + + def test_dedented_heredoc_concatenation + assert_equal(" \n0\n1", eval("<<~0 '1'\n \n0\#{}\n0")) + end + + def test_heredoc_mixed_encoding + e = assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source') + #encoding: cp932 + <<-TEXT + \xe9\x9d\u1234 + TEXT + HEREDOC + assert_not_match(/end-of-input/, e.message) + + e = assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source') + #encoding: cp932 + <<-TEXT + \xe9\x9d + \u1234 + TEXT + HEREDOC + assert_not_match(/end-of-input/, e.message) + + e = assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source') + #encoding: cp932 + <<-TEXT + \u1234\xe9\x9d + TEXT + HEREDOC + assert_not_match(/end-of-input/, e.message) + + e = assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source') + #encoding: cp932 + <<-TEXT + \u1234 + \xe9\x9d + TEXT + HEREDOC + assert_not_match(/end-of-input/, e.message) + end + + def test_invalid_regexp + bug20295 = '[ruby-core:116913] [Bug #20295]' + + assert_syntax_error("/[/=~s", /premature end of char-class/, bug20295) + assert_syntax_error("/(?<>)/=~s", /group name is empty/, bug20295) + assert_syntax_error("/(?<a>[)/=~s", /premature end of char-class/, bug20295) + end + def test_lineno_operation_brace_block expected = __LINE__ + 1 actual = caller_lineno\ @@ -293,7 +1081,7 @@ eom ["p ", ""], # no-pop ["", "p Foo::Bar"], # pop ].each do |p1, p2| - src = <<-EOM.gsub(/^\s*\n/, '') + src = <<~EOM class Foo #{"Bar = " + preset if preset} end @@ -325,7 +1113,7 @@ eom ["p ", ""], # no-pop ["", "p ::Bar"], # pop ].each do |p1, p2| - src = <<-EOM.gsub(/^\s*\n/, '') + src = <<~EOM #{"Bar = " + preset if preset} class Foo #{p1}::Bar #{op}= 42 @@ -352,12 +1140,1228 @@ eom assert_constant_reassignment_toplevel("11", "+", %w[53], already) end + def test_integer_suffix + ["1if true", "begin 1end"].each do |src| + assert_valid_syntax(src) + assert_equal(1, eval(src), src) + end + end + + def test_value_of_def + assert_separately [], <<-EOS + assert_equal(:foo, (def foo; end)) + assert_equal(:foo, (def (Object.new).foo; end)) + EOS + end + + def test_heredoc_cr + assert_syntax_error("puts <<""EOS\n""ng\n""EOS\r""NO\n", /can't find string "EOS" anywhere before EOF/) + end + + def test_heredoc_no_terminator + assert_syntax_error("puts <<""A\n", /can't find string "A" anywhere before EOF/) + assert_syntax_error("puts <<""A + <<""B\n", /can't find string "A" anywhere before EOF/) + assert_syntax_error("puts <<""A + <<""B\n", /can't find string "B" anywhere before EOF/) + end + + def test_unterminated_heredoc + assert_syntax_error("<<\"EOS\n\nEOS\n", /unterminated/) + assert_syntax_error("<<\"EOS\n\"\nEOS\n", /unterminated/) + end + + def test_unterminated_heredoc_cr + %W[\r\n \n].each do |nl| + assert_syntax_error("<<\"\r\"#{nl}\r#{nl}", /unterminated/, nil, "CR with #{nl.inspect}") + end + end + + def test__END___cr + assert_syntax_error("__END__\r<<<<<\n", /unexpected <</) + end + + def test_warning_for_cr + feature8699 = '[ruby-core:56240] [Feature #8699]' + s = assert_warning(/encountered \\r/, feature8699) do + eval("'\r'\r") + end + assert_equal("\r", s) + s = assert_warning('') do + eval("'\r'\r\n") + end + assert_equal("\r", s) + end + + def test_unexpected_fraction + msg = /unexpected fraction/ + assert_syntax_error("0x0.0", msg) + assert_syntax_error("0b0.0", msg) + assert_syntax_error("0d0.0", msg) + assert_syntax_error("0o0.0", msg) + assert_syntax_error("0.0.0", msg) + end + def test_error_message_encoding bug10114 = '[ruby-core:64228] [Bug #10114]' code = "# -*- coding: utf-8 -*-\n" "def n \"\u{2208}\"; end" assert_syntax_error(code, /def n "\u{2208}"; end/, bug10114) end + def test_null_range_cmdarg + bug10957 = '[ruby-core:68477] [Bug #10957]' + assert_ruby_status(['-c', '-e', 'p ()..0'], "", bug10957) + assert_ruby_status(['-c', '-e', 'p ()...0'], "", bug10957) + assert_syntax_error('0..%q.', /unterminated string/, bug10957) + assert_syntax_error('0...%q.', /unterminated string/, bug10957) + end + + def test_range_at_eol + assert_warn(/\.\.\. at EOL/) {eval("1...\n2")} + assert_warn('') {eval("(1...)")} + assert_warn('') {eval("(1...\n2)")} + assert_warn('') {eval("{a: 1...\n2}")} + + assert_warn(/\.\.\. at EOL/) do + assert_valid_syntax('foo.[]= ...', verbose: true) + end + assert_warn(/\.\.\. at EOL/) do + assert_valid_syntax('foo.[] ...', verbose: true) + end + assert_warn(/\.\.\. at EOL/) do + assert_syntax_error('foo.[]= bar, ...', /unexpected/, verbose: true) + end + assert_warn(/\.\.\. at EOL/) do + assert_syntax_error('foo.[] bar, ...', /unexpected/, verbose: true) + end + end + + def test_too_big_nth_ref + bug11192 = '[ruby-core:69393] [Bug #11192]' + assert_warn(/too big/, bug11192) do + eval('$99999999999999999') + end + end + + def test_invalid_symbol_space + assert_syntax_error(": foo", /unexpected ':'/) + assert_syntax_error(": #\n foo", /unexpected ':'/) + assert_syntax_error(":#\n foo", /unexpected ':'/) + end + + def test_invalid_literal_message + assert_syntax_error("def :foo", /unexpected symbol literal/) + assert_syntax_error("def 'foo'", /unexpected string literal/) + end + + def test_fluent_dot + assert_valid_syntax("a\n.foo") + assert_valid_syntax("a\n&.foo") + assert_valid_syntax("a #\n#\n.foo\n") + assert_valid_syntax("a #\n#\n&.foo\n") + end + + def test_fluent_and + assert_valid_syntax("a\n" "&& foo") + assert_valid_syntax("a\n" "and foo") + + assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}")) + begin; + a = true + if a + && (a = :ok; true) + a + end + end; + + assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}")) + begin; + a = true + if a + and (a = :ok; true) + a + end + end; + end + + def test_fluent_or + assert_valid_syntax("a\n" "|| foo") + assert_valid_syntax("a\n" "or foo") + + assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}")) + begin; + a = false + if a + || (a = :ok; true) + a + end + end; + + assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}")) + begin; + a = false + if a + or (a = :ok; true) + a + end + end; + end + + def test_safe_call_in_massign_lhs + assert_syntax_error("*a&.x=0", /multiple assignment destination/) + assert_syntax_error("a&.x,=0", /multiple assignment destination/) + end + + def test_safe_call_in_for_variable + assert_valid_syntax("for x&.bar in []; end") + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + foo = nil + for foo&.bar in [1]; end + assert_nil(foo) + + foo = Struct.new(:bar).new + for foo&.bar in [1]; end + assert_equal(1, foo.bar) + end; + end + + def test_no_warning_logop_literal + assert_warning("") do + eval("true||raise;nil") + end + assert_warning("") do + eval("false&&raise;nil") + end + assert_warning("") do + eval("''||raise;nil") + end + end + + def test_warning_literal_in_condition + assert_warn(/string literal in condition/) do + eval('1 if ""') + end + assert_warning(/string literal in condition/) do + eval('1 if __FILE__') + end + assert_warn(/regex literal in condition/) do + eval('1 if //') + end + assert_warning(/literal in condition/) do + eval('1 if 1') + end + assert_warning(/literal in condition/) do + eval('1 if __LINE__') + end + assert_warning(/symbol literal in condition/) do + eval('1 if :foo') + end + assert_warning(/symbol literal in condition/) do + eval('1 if :"#{"foo".upcase}"') + end + + assert_warn('') do + eval('1 if !""') + end + assert_warn('') do + eval('1 if !//') + end + assert_warn('') do + eval('1 if !(true..false)') + end + assert_warning('') do + eval('1 if !1') + end + assert_warning('') do + eval('1 if !:foo') + end + assert_warning('') do + eval('1 if !:"#{"foo".upcase}"') + end + end + + def test_warning_literal_in_flip_flop + assert_warn(/literal in flip-flop/) do + eval('1 if ""..false') + end + assert_warning(/literal in flip-flop/) do + eval('1 if :foo..false') + end + assert_warning(/literal in flip-flop/) do + eval('1 if :"#{"foo".upcase}"..false') + end + assert_warn(/literal in flip-flop/) do + eval('1 if ""...false') + end + assert_warning(/literal in flip-flop/) do + eval('1 if :foo...false') + end + assert_warning(/literal in flip-flop/) do + eval('1 if :"#{"foo".upcase}"...false') + end + end + + def test_alias_symbol + bug8851 = '[ruby-dev:47681] [Bug #8851]' + formats = ['%s', ":'%s'", ':"%s"', '%%s(%s)'] + all_assertions(bug8851) do |all| + formats.product(formats) do |form1, form2| + all.for(code = "alias #{form1 % 'a'} #{form2 % 'p'}") do + assert_valid_syntax(code) + end + end + end + end + + def test_undef_symbol + bug8851 = '[ruby-dev:47681] [Bug #8851]' + formats = ['%s', ":'%s'", ':"%s"', '%%s(%s)'] + all_assertions(bug8851) do |all| + formats.product(formats) do |form1, form2| + all.for(code = "undef #{form1 % 'a'}, #{form2 % 'p'}") do + assert_valid_syntax(code) + end + end + end + end + + def test_parenthesised_statement_argument + assert_syntax_error("foo(bar rescue nil)", /unexpected 'rescue' modifier/) + assert_valid_syntax("foo (bar rescue nil)") + end + + def test_cmdarg_in_paren + bug11873 = '[ruby-core:72482] [Bug #11873]' + assert_valid_syntax %q{a b{c d}, :e do end}, bug11873 + assert_valid_syntax %q{a b(c d), :e do end}, bug11873 + assert_valid_syntax %q{a b{c(d)}, :e do end}, bug11873 + assert_valid_syntax %q{a b(c(d)), :e do end}, bug11873 + assert_valid_syntax %q{a b{c d}, 1 do end}, bug11873 + assert_valid_syntax %q{a b(c d), 1 do end}, bug11873 + assert_valid_syntax %q{a b{c(d)}, 1 do end}, bug11873 + assert_valid_syntax %q{a b(c(d)), 1 do end}, bug11873 + assert_valid_syntax %q{a b{c d}, "x" do end}, bug11873 + assert_valid_syntax %q{a b(c d), "x" do end}, bug11873 + assert_valid_syntax %q{a b{c(d)}, "x" do end}, bug11873 + assert_valid_syntax %q{a b(c(d)), "x" do end}, bug11873 + end + + def test_block_after_cmdarg_in_paren + bug11873 = '[ruby-core:72482] [Bug #11873]' + def bug11873.p(*, &);end; + + assert_raise(LocalJumpError, bug11873) do + bug11873.instance_eval do + p p{p p;p(p)}, tap do + raise SyntaxError, "should not be passed to tap" + end + end + end + + assert_raise(LocalJumpError, bug11873) do + bug11873.instance_eval do + p p{p(p);p p}, tap do + raise SyntaxError, "should not be passed to tap" + end + end + end + end + + def test_do_block_in_hash_brace + bug13073 = '[ruby-core:78837] [Bug #13073]' + assert_valid_syntax 'p :foo, {a: 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, {"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_invalid_encoding_symbol + assert_syntax_error('{"\xC3": 1}', "invalid symbol") + end + + def test_invalid_symbol_in_hash_memory_leak + assert_no_memory_leak([], "#{<<-'begin;'}", "#{<<-'end;'}", rss: true) + str = '{"\xC3": 1}'.force_encoding("UTF-8") + code = proc do + eval(str) + raise "unreachable" + rescue SyntaxError + end + + 1_000.times(&code) + begin; + 1_000_000.times(&code) + end; + 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 + nil&defined?0--begin e=no_method_error(); return; 0;end + return puts('ignored') #=> ignored + BEGIN {return} + END {return if false} + 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_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 + + 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_return_in_END + assert_normal_exit('END {return}') + end + + def test_return_in_BEGIN_in_eval + # `BEGIN` in `eval` is allowed, even inside a method, and `return` + # from that block exits from that method without `LocalJumpError`. + obj = Object.new + def obj.ok + eval("BEGIN {return :ok}") + end + assert_equal :ok, assert_nothing_raised(LocalJumpError) {obj.ok} + 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: |~ Invalid redo/) + 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_assignment_return_in_loop + obj = Object.new + def obj.test + x = nil + _y = (return until x unless x) + end + assert_nil obj.test, "[Bug #16695]" + 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_endless + assert_valid_syntax('private def foo = 42') + assert_valid_syntax('private def foo() = 42') + assert_valid_syntax('private def inc(x) = x + 1') + assert_valid_syntax('private def obj.foo = 42') + assert_valid_syntax('private def obj.foo() = 42') + assert_valid_syntax('private def obj.inc(x) = x + 1') + k = Class.new do + class_eval('def rescued(x) = raise("to be caught") rescue "instance #{x}"') + class_eval('def self.rescued(x) = raise("to be caught") rescue "class #{x}"') + end + assert_equal("class ok", k.rescued("ok")) + assert_equal("instance ok", k.new.rescued("ok")) + + error = /setter method cannot be defined in an endless method definition/ + assert_syntax_error('def foo=() = 42', error) + assert_syntax_error('def obj.foo=() = 42', error) + assert_syntax_error('def foo=() = 42 rescue nil', error) + assert_syntax_error('def obj.foo=() = 42 rescue nil', error) + end + + def test_methoddef_endless_command + assert_valid_syntax('def foo = puts "Hello"') + assert_valid_syntax('def foo() = puts "Hello"') + assert_valid_syntax('def foo(x) = puts x') + assert_valid_syntax('def obj.foo = puts "Hello"') + assert_valid_syntax('def obj.foo() = puts "Hello"') + assert_valid_syntax('def obj.foo(x) = puts x') + k = Class.new do + class_eval('def rescued(x) = raise "to be caught" rescue "instance #{x}"') + class_eval('def self.rescued(x) = raise "to be caught" rescue "class #{x}"') + end + assert_equal("class ok", k.rescued("ok")) + assert_equal("instance ok", k.new.rescued("ok")) + + assert_valid_syntax('private def foo = puts "Hello"') + assert_valid_syntax('private def foo() = puts "Hello"') + assert_valid_syntax('private def foo(x) = puts x') + assert_valid_syntax('private def obj.foo = puts "Hello"') + assert_valid_syntax('private def obj.foo() = puts "Hello"') + assert_valid_syntax('private def obj.foo(x) = puts x') + 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) {}') + assert_valid_syntax('bar {}') + assert_valid_syntax('Bar {}') + assert_valid_syntax('bar() {}') + assert_valid_syntax('Bar() {}') + assert_valid_syntax('Foo::bar {}') + assert_valid_syntax('Foo::Bar {}') + assert_valid_syntax('Foo::bar() {}') + assert_valid_syntax('Foo::Bar() {}') + end + + def test_command_newline_in_tlparen_args + assert_valid_syntax("p (1\n2\n),(3),(4)") + assert_valid_syntax("p (\n),(),()") + assert_valid_syntax("a.b (1\n2\n),(3),(4)") + assert_valid_syntax("a.b (\n),(),()") + end + + def test_command_semicolon_in_tlparen_at_the_first_arg + bug19281 = '[ruby-core:111499] [Bug #19281]' + assert_valid_syntax('p (1;2),(3),(4)', bug19281) + assert_valid_syntax('p (;),(),()', bug19281) + assert_valid_syntax('a.b (1;2),(3),(4)', bug19281) + assert_valid_syntax('a.b (;),(),()', bug19281) + end + + def test_command_do_block_call_with_empty_args_brace_block + assert_valid_syntax('cmd 1, 2 do end.m() { blk_body }') + 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/) + assert_syntax_error('proc {_1 = nil}', /_1 is reserved for numbered parameter/) + assert_syntax_error('_2=1', /_2 is reserved for numbered parameter/) + assert_syntax_error('proc {|_3|}', /_3 is reserved for numbered parameter/) + assert_syntax_error('def x(_4) end', /_4 is reserved for numbered parameter/) + assert_syntax_error('def _5; end', /_5 is reserved for numbered parameter/) + assert_syntax_error('def self._6; end', /_6 is reserved for numbered parameter/) + 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 + + 1.times { + [ + _1, + assert_equal([:a], eval("[:a].map{_1}")), + assert_raise(NameError) {eval("_1")}, + ] + } + + assert_valid_syntax("proc {def foo(_);end;_1}") + assert_valid_syntax("p { [_1 **2] }") + assert_valid_syntax("proc {_1;def foo();end;_1}") + end + + def test_it + assert_valid_syntax('proc {it}') + assert_syntax_error('[1,2].then {it+_2}', /'it' is already used/) + assert_syntax_error('[1,2].then {_2+it}', /numbered parameter is already used/) + assert_equal([1, 2], eval('[1,2].then {it}')) + assert_syntax_error('[1,2].then {"#{it}#{_2}"}', /'it' is already used/) + assert_syntax_error('[1,2].then {"#{_2}#{it}"}', /numbered parameter is already used/) + assert_syntax_error('->{it+_2}.call(1,2)', /'it' is already used/) + assert_syntax_error('->{_2+it}.call(1,2)', /numbered parameter is already used/) + assert_equal(4, eval('->(a=->{it}){a}.call.call(4)')) + assert_equal(5, eval('-> a: ->{it} {a}.call.call(5)')) + assert_syntax_error('proc {|| it}', /ordinary parameter is defined/) + assert_syntax_error('proc {|;a| it}', /ordinary parameter is defined/) + assert_syntax_error("proc {|\n| it}", /ordinary parameter is defined/) + assert_syntax_error('proc {|x| it}', /ordinary parameter is defined/) + assert_equal([1, 2], eval('1.then {[it, 2.then {_1}]}')) + assert_equal([2, 1], eval('1.then {[2.then {_1}, it]}')) + assert_syntax_error('->(){it}', /ordinary parameter is defined/) + assert_syntax_error('->(x){it}', /ordinary parameter is defined/) + assert_syntax_error('->x{it}', /ordinary parameter is defined/) + assert_syntax_error('->x:_1{}', /ordinary parameter is defined/) + assert_syntax_error('->x=it{}', /ordinary parameter is defined/) + assert_valid_syntax('-> {it; -> {_2}}') + assert_valid_syntax('-> {-> {it}; _2}') + assert_equal([1, nil], eval('proc {that=it; it=nil; [that, it]}.call(1)')) + assert_equal(1, eval('proc {it = 1}.call')) + assert_warning(/1: warning: assigned but unused variable - it/) { + assert_equal(2, eval('a=Object.new; def a.foo; it = 2; end; a.foo')) + } + assert_equal(3, eval('proc {|it| it}.call(3)')) + assert_equal(4, eval('a=Object.new; def a.foo(it); it; end; a.foo(4)')) + assert_equal(5, eval('a=Object.new; def a.it; 5; end; a.it')) + assert_equal(6, eval('a=Class.new; a.class_eval{ def it; 6; end }; a.new.it')) + assert_equal([7, 8], eval('a=[]; [7].each { a << it; [8].each { a << it } }; a')) + assert_raise_with_message(NameError, /undefined local variable or method 'it'/) do + eval('it') + end + ['class C', 'class << C', 'module M', 'def m', 'def o.m'].each do |c| + assert_valid_syntax("->{#{c};->{it};end;it}\n") + assert_valid_syntax("->{it;#{c};->{it};end}\n") + end + 1.times do + [ + assert_equal(0, it), + assert_equal([:a], eval('[:a].map{it}')), + assert_raise(NameError) {eval('it')}, + ] + end + assert_valid_syntax('proc {def foo(_);end;it}') + assert_syntax_error('p { [it **2] }', /unexpected \*\*/) + assert_equal(1, eval('1.then { raise rescue it }')) + assert_equal(2, eval('1.then { 2.then { raise rescue it } }')) + assert_equal(3, eval('3.then { begin; raise; rescue; it; end }')) + assert_equal(4, eval('4.tap { begin; raise ; rescue; raise rescue it; end; }')) + assert_equal(5, eval('a = 0; 5.then { begin; nil; ensure; a = it; end }; a')) + assert_equal(6, eval('a = 0; 6.then { begin; nil; rescue; ensure; a = it; end }; a')) + assert_equal(7, eval('a = 0; 7.then { begin; raise; ensure; a = it; end } rescue a')) + assert_equal(8, eval('a = 0; 8.then { begin; raise; rescue; ensure; a = it; end }; a')) + assert_equal(/9/, eval('9.then { /#{it}/o }')) + end + + def test_it_with_splat_super_method + bug21256 = '[ruby-core:121592] [Bug #21256]' + + a = Class.new do + define_method(:foo) { it } + end + b = Class.new(a) do + def foo(*args) = super + end + + assert_equal(1, b.new.foo(1), bug21256) + 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_value_expr_in_singleton + mesg = /void value expression/ + assert_syntax_error("class << (return); end", mesg) + end + + def test_tautological_condition + assert_valid_syntax("def f() return if false and invalid; nil end") + assert_valid_syntax("def f() return unless true or invalid; nil end") + end + + def test_argument_forwarding + assert_valid_syntax('def foo(...) bar(...) end') + assert_valid_syntax('def foo(...) end') + assert_valid_syntax('def foo(a, ...) bar(...) end') + assert_valid_syntax("def foo ...\n bar(...)\nend") + assert_valid_syntax("def foo a, ...\n bar(...)\nend") + assert_valid_syntax("def foo b = 1, ...\n bar(...)\nend") + assert_valid_syntax("def foo ...; bar(...); end") + assert_valid_syntax("def foo a, ...; bar(...); end") + assert_valid_syntax("def foo b = 1, ...; bar(...); end") + assert_valid_syntax("(def foo ...\n bar(...)\nend)") + assert_valid_syntax("(def foo ...; bar(...); end)") + assert_valid_syntax("def (1...).foo ...; bar(...); end") + assert_valid_syntax("def (tap{1...}).foo ...; bar(...); end") + assert_valid_syntax('def ==(...) end') + assert_valid_syntax('def [](...) end') + assert_valid_syntax('def nil(...) end') + assert_valid_syntax('def true(...) end') + assert_valid_syntax('def false(...) end') + assert_valid_syntax('->a=1...{}') + unexpected = /unexpected \.{3}/ + assert_syntax_error('iter do |...| end', /unexpected/) + assert_syntax_error('iter {|...|}', /unexpected/) + assert_syntax_error('->... {}', unexpected) + assert_syntax_error('->(...) {}', unexpected) + assert_syntax_error('->a,... {}', unexpected) + assert_syntax_error('->(a,...) {}', unexpected) + assert_syntax_error('->a=1,... {}', unexpected) + assert_syntax_error('->(a=1,...) {}', 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/) + assert_syntax_error('def foo(*rest, ...) end', '... after rest argument') + assert_syntax_error('def foo(*, ...) end', '... after rest argument') + + obj1 = Object.new + def obj1.bar(*args, **kws, &block) + if block + block.call(args, kws) + else + [args, kws] + end + end + obj4 = obj1.clone + obj5 = obj1.clone + obj6 = obj1.clone + obj1.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__) + obj4.instance_eval("def foo ...\n bar(...)\n""end", __FILE__, __LINE__) + obj5.instance_eval("def foo ...; bar(...); end", __FILE__, __LINE__) + obj6.instance_eval('def foo(...) eval("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, obj4, obj5, obj6].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)) + } + array = obj == obj3 ? [] : [{}] + assert_warning('') { + assert_equal([array, {}], obj.foo({}) {|*x| x}) + } + assert_warning('') { + assert_equal([array, {}], obj.foo({})) + } + assert_equal(-1, obj.method(:foo).arity) + parameters = obj.method(:foo).parameters + assert_equal(:rest, parameters.dig(0, 0)) + assert_equal(:keyrest, parameters.dig(1, 0)) + assert_equal(:block, parameters.dig(2, 0)) + end + end + + def test_argument_forwarding_with_leading_arguments + obj = Object.new + def obj.bar(*args, **kws, &block) + if block + block.call(args, kws) + else + [args, kws] + end + end + obj.instance_eval('def foo(_a, ...) bar(...) end', __FILE__, __LINE__) + assert_equal [[], {}], obj.foo(1) + assert_equal [[2], {}], obj.foo(1, 2) + assert_equal [[2, 3], {}], obj.foo(1, 2, 3) + assert_equal [[], {a: 1}], obj.foo(1, a: 1) + assert_equal [[2], {a: 1}], obj.foo(1, 2, a: 1) + assert_equal [[2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1) + assert_equal [[2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(...) bar(1, ...) end', __FILE__, __LINE__) + assert_equal [[1], {}], obj.foo + assert_equal [[1, 1], {}], obj.foo(1) + assert_equal [[1, 1, 2], {}], obj.foo(1, 2) + assert_equal [[1, 1, 2, 3], {}], obj.foo(1, 2, 3) + assert_equal [[1], {a: 1}], obj.foo(a: 1) + assert_equal [[1, 1], {a: 1}], obj.foo(1, a: 1) + assert_equal [[1, 1, 2], {a: 1}], obj.foo(1, 2, a: 1) + assert_equal [[1, 1, 2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1) + assert_equal [[1, 1, 2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(a, ...) bar(a, ...) end', __FILE__, __LINE__) + assert_equal [[4], {}], obj.foo(4) + assert_equal [[4, 2], {}], obj.foo(4, 2) + assert_equal [[4, 2, 3], {}], obj.foo(4, 2, 3) + assert_equal [[4], {a: 1}], obj.foo(4, a: 1) + assert_equal [[4, 2], {a: 1}], obj.foo(4, 2, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(_a, ...) bar(1, ...) end', __FILE__, __LINE__) + assert_equal [[1], {}], obj.foo(4) + assert_equal [[1, 2], {}], obj.foo(4, 2) + assert_equal [[1, 2, 3], {}], obj.foo(4, 2, 3) + assert_equal [[1], {a: 1}], obj.foo(4, a: 1) + assert_equal [[1, 2], {a: 1}], obj.foo(4, 2, a: 1) + assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1) + assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(_a, _b, ...) bar(...) end', __FILE__, __LINE__) + assert_equal [[], {}], obj.foo(4, 5) + assert_equal [[2], {}], obj.foo(4, 5, 2) + assert_equal [[2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(_a, _b, ...) bar(1, ...) end', __FILE__, __LINE__) + assert_equal [[1], {}], obj.foo(4, 5) + assert_equal [[1, 2], {}], obj.foo(4, 5, 2) + assert_equal [[1, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[1], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[1, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(_a, ...) bar(1, 2, ...) end', __FILE__, __LINE__) + assert_equal [[1, 2], {}], obj.foo(5) + assert_equal [[1, 2, 5], {}], obj.foo(4, 5) + assert_equal [[1, 2, 5, 2], {}], obj.foo(4, 5, 2) + assert_equal [[1, 2, 5, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[1, 2, 5], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[1, 2, 5, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[1, 2, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[1, 2, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(a, b, ...) bar(b, a, ...) end', __FILE__, __LINE__) + assert_equal [[5, 4], {}], obj.foo(4, 5) + assert_equal [[5, 4, 2], {}], obj.foo(4, 5, 2) + assert_equal [[5, 4, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[5, 4], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[5, 4, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[5, 4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[5, 4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(a, _b, ...) bar(a, ...) end', __FILE__, __LINE__) + assert_equal [[4], {}], obj.foo(4, 5) + assert_equal [[4, 2], {}], obj.foo(4, 5, 2) + assert_equal [[4, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[4], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[4, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(a, ...) bar(a, 1, ...) end', __FILE__, __LINE__) + assert_equal [[4, 1], {}], obj.foo(4) + assert_equal [[4, 1, 5], {}], obj.foo(4, 5) + assert_equal [[4, 1, 5, 2], {}], obj.foo(4, 5, 2) + assert_equal [[4, 1, 5, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[4, 1, 5], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[4, 1, 5, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[4, 1, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[4, 1, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval("def foo a, ...\n bar(a, ...)\n"" end", __FILE__, __LINE__) + assert_equal [[4], {}], obj.foo(4) + assert_equal [[4, 2], {}], obj.foo(4, 2) + assert_equal [[4, 2, 3], {}], obj.foo(4, 2, 3) + assert_equal [[4], {a: 1}], obj.foo(4, a: 1) + assert_equal [[4, 2], {a: 1}], obj.foo(4, 2, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval("def foo a, ...; bar(a, ...); end", __FILE__, __LINE__) + assert_equal [[4], {}], obj.foo(4) + assert_equal [[4, 2], {}], obj.foo(4, 2) + assert_equal [[4, 2, 3], {}], obj.foo(4, 2, 3) + assert_equal [[4], {a: 1}], obj.foo(4, a: 1) + assert_equal [[4, 2], {a: 1}], obj.foo(4, 2, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1){|args, kws| [args, kws]} + + exp = eval("-> (a: nil) {a...1}") + assert_equal 0...1, exp.call(a: 0) + end + + def test_argument_forwarding_with_super + assert_valid_syntax('def foo(...) super {}; end') + assert_valid_syntax('def foo(...) super() {}; end') + assert_syntax_error('def foo(...) super(...) {}; end', /both block arg and actual block/) + assert_syntax_error('def foo(...) super(1, ...) {}; end', /both block arg and actual block/) + end + + def test_argument_forwarding_with_super_memory_leak + assert_no_memory_leak([], "#{<<-'begin;'}", "#{<<-'end;'}", rss: true) + code = proc do + eval("def foo(...) super(...) {}; end") + raise "unreachable" + rescue SyntaxError + end + + 1_000.times(&code) + begin; + 100_000.times(&code) + end; + end + + def test_class_module_Object_ancestors + assert_ruby_status([], <<-RUBY) + m = Module.new + m::Bug18832 = 1 + include m + class Bug18832; end + RUBY + assert_ruby_status([], <<-RUBY) + m = Module.new + m::Bug18832 = 1 + include m + module Bug18832; end + RUBY + end + + def test_cdhash + assert_separately([], <<-RUBY) + n = case 1 when 2r then false else true end + assert_equal(n, true, '[ruby-core:103759] [Bug #17854]') + RUBY + assert_separately([], <<-RUBY) + n = case 3/2r when 1.5r then true else false end + assert_equal(n, true, '[ruby-core:103759] [Bug #17854]') + RUBY + assert_separately([], <<-RUBY) + n = case 1i when 1i then true else false end + assert_equal(n, true, '[ruby-core:103759] [Bug #17854]') + RUBY + end + + def test_defined_in_short_circuit_if_condition + bug = '[ruby-core:20501]' + conds = [ + "false && defined?(Some::CONSTANT)", + "true || defined?(Some::CONSTANT)", + "(false && defined?(Some::CONSTANT))", # parens exercise different code path + "(true || defined?(Some::CONSTANT))", + "@val && false && defined?(Some::CONSTANT)", + "@val || true || defined?(Some::CONSTANT)" + ] + + conds.each do |cond| + code = %Q{ + def my_method + var = "there" + if #{cond} + var = "here" + end + raise var + end + begin + my_method + rescue + print 'ok' + else + print 'ng' + end + } + # Invoke in a subprocess because the bug caused a segfault using the parse.y compiler. + # Don't use assert_separately because the bug was best reproducible in a clean slate, + # without test env loaded. + out, _err, status = EnvUtil.invoke_ruby(["--disable-gems"], code, true, false) + assert_predicate(status, :success?, bug) + assert_equal 'ok', out + end + end + private def not_label(x) @result = x; @not_label ||= nil end @@ -392,7 +2396,7 @@ eom end end - def caller_lineno(*) + def caller_lineno(*, &) caller_locations(1, 1)[0].lineno end end |
