summaryrefslogtreecommitdiff
path: root/test/ruby/test_syntax.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_syntax.rb')
-rw-r--r--test/ruby/test_syntax.rb2048
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