summaryrefslogtreecommitdiff
path: root/test/ruby/test_parse.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_parse.rb')
-rw-r--r--test/ruby/test_parse.rb1150
1 files changed, 863 insertions, 287 deletions
diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb
index c356f68ecc..def41d6017 100644
--- a/test/ruby/test_parse.rb
+++ b/test/ruby/test_parse.rb
@@ -2,11 +2,11 @@
# frozen_string_literal: false
require 'test/unit'
require 'stringio'
+require_relative '../lib/parser_support'
class TestParse < Test::Unit::TestCase
def setup
@verbose = $VERBOSE
- $VERBOSE = nil
end
def teardown
@@ -15,10 +15,11 @@ class TestParse < Test::Unit::TestCase
def test_error_line
assert_syntax_error('------,,', /\n\z/, 'Message to pipe should end with a newline')
+ assert_syntax_error("{hello\n world}", /hello/)
end
def test_else_without_rescue
- assert_syntax_error(<<-END, %r":#{__LINE__+2}: else without rescue"o, [__FILE__, __LINE__+1])
+ assert_syntax_error(<<-END, %r"(:#{__LINE__+2}:|#{__LINE__+2} \|.+?\n.+?\^~.+?;) else without rescue"o, [__FILE__, __LINE__+1])
begin
else
42
@@ -27,10 +28,10 @@ class TestParse < Test::Unit::TestCase
end
def test_alias_backref
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /can't make alias/) do
+ begin;
alias $foo $1
- END
+ end;
end
end
@@ -85,10 +86,10 @@ class TestParse < Test::Unit::TestCase
assert_equal([42, 42], [o.Foo, o.Bar])
assert_equal([42, 42], [o::baz, o::qux])
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /Can't set variable/) do
+ begin;
$1 ||= t.foo 42
- END
+ end;
end
def t.bar(x); x + yield; end
@@ -153,67 +154,74 @@ class TestParse < Test::Unit::TestCase
end
def test_dynamic_constant_assignment
- assert_raise(SyntaxError) do
- Object.new.instance_eval <<-END, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /dynamic constant/) do
+ begin;
def foo
self::FOO, self::BAR = 1, 2
::FOO, ::BAR = 1, 2
end
- END
+ end;
end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /Can't set variable/) do
+ begin;
$1, $2 = 1, 2
- END
+ end;
end
- assert_raise(SyntaxError) do
- Object.new.instance_eval <<-END, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /dynamic constant/) do
+ begin;
def foo
::FOO = 1
end
- END
+ end;
end
c = Class.new
c.freeze
- assert_nothing_raised(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
- if false
+ assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") do
+ begin;
c::FOO &= 1
::FOO &= 1
- end
- END
+ end;
end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ c = Class.new
+ c.freeze
+ assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") do
+ begin;
+ c::FOO &= p 1
+ ::FOO &= p 1
+ end;
+ end
+
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /Can't set variable/) do
+ begin;
$1 &= 1
- END
+ end;
end
end
def test_class_module
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /must be CONSTANT/) do
+ begin;
class foo; end
- END
+ end;
end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /in method body/) do
+ begin;
def foo
class Foo; end
module Bar; end
end
- END
+ end;
end
- assert_nothing_raised(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") do
+ begin;
class Foo 1; end
- END
+ end;
end
end
@@ -273,37 +281,34 @@ class TestParse < Test::Unit::TestCase
end
def test_bad_arg
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be a constant/) do
+ begin;
def foo(FOO); end
- END
+ end;
end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be an instance variable/) do
+ begin;
def foo(@foo); end
- END
+ end;
end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be a global variable/) do
+ begin;
def foo($foo); end
- END
+ end;
end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be a class variable/) do
+ begin;
def foo(@@foo); end
- END
+ end;
end
- o = Object.new
- def o.foo(*r); yield(*r); end
-
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
- o.foo 1 {|; @a| @a = 42 }
- END
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be an instance variable/) do
+ begin;
+ o.foo {|; @a| @a = 42 }
+ end;
end
end
@@ -347,6 +352,21 @@ class TestParse < Test::Unit::TestCase
assert_equal("foobar", b)
end
+ def test_call_command
+ a = b = nil
+ o = Object.new
+ def o.m(*arg); proc {|a| arg.join + a }; end
+
+ assert_nothing_raised do
+ o.instance_eval <<-END, __FILE__, __LINE__+1
+ a = o.m "foo", "bar" do end.("buz")
+ b = o.m "foo", "bar" do end::("buz")
+ END
+ end
+ assert_equal("foobarbuz", a)
+ assert_equal("foobarbuz", b)
+ end
+
def test_xstring
assert_raise(Errno::ENOENT) do
eval("``")
@@ -363,6 +383,7 @@ class TestParse < Test::Unit::TestCase
assert_equal("foo 1 bar", "foo #@@foo bar")
"1" =~ /(.)/
assert_equal("foo 1 bar", "foo #$1 bar")
+ assert_equal('foo #@1 bar', eval('"foo #@1 bar"'))
end
def test_dstr_disallowed_variable
@@ -382,10 +403,10 @@ class TestParse < Test::Unit::TestCase
def assert_disallowed_variable(type, noname, invalid)
noname.each do |name|
- assert_syntax_error("proc{a = #{name} }", "`#{noname[0]}' without identifiers is not allowed as #{type} variable name")
+ assert_syntax_error("proc{a = #{name} }", "'#{noname[0]}' without identifiers is not allowed as #{type} variable name")
end
invalid.each do |name|
- assert_syntax_error("proc {a = #{name} }", "`#{name}' is not allowed as #{type} variable name")
+ assert_syntax_error("proc {a = #{name} }", "'#{name}' is not allowed as #{type} variable name")
end
end
@@ -403,7 +424,6 @@ class TestParse < Test::Unit::TestCase
def test_arg2
o = Object.new
-
assert_nothing_raised do
eval <<-END, nil, __FILE__, __LINE__+1
def o.foo(a=42,*r,z,&b); b.call(r.inject(a*1000+z*100, :+)); end
@@ -415,6 +435,7 @@ class TestParse < Test::Unit::TestCase
assert_equal(-42100, o.foo(1) {|x| -x })
assert_raise(ArgumentError) { o.foo() }
+ o = Object.new
assert_nothing_raised do
eval <<-END, nil, __FILE__, __LINE__+1
def o.foo(a=42,z,&b); b.call(a*1000+z*100); end
@@ -424,6 +445,7 @@ class TestParse < Test::Unit::TestCase
assert_equal(-42100, o.foo(1) {|x| -x } )
assert_raise(ArgumentError) { o.foo() }
+ o = Object.new
assert_nothing_raised do
eval <<-END, nil, __FILE__, __LINE__+1
def o.foo(*r,z,&b); b.call(r.inject(z*100, :+)); end
@@ -436,30 +458,72 @@ class TestParse < Test::Unit::TestCase
end
def test_duplicate_argument
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", '') do
+ begin;
1.times {|&b?| }
- END
+ end;
end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /duplicated argument/) do
+ begin;
1.times {|a, a|}
- END
+ end;
end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /duplicated argument/) do
+ begin;
def foo(a, a); end
- END
+ end;
end
end
def test_define_singleton_error
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
- def ("foo").foo; end
- END
+ msg = /singleton method for literals/
+ assert_parse_error(%q[def ("foo").foo; end], msg)
+ assert_parse_error(%q[def (1).foo; end], msg)
+ assert_parse_error(%q[def ((1;1)).foo; end], msg)
+ assert_parse_error(%q[def ((;1)).foo; end], msg)
+ assert_parse_error(%q[def ((1+1;1)).foo; end], msg)
+ assert_parse_error(%q[def ((%s();1)).foo; end], msg)
+ assert_parse_error(%q[def ((%w();1)).foo; end], msg)
+ assert_parse_error(%q[def ("#{42}").foo; end], msg)
+ assert_parse_error(%q[def (:"#{42}").foo; end], msg)
+ assert_parse_error(%q[def ([]).foo; end], msg)
+ assert_parse_error(%q[def ([1]).foo; end], msg)
+ assert_parse_error(%q[def (__FILE__).foo; end], msg)
+ assert_parse_error(%q[def (__LINE__).foo; end], msg)
+ assert_parse_error(%q[def (__ENCODING__).foo; end], msg)
+ assert_parse_error(%q[def __FILE__.foo; end], msg)
+ assert_parse_error(%q[def __LINE__.foo; end], msg)
+ assert_parse_error(%q[def __ENCODING__.foo; end], msg)
+ end
+
+ def test_flip_flop
+ all_assertions_foreach(nil,
+ ['(cond1..cond2)', true],
+ ['((cond1..cond2))', true],
+
+ # '(;;;cond1..cond2)', # don't care
+
+ '(1; cond1..cond2)',
+ '(%s(); cond1..cond2)',
+ '(%w(); cond1..cond2)',
+ '(1; (2; (3; 4; cond1..cond2)))',
+ '(1+1; cond1..cond2)',
+ ) do |code, pass|
+ code = code.sub("cond1", "n==4").sub("cond2", "n==5")
+ if pass
+ assert_equal([4,5], eval("(1..9).select {|n| true if #{code}}"))
+ else
+ assert_raise_with_message(ArgumentError, /bad value for range/, code) {
+ verbose_bak, $VERBOSE = $VERBOSE, nil # disable "warning: possibly useless use of a literal in void context"
+ begin
+ eval("[4].each {|n| true if #{code}}")
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ }
+ end
end
end
@@ -467,6 +531,10 @@ class TestParse < Test::Unit::TestCase
t = Object.new
a = []
blk = proc {|x| a << x }
+
+ # Prevent an "assigned but unused variable" warning
+ _ = blk
+
def t.[](_)
yield(:aref)
nil
@@ -476,16 +544,16 @@ class TestParse < Test::Unit::TestCase
end
def t.dummy(_)
end
- eval <<-END, nil, __FILE__, __LINE__+1
+
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /block arg given in index assignment/)
+ begin;
t[42, &blk] ||= 42
- END
- assert_equal([:aref, :aset], a)
- a.clear
- eval <<-END, nil, __FILE__, __LINE__+1
- t[42, &blk] ||= t.dummy 42 # command_asgn test
- END
- assert_equal([:aref, :aset], a)
- blk
+ end;
+
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /block arg given in index assignment/)
+ begin;
+ t[42, &blk] ||= t.dummy 42 # command_asgn test
+ end;
end
def test_backquote
@@ -496,7 +564,7 @@ class TestParse < Test::Unit::TestCase
def t.`(x); "foo" + x + "bar"; end
END
end
- a = b = nil
+ a = b = c = nil
assert_nothing_raised do
eval <<-END, nil, __FILE__, __LINE__+1
a = t.` "zzz"
@@ -504,10 +572,12 @@ class TestParse < Test::Unit::TestCase
END
t.instance_eval <<-END, __FILE__, __LINE__+1
b = `zzz`
+ c = %x(ccc)
END
end
assert_equal("foozzzbar", a)
assert_equal("foozzzbar", b)
+ assert_equal("foocccbar", c)
end
def test_carrige_return
@@ -518,34 +588,42 @@ class TestParse < Test::Unit::TestCase
mesg = 'from the backslash through the invalid char'
e = assert_syntax_error('"\xg1"', /hex escape/)
- assert_equal(' ^~'"\n", e.message.lines.last, mesg)
+ assert_match(/(^|\| ) \^~(?!~)/, e.message.lines.last, mesg)
e = assert_syntax_error('"\u{1234"', 'unterminated Unicode escape')
- assert_equal(' ^'"\n", e.message.lines.last, mesg)
+ assert_match(/(^|\| ) \^(?!~)/, e.message.lines.last, mesg)
e = assert_syntax_error('"\u{xxxx}"', 'invalid Unicode escape')
- assert_equal(' ^'"\n", e.message.lines.last, mesg)
+ assert_match(/(^|\| ) \^(?!~)/, e.message.lines.last, mesg)
e = assert_syntax_error('"\u{xxxx', 'Unicode escape')
- assert_pattern_list([
- /.*: invalid Unicode escape\n.*\n/,
- / \^/,
- /\n/,
- /.*: unterminated Unicode escape\n.*\n/,
- / \^/,
- /\n/,
- /.*: unterminated string.*\n.*\n/,
- / \^\n/,
- ], e.message)
+ if e.message.lines.first == "#{__FILE__}:#{__LINE__ - 1}: syntax errors found\n"
+ assert_pattern_list([
+ /\s+\| \^ unterminated string;.+\n/,
+ /\s+\| \^ unterminated Unicode escape\n/,
+ /\s+\| \^ invalid Unicode escape sequence\n/,
+ ], e.message.lines[2..-1].join)
+ else
+ assert_pattern_list([
+ /.*: invalid Unicode escape\n.*\n/,
+ / \^/,
+ /\n/,
+ /.*: unterminated Unicode escape\n.*\n/,
+ / \^/,
+ /\n/,
+ /.*: unterminated string.*\n.*\n/,
+ / \^\n/,
+ ], e.message)
+ end
e = assert_syntax_error('"\M1"', /escape character syntax/)
- assert_equal(' ^~~'"\n", e.message.lines.last, mesg)
+ assert_match(/(^|\| ) \^~~(?!~)/, e.message.lines.last, mesg)
e = assert_syntax_error('"\C1"', /escape character syntax/)
- assert_equal(' ^~~'"\n", e.message.lines.last, mesg)
+ assert_match(/(^|\| ) \^~~(?!~)/, e.message.lines.last, mesg)
src = '"\xD0\u{90'"\n""000000000000000000000000"
- assert_syntax_error(src, /:#{__LINE__}: unterminated/o)
+ assert_syntax_error(src, /(:#{__LINE__}:|> #{__LINE__} \|.+) unterminated/om)
assert_syntax_error('"\u{100000000}"', /invalid Unicode escape/)
assert_equal("", eval('"\u{}"'))
@@ -566,22 +644,42 @@ class TestParse < Test::Unit::TestCase
assert_syntax_error("\"\\M-\x01\"", 'Invalid escape character syntax')
assert_syntax_error("\"\\M-\\C-\x01\"", 'Invalid escape character syntax')
assert_syntax_error("\"\\C-\\M-\x01\"", 'Invalid escape character syntax')
+
+ e = assert_syntax_error('"\c\u0000"', 'Invalid escape character syntax')
+ assert_match(/(^|\| ) \^~~~(?!~)/, e.message.lines.last)
+ e = assert_syntax_error('"\c\U0000"', 'Invalid escape character syntax')
+ assert_match(/(^|\| ) \^~~~(?!~)/, e.message.lines.last)
+
+ e = assert_syntax_error('"\C-\u0000"', 'Invalid escape character syntax')
+ assert_match(/(^|\| ) \^~~~~(?!~)/, e.message.lines.last)
+ e = assert_syntax_error('"\C-\U0000"', 'Invalid escape character syntax')
+ assert_match(/(^|\| ) \^~~~~(?!~)/, e.message.lines.last)
+
+ e = assert_syntax_error('"\M-\u0000"', 'Invalid escape character syntax')
+ assert_match(/(^|\| ) \^~~~~(?!~)/, e.message.lines.last)
+ e = assert_syntax_error('"\M-\U0000"', 'Invalid escape character syntax')
+ assert_match(/(^|\| ) \^~~~~(?!~)/, e.message.lines.last)
+
+ e = assert_syntax_error(%["\\C-\u3042"], 'Invalid escape character syntax')
+ assert_match(/(^|\|\s)\s \^(?# \\ ) ~(?# C ) ~(?# - ) ~+(?# U+3042 )($|\s)/x, e.message.lines.last)
+ assert_not_include(e.message, "invalid multibyte char")
end
def test_question
- assert_raise(SyntaxError) { eval('?') }
- assert_raise(SyntaxError) { eval('? ') }
- assert_raise(SyntaxError) { eval("?\n") }
- assert_raise(SyntaxError) { eval("?\t") }
- assert_raise(SyntaxError) { eval("?\v") }
- assert_raise(SyntaxError) { eval("?\r") }
- assert_raise(SyntaxError) { eval("?\f") }
- assert_raise(SyntaxError) { eval("?\f") }
- assert_raise(SyntaxError) { eval(" ?a\x8a".force_encoding("utf-8")) }
+ assert_syntax_error('?', /incomplete/)
+ assert_syntax_error('? ', /unexpected/)
+ assert_syntax_error("?\n", /unexpected/)
+ assert_syntax_error("?\t", /unexpected/)
+ assert_syntax_error("?\v", /unexpected/)
+ assert_syntax_error("?\r", /unexpected/)
+ assert_syntax_error("?\f", /unexpected/)
+ assert_syntax_error(" ?a\x8a".force_encoding("utf-8"), /invalid multibyte/)
assert_equal("\u{1234}", eval("?\u{1234}"))
assert_equal("\u{1234}", eval('?\u{1234}'))
assert_equal("\u{1234}", eval('?\u1234'))
assert_syntax_error('?\u{41 42}', 'Multiple codepoints at single character literal')
+ assert_syntax_error("?and", /unexpected '\?'/)
+ assert_syntax_error("?\u1234and", /unexpected '\?'/)
e = assert_syntax_error('"#{?\u123}"', 'invalid Unicode escape')
assert_not_match(/end-of-input/, e.message)
@@ -597,13 +695,18 @@ class TestParse < Test::Unit::TestCase
assert_syntax_error("?\\M-\x01", 'Invalid escape character syntax')
assert_syntax_error("?\\M-\\C-\x01", 'Invalid escape character syntax')
assert_syntax_error("?\\C-\\M-\x01", 'Invalid escape character syntax')
+
+ assert_equal("\xff", eval("# encoding: ascii-8bit\n""?\\\xFF"))
end
def test_percent
assert_equal(:foo, eval('%s(foo)'))
- assert_raise(SyntaxError) { eval('%s') }
- assert_raise(SyntaxError) { eval('%ss') }
- assert_raise(SyntaxError) { eval('%z()') }
+ assert_syntax_error('%s', /unterminated quoted string/)
+ assert_syntax_error('%ss', /unknown type/)
+ assert_syntax_error('%z()', /unknown type/)
+ assert_syntax_error("%\u3042", /unknown type/)
+ assert_syntax_error("%q\u3042", /unknown type/)
+ assert_syntax_error("%", /unterminated quoted string/)
end
def test_symbol
@@ -627,24 +730,17 @@ class TestParse < Test::Unit::TestCase
assert_syntax_error(':@@1', /is not allowed/)
assert_syntax_error(':@', /is not allowed/)
assert_syntax_error(':@1', /is not allowed/)
+ assert_syntax_error(':$01234', /is not allowed/)
end
def test_parse_string
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
-/
- END
- end
+ assert_syntax_error("/\n", /unterminated/)
end
def test_here_document
x = nil
- assert_raise(SyntaxError) do
- eval %Q(
-<\<FOO
- )
- end
+ assert_syntax_error("<\<FOO\n", /can't find string "FOO"/)
assert_nothing_raised(SyntaxError) do
x = eval %q(
@@ -655,23 +751,11 @@ FOO
end
assert_equal "\#$\n", x
- assert_raise(SyntaxError) do
- eval %Q(
-<\<\"
- )
- end
+ assert_syntax_error("<\<\"\n", /unterminated here document identifier/)
- assert_raise(SyntaxError) do
- eval %q(
-<<``
- )
- end
+ assert_syntax_error("<<``\n", /can't find string ""/)
- assert_raise(SyntaxError) do
- eval %q(
-<<--
- )
- end
+ assert_syntax_error("<<--\n", /unexpected <</)
assert_nothing_raised(SyntaxError) do
x = eval %q(
@@ -687,10 +771,29 @@ FOO
eval "x = <<""FOO\r\n1\r\nFOO"
end
assert_equal("1\n", x)
+
+ assert_nothing_raised do
+ x = eval "<<' FOO'\n""[Bug #19539]\n"" FOO\n"
+ end
+ assert_equal("[Bug #19539]\n", x)
+
+ assert_nothing_raised do
+ x = eval "<<-' FOO'\n""[Bug #19539]\n"" FOO\n"
+ end
+ assert_equal("[Bug #19539]\n", x)
end
def test_magic_comment
x = nil
+
+ assert_nothing_raised do
+ eval <<-END, nil, __FILE__, __LINE__+1
+# coding: utf-8
+x = __ENCODING__
+ END
+ end
+ assert_equal(Encoding.find("UTF-8"), x)
+
assert_nothing_raised do
eval <<-END, nil, __FILE__, __LINE__+1
# coding = utf-8
@@ -705,6 +808,62 @@ x = __ENCODING__
x = __ENCODING__
END
end
+
+ assert_nothing_raised do
+ eval <<-END, nil, __FILE__, __LINE__+1
+# xxxx : coding sjis
+x = __ENCODING__
+ END
+ end
+ assert_equal(__ENCODING__, x)
+
+ assert_raise(ArgumentError) do
+ EnvUtil.with_default_external(Encoding::US_ASCII) {eval <<-END, nil, __FILE__, __LINE__+1}
+# coding = external
+x = __ENCODING__
+ END
+ end
+
+ assert_raise(ArgumentError) do
+ EnvUtil.with_default_internal(Encoding::US_ASCII) {eval <<-END, nil, __FILE__, __LINE__+1}
+# coding = internal
+x = __ENCODING__
+ END
+ end
+
+ assert_raise(ArgumentError) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+# coding = filesystem
+x = __ENCODING__
+ END
+ end
+
+ assert_raise(ArgumentError) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+# coding = locale
+x = __ENCODING__
+ END
+ end
+
+ e = assert_raise(ArgumentError) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+# coding: foo
+ END
+ end
+
+ message = e.message.gsub(/\033\[.*?m/, "")
+ assert_include(message, "# coding: foo\n")
+ assert_include(message, " ^")
+
+ e = assert_raise(ArgumentError) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+# coding = foo
+ END
+ end
+
+ message = e.message.gsub(/\033\[.*?m/, "")
+ assert_include(message, "# coding = foo\n")
+ assert_include(message, " ^")
end
def test_utf8_bom
@@ -734,46 +893,50 @@ x = __ENCODING__
end
def test_embedded_rd
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
-=begin
- END
- end
+ assert_valid_syntax("=begin\n""=end")
+ assert_valid_syntax("=begin\n""=end\0")
+ assert_valid_syntax("=begin\n""=end\C-d")
+ assert_valid_syntax("=begin\n""=end\C-z")
+ end
+
+ def test_embedded_rd_error
+ error = 'embedded document meets end of file'
+ assert_syntax_error("=begin\n", error)
+ assert_syntax_error("=begin", error)
end
def test_float
- assert_equal(1.0/0, eval("1e10000"))
- assert_raise(SyntaxError) { eval('1_E') }
- assert_raise(SyntaxError) { eval('1E1E1') }
+ assert_predicate(assert_warning(/out of range/) {eval("1e10000")}, :infinite?)
+ assert_syntax_error('1_E', /trailing '_'/)
+ assert_syntax_error('1E1E1', /unexpected constant/)
end
def test_global_variable
- assert_equal(nil, eval('$-x'))
+ assert_equal(nil, assert_warning(/not initialized/) {eval('$-x')})
assert_equal(nil, eval('alias $preserve_last_match $&'))
assert_equal(nil, eval('alias $& $test_parse_foobarbazqux'))
$test_parse_foobarbazqux = nil
assert_equal(nil, $&)
assert_equal(nil, eval('alias $& $preserve_last_match'))
- assert_raise_with_message(SyntaxError, /as a global variable name\na = \$\#\n \^~$/) do
- eval('a = $#')
- end
+ assert_syntax_error('a = $#', /as a global variable name/)
+ assert_syntax_error('a = $#', /a = \$\#\n(^|.+?\| ) \^~(?!~)/)
end
def test_invalid_instance_variable
pattern = /without identifiers is not allowed as an instance variable name/
- assert_raise_with_message(SyntaxError, pattern) { eval('@%') }
- assert_raise_with_message(SyntaxError, pattern) { eval('@') }
+ assert_syntax_error('@%', pattern)
+ assert_syntax_error('@', pattern)
end
def test_invalid_class_variable
pattern = /without identifiers is not allowed as a class variable name/
- assert_raise_with_message(SyntaxError, pattern) { eval('@@%') }
- assert_raise_with_message(SyntaxError, pattern) { eval('@@') }
+ assert_syntax_error('@@%', pattern)
+ assert_syntax_error('@@', pattern)
end
def test_invalid_char
bug10117 = '[ruby-core:64243] [Bug #10117]'
- invalid_char = /Invalid char `\\x01'/
+ invalid_char = /Invalid char '\\x01'/
x = 1
assert_in_out_err(%W"-e \x01x", "", [], invalid_char, bug10117)
assert_syntax_error("\x01x", invalid_char, bug10117)
@@ -788,109 +951,56 @@ x = __ENCODING__
end
def test_unassignable
- assert_raise(SyntaxError) do
- eval %q(self = 1)
- end
- assert_raise(SyntaxError) do
- eval %q(nil = 1)
- end
- assert_raise(SyntaxError) do
- eval %q(true = 1)
- end
- assert_raise(SyntaxError) do
- eval %q(false = 1)
- end
- assert_raise(SyntaxError) do
- eval %q(__FILE__ = 1)
- end
- assert_raise(SyntaxError) do
- eval %q(__LINE__ = 1)
- end
- assert_raise(SyntaxError) do
- eval %q(__ENCODING__ = 1)
- end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
- def foo
- FOO = 1
- end
- END
- end
- assert_raise(SyntaxError) do
- eval "#{<<~"begin;"}\n#{<<~'end;'}", nil, __FILE__, __LINE__+1
- begin;
- x, true
- end;
- end
+ assert_syntax_error(%q(self = 1), /Can't change the value of self/)
+ assert_syntax_error(%q(nil = 1), /Can't assign to nil/)
+ assert_syntax_error(%q(true = 1), /Can't assign to true/)
+ assert_syntax_error(%q(false = 1), /Can't assign to false/)
+ assert_syntax_error(%q(__FILE__ = 1), /Can't assign to __FILE__/)
+ assert_syntax_error(%q(__LINE__ = 1), /Can't assign to __LINE__/)
+ assert_syntax_error(%q(__ENCODING__ = 1), /Can't assign to __ENCODING__/)
+ assert_syntax_error("def foo; FOO = 1; end", /dynamic constant assignment/)
+ assert_syntax_error("x, true", /Can't assign to true/)
end
def test_block_dup
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
- foo(&proc{}) {}
- END
- end
+ assert_syntax_error("foo(&proc{}) {}", /both block arg and actual block/)
end
def test_set_backref
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
- $& = 1
- END
- end
- end
-
- def test_arg_concat
- o = Object.new
- class << o; self; end.instance_eval do
- define_method(:[]=) {|*r, &b| b.call(r) }
- end
- r = nil
- assert_nothing_raised do
- eval <<-END, nil, __FILE__, __LINE__+1
- o[&proc{|x| r = x }] = 1
- END
- end
- assert_equal([1], r)
+ assert_syntax_error("$& = 1", /Can't set variable/)
end
def test_void_expr_stmts_value
- # This test checks if void contexts are warned correctly.
- # Thus, warnings MUST NOT be suppressed.
- $VERBOSE = true
- stderr = $stderr
- $stderr = StringIO.new("")
x = 1
- assert_nil eval("x; nil")
- assert_nil eval("1+1; nil")
- assert_nil eval("1.+(1); nil")
- assert_nil eval("TestParse; nil")
- assert_nil eval("::TestParse; nil")
- assert_nil eval("x..x; nil")
- assert_nil eval("x...x; nil")
- assert_nil eval("self; nil")
- assert_nil eval("nil; nil")
- assert_nil eval("true; nil")
- assert_nil eval("false; nil")
- assert_nil eval("defined?(1); nil")
+ useless_use = /useless use/
+ assert_nil assert_warning(useless_use) {eval("x; nil")}
+ assert_nil assert_warning(useless_use) {eval("1+1; nil")}
+ assert_nil assert_warning('') {eval("1.+(1); nil")}
+ assert_nil assert_warning(useless_use) {eval("TestParse; nil")}
+ assert_nil assert_warning(useless_use) {eval("::TestParse; nil")}
+ assert_nil assert_warning(useless_use) {eval("x..x; nil")}
+ assert_nil assert_warning(useless_use) {eval("x...x; nil")}
+ assert_nil assert_warning(useless_use) {eval("self; nil")}
+ assert_nil assert_warning(useless_use) {eval("nil; nil")}
+ assert_nil assert_warning(useless_use) {eval("true; nil")}
+ assert_nil assert_warning(useless_use) {eval("false; nil")}
+ assert_nil assert_warning(useless_use) {eval("defined?(1); nil")}
+ assert_nil assert_warning(useless_use) {eval("begin; ensure; x; end")}
assert_equal 1, x
- assert_raise(SyntaxError) do
- eval %q(1; next; 2)
- end
-
- assert_equal(13, $stderr.string.lines.to_a.size)
- $stderr = stderr
+ assert_syntax_error("1; next; 2", /Invalid next/)
end
def test_assign_in_conditional
- assert_nothing_raised do
+ # multiple assignment
+ assert_warning(/'= literal' in conditional/) do
eval <<-END, nil, __FILE__, __LINE__+1
(x, y = 1, 2) ? 1 : 2
END
end
- assert_nothing_raised do
+ # instance variable assignment
+ assert_warning(/'= literal' in conditional/) do
eval <<-END, nil, __FILE__, __LINE__+1
if @x = true
1
@@ -899,16 +1009,81 @@ x = __ENCODING__
end
END
end
+
+ # local variable assignment
+ assert_warning(/'= literal' in conditional/) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+ def m
+ if x = true
+ 1
+ else
+ 2
+ end
+ end
+ END
+ end
+
+ # global variable assignment
+ assert_separately([], <<-RUBY)
+ assert_warning(/'= literal' in conditional/) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+ if $x = true
+ 1
+ else
+ 2
+ end
+ END
+ end
+ RUBY
+
+ # dynamic variable assignment
+ assert_warning(/'= literal' in conditional/) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+ y = 1
+
+ 1.times do
+ if y = true
+ 1
+ else
+ 2
+ end
+ end
+ END
+ end
+
+ # class variable assignment
+ assert_warning(/'= literal' in conditional/) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+ c = Class.new
+ class << c
+ if @@a = 1
+ end
+ end
+ END
+ end
+
+ # constant declaration
+ assert_separately([], <<-RUBY)
+ assert_warning(/'= literal' in conditional/) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+ if Const = true
+ 1
+ else
+ 2
+ end
+ END
+ end
+ RUBY
end
def test_literal_in_conditional
- assert_nothing_raised do
+ assert_warning(/string literal in condition/) do
eval <<-END, nil, __FILE__, __LINE__+1
"foo" ? 1 : 2
END
end
- assert_nothing_raised do
+ assert_warning(/regex literal in condition/) do
x = "bar"
eval <<-END, nil, __FILE__, __LINE__+1
/foo#{x}baz/ ? 1 : 2
@@ -921,13 +1096,13 @@ x = __ENCODING__
END
end
- assert_nothing_raised do
+ assert_warning(/string literal in flip-flop/) do
eval <<-END, nil, __FILE__, __LINE__+1
("foo".."bar") ? 1 : 2
END
end
- assert_nothing_raised do
+ assert_warning(/literal in condition/) do
x = "bar"
eval <<-END, nil, __FILE__, __LINE__+1
:"foo#{"x"}baz" ? 1 : 2
@@ -937,11 +1112,7 @@ x = __ENCODING__
end
def test_no_blockarg
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
- yield(&:+)
- END
- end
+ assert_syntax_error("yield(&:+)", /block argument should not be given/)
end
def test_method_block_location
@@ -962,6 +1133,10 @@ x = __ENCODING__
assert_no_warning(/shadowing outer local variable/) {eval("a=1; tap {|a|}")}
end
+ def test_shadowing_private_local_variable
+ assert_equal 1, eval("_ = 1; [[2]].each{ |(_)| }; _")
+ end
+
def test_unused_variable
o = Object.new
assert_warning(/assigned but unused variable/) {o.instance_eval("def foo; a=1; nil; end")}
@@ -975,6 +1150,20 @@ x = __ENCODING__
assert_warning('') {o.instance_eval("def marg2((a)); nil; end")}
end
+ def test_parsing_begin_statement_inside_method_definition
+ assert_equal :bug_20234, eval("def (begin;end).bug_20234; end")
+ NilClass.remove_method(:bug_20234)
+ assert_equal :bug_20234, eval("def (begin;rescue;end).bug_20234; end")
+ NilClass.remove_method(:bug_20234)
+ assert_equal :bug_20234, eval("def (begin;ensure;end).bug_20234; end")
+ NilClass.remove_method(:bug_20234)
+ assert_equal :bug_20234, eval("def (begin;rescue;else;end).bug_20234; end")
+ NilClass.remove_method(:bug_20234)
+
+ assert_raise(SyntaxError) { eval("def (begin;else;end).bug_20234; end") }
+ assert_raise(SyntaxError) { eval("def (begin;ensure;else;end).bug_20234; end") }
+ end
+
def test_named_capture_conflict
a = 1
assert_warning('') {eval("a = 1; /(?<a>)/ =~ ''")}
@@ -982,6 +1171,30 @@ x = __ENCODING__
assert_warning('') {eval("#{a} = 1; /(?<#{a}>)/ =~ ''")}
end
+ def test_named_capture_in_block
+ all_assertions_foreach(nil,
+ '(/(?<a>.*)/)',
+ '(;/(?<a>.*)/)',
+ '(%s();/(?<a>.*)/)',
+ '(%w();/(?<a>.*)/)',
+ '(1; (2; 3; (4; /(?<a>.*)/)))',
+ '(1+1; /(?<a>.*)/)',
+ '/#{""}(?<a>.*)/',
+ ) do |code, pass|
+ token = Random.bytes(4).unpack1("H*")
+ if pass
+ assert_equal(token, eval("#{code} =~ #{token.dump}; a"))
+ else
+ verbose_bak, $VERBOSE = $VERBOSE, nil # disable "warning: possibly useless use of a literal in void context"
+ begin
+ assert_nil(eval("#{code} =~ #{token.dump}; defined?(a)"), code)
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ end
+ end
+ end
+
def test_rescue_in_command_assignment
bug = '[ruby-core:75621] [Bug #12402]'
all_assertions(bug) do |a|
@@ -1069,14 +1282,30 @@ x = __ENCODING__
assert_syntax_error(" 0b\n", /\^/)
end
+ def test_unclosed_unicode_escape_at_eol_bug_19750
+ assert_separately([], "#{<<-"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_syntax_error("/\\u", /too short escape sequence/)
+ assert_syntax_error("/\\u{", /unterminated regexp meets end of file/)
+ assert_syntax_error("/\\u{\\n", /invalid Unicode list/)
+ assert_syntax_error("/a#\\u{\\n/", /invalid Unicode list/)
+ re = eval("/a#\\u{\n$/x")
+ assert_match(re, 'a')
+ assert_not_match(re, 'a#')
+ re = eval("/a#\\u\n$/x")
+ assert_match(re, 'a')
+ assert_not_match(re, 'a#')
+ end;
+ end
+
def test_error_def_in_argument
assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}")
begin;
assert_syntax_error("def f r:def d; def f 0end", /unexpected/)
end;
- assert_syntax_error("def\nf(000)end", /^ \^~~/)
- assert_syntax_error("def\nf(&)end", /^ \^/)
+ assert_syntax_error("def\nf(000)end", /(^|\| ) \^~~/)
+ assert_syntax_error("def\nf(&0)end", /(^|\| ) \^/)
end
def test_method_location_in_rescue
@@ -1112,39 +1341,68 @@ x = __ENCODING__
end;
end
+ def test_heredoc_interpolation
+ var = 1
+
+ v1 = <<~HEREDOC
+ something
+ #{"/#{var}"}
+ HEREDOC
+
+ v2 = <<~HEREDOC
+ something
+ #{_other = "/#{var}"}
+ HEREDOC
+
+ v3 = <<~HEREDOC
+ something
+ #{("/#{var}")}
+ HEREDOC
+
+ assert_equal "something\n/1\n", v1
+ assert_equal "something\n/1\n", v2
+ assert_equal "something\n/1\n", v3
+ assert_equal v1, v2
+ assert_equal v2, v3
+ assert_equal v1, v3
+ end
+
+ def test_heredoc_unterminated_interpolation
+ code = <<~'HEREDOC'
+ <<A+1
+ #{
+ HEREDOC
+
+ assert_syntax_error(code, /can't find string "A"/)
+ end
+
def test_unexpected_token_error
- assert_raise(SyntaxError) do
- eval('"x"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
- end
+ assert_syntax_error('"x"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', /unexpected/)
end
def test_unexpected_token_after_numeric
- assert_raise_with_message(SyntaxError, /^ \^~~\Z/) do
- eval('0000xyz')
- end
- assert_raise_with_message(SyntaxError, /^ \^~~\Z/) do
- eval('1.2i1.1')
- end
- assert_raise_with_message(SyntaxError, /^ \^~\Z/) do
- eval('1.2.3')
- end
+ assert_syntax_error('0000xyz', /(^|\| ) \^~~(?!~)/)
+ assert_syntax_error('1.2i1.1', /(^|\| ) \^~~(?!~)/)
+ assert_syntax_error('1.2.3', /(^|\| ) \^~(?!~)/)
+ assert_syntax_error('1.', /unexpected end-of-input/)
+ assert_syntax_error('1e', /expecting end-of-input/)
end
def test_truncated_source_line
- e = assert_raise_with_message(SyntaxError, /unexpected local variable or method/) do
- eval("'0123456789012345678901234567890123456789' abcdefghijklmnopqrstuvwxyz0123456789 0123456789012345678901234567890123456789")
- end
+ lineno = __LINE__ + 1
+ e = assert_syntax_error("'0123456789012345678901234567890123456789' abcdefghijklmnopqrstuvwxyz0123456789 123456789012345678901234567890123456789",
+ /unexpected local variable or method/)
+
line = e.message.lines[1]
+ line.delete_prefix!("> #{lineno} | ") if line.start_with?(">")
+
assert_operator(line, :start_with?, "...")
assert_operator(line, :end_with?, "...\n")
end
def test_unterminated_regexp_error
- e = assert_raise(SyntaxError) do
- eval("/x")
- end.message
- assert_match(/unterminated regexp meets end of file/, e)
- assert_not_match(/unexpected tSTRING_END/, e)
+ e = assert_syntax_error("/x", /unterminated regexp meets end of file/)
+ assert_not_match(/unexpected tSTRING_END/, e.message)
end
def test_lparenarg
@@ -1168,37 +1426,34 @@ x = __ENCODING__
end
end
+ def test_eof
+ assert_equal(42, eval("42\0""end"))
+ assert_equal(42, eval("42\C-d""end"))
+ assert_equal(42, eval("42\C-z""end"))
+ end
+
def test_eof_in_def
- assert_raise(SyntaxError) { eval("def m\n\0""end") }
- assert_raise(SyntaxError) { eval("def m\n\C-d""end") }
- assert_raise(SyntaxError) { eval("def m\n\C-z""end") }
+ assert_syntax_error("def m\n\0""end", /unexpected/)
+ assert_syntax_error("def m\n\C-d""end", /unexpected/)
+ assert_syntax_error("def m\n\C-z""end", /unexpected/)
+ end
+
+ def test_unexpected_eof
+ assert_syntax_error('unless', /(^|\| ) \^(?!~)/)
end
def test_location_of_invalid_token
- assert_raise_with_message(SyntaxError, /^ \^~~\Z/) do
- eval('class xxx end')
- end
+ assert_syntax_error('class xxx end', /(^|\| ) \^~~(?!~)/)
end
def test_whitespace_warning
- assert_raise_with_message(SyntaxError, /backslash/) do
- eval("\\foo")
- end
- assert_raise_with_message(SyntaxError, /escaped space/) do
- eval("\\ ")
- end
- assert_raise_with_message(SyntaxError, /escaped horizontal tab/) do
- eval("\\\t")
- end
- assert_raise_with_message(SyntaxError, /escaped form feed/) do
- eval("\\\f")
- end
- assert_raise_with_message(SyntaxError, /escaped carriage return/) do
- assert_warn(/middle of line/) {eval("\\\r")}
- end
- assert_raise_with_message(SyntaxError, /escaped vertical tab/) do
- eval("\\\v")
- end
+ assert_syntax_error("\\foo", /backslash/)
+ assert_syntax_error("\\ ", /escaped space/)
+ assert_syntax_error("\\\t", /escaped horizontal tab/)
+ assert_syntax_error("\\\f", /escaped form feed/)
+ assert_syntax_error("\\\r", /escaped carriage return/)
+ assert_warn(/middle of line/) {eval(" \r ")}
+ assert_syntax_error("\\\v", /escaped vertical tab/)
end
def test_command_def_cmdarg
@@ -1252,9 +1507,330 @@ x = __ENCODING__
assert_valid_syntax('let () { m(a) do; end }')
end
+ def test_void_value_in_rhs
+ w = "void value expression"
+ [
+ "x = return 1", "x = return, 1", "x = 1, return", "x, y = return",
+ "x = begin return ensure end",
+ "x = begin ensure return end",
+ "x = begin return ensure return end",
+ "x = begin return; rescue; return end",
+ "x = begin return; rescue; return; else return end",
+ ].each do |code|
+ ex = assert_syntax_error(code, w)
+ assert_equal(1, ex.message.scan(w).size, ->{"same #{w.inspect} warning should be just once\n#{w.message}"})
+ end
+ [
+ "x = begin return; rescue; end",
+ "x = begin return; rescue; return; else end",
+ ].each do |code|
+ assert_valid_syntax(code)
+ end
+ end
+
+ def eval_separately(code)
+ Class.new.class_eval(code)
+ end
+
+ def assert_raise_separately(error, message, code)
+ assert_raise_with_message(error, message) do
+ eval_separately(code)
+ end
+ end
+
+ def assert_ractor_shareable(obj)
+ assert Ractor.shareable?(obj), ->{"Expected #{mu_pp(obj)} to be ractor shareable"}
+ end
+
+ def assert_not_ractor_shareable(obj)
+ assert !Ractor.shareable?(obj), ->{"Expected #{mu_pp(obj)} not to be ractor shareable"}
+ end
+
+ def test_shareable_constant_value_invalid
+ assert_warning(/invalid value/) do
+ assert_valid_syntax("# shareable_constant_value: invalid-option", verbose: true)
+ end
+ end
+
+ def test_shareable_constant_value_ignored
+ assert_warning(/ignored/) do
+ assert_valid_syntax("nil # shareable_constant_value: true", verbose: true)
+ end
+ end
+
+ def test_shareable_constant_value_simple
+ obj = [['unshareable_value']]
+ a, b, c = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: experimental_everything
+ A = [[1]]
+ # shareable_constant_value: none
+ B = [[2]]
+ # shareable_constant_value: literal
+ C = [["shareable", "constant#{nil}"]]
+ D = A
+
+ [A, B, C]
+ end;
+ assert_ractor_shareable(a)
+ assert_not_ractor_shareable(b)
+ assert_ractor_shareable(c)
+ assert_equal([1], a[0])
+ assert_ractor_shareable(a[0])
+
+ a, obj = eval_separately(<<~'end;')
+ # shareable_constant_value: experimental_copy
+ obj = [["unshareable"]]
+ A = obj
+ [A, obj]
+ end;
+
+ assert_ractor_shareable(a)
+ assert_not_ractor_shareable(obj)
+ assert_equal obj, a
+ assert_not_same obj, a
+
+ bug_20339 = '[ruby-core:117186] [Bug #20339]'
+ bug_20341 = '[ruby-core:117197] [Bug #20341]'
+ a, b = eval_separately(<<~'end;')
+ # shareable_constant_value: literal
+ foo = 1
+ bar = 2
+ A = { foo => bar }
+ B = [foo, bar]
+ [A, B]
+ end;
+
+ assert_ractor_shareable(a)
+ assert_ractor_shareable(b)
+ assert_equal([1], a.keys, bug_20339)
+ assert_equal([2], a.values, bug_20339)
+ assert_equal(1, b[0], bug_20341)
+ assert_equal(2, b[1], bug_20341)
+ end
+
+ def test_shareable_constant_value_literal_const_refs
+ a = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ # [Bug #20668]
+ SOME_CONST = {
+ 'Object' => Object,
+ 'String' => String,
+ 'Array' => Array,
+ }
+ SOME_CONST
+ end;
+ assert_ractor_shareable(a)
+ end
+
+ def test_shareable_constant_value_nested
+ a, b = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: none
+ class X
+ # shareable_constant_value: experimental_everything
+ var = [[1]]
+ A = var
+ end
+ B = []
+ [X::A, B]
+ end;
+ assert_ractor_shareable(a)
+ assert_not_ractor_shareable(b)
+ assert_equal([1], a[0])
+ assert_ractor_shareable(a[0])
+ end
+
+ def test_shareable_constant_value_hash_with_keyword_splat
+ a, b = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: experimental_everything
+ # [Bug #20927]
+ x = { x: {} }
+ y = { y: {} }
+ A = { **x }
+ B = { x: {}, **y }
+ [A, B]
+ end;
+ assert_ractor_shareable(a)
+ assert_ractor_shareable(b)
+ assert_equal({ x: {}}, a)
+ assert_equal({ x: {}, y: {}}, b)
+ end
+
+ def test_shareable_constant_value_unshareable_literal
+ assert_raise_separately(Ractor::IsolationError, /unshareable object to C/,
+ "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ C = ["Not " + "shareable"]
+ end;
+
+ assert_raise_separately(Ractor::IsolationError, /unshareable object to B::C/,
+ "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ B = Class.new
+ B::C = ["Not " + "shareable"]
+ end;
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::C/) do
+ # shareable_constant_value: literal
+ ::C = ["Not " + "shareable"]
+ end
+ end;
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::B::C/) do
+ # shareable_constant_value: literal
+ ::B = Class.new
+ ::B::C = ["Not " + "shareable"]
+ end
+ end;
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::C/) do
+ # shareable_constant_value: literal
+ ::C ||= ["Not " + "shareable"]
+ end
+ end;
+
+ assert_raise_separately(Ractor::IsolationError, /unshareable object to B::C/,
+ "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ B = Class.new
+ B::C ||= ["Not " + "shareable"]
+ end;
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::B::C/) do
+ # shareable_constant_value: literal
+ ::B = Class.new
+ ::B::C ||= ["Not " + "shareable"]
+ end
+ end;
+
+ assert_raise_separately(Ractor::IsolationError, /unshareable object to ...::C/,
+ "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ B = Class.new
+ def self.expr; B; end
+ expr::C ||= ["Not " + "shareable"]
+ end;
+ end
+
+ def test_shareable_constant_value_nonliteral
+ assert_raise_separately(Ractor::IsolationError, /unshareable/, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ var = [:not_frozen]
+ C = var
+ end;
+
+ assert_raise_separately(Ractor::IsolationError, /unshareable/, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ D = begin [] end
+ end;
+ end
+
+ def test_shareable_constant_value_unfrozen
+ assert_raise_separately(Ractor::Error, /does not freeze object correctly/,
+ "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: experimental_everything
+ o = Object.new
+ def o.freeze; self; end
+ C = [o]
+ end;
+ end
+
+ def test_shareable_constant_value_massign
+ a = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: experimental_everything
+ A, = 1
+ end;
+ assert_equal(1, a)
+ end
+
+ def test_if_after_class
+ assert_valid_syntax('module if true; Object end::Kernel; end')
+ assert_valid_syntax('module while true; break Object end::Kernel; end')
+ assert_valid_syntax('class if true; Object end::Kernel; end')
+ assert_valid_syntax('class while true; break Object end::Kernel; end')
+ end
+
+ def test_escaped_space
+ assert_syntax_error('x = \ 42', /escaped space/)
+ end
+
+ def test_label
+ expected = {:foo => 1}
+
+ code = '{"foo": 1}'
+ assert_valid_syntax(code)
+ assert_equal(expected, eval(code))
+
+ code = '{foo: 1}'
+ assert_valid_syntax(code)
+ assert_equal(expected, eval(code))
+
+ class << (obj = Object.new)
+ attr_reader :arg
+ def set(arg)
+ @arg = arg
+ end
+ end
+
+ assert_valid_syntax(code = "#{<<~"do;"}\n#{<<~'end;'}")
+ do;
+ obj.set foo:
+ 1
+ end;
+ assert_equal(expected, eval(code))
+ assert_equal(expected, obj.arg)
+
+ assert_valid_syntax(code = "#{<<~"do;"}\n#{<<~'end;'}")
+ do;
+ obj.set "foo":
+ 1
+ end;
+ assert_equal(expected, eval(code))
+ assert_equal(expected, obj.arg)
+ end
+
+ def test_ungettable_gvar
+ assert_syntax_error('$01234', /not allowed/)
+ assert_syntax_error('"#$01234"', /not allowed/)
+ end
+
=begin
def test_past_scope_variable
assert_warning(/past scope/) {catch {|tag| eval("BEGIN{throw tag}; tap {a = 1}; a")}}
end
=end
+
+ def assert_parse(code)
+ assert_kind_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.parse(code))
+ end
+
+ def assert_parse_error(code, message)
+ assert_raise_with_message(SyntaxError, message) do
+ $VERBOSE, verbose_bak = nil, $VERBOSE
+ begin
+ RubyVM::AbstractSyntaxTree.parse(code)
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ end
+ end
end