mirror of
https://github.com/ruby/ruby.git
synced 2025-08-27 15:06:10 +02:00

This fixes an infinite loop possible after ec3542229b
.
For \u{} escapes in regexps, skip validation in the parser, and rely on the regexp
code to handle validation. This is necessary so that invalid unicode escapes in
comments in extended regexps are allowed.
Fixes [Bug #19750]
Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
1451 lines
39 KiB
Ruby
1451 lines
39 KiB
Ruby
# coding: US-ASCII
|
|
# frozen_string_literal: false
|
|
require 'test/unit'
|
|
require 'stringio'
|
|
|
|
class TestParse < Test::Unit::TestCase
|
|
def setup
|
|
@verbose = $VERBOSE
|
|
end
|
|
|
|
def teardown
|
|
$VERBOSE = @verbose
|
|
end
|
|
|
|
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])
|
|
begin
|
|
else
|
|
42
|
|
end
|
|
END
|
|
end
|
|
|
|
def test_alias_backref
|
|
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /can't make alias/) do
|
|
begin;
|
|
alias $foo $1
|
|
end;
|
|
end
|
|
end
|
|
|
|
def test_command_call
|
|
t = Object.new
|
|
def t.foo(x); x; end
|
|
|
|
a = false
|
|
b = c = d = true
|
|
assert_nothing_raised do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
a &&= t.foo 42
|
|
b &&= t.foo 42
|
|
c &&= t.foo nil
|
|
d &&= t.foo false
|
|
END
|
|
end
|
|
assert_equal([false, 42, nil, false], [a, b, c, d])
|
|
|
|
a = 3
|
|
assert_nothing_raised { eval("a &= t.foo 5") }
|
|
assert_equal(1, a)
|
|
|
|
a = [nil, nil, true, true]
|
|
assert_nothing_raised do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
a[0] ||= t.foo 42
|
|
a[1] &&= t.foo 42
|
|
a[2] ||= t.foo 42
|
|
a[3] &&= t.foo 42
|
|
END
|
|
end
|
|
assert_equal([42, nil, true, 42], a)
|
|
|
|
o = Object.new
|
|
class << o
|
|
attr_accessor :foo, :bar, :Foo, :Bar, :baz, :qux
|
|
end
|
|
o.foo = o.Foo = o::baz = nil
|
|
o.bar = o.Bar = o::qux = 1
|
|
assert_nothing_raised do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
o.foo ||= t.foo 42
|
|
o.bar &&= t.foo 42
|
|
o.Foo ||= t.foo 42
|
|
o.Bar &&= t.foo 42
|
|
o::baz ||= t.foo 42
|
|
o::qux &&= t.foo 42
|
|
END
|
|
end
|
|
assert_equal([42, 42], [o.foo, o.bar])
|
|
assert_equal([42, 42], [o.Foo, o.Bar])
|
|
assert_equal([42, 42], [o::baz, o::qux])
|
|
|
|
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /Can't set variable/) do
|
|
begin;
|
|
$1 ||= t.foo 42
|
|
end;
|
|
end
|
|
|
|
def t.bar(x); x + yield; end
|
|
|
|
a = b = nil
|
|
assert_nothing_raised do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
a = t.bar "foo" do
|
|
"bar"
|
|
end.gsub "ob", "OB"
|
|
b = t.bar "foo" do
|
|
"bar"
|
|
end::gsub "ob", "OB"
|
|
END
|
|
end
|
|
assert_equal("foOBar", a)
|
|
assert_equal("foOBar", b)
|
|
|
|
a = nil
|
|
assert_nothing_raised do
|
|
t.instance_eval <<-END, __FILE__, __LINE__+1
|
|
a = bar "foo" do "bar" end
|
|
END
|
|
end
|
|
assert_equal("foobar", a)
|
|
|
|
a = nil
|
|
assert_nothing_raised do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
a = t::bar "foo" do "bar" end
|
|
END
|
|
end
|
|
assert_equal("foobar", a)
|
|
|
|
def t.baz(*r)
|
|
@baz = r + (block_given? ? [yield] : [])
|
|
end
|
|
|
|
assert_nothing_raised do
|
|
t.instance_eval "baz (1), 2"
|
|
end
|
|
assert_equal([1, 2], t.instance_eval { @baz })
|
|
end
|
|
|
|
def test_mlhs_node
|
|
c = Class.new
|
|
class << c
|
|
attr_accessor :foo, :bar, :Foo, :Bar
|
|
FOO = BAR = nil
|
|
end
|
|
|
|
assert_nothing_raised do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
c::foo, c::bar = 1, 2
|
|
c.Foo, c.Bar = 1, 2
|
|
c::FOO, c::BAR = 1, 2
|
|
END
|
|
end
|
|
assert_equal([1, 2], [c::foo, c::bar])
|
|
assert_equal([1, 2], [c.Foo, c.Bar])
|
|
assert_equal([1, 2], [c::FOO, c::BAR])
|
|
end
|
|
|
|
def test_dynamic_constant_assignment
|
|
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
|
|
|
|
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /Can't set variable/) do
|
|
begin;
|
|
$1, $2 = 1, 2
|
|
end;
|
|
end
|
|
|
|
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /dynamic constant/) do
|
|
begin;
|
|
def foo
|
|
::FOO = 1
|
|
end
|
|
end;
|
|
end
|
|
|
|
c = Class.new
|
|
c.freeze
|
|
assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") do
|
|
begin;
|
|
c::FOO &= 1
|
|
::FOO &= 1
|
|
end;
|
|
end
|
|
|
|
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /Can't set variable/) do
|
|
begin;
|
|
$1 &= 1
|
|
end;
|
|
end
|
|
end
|
|
|
|
def test_class_module
|
|
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /must be CONSTANT/) do
|
|
begin;
|
|
class foo; end
|
|
end;
|
|
end
|
|
|
|
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /in method body/) do
|
|
begin;
|
|
def foo
|
|
class Foo; end
|
|
module Bar; end
|
|
end
|
|
end;
|
|
end
|
|
|
|
assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") do
|
|
begin;
|
|
class Foo 1; end
|
|
end;
|
|
end
|
|
end
|
|
|
|
def test_op_name
|
|
o = Object.new
|
|
def o.>(x); x; end
|
|
def o./(x); x; end
|
|
|
|
assert_nothing_raised do
|
|
o.instance_eval <<-END, __FILE__, __LINE__+1
|
|
undef >, /
|
|
END
|
|
end
|
|
end
|
|
|
|
def test_arg
|
|
o = Object.new
|
|
class << o
|
|
attr_accessor :foo, :bar, :Foo, :Bar, :baz, :qux
|
|
end
|
|
o.foo = o.Foo = o::baz = nil
|
|
o.bar = o.Bar = o::qux = 1
|
|
assert_nothing_raised do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
o.foo ||= 42
|
|
o.bar &&= 42
|
|
o.Foo ||= 42
|
|
o.Bar &&= 42
|
|
o::baz ||= 42
|
|
o::qux &&= 42
|
|
END
|
|
end
|
|
assert_equal([42, 42], [o.foo, o.bar])
|
|
assert_equal([42, 42], [o.Foo, o.Bar])
|
|
assert_equal([42, 42], [o::baz, o::qux])
|
|
|
|
a = nil
|
|
assert_nothing_raised do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
a = -2.0 ** 2
|
|
END
|
|
end
|
|
assert_equal(-4.0, a)
|
|
end
|
|
|
|
def test_block_variable
|
|
o = Object.new
|
|
def o.foo(*r); yield(*r); end
|
|
|
|
a = nil
|
|
assert_nothing_raised do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
o.foo 1 do|; a| a = 42 end
|
|
END
|
|
end
|
|
assert_nil(a)
|
|
end
|
|
|
|
def test_bad_arg
|
|
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be a constant/) do
|
|
begin;
|
|
def foo(FOO); end
|
|
end;
|
|
end
|
|
|
|
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be an instance variable/) do
|
|
begin;
|
|
def foo(@foo); end
|
|
end;
|
|
end
|
|
|
|
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be a global variable/) do
|
|
begin;
|
|
def foo($foo); end
|
|
end;
|
|
end
|
|
|
|
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be a class variable/) do
|
|
begin;
|
|
def foo(@@foo); end
|
|
end;
|
|
end
|
|
|
|
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be an instance variable/) do
|
|
begin;
|
|
o.foo {|; @a| @a = 42 }
|
|
end;
|
|
end
|
|
end
|
|
|
|
def test_do_lambda
|
|
a = b = nil
|
|
assert_nothing_raised do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
a = -> do
|
|
b = 42
|
|
end
|
|
END
|
|
end
|
|
a.call
|
|
assert_equal(42, b)
|
|
end
|
|
|
|
def test_block_call_colon2
|
|
o = Object.new
|
|
def o.foo(x); x + yield; end
|
|
|
|
a = b = nil
|
|
assert_nothing_raised do
|
|
o.instance_eval <<-END, __FILE__, __LINE__+1
|
|
a = foo 1 do 42 end.to_s
|
|
b = foo 1 do 42 end::to_s
|
|
END
|
|
end
|
|
assert_equal("43", a)
|
|
assert_equal("43", b)
|
|
end
|
|
|
|
def test_call_method
|
|
a = b = nil
|
|
assert_nothing_raised do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
a = proc {|x| x + "bar" }.("foo")
|
|
b = proc {|x| x + "bar" }::("foo")
|
|
END
|
|
end
|
|
assert_equal("foobar", a)
|
|
assert_equal("foobar", b)
|
|
end
|
|
|
|
def test_xstring
|
|
assert_raise(Errno::ENOENT) do
|
|
eval("``")
|
|
end
|
|
end
|
|
|
|
def test_words
|
|
assert_equal([], %W( ))
|
|
assert_syntax_error('%w[abc', /unterminated list/)
|
|
end
|
|
|
|
def test_dstr
|
|
@@foo = 1
|
|
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
|
|
bug8375 = '[ruby-core:54885] [Bug #8375]'
|
|
%w[@ @. @@ @@1 @@. $ $%].each do |src|
|
|
src = '#'+src+' '
|
|
str = assert_nothing_raised(SyntaxError, "#{bug8375} #{src.dump}") do
|
|
break eval('"'+src+'"')
|
|
end
|
|
assert_equal(src, str, bug8375)
|
|
end
|
|
end
|
|
|
|
def test_dsym
|
|
assert_nothing_raised { eval(':""') }
|
|
end
|
|
|
|
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")
|
|
end
|
|
invalid.each do |name|
|
|
assert_syntax_error("proc {a = #{name} }", "`#{name}' is not allowed as #{type} variable name")
|
|
end
|
|
end
|
|
|
|
def test_disallowed_instance_variable
|
|
assert_disallowed_variable("an instance", %w[@ @.], %w[])
|
|
end
|
|
|
|
def test_disallowed_class_variable
|
|
assert_disallowed_variable("a class", %w[@@ @@.], %w[@@1])
|
|
end
|
|
|
|
def test_disallowed_gloal_variable
|
|
assert_disallowed_variable("a global", %w[$], %w[$%])
|
|
end
|
|
|
|
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
|
|
END
|
|
end
|
|
assert_equal(-1405, o.foo(1,2,3,4) {|x| -x })
|
|
assert_equal(-1302, o.foo(1,2,3) {|x| -x })
|
|
assert_equal(-1200, o.foo(1,2) {|x| -x })
|
|
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
|
|
END
|
|
end
|
|
assert_equal(-1200, o.foo(1,2) {|x| -x } )
|
|
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
|
|
END
|
|
end
|
|
assert_equal(-303, o.foo(1,2,3) {|x| -x } )
|
|
assert_equal(-201, o.foo(1,2) {|x| -x } )
|
|
assert_equal(-100, o.foo(1) {|x| -x } )
|
|
assert_raise(ArgumentError) { o.foo() }
|
|
end
|
|
|
|
def test_duplicate_argument
|
|
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", '') do
|
|
begin;
|
|
1.times {|&b?| }
|
|
end;
|
|
end
|
|
|
|
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /duplicated argument/) do
|
|
begin;
|
|
1.times {|a, a|}
|
|
end;
|
|
end
|
|
|
|
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /duplicated argument/) do
|
|
begin;
|
|
def foo(a, a); end
|
|
end;
|
|
end
|
|
end
|
|
|
|
def test_define_singleton_error
|
|
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /singleton method for literals/) do
|
|
begin;
|
|
def ("foo").foo; end
|
|
end;
|
|
end
|
|
end
|
|
|
|
def test_op_asgn1_with_block
|
|
t = Object.new
|
|
a = []
|
|
blk = proc {|x| a << x }
|
|
def t.[](_)
|
|
yield(:aref)
|
|
nil
|
|
end
|
|
def t.[]=(_, _)
|
|
yield(:aset)
|
|
end
|
|
def t.dummy(_)
|
|
end
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
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
|
|
|
|
def test_backquote
|
|
t = Object.new
|
|
|
|
assert_nothing_raised do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
def t.`(x); "foo" + x + "bar"; end
|
|
END
|
|
end
|
|
a = b = nil
|
|
assert_nothing_raised do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
a = t.` "zzz"
|
|
1.times {|;z| t.` ("zzz") }
|
|
END
|
|
t.instance_eval <<-END, __FILE__, __LINE__+1
|
|
b = `zzz`
|
|
END
|
|
end
|
|
assert_equal("foozzzbar", a)
|
|
assert_equal("foozzzbar", b)
|
|
end
|
|
|
|
def test_carrige_return
|
|
assert_equal(2, eval("1 +\r\n1"))
|
|
end
|
|
|
|
def test_string
|
|
mesg = 'from the backslash through the invalid char'
|
|
|
|
e = assert_syntax_error('"\xg1"', /hex escape/)
|
|
assert_equal(' ^~'"\n", e.message.lines.last, mesg)
|
|
|
|
e = assert_syntax_error('"\u{1234"', 'unterminated Unicode escape')
|
|
assert_equal(' ^'"\n", e.message.lines.last, mesg)
|
|
|
|
e = assert_syntax_error('"\u{xxxx}"', 'invalid Unicode escape')
|
|
assert_equal(' ^'"\n", 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)
|
|
|
|
e = assert_syntax_error('"\M1"', /escape character syntax/)
|
|
assert_equal(' ^~~'"\n", e.message.lines.last, mesg)
|
|
|
|
e = assert_syntax_error('"\C1"', /escape character syntax/)
|
|
assert_equal(' ^~~'"\n", e.message.lines.last, mesg)
|
|
|
|
src = '"\xD0\u{90'"\n""000000000000000000000000"
|
|
assert_syntax_error(src, /:#{__LINE__}: unterminated/o)
|
|
|
|
assert_syntax_error('"\u{100000000}"', /invalid Unicode escape/)
|
|
assert_equal("", eval('"\u{}"'))
|
|
assert_equal("", eval('"\u{ }"'))
|
|
|
|
assert_equal("\x81", eval('"\C-\M-a"'))
|
|
assert_equal("\177", eval('"\c?"'))
|
|
|
|
assert_warning(/use \\C-\\s/) {assert_equal("\x00", eval('"\C- "'))}
|
|
assert_warning(/use \\M-\\s/) {assert_equal("\xa0", eval('"\M- "'))}
|
|
assert_warning(/use \\M-\\C-\\s/) {assert_equal("\x80", eval('"\M-\C- "'))}
|
|
assert_warning(/use \\C-\\M-\\s/) {assert_equal("\x80", eval('"\C-\M- "'))}
|
|
assert_warning(/use \\t/) {assert_equal("\x09", eval("\"\\C-\t\""))}
|
|
assert_warning(/use \\M-\\t/) {assert_equal("\x89", eval("\"\\M-\t\""))}
|
|
assert_warning(/use \\M-\\t/) {assert_equal("\x89", eval("\"\\M-\\C-\t\""))}
|
|
assert_warning(/use \\M-\\t/) {assert_equal("\x89", eval("\"\\C-\\M-\t\""))}
|
|
assert_syntax_error("\"\\C-\x01\"", 'Invalid escape character syntax')
|
|
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_equal(' ^~~~'"\n", e.message.lines.last)
|
|
e = assert_syntax_error('"\c\U0000"', 'Invalid escape character syntax')
|
|
assert_equal(' ^~~~'"\n", e.message.lines.last)
|
|
|
|
e = assert_syntax_error('"\C-\u0000"', 'Invalid escape character syntax')
|
|
assert_equal(' ^~~~~'"\n", e.message.lines.last)
|
|
e = assert_syntax_error('"\C-\U0000"', 'Invalid escape character syntax')
|
|
assert_equal(' ^~~~~'"\n", e.message.lines.last)
|
|
|
|
e = assert_syntax_error('"\M-\u0000"', 'Invalid escape character syntax')
|
|
assert_equal(' ^~~~~'"\n", e.message.lines.last)
|
|
e = assert_syntax_error('"\M-\U0000"', 'Invalid escape character syntax')
|
|
assert_equal(' ^~~~~'"\n", e.message.lines.last)
|
|
end
|
|
|
|
def test_question
|
|
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')
|
|
e = assert_syntax_error('"#{?\u123}"', 'invalid Unicode escape')
|
|
assert_not_match(/end-of-input/, e.message)
|
|
|
|
assert_warning(/use ?\\C-\\s/) {assert_equal("\x00", eval('?\C- '))}
|
|
assert_warning(/use ?\\M-\\s/) {assert_equal("\xa0", eval('?\M- '))}
|
|
assert_warning(/use ?\\M-\\C-\\s/) {assert_equal("\x80", eval('?\M-\C- '))}
|
|
assert_warning(/use ?\\C-\\M-\\s/) {assert_equal("\x80", eval('?\C-\M- '))}
|
|
assert_warning(/use ?\\t/) {assert_equal("\x09", eval("?\\C-\t"))}
|
|
assert_warning(/use ?\\M-\\t/) {assert_equal("\x89", eval("?\\M-\t"))}
|
|
assert_warning(/use ?\\M-\\t/) {assert_equal("\x89", eval("?\\M-\\C-\t"))}
|
|
assert_warning(/use ?\\M-\\t/) {assert_equal("\x89", eval("?\\C-\\M-\t"))}
|
|
assert_syntax_error("?\\C-\x01", 'Invalid escape character syntax')
|
|
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')
|
|
end
|
|
|
|
def test_percent
|
|
assert_equal(:foo, eval('%s(foo)'))
|
|
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
|
|
bug = '[ruby-dev:41447]'
|
|
sym = "foo\0bar".to_sym
|
|
assert_nothing_raised(SyntaxError, bug) do
|
|
assert_equal(sym, eval(":'foo\0bar'"))
|
|
end
|
|
assert_nothing_raised(SyntaxError, bug) do
|
|
assert_equal(sym, eval(':"foo\u0000bar"'))
|
|
end
|
|
assert_nothing_raised(SyntaxError, bug) do
|
|
assert_equal(sym, eval(':"foo\u{0}bar"'))
|
|
end
|
|
assert_nothing_raised(SyntaxError) do
|
|
assert_equal(:foobar, eval(':"foo\u{}bar"'))
|
|
assert_equal(:foobar, eval(':"foo\u{ }bar"'))
|
|
end
|
|
|
|
assert_syntax_error(':@@', /is not allowed/)
|
|
assert_syntax_error(':@@1', /is not allowed/)
|
|
assert_syntax_error(':@', /is not allowed/)
|
|
assert_syntax_error(':@1', /is not allowed/)
|
|
end
|
|
|
|
def test_parse_string
|
|
assert_syntax_error("/\n", /unterminated/)
|
|
end
|
|
|
|
def test_here_document
|
|
x = nil
|
|
|
|
assert_syntax_error("<\<FOO\n", /can't find string "FOO"/)
|
|
|
|
assert_nothing_raised(SyntaxError) do
|
|
x = eval %q(
|
|
<<FOO
|
|
#$
|
|
FOO
|
|
)
|
|
end
|
|
assert_equal "\#$\n", x
|
|
|
|
assert_syntax_error("<\<\"\n", /unterminated here document identifier/)
|
|
|
|
assert_syntax_error("<<``\n", /can't find string ""/)
|
|
|
|
assert_syntax_error("<<--\n", /unexpected <</)
|
|
|
|
assert_nothing_raised(SyntaxError) do
|
|
x = eval %q(
|
|
<<FOO
|
|
#$
|
|
foo
|
|
FOO
|
|
)
|
|
end
|
|
assert_equal "\#$\nfoo\n", x
|
|
|
|
assert_nothing_raised do
|
|
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
|
|
x = __ENCODING__
|
|
END
|
|
end
|
|
assert_equal(Encoding.find("UTF-8"), x)
|
|
|
|
assert_raise(ArgumentError) do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
# coding = foobarbazquxquux_dummy_enconding
|
|
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)
|
|
end
|
|
|
|
def test_utf8_bom
|
|
x = nil
|
|
assert_nothing_raised do
|
|
eval "\xef\xbb\xbf x = __ENCODING__"
|
|
end
|
|
assert_equal(Encoding.find("UTF-8"), x)
|
|
assert_raise(NameError) { eval "\xef" }
|
|
end
|
|
|
|
def test_dot_in_next_line
|
|
x = nil
|
|
assert_nothing_raised do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
x = 1
|
|
.to_s
|
|
END
|
|
end
|
|
assert_equal("1", x)
|
|
end
|
|
|
|
def test_pow_asgn
|
|
x = 3
|
|
assert_nothing_raised { eval("x **= 2") }
|
|
assert_equal(9, x)
|
|
end
|
|
|
|
def test_embedded_rd
|
|
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_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, 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_syntax_error('a = $#', /as a global variable name\na = \$\#\n \^~$/)
|
|
end
|
|
|
|
def test_invalid_instance_variable
|
|
pattern = /without identifiers is not allowed as an instance variable name/
|
|
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_syntax_error('@@%', pattern)
|
|
assert_syntax_error('@@', pattern)
|
|
end
|
|
|
|
def test_invalid_char
|
|
bug10117 = '[ruby-core:64243] [Bug #10117]'
|
|
invalid_char = /Invalid char `\\x01'/
|
|
x = 1
|
|
assert_in_out_err(%W"-e \x01x", "", [], invalid_char, bug10117)
|
|
assert_syntax_error("\x01x", invalid_char, bug10117)
|
|
assert_equal(nil, eval("\x04x"))
|
|
assert_equal 1, x
|
|
end
|
|
|
|
def test_literal_concat
|
|
x = "baz"
|
|
assert_equal("foobarbaz", eval('"foo" "bar#{x}"'))
|
|
assert_equal("baz", x)
|
|
end
|
|
|
|
def test_unassignable
|
|
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_syntax_error("foo(&proc{}) {}", /both block arg and actual block/)
|
|
end
|
|
|
|
def test_set_backref
|
|
assert_syntax_error("$& = 1", /Can't set variable/)
|
|
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)
|
|
end
|
|
|
|
def test_void_expr_stmts_value
|
|
x = 1
|
|
useless_use = /useless use/
|
|
unused = /unused/
|
|
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(unused) {eval("self; nil")}
|
|
assert_nil assert_warning(unused) {eval("nil; nil")}
|
|
assert_nil assert_warning(unused) {eval("true; nil")}
|
|
assert_nil assert_warning(unused) {eval("false; nil")}
|
|
assert_nil assert_warning(useless_use) {eval("defined?(1); nil")}
|
|
assert_equal 1, x
|
|
|
|
assert_syntax_error("1; next; 2", /Invalid next/)
|
|
end
|
|
|
|
def test_assign_in_conditional
|
|
assert_warning(/`= literal' in conditional/) do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
(x, y = 1, 2) ? 1 : 2
|
|
END
|
|
end
|
|
|
|
assert_warning(/`= literal' in conditional/) do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
if @x = true
|
|
1
|
|
else
|
|
2
|
|
end
|
|
END
|
|
end
|
|
end
|
|
|
|
def test_literal_in_conditional
|
|
assert_warning(/string literal in condition/) do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
"foo" ? 1 : 2
|
|
END
|
|
end
|
|
|
|
assert_warning(/regex literal in condition/) do
|
|
x = "bar"
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
/foo#{x}baz/ ? 1 : 2
|
|
END
|
|
end
|
|
|
|
assert_nothing_raised do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
(true..false) ? 1 : 2
|
|
END
|
|
end
|
|
|
|
assert_warning(/string literal in flip-flop/) do
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
("foo".."bar") ? 1 : 2
|
|
END
|
|
end
|
|
|
|
assert_warning(/literal in condition/) do
|
|
x = "bar"
|
|
eval <<-END, nil, __FILE__, __LINE__+1
|
|
:"foo#{"x"}baz" ? 1 : 2
|
|
END
|
|
assert_equal "bar", x
|
|
end
|
|
end
|
|
|
|
def test_no_blockarg
|
|
assert_syntax_error("yield(&:+)", /block argument should not be given/)
|
|
end
|
|
|
|
def test_method_block_location
|
|
bug5614 = '[ruby-core:40936]'
|
|
expected = nil
|
|
e = assert_raise(NoMethodError) do
|
|
1.times do
|
|
expected = __LINE__+1
|
|
end.print do
|
|
#
|
|
end
|
|
end
|
|
actual = e.backtrace.first[/\A#{Regexp.quote(__FILE__)}:(\d+):/o, 1].to_i
|
|
assert_equal(expected, actual, bug5614)
|
|
end
|
|
|
|
def test_no_shadowing_variable_warning
|
|
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")}
|
|
assert_warning(/assigned but unused variable/) {o.instance_eval("def bar; a=1; a(); end")}
|
|
a = "\u{3042}"
|
|
assert_warning(/#{a}/) {o.instance_eval("def foo0; #{a}=1; nil; end")}
|
|
assert_warning(/assigned but unused variable/) {o.instance_eval("def foo1; tap {a=1; a()}; end")}
|
|
assert_warning('') {o.instance_eval("def bar1; a=a=1; nil; end")}
|
|
assert_warning(/assigned but unused variable/) {o.instance_eval("def bar2; a, = 1, 2; end")}
|
|
assert_warning('') {o.instance_eval("def marg1(a); nil; end")}
|
|
assert_warning('') {o.instance_eval("def marg2((a)); nil; end")}
|
|
end
|
|
|
|
def test_named_capture_conflict
|
|
a = 1
|
|
assert_warning('') {eval("a = 1; /(?<a>)/ =~ ''")}
|
|
a = "\u{3042}"
|
|
assert_warning('') {eval("#{a} = 1; /(?<#{a}>)/ =~ ''")}
|
|
end
|
|
|
|
def test_rescue_in_command_assignment
|
|
bug = '[ruby-core:75621] [Bug #12402]'
|
|
all_assertions(bug) do |a|
|
|
a.for("lhs = arg") do
|
|
v = bug
|
|
v = raise(bug) rescue "ok"
|
|
assert_equal("ok", v)
|
|
end
|
|
a.for("lhs op_asgn arg") do
|
|
v = 0
|
|
v += raise(bug) rescue 1
|
|
assert_equal(1, v)
|
|
end
|
|
a.for("lhs[] op_asgn arg") do
|
|
v = [0]
|
|
v[0] += raise(bug) rescue 1
|
|
assert_equal([1], v)
|
|
end
|
|
a.for("lhs.m op_asgn arg") do
|
|
k = Struct.new(:m)
|
|
v = k.new(0)
|
|
v.m += raise(bug) rescue 1
|
|
assert_equal(k.new(1), v)
|
|
end
|
|
a.for("lhs::m op_asgn arg") do
|
|
k = Struct.new(:m)
|
|
v = k.new(0)
|
|
v::m += raise(bug) rescue 1
|
|
assert_equal(k.new(1), v)
|
|
end
|
|
a.for("lhs.C op_asgn arg") do
|
|
k = Struct.new(:C)
|
|
v = k.new(0)
|
|
v.C += raise(bug) rescue 1
|
|
assert_equal(k.new(1), v)
|
|
end
|
|
a.for("lhs::C op_asgn arg") do
|
|
v = Class.new
|
|
v::C ||= raise(bug) rescue 1
|
|
assert_equal(1, v::C)
|
|
end
|
|
a.for("lhs = command") do
|
|
v = bug
|
|
v = raise bug rescue "ok"
|
|
assert_equal("ok", v)
|
|
end
|
|
a.for("lhs op_asgn command") do
|
|
v = 0
|
|
v += raise bug rescue 1
|
|
assert_equal(1, v)
|
|
end
|
|
a.for("lhs[] op_asgn command") do
|
|
v = [0]
|
|
v[0] += raise bug rescue 1
|
|
assert_equal([1], v)
|
|
end
|
|
a.for("lhs.m op_asgn command") do
|
|
k = Struct.new(:m)
|
|
v = k.new(0)
|
|
v.m += raise bug rescue 1
|
|
assert_equal(k.new(1), v)
|
|
end
|
|
a.for("lhs::m op_asgn command") do
|
|
k = Struct.new(:m)
|
|
v = k.new(0)
|
|
v::m += raise bug rescue 1
|
|
assert_equal(k.new(1), v)
|
|
end
|
|
a.for("lhs.C op_asgn command") do
|
|
k = Struct.new(:C)
|
|
v = k.new(0)
|
|
v.C += raise bug rescue 1
|
|
assert_equal(k.new(1), v)
|
|
end
|
|
a.for("lhs::C op_asgn command") do
|
|
v = Class.new
|
|
v::C ||= raise bug rescue 1
|
|
assert_equal(1, v::C)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_yyerror_at_eol
|
|
assert_syntax_error(" 0b", /\^/)
|
|
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(&0)end", /^ \^/)
|
|
end
|
|
|
|
def test_method_location_in_rescue
|
|
bug = '[ruby-core:79388] [Bug #13181]'
|
|
obj, line = Object.new, __LINE__+1
|
|
def obj.location
|
|
#
|
|
raise
|
|
rescue
|
|
caller_locations(1, 1)[0]
|
|
end
|
|
|
|
assert_equal(line, obj.location.lineno, bug)
|
|
end
|
|
|
|
def test_negative_line_number
|
|
bug = '[ruby-core:80920] [Bug #13523]'
|
|
obj = Object.new
|
|
obj.instance_eval("def t(e = false);raise if e; __LINE__;end", "test", -100)
|
|
assert_equal(-100, obj.t, bug)
|
|
assert_equal(-100, obj.method(:t).source_location[1], bug)
|
|
e = assert_raise(RuntimeError) {obj.t(true)}
|
|
assert_equal(-100, e.backtrace_locations.first.lineno, bug)
|
|
end
|
|
|
|
def test_file_in_indented_heredoc
|
|
name = '[ruby-core:80987] [Bug #13540]' # long enough to be shared
|
|
assert_equal(name+"\n", eval("#{<<-"begin;"}\n#{<<-'end;'}", nil, name))
|
|
begin;
|
|
<<~HEREDOC
|
|
#{__FILE__}
|
|
HEREDOC
|
|
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_syntax_error('"x"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', /unexpected/)
|
|
end
|
|
|
|
def test_unexpected_token_after_numeric
|
|
assert_syntax_error('0000xyz', /^ \^~~\Z/)
|
|
assert_syntax_error('1.2i1.1', /^ \^~~\Z/)
|
|
assert_syntax_error('1.2.3', /^ \^~\Z/)
|
|
assert_syntax_error('1.', /unexpected end-of-input/)
|
|
assert_syntax_error('1e', /expecting end-of-input/)
|
|
end
|
|
|
|
def test_truncated_source_line
|
|
e = assert_syntax_error("'0123456789012345678901234567890123456789' abcdefghijklmnopqrstuvwxyz0123456789 0123456789012345678901234567890123456789",
|
|
/unexpected local variable or method/)
|
|
line = e.message.lines[1]
|
|
assert_operator(line, :start_with?, "...")
|
|
assert_operator(line, :end_with?, "...\n")
|
|
end
|
|
|
|
def test_unterminated_regexp_error
|
|
e = assert_syntax_error("/x", /unterminated regexp meets end of file/)
|
|
assert_not_match(/unexpected tSTRING_END/, e.message)
|
|
end
|
|
|
|
def test_lparenarg
|
|
o = Struct.new(:x).new
|
|
def o.i(x)
|
|
self.x = x
|
|
end
|
|
o.instance_eval {i (-1.3).abs}
|
|
assert_equal(1.3, o.x)
|
|
o.i(nil)
|
|
o.instance_eval {i = 0; i (-1.3).abs; i}
|
|
assert_equal(1.3, o.x)
|
|
end
|
|
|
|
def test_serial_comparison
|
|
assert_warning(/comparison '<' after/) do
|
|
$VERBOSE = true
|
|
x = 1
|
|
eval("if false; 0 < x < 2; end")
|
|
x
|
|
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_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', /^ \^\Z/)
|
|
end
|
|
|
|
def test_location_of_invalid_token
|
|
assert_syntax_error('class xxx end', /^ \^~~\Z/)
|
|
end
|
|
|
|
def test_whitespace_warning
|
|
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
|
|
assert_valid_syntax("\n#{<<~"begin;"}\n#{<<~'end;'}")
|
|
begin;
|
|
m def x(); end
|
|
1.tap do end
|
|
end;
|
|
end
|
|
|
|
NONASCII_CONSTANTS = [
|
|
*%W"\u{00de} \u{00C0}".flat_map {|c| [c, c.encode("iso-8859-15")]},
|
|
"\u{1c4}", "\u{1f2}", "\u{1f88}", "\u{370}",
|
|
*%W"\u{391} \u{ff21}".flat_map {|c| [c, c.encode("cp932"), c.encode("euc-jp")]},
|
|
]
|
|
|
|
def assert_nonascii_const
|
|
assert_all_assertions_foreach("NONASCII_CONSTANTS", *NONASCII_CONSTANTS) do |n|
|
|
m = Module.new
|
|
assert_not_operator(m, :const_defined?, n)
|
|
assert_raise_with_message(NameError, /uninitialized/) do
|
|
m.const_get(n)
|
|
end
|
|
assert_nil(eval("defined?(m::#{n})"))
|
|
|
|
v = yield m, n
|
|
|
|
assert_operator(m, :const_defined?, n)
|
|
assert_equal("constant", eval("defined?(m::#{n})"))
|
|
assert_same(v, m.const_get(n))
|
|
|
|
m.__send__(:remove_const, n)
|
|
assert_not_operator(m, :const_defined?, n)
|
|
assert_nil(eval("defined?(m::#{n})"))
|
|
end
|
|
end
|
|
|
|
def test_nonascii_const_set
|
|
assert_nonascii_const do |m, n|
|
|
m.const_set(n, 42)
|
|
end
|
|
end
|
|
|
|
def test_nonascii_constant
|
|
assert_nonascii_const do |m, n|
|
|
m.module_eval("class #{n}; self; end")
|
|
end
|
|
end
|
|
|
|
def test_cdmarg_after_command_args_and_tlbrace_arg
|
|
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"].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
|
|
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 = [['unsharable_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 !obj.equal?(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_unshareable_literal
|
|
assert_raise_separately(Ractor::IsolationError, /unshareable/,
|
|
"#{<<~"begin;"}\n#{<<~'end;'}")
|
|
begin;
|
|
# shareable_constant_value: literal
|
|
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_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
|
|
|
|
=begin
|
|
def test_past_scope_variable
|
|
assert_warning(/past scope/) {catch {|tag| eval("BEGIN{throw tag}; tap {a = 1}; a")}}
|
|
end
|
|
=end
|
|
end
|