diff options
Diffstat (limited to 'spec/ruby/language')
35 files changed, 1582 insertions, 744 deletions
diff --git a/spec/ruby/language/assignments_spec.rb b/spec/ruby/language/assignments_spec.rb index 2773508d8d..c4adf73c1c 100644 --- a/spec/ruby/language/assignments_spec.rb +++ b/spec/ruby/language/assignments_spec.rb @@ -23,25 +23,12 @@ describe 'Assignments' do ScratchPad.recorded.should == [:receiver, :argument, :rhs] end - # similar tests for evaluation order are located in language/constants_spec.rb - ruby_version_is ''...'3.2' do - it 'evaluates expressions right to left when assignment with compounded constant' do - m = Module.new - ScratchPad.record [] - - (ScratchPad << :module; m)::A = (ScratchPad << :rhs; :value) - ScratchPad.recorded.should == [:rhs, :module] - end - end - - ruby_version_is '3.2' do - it 'evaluates expressions left to right when assignment with compounded constant' do - m = Module.new - ScratchPad.record [] + it 'evaluates expressions left to right when assignment with compounded constant' do + m = Module.new + ScratchPad.record [] - (ScratchPad << :module; m)::A = (ScratchPad << :rhs; :value) - ScratchPad.recorded.should == [:module, :rhs] - end + (ScratchPad << :module; m)::A = (ScratchPad << :rhs; :value) + ScratchPad.recorded.should == [:module, :rhs] end it 'raises TypeError after evaluation of right-hand-side when compounded constant module is not a module' do @@ -54,6 +41,65 @@ describe 'Assignments' do ScratchPad.recorded.should == [:rhs] end end + + context "given block argument" do + before do + @klass = Class.new do + def initialize(h) @h = h end + def [](k, &block) @h[k]; end + def []=(k, v, &block) @h[k] = v; end + end + end + + ruby_version_is ""..."3.4" do + it "accepts block argument" do + obj = @klass.new(a: 1) + block = proc {} + + eval "obj[:a, &block] = 2" + eval("obj[:a, &block]").should == 2 + end + end + + ruby_version_is "3.4" do + it "raises SyntaxError" do + obj = @klass.new(a: 1) + block = proc {} + + -> { + eval "obj[:a, &block] = 2" + }.should raise_error(SyntaxError, /unexpected block arg given in index assignment|block arg given in index assignment/) + end + end + end + + context "given keyword arguments" do + before do + @klass = Class.new do + attr_reader :x + + def []=(*args, **kw) + @x = [args, kw] + end + end + end + + ruby_version_is ""..."3.4" do + it "supports keyword arguments in index assignments" do + a = @klass.new + eval "a[1, 2, 3, b: 4] = 5" + a.x.should == [[1, 2, 3, {b: 4}, 5], {}] + end + end + + ruby_version_is "3.4" do + it "raises SyntaxError when given keyword arguments in index assignments" do + a = @klass.new + -> { eval "a[1, 2, 3, b: 4] = 5" }.should raise_error(SyntaxError, + /keywords are not allowed in index assignment expressions|keyword arg given in index assignment/) # prism|parse.y + end + end + end end describe 'using +=' do @@ -127,6 +173,77 @@ describe 'Assignments' do a.public_method(:k, 2).should == 2 end + context "given block argument" do + before do + @klass = Class.new do + def initialize(h) @h = h end + def [](k, &block) @h[k]; end + def []=(k, v, &block) @h[k] = v; end + end + end + + ruby_version_is ""..."3.4" do + it "accepts block argument" do + obj = @klass.new(a: 1) + block = proc {} + + eval "obj[:a, &block] += 2" + eval("obj[:a, &block]").should == 3 + end + end + + ruby_version_is "3.4" do + it "raises SyntaxError" do + obj = @klass.new(a: 1) + block = proc {} + + -> { + eval "obj[:a, &block] += 2" + }.should raise_error(SyntaxError, /unexpected block arg given in index assignment|block arg given in index assignment/) + end + end + end + + context "given keyword arguments" do + before do + @klass = Class.new do + attr_reader :x + + def [](*args) + 100 + end + + def []=(*args, **kw) + @x = [args, kw] + end + end + end + + ruby_version_is ""..."3.3" do + it "supports keyword arguments in index assignments" do + a = @klass.new + eval "a[1, 2, 3, b: 4] += 5" + a.x.should == [[1, 2, 3, {b: 4}, 105], {}] + end + end + + ruby_version_is "3.3"..."3.4" do + it "supports keyword arguments in index assignments" do + a = @klass.new + eval "a[1, 2, 3, b: 4] += 5" + a.x.should == [[1, 2, 3, 105], {b: 4}] + end + end + + ruby_version_is "3.4" do + it "raises SyntaxError when given keyword arguments in index assignments" do + a = @klass.new + -> { eval "a[1, 2, 3, b: 4] += 5" }.should raise_error(SyntaxError, + /keywords are not allowed in index assignment expressions|keyword arg given in index assignment/) # prism|parse.y + end + end + end + context 'splatted argument' do it 'correctly handles it' do @b[:m] = 10 @@ -276,158 +393,102 @@ end describe 'Multiple assignments' do describe 'evaluation order' do - ruby_version_is ''...'3.1' do - it 'evaluates expressions right to left when assignment with an accessor' do - object = Object.new - def object.a=(value) end - ScratchPad.record [] - - (ScratchPad << :a; object).a, (ScratchPad << :b; object).a = (ScratchPad << :c; :c), (ScratchPad << :d; :d) - ScratchPad.recorded.should == [:c, :d, :a, :b] - end - - it 'evaluates expressions right to left when assignment with a nested accessor' do - object = Object.new - def object.a=(value) end - ScratchPad.record [] + it 'evaluates expressions left to right when assignment with an accessor' do + object = Object.new + def object.a=(value) end + ScratchPad.record [] - ((ScratchPad << :a; object).a, foo), bar = [(ScratchPad << :b; :b)] - ScratchPad.recorded.should == [:b, :a] - end + (ScratchPad << :a; object).a, (ScratchPad << :b; object).a = (ScratchPad << :c; :c), (ScratchPad << :d; :d) + ScratchPad.recorded.should == [:a, :b, :c, :d] end - ruby_version_is '3.1' do - it 'evaluates expressions left to right when assignment with an accessor' do - object = Object.new - def object.a=(value) end - ScratchPad.record [] - - (ScratchPad << :a; object).a, (ScratchPad << :b; object).a = (ScratchPad << :c; :c), (ScratchPad << :d; :d) - ScratchPad.recorded.should == [:a, :b, :c, :d] - end - - it 'evaluates expressions left to right when assignment with a nested accessor' do - object = Object.new - def object.a=(value) end - ScratchPad.record [] - - ((ScratchPad << :a; object).a, foo), bar = [(ScratchPad << :b; :b)] - ScratchPad.recorded.should == [:a, :b] - end - - it 'evaluates expressions left to right when assignment with a deeply nested accessor' do - o = Object.new - def o.a=(value) end - def o.b=(value) end - def o.c=(value) end - def o.d=(value) end - def o.e=(value) end - def o.f=(value) end - ScratchPad.record [] - - (ScratchPad << :a; o).a, - ((ScratchPad << :b; o).b, - ((ScratchPad << :c; o).c, (ScratchPad << :d; o).d), - (ScratchPad << :e; o).e), - (ScratchPad << :f; o).f = (ScratchPad << :value; :value) + it 'evaluates expressions left to right when assignment with a nested accessor' do + object = Object.new + def object.a=(value) end + ScratchPad.record [] - ScratchPad.recorded.should == [:a, :b, :c, :d, :e, :f, :value] - end + ((ScratchPad << :a; object).a, foo), bar = [(ScratchPad << :b; :b)] + ScratchPad.recorded.should == [:a, :b] end - ruby_version_is ''...'3.1' do - it 'evaluates expressions right to left when assignment with a #[]=' do - object = Object.new - def object.[]=(_, _) end - ScratchPad.record [] - - (ScratchPad << :a; object)[(ScratchPad << :b; :b)], (ScratchPad << :c; object)[(ScratchPad << :d; :d)] = (ScratchPad << :e; :e), (ScratchPad << :f; :f) - ScratchPad.recorded.should == [:e, :f, :a, :b, :c, :d] - end - - it 'evaluates expressions right to left when assignment with a nested #[]=' do - object = Object.new - def object.[]=(_, _) end - ScratchPad.record [] - - ((ScratchPad << :a; object)[(ScratchPad << :b; :b)], foo), bar = [(ScratchPad << :c; :c)] - ScratchPad.recorded.should == [:c, :a, :b] - end + it 'evaluates expressions left to right when assignment with a deeply nested accessor' do + o = Object.new + def o.a=(value) end + def o.b=(value) end + def o.c=(value) end + def o.d=(value) end + def o.e=(value) end + def o.f=(value) end + ScratchPad.record [] + + (ScratchPad << :a; o).a, + ((ScratchPad << :b; o).b, + ((ScratchPad << :c; o).c, (ScratchPad << :d; o).d), + (ScratchPad << :e; o).e), + (ScratchPad << :f; o).f = (ScratchPad << :value; :value) + + ScratchPad.recorded.should == [:a, :b, :c, :d, :e, :f, :value] end - ruby_version_is '3.1' do - it 'evaluates expressions left to right when assignment with a #[]=' do - object = Object.new - def object.[]=(_, _) end - ScratchPad.record [] + it 'evaluates expressions left to right when assignment with a #[]=' do + object = Object.new + def object.[]=(_, _) end + ScratchPad.record [] - (ScratchPad << :a; object)[(ScratchPad << :b; :b)], (ScratchPad << :c; object)[(ScratchPad << :d; :d)] = (ScratchPad << :e; :e), (ScratchPad << :f; :f) - ScratchPad.recorded.should == [:a, :b, :c, :d, :e, :f] - end + (ScratchPad << :a; object)[(ScratchPad << :b; :b)], (ScratchPad << :c; object)[(ScratchPad << :d; :d)] = (ScratchPad << :e; :e), (ScratchPad << :f; :f) + ScratchPad.recorded.should == [:a, :b, :c, :d, :e, :f] + end - it 'evaluates expressions left to right when assignment with a nested #[]=' do - object = Object.new - def object.[]=(_, _) end - ScratchPad.record [] + it 'evaluates expressions left to right when assignment with a nested #[]=' do + object = Object.new + def object.[]=(_, _) end + ScratchPad.record [] - ((ScratchPad << :a; object)[(ScratchPad << :b; :b)], foo), bar = [(ScratchPad << :c; :c)] - ScratchPad.recorded.should == [:a, :b, :c] - end + ((ScratchPad << :a; object)[(ScratchPad << :b; :b)], foo), bar = [(ScratchPad << :c; :c)] + ScratchPad.recorded.should == [:a, :b, :c] + end - it 'evaluates expressions left to right when assignment with a deeply nested #[]=' do - o = Object.new - def o.[]=(_, _) end - ScratchPad.record [] + it 'evaluates expressions left to right when assignment with a deeply nested #[]=' do + o = Object.new + def o.[]=(_, _) end + ScratchPad.record [] - (ScratchPad << :ra; o)[(ScratchPad << :aa; :aa)], - ((ScratchPad << :rb; o)[(ScratchPad << :ab; :ab)], - ((ScratchPad << :rc; o)[(ScratchPad << :ac; :ac)], (ScratchPad << :rd; o)[(ScratchPad << :ad; :ad)]), - (ScratchPad << :re; o)[(ScratchPad << :ae; :ae)]), - (ScratchPad << :rf; o)[(ScratchPad << :af; :af)] = (ScratchPad << :value; :value) + (ScratchPad << :ra; o)[(ScratchPad << :aa; :aa)], + ((ScratchPad << :rb; o)[(ScratchPad << :ab; :ab)], + ((ScratchPad << :rc; o)[(ScratchPad << :ac; :ac)], (ScratchPad << :rd; o)[(ScratchPad << :ad; :ad)]), + (ScratchPad << :re; o)[(ScratchPad << :ae; :ae)]), + (ScratchPad << :rf; o)[(ScratchPad << :af; :af)] = (ScratchPad << :value; :value) - ScratchPad.recorded.should == [:ra, :aa, :rb, :ab, :rc, :ac, :rd, :ad, :re, :ae, :rf, :af, :value] - end + ScratchPad.recorded.should == [:ra, :aa, :rb, :ab, :rc, :ac, :rd, :ad, :re, :ae, :rf, :af, :value] end - ruby_version_is ''...'3.2' do - it 'evaluates expressions right to left when assignment with compounded constant' do - m = Module.new - ScratchPad.record [] + it 'evaluates expressions left to right when assignment with compounded constant' do + m = Module.new + ScratchPad.record [] - (ScratchPad << :a; m)::A, (ScratchPad << :b; m)::B = (ScratchPad << :c; :c), (ScratchPad << :d; :d) - ScratchPad.recorded.should == [:c, :d, :a, :b] - end + (ScratchPad << :a; m)::A, (ScratchPad << :b; m)::B = (ScratchPad << :c; :c), (ScratchPad << :d; :d) + ScratchPad.recorded.should == [:a, :b, :c, :d] end - ruby_version_is '3.2' do - it 'evaluates expressions left to right when assignment with compounded constant' do - m = Module.new - ScratchPad.record [] - - (ScratchPad << :a; m)::A, (ScratchPad << :b; m)::B = (ScratchPad << :c; :c), (ScratchPad << :d; :d) - ScratchPad.recorded.should == [:a, :b, :c, :d] - end - - it 'evaluates expressions left to right when assignment with a nested compounded constant' do - m = Module.new - ScratchPad.record [] + it 'evaluates expressions left to right when assignment with a nested compounded constant' do + m = Module.new + ScratchPad.record [] - ((ScratchPad << :a; m)::A, foo), bar = [(ScratchPad << :b; :b)] - ScratchPad.recorded.should == [:a, :b] - end + ((ScratchPad << :a; m)::A, foo), bar = [(ScratchPad << :b; :b)] + ScratchPad.recorded.should == [:a, :b] + end - it 'evaluates expressions left to right when assignment with deeply nested compounded constants' do - m = Module.new - ScratchPad.record [] + it 'evaluates expressions left to right when assignment with deeply nested compounded constants' do + m = Module.new + ScratchPad.record [] - (ScratchPad << :a; m)::A, - ((ScratchPad << :b; m)::B, - ((ScratchPad << :c; m)::C, (ScratchPad << :d; m)::D), - (ScratchPad << :e; m)::E), - (ScratchPad << :f; m)::F = (ScratchPad << :value; :value) + (ScratchPad << :a; m)::A, + ((ScratchPad << :b; m)::B, + ((ScratchPad << :c; m)::C, (ScratchPad << :d; m)::D), + (ScratchPad << :e; m)::E), + (ScratchPad << :f; m)::F = (ScratchPad << :value; :value) - ScratchPad.recorded.should == [:a, :b, :c, :d, :e, :f, :value] - end + ScratchPad.recorded.should == [:a, :b, :c, :d, :e, :f, :value] end end diff --git a/spec/ruby/language/block_spec.rb b/spec/ruby/language/block_spec.rb index 75c1e71bc2..cc003b8946 100644 --- a/spec/ruby/language/block_spec.rb +++ b/spec/ruby/language/block_spec.rb @@ -62,37 +62,18 @@ describe "A block yielded a single" do m([1, 2, 3, 4, 5, 6]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 2, 3, [4], 5, 6] end - ruby_version_is "3.2" do - it "does not autosplat single argument to required arguments when a keyword rest argument is present" do - m([1, 2]) { |a, **k| [a, k] }.should == [[1, 2], {}] - end - - it "does not autosplat single argument to required arguments when keyword arguments are present" do - m([1, 2]) { |a, b: :b, c: :c| [a, b, c] }.should == [[1, 2], :b, :c] - end - - it "raises error when required keyword arguments are present" do - -> { - m([1, 2]) { |a, b:, c:| [a, b, c] } - }.should raise_error(ArgumentError, "missing keywords: :b, :c") - end + it "does not autosplat single argument to required arguments when a keyword rest argument is present" do + m([1, 2]) { |a, **k| [a, k] }.should == [[1, 2], {}] end - ruby_version_is ''..."3.2" do - # https://bugs.ruby-lang.org/issues/18633 - it "autosplats single argument to required arguments when a keyword rest argument is present" do - m([1, 2]) { |a, **k| [a, k] }.should == [1, {}] - end - - it "autosplats single argument to required arguments when optional keyword arguments are present" do - m([1, 2]) { |a, b: :b, c: :c| [a, b, c] }.should == [1, :b, :c] - end + it "does not autosplat single argument to required arguments when keyword arguments are present" do + m([1, 2]) { |a, b: :b, c: :c| [a, b, c] }.should == [[1, 2], :b, :c] + end - it "raises error when required keyword arguments are present" do - -> { - m([1, 2]) { |a, b:, c:| [a, b, c] } - }.should raise_error(ArgumentError, "missing keywords: :b, :c") - end + it "raises error when required keyword arguments are present" do + -> { + m([1, 2]) { |a, b:, c:| [a, b, c] } + }.should raise_error(ArgumentError, "missing keywords: :b, :c") end it "assigns elements to mixed argument types" do @@ -294,7 +275,7 @@ describe "A block" do end it "may include a rescue clause" do - eval("@y.z do raise ArgumentError; rescue ArgumentError; 7; end").should == 7 + @y.z do raise ArgumentError; rescue ArgumentError; 7; end.should == 7 end end @@ -308,7 +289,7 @@ describe "A block" do end it "may include a rescue clause" do - eval('@y.z do || raise ArgumentError; rescue ArgumentError; 7; end').should == 7 + @y.z do || raise ArgumentError; rescue ArgumentError; 7; end.should == 7 end end @@ -337,7 +318,7 @@ describe "A block" do end it "may include a rescue clause" do - eval('@y.s(1) do |x| raise ArgumentError; rescue ArgumentError; 7; end').should == 7 + @y.s(1) do |x| raise ArgumentError; rescue ArgumentError; 7; end.should == 7 end end @@ -737,9 +718,9 @@ describe "A block" do end it "accepts unnamed arguments" do - eval("lambda { |_,_| }").should be_an_instance_of(Proc) - eval("->(_,_) {}").should be_an_instance_of(Proc) - eval("Proc.new { |_,_| }").should be_an_instance_of(Proc) + lambda { |_,_| }.should be_an_instance_of(Proc) # rubocop:disable Style/Lambda + -> _,_ {}.should be_an_instance_of(Proc) + Proc.new { |_,_| }.should be_an_instance_of(Proc) end end @@ -1001,76 +982,62 @@ end # tested more thoroughly in language/delegation_spec.rb describe "Anonymous block forwarding" do - ruby_version_is "3.1" do - it "forwards blocks to other method that formally declares anonymous block" do - eval <<-EOF - def b(&); c(&) end - def c(&); yield :non_null end - EOF + it "forwards blocks to other method that formally declares anonymous block" do + def b(&); c(&) end + def c(&); yield :non_null end - b { |c| c }.should == :non_null - end + b { |c| c }.should == :non_null + end - it "requires the anonymous block parameter to be declared if directly passing a block" do - -> { eval "def a; b(&); end; def b; end" }.should raise_error(SyntaxError) - end + it "requires the anonymous block parameter to be declared if directly passing a block" do + -> { eval "def a; b(&); end; def b; end" }.should raise_error(SyntaxError) + end - it "works when it's the only declared parameter" do - eval <<-EOF - def inner; yield end - def block_only(&); inner(&) end - EOF + it "works when it's the only declared parameter" do + def inner; yield end + def block_only(&); inner(&) end - block_only { 1 }.should == 1 - end + block_only { 1 }.should == 1 + end - it "works alongside positional parameters" do - eval <<-EOF - def inner; yield end - def pos(arg1, &); inner(&) end - EOF + it "works alongside positional parameters" do + def inner; yield end + def pos(arg1, &); inner(&) end - pos(:a) { 1 }.should == 1 - end + pos(:a) { 1 }.should == 1 + end - it "works alongside positional arguments and splatted keyword arguments" do - eval <<-EOF - def inner; yield end - def pos_kwrest(arg1, **kw, &); inner(&) end - EOF + it "works alongside positional arguments and splatted keyword arguments" do + def inner; yield end + def pos_kwrest(arg1, **kw, &); inner(&) end - pos_kwrest(:a, arg: 3) { 1 }.should == 1 - end + pos_kwrest(:a, arg: 3) { 1 }.should == 1 + end - it "works alongside positional arguments and disallowed keyword arguments" do - eval <<-EOF - def inner; yield end - def no_kw(arg1, **nil, &); inner(&) end - EOF + it "works alongside positional arguments and disallowed keyword arguments" do + def inner; yield end + def no_kw(arg1, **nil, &); inner(&) end - no_kw(:a) { 1 }.should == 1 - end + no_kw(:a) { 1 }.should == 1 end - ruby_version_is "3.2" do - it "works alongside explicit keyword arguments" do - eval <<-EOF - def inner; yield end - def rest_kw(*a, kwarg: 1, &); inner(&) end - def kw(kwarg: 1, &); inner(&) end - def pos_kw_kwrest(arg1, kwarg: 1, **kw, &); inner(&) end - def pos_rkw(arg1, kwarg1:, &); inner(&) end - def all(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, &); inner(&) end - def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, **kw, &); inner(&) end - EOF - - rest_kw { 1 }.should == 1 - kw { 1 }.should == 1 - pos_kw_kwrest(:a) { 1 }.should == 1 - pos_rkw(:a, kwarg1: 3) { 1 }.should == 1 - all(:a, :b, :c, :d, :e, okw1: 'x', okw2: 'y') { 1 }.should == 1 - all_kwrest(:a, :b, :c, :d, :e, okw1: 'x', okw2: 'y') { 1 }.should == 1 - end + it "works alongside explicit keyword arguments" do + eval <<-EOF + def inner; yield end + def rest_kw(*a, kwarg: 1, &); inner(&) end + def kw(kwarg: 1, &); inner(&) end + def pos_kw_kwrest(arg1, kwarg: 1, **kw, &); inner(&) end + def pos_rkw(arg1, kwarg1:, &); inner(&) end + def all(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, &); inner(&) end + def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, **kw, &); inner(&) end + EOF + + rest_kw { 1 }.should == 1 + kw { 1 }.should == 1 + pos_kw_kwrest(:a) { 1 }.should == 1 + pos_rkw(:a, kwarg1: 3) { 1 }.should == 1 + all(:a, :b, :c, :d, :e, okw1: 'x', okw2: 'y') { 1 }.should == 1 + all_kwrest(:a, :b, :c, :d, :e, okw1: 'x', okw2: 'y') { 1 }.should == 1 end end @@ -1082,6 +1049,12 @@ describe "`it` calls without arguments in a block with no ordinary parameters" d }.should complain(/warning: `it` calls without arguments will refer to the first block param in Ruby 3.4; use it\(\) or self.it/) end + it "emits a deprecation warning if numbered parameters are used" do + -> { + eval "proc { it; _1 }" + }.should complain(/warning: `it` calls without arguments will refer to the first block param in Ruby 3.4; use it\(\) or self.it/) + end + it "does not emit a deprecation warning when a block has parameters" do -> { eval "proc { |a, b| it }" }.should_not complain -> { eval "proc { |*rest| it }" }.should_not complain @@ -1091,14 +1064,75 @@ describe "`it` calls without arguments in a block with no ordinary parameters" d -> { eval "proc { |**| it }" }.should_not complain -> { eval "proc { |&block| it }" }.should_not complain -> { eval "proc { |&| it }" }.should_not complain + -> { eval "proc { || it }" }.should_not complain end it "does not emit a deprecation warning when `it` calls with arguments" do -> { eval "proc { it(42) }" }.should_not complain + -> { eval "proc { it 42 }" }.should_not complain + end + + it "does not emit a deprecation warning when `it` calls with a block" do + -> { eval "proc { it {} }" }.should_not complain + end + + it "does not emit a deprecation warning when a local variable inside the block named `it` exists" do + -> { eval "proc { it = 42; it }" }.should_not complain end it "does not emit a deprecation warning when `it` calls with explicit empty arguments list" do -> { eval "proc { it() }" }.should_not complain end + + it "calls the method `it` if defined" do + o = Object.new + def o.it + 21 + end + suppress_warning do + o.instance_eval("proc { it * 2 }").call(1).should == 42 + end + end + end + + ruby_version_is "3.4" do + it "does not emit a deprecation warning" do + -> { + eval "proc { it }" + }.should_not complain + end + + it "acts as the first argument if no local variables exist" do + eval("proc { it * 2 }").call(5).should == 10 + end + + it "can be reassigned to act as a local variable" do + eval("proc { tmp = it; it = tmp * 2; it }").call(21).should == 42 + end + + it "can be used in nested calls" do + eval("proc { it.map { it * 2 } }").call([1, 2, 3]).should == [2, 4, 6] + end + + it "cannot be mixed with numbered parameters" do + -> { + eval "proc { it + _1 }" + }.should raise_error(SyntaxError, /numbered parameters are not allowed when 'it' is already used|'it' is already used in/) + + -> { + eval "proc { _1 + it }" + }.should raise_error(SyntaxError, /numbered parameter is already used in|'it' is not allowed when a numbered parameter is already used/) + end + end +end + +describe "if `it` is defined as a variable" do + it "treats `it` as a captured variable if defined outside of a block" do + it = 5 + proc { it }.call(0).should == 5 + end + + it "treats `it` as a local variable if defined inside of a block" do + proc { it = 5; it }.call(0).should == 5 end end diff --git a/spec/ruby/language/class_spec.rb b/spec/ruby/language/class_spec.rb index eab3cd0651..6fb785fd56 100644 --- a/spec/ruby/language/class_spec.rb +++ b/spec/ruby/language/class_spec.rb @@ -46,7 +46,14 @@ describe "A class definition" do -> { class ClassSpecsNumber end - }.should raise_error(TypeError) + }.should raise_error(TypeError, /\AClassSpecsNumber is not a class/) + end + + it "raises TypeError if constant given as class name exists and is a Module but not a Class" do + -> { + class ClassSpecs + end + }.should raise_error(TypeError, /\AClassSpecs is not a class/) end # test case known to be detecting bugs (JRuby, MRI) @@ -271,6 +278,8 @@ describe "A class definition" do AnonWithConstant.name.should == 'AnonWithConstant' klass.get_class_name.should == 'AnonWithConstant' + ensure + Object.send(:remove_const, :AnonWithConstant) end end end @@ -344,6 +353,39 @@ describe "Reopening a class" do ClassSpecs::M.m.should == 1 ClassSpecs::L.singleton_class.send(:remove_method, :m) end + + it "does not reopen a class included in Object" do + ruby_exe(<<~RUBY).should == "false" + module IncludedInObject + class IncludedClass + end + end + class Object + include IncludedInObject + end + class IncludedClass + end + print IncludedInObject::IncludedClass == Object::IncludedClass + RUBY + end + + it "does not reopen a class included in non-Object modules" do + ruby_exe(<<~RUBY).should == "false/false" + module Included + module IncludedClass; end + end + module M + include Included + module IncludedClass; end + end + class C + include Included + module IncludedClass; end + end + print Included::IncludedClass == M::IncludedClass, "/", + Included::IncludedClass == C::IncludedClass + RUBY + end end describe "class provides hooks" do diff --git a/spec/ruby/language/constants_spec.rb b/spec/ruby/language/constants_spec.rb index 08c534487e..063c52c422 100644 --- a/spec/ruby/language/constants_spec.rb +++ b/spec/ruby/language/constants_spec.rb @@ -72,39 +72,60 @@ describe "Literal (A::X) constant resolution" do ConstantSpecs::ModuleA::CS_CONST101 = :const101_5 ConstantSpecs::ModuleA::CS_CONST101.should == :const101_5 + ensure + ConstantSpecs::ClassB.send(:remove_const, :CS_CONST101) + ConstantSpecs::ParentB.send(:remove_const, :CS_CONST101) + ConstantSpecs::ContainerB.send(:remove_const, :CS_CONST101) + ConstantSpecs::ContainerB::ChildB.send(:remove_const, :CS_CONST101) + ConstantSpecs::ModuleA.send(:remove_const, :CS_CONST101) end it "searches a module included in the immediate class before the superclass" do ConstantSpecs::ParentB::CS_CONST102 = :const102_1 ConstantSpecs::ModuleF::CS_CONST102 = :const102_2 ConstantSpecs::ContainerB::ChildB::CS_CONST102.should == :const102_2 + ensure + ConstantSpecs::ParentB.send(:remove_const, :CS_CONST102) + ConstantSpecs::ModuleF.send(:remove_const, :CS_CONST102) end it "searches the superclass before a module included in the superclass" do ConstantSpecs::ModuleE::CS_CONST103 = :const103_1 ConstantSpecs::ParentB::CS_CONST103 = :const103_2 ConstantSpecs::ContainerB::ChildB::CS_CONST103.should == :const103_2 + ensure + ConstantSpecs::ModuleE.send(:remove_const, :CS_CONST103) + ConstantSpecs::ParentB.send(:remove_const, :CS_CONST103) end it "searches a module included in the superclass" do ConstantSpecs::ModuleA::CS_CONST104 = :const104_1 ConstantSpecs::ModuleE::CS_CONST104 = :const104_2 ConstantSpecs::ContainerB::ChildB::CS_CONST104.should == :const104_2 + ensure + ConstantSpecs::ModuleA.send(:remove_const, :CS_CONST104) + ConstantSpecs::ModuleE.send(:remove_const, :CS_CONST104) end it "searches the superclass chain" do ConstantSpecs::ModuleA::CS_CONST105 = :const105 ConstantSpecs::ContainerB::ChildB::CS_CONST105.should == :const105 + ensure + ConstantSpecs::ModuleA.send(:remove_const, :CS_CONST105) end it "searches Object if no class or module qualifier is given" do CS_CONST106 = :const106 CS_CONST106.should == :const106 + ensure + Object.send(:remove_const, :CS_CONST106) end it "searches Object if a toplevel qualifier (::X) is given" do ::CS_CONST107 = :const107 ::CS_CONST107.should == :const107 + ensure + Object.send(:remove_const, :CS_CONST107) end it "does not search the singleton class of the class or module" do @@ -123,6 +144,9 @@ describe "Literal (A::X) constant resolution" do end -> { ConstantSpecs::CS_CONST108 }.should raise_error(NameError) + ensure + ConstantSpecs::ContainerB::ChildB.singleton_class.send(:remove_const, :CS_CONST108) + ConstantSpecs.singleton_class.send(:remove_const, :CS_CONST108) end it "returns the updated value when a constant is reassigned" do @@ -133,36 +157,20 @@ describe "Literal (A::X) constant resolution" do ConstantSpecs::ClassB::CS_CONST109 = :const109_2 }.should complain(/already initialized constant/) ConstantSpecs::ClassB::CS_CONST109.should == :const109_2 + ensure + ConstantSpecs::ClassB.send(:remove_const, :CS_CONST109) end - ruby_version_is "3.2" do - it "evaluates left-to-right" do - mod = Module.new + it "evaluates left-to-right" do + mod = Module.new - mod.module_eval <<-EOC - order = [] - ConstantSpecsRHS = Module.new - (order << :lhs; ConstantSpecsRHS)::B = (order << :rhs) - EOC + mod.module_eval <<-EOC + order = [] + ConstantSpecsRHS = Module.new + (order << :lhs; ConstantSpecsRHS)::B = (order << :rhs) + EOC - mod::ConstantSpecsRHS::B.should == [:lhs, :rhs] - end - end - - ruby_version_is ""..."3.2" do - it "evaluates the right hand side before evaluating a constant path" do - mod = Module.new - - mod.module_eval <<-EOC - ConstantSpecsRHS::B = begin - module ConstantSpecsRHS; end - - "hello" - end - EOC - - mod::ConstantSpecsRHS::B.should == 'hello' - end + mod::ConstantSpecsRHS::B.should == [:lhs, :rhs] end end @@ -292,6 +300,12 @@ describe "Constant resolution within methods" do ConstantSpecs::ClassB.new.const201.should == :const201_2 ConstantSpecs::ParentB.new.const201.should == :const201_3 ConstantSpecs::ContainerB::ChildB.new.const201.should == :const201_5 + ensure + ConstantSpecs::ModuleA.send(:remove_const, :CS_CONST201) + ConstantSpecs::ClassB.send(:remove_const, :CS_CONST201) + ConstantSpecs::ParentB.send(:remove_const, :CS_CONST201) + ConstantSpecs::ContainerB.send(:remove_const, :CS_CONST201) + ConstantSpecs::ContainerB::ChildB.send(:remove_const, :CS_CONST201) end it "searches a module included in the immediate class before the superclass" do @@ -300,6 +314,9 @@ describe "Constant resolution within methods" do ConstantSpecs::ContainerB::ChildB.const202.should == :const202_1 ConstantSpecs::ContainerB::ChildB.new.const202.should == :const202_1 + ensure + ConstantSpecs::ParentB.send(:remove_const, :CS_CONST202) + ConstantSpecs::ContainerB::ChildB.send(:remove_const, :CS_CONST202) end it "searches the superclass before a module included in the superclass" do @@ -308,6 +325,9 @@ describe "Constant resolution within methods" do ConstantSpecs::ContainerB::ChildB.const203.should == :const203_1 ConstantSpecs::ContainerB::ChildB.new.const203.should == :const203_1 + ensure + ConstantSpecs::ParentB.send(:remove_const, :CS_CONST203) + ConstantSpecs::ModuleE.send(:remove_const, :CS_CONST203) end it "searches a module included in the superclass" do @@ -316,6 +336,9 @@ describe "Constant resolution within methods" do ConstantSpecs::ContainerB::ChildB.const204.should == :const204_1 ConstantSpecs::ContainerB::ChildB.new.const204.should == :const204_1 + ensure + ConstantSpecs::ModuleA.send(:remove_const, :CS_CONST204) + ConstantSpecs::ModuleE.send(:remove_const, :CS_CONST204) end it "searches the superclass chain" do @@ -323,6 +346,8 @@ describe "Constant resolution within methods" do ConstantSpecs::ContainerB::ChildB.const205.should == :const205 ConstantSpecs::ContainerB::ChildB.new.const205.should == :const205 + ensure + ConstantSpecs::ModuleA.send(:remove_const, :CS_CONST205) end it "searches the lexical scope of the method not the receiver's immediate class" do @@ -334,6 +359,9 @@ describe "Constant resolution within methods" do end ConstantSpecs::ContainerB::ChildB.const206.should == :const206_1 + ensure + ConstantSpecs::ContainerB::ChildB.send(:remove_const, :CS_CONST206) + ConstantSpecs::ContainerB::ChildB.singleton_class.send(:remove_const, :CS_CONST206) end it "searches the lexical scope of a singleton method" do @@ -341,12 +369,17 @@ describe "Constant resolution within methods" do ConstantSpecs::ClassB::CS_CONST207 = :const207_2 ConstantSpecs::CS_CONST208.const207.should == :const207_1 + ensure + ConstantSpecs.send(:remove_const, :CS_CONST207) + ConstantSpecs::ClassB.send(:remove_const, :CS_CONST207) end it "does not search the lexical scope of the caller" do ConstantSpecs::ClassB::CS_CONST209 = :const209 -> { ConstantSpecs::ClassB.const209 }.should raise_error(NameError) + ensure + ConstantSpecs::ClassB.send(:remove_const, :CS_CONST209) end it "searches the lexical scope of a block" do @@ -354,6 +387,9 @@ describe "Constant resolution within methods" do ConstantSpecs::ParentB::CS_CONST210 = :const210_2 ConstantSpecs::ClassB.const210.should == :const210_1 + ensure + ConstantSpecs::ClassB.send(:remove_const, :CS_CONST210) + ConstantSpecs::ParentB.send(:remove_const, :CS_CONST210) end it "searches Object as a lexical scope only if Object is explicitly opened" do @@ -364,6 +400,11 @@ describe "Constant resolution within methods" do Object::CS_CONST212 = :const212_2 ConstantSpecs::ParentB::CS_CONST212 = :const212_1 ConstantSpecs::ContainerB::ChildB.const212.should == :const212_1 + ensure + Object.send(:remove_const, :CS_CONST211) + ConstantSpecs::ParentB.send(:remove_const, :CS_CONST211) + Object.send(:remove_const, :CS_CONST212) + ConstantSpecs::ParentB.send(:remove_const, :CS_CONST212) end it "returns the updated value when a constant is reassigned" do @@ -376,6 +417,8 @@ describe "Constant resolution within methods" do }.should complain(/already initialized constant/) ConstantSpecs::ContainerB::ChildB.const213.should == :const213_2 ConstantSpecs::ContainerB::ChildB.new.const213.should == :const213_2 + ensure + ConstantSpecs::ParentB.send(:remove_const, :CS_CONST213) end it "does not search the lexical scope of qualifying modules" do @@ -384,6 +427,8 @@ describe "Constant resolution within methods" do -> do ConstantSpecs::ContainerB::ChildB.const214 end.should raise_error(NameError) + ensure + ConstantSpecs::ContainerB.send(:remove_const, :CS_CONST214) end end @@ -484,6 +529,10 @@ describe "Module#private_constant marked constants" do PrivateModule::X.should == 1 end + ensure + module ::ConstantVisibility::ModuleContainer + PrivateModule.send(:remove_const, :X) + end end it "can be reopened as a class where constant is not private" do @@ -494,6 +543,10 @@ describe "Module#private_constant marked constants" do PrivateClass::X.should == 1 end + ensure + module ::ConstantVisibility::ModuleContainer + PrivateClass.send(:remove_const, :X) + end end it "is not defined? with A::B form" do @@ -565,6 +618,10 @@ describe "Module#private_constant marked constants" do PrivateModule::X.should == 1 end + ensure + class ::ConstantVisibility::ClassContainer + PrivateModule.send(:remove_const, :X) + end end it "can be reopened as a class where constant is not private" do @@ -575,6 +632,10 @@ describe "Module#private_constant marked constants" do PrivateClass::X.should == 1 end + ensure + class ::ConstantVisibility::ClassContainer + PrivateClass.send(:remove_const, :X) + end end it "is not defined? with A::B form" do diff --git a/spec/ruby/language/def_spec.rb b/spec/ruby/language/def_spec.rb index ce8077eb69..0cf1790791 100644 --- a/spec/ruby/language/def_spec.rb +++ b/spec/ruby/language/def_spec.rb @@ -97,7 +97,8 @@ describe "An instance method" do def foo; end end }.should raise_error(FrozenError) { |e| - e.message.should.start_with? "can't modify frozen module" + msg_class = ruby_version_is("4.0") ? "Module" : "module" + e.message.should == "can't modify frozen #{msg_class}: #{e.receiver}" } -> { @@ -106,7 +107,8 @@ describe "An instance method" do def foo; end end }.should raise_error(FrozenError){ |e| - e.message.should.start_with? "can't modify frozen class" + msg_class = ruby_version_is("4.0") ? "Class" : "class" + e.message.should == "can't modify frozen #{msg_class}: #{e.receiver}" } end end @@ -283,20 +285,21 @@ describe "A singleton method definition" do it "raises FrozenError with the correct class name" do obj = Object.new obj.freeze - -> { def obj.foo; end }.should raise_error(FrozenError){ |e| - e.message.should.start_with? "can't modify frozen object" - } + msg_class = ruby_version_is("4.0") ? "Object" : "object" + -> { def obj.foo; end }.should raise_error(FrozenError, "can't modify frozen #{msg_class}: #{obj}") + obj = Object.new c = obj.singleton_class - -> { def c.foo; end }.should raise_error(FrozenError){ |e| - e.message.should.start_with? "can't modify frozen Class" - } + c.singleton_class.freeze + -> { def c.foo; end }.should raise_error(FrozenError, "can't modify frozen Class: #{c}") + + c = Class.new + c.freeze + -> { def c.foo; end }.should raise_error(FrozenError, "can't modify frozen Class: #{c}") m = Module.new m.freeze - -> { def m.foo; end }.should raise_error(FrozenError){ |e| - e.message.should.start_with? "can't modify frozen Module" - } + -> { def m.foo; end }.should raise_error(FrozenError, "can't modify frozen Module: #{m}") end end @@ -524,6 +527,8 @@ describe "A nested method definition" do obj = DefSpecNested.new obj.inherited_method.should == obj + ensure + DefSpecNested.send(:remove_const, :TARGET) end # See http://yugui.jp/articles/846#label-3 @@ -545,6 +550,8 @@ describe "A nested method definition" do DefSpecNested.should_not have_instance_method :arg_method DefSpecNested.should_not have_instance_method :body_method + ensure + DefSpecNested.send(:remove_const, :OBJ) end it "creates an instance method inside Class.new" do diff --git a/spec/ruby/language/delegation_spec.rb b/spec/ruby/language/delegation_spec.rb index b75f3f5f7c..c711a536c2 100644 --- a/spec/ruby/language/delegation_spec.rb +++ b/spec/ruby/language/delegation_spec.rb @@ -87,77 +87,71 @@ describe "delegation with def(x, ...)" do end end -ruby_version_is "3.2" do - describe "delegation with def(*)" do - it "delegates rest" do - a = Class.new(DelegationSpecs::Target) - a.class_eval(<<-RUBY) - def delegate(*) - target(*) - end - RUBY - - a.new.delegate(0, 1).should == [[0, 1], {}, nil] +describe "delegation with def(*)" do + it "delegates rest" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate(*) + target(*) end + RUBY - ruby_version_is "3.3" do - context "within a block that accepts anonymous rest within a method that accepts anonymous rest" do - it "does not allow delegating rest" do - -> { - eval "def m(*); proc { |*| n(*) } end" - }.should raise_error(SyntaxError, /anonymous rest parameter is also used within block/) - end + a.new.delegate(0, 1).should == [[0, 1], {}, nil] + end + + ruby_version_is "3.3" do + context "within a block that accepts anonymous rest within a method that accepts anonymous rest" do + it "does not allow delegating rest" do + -> { + eval "def m(*); proc { |*| n(*) } end" + }.should raise_error(SyntaxError, /anonymous rest parameter is also used within block/) end end end end -ruby_version_is "3.2" do - describe "delegation with def(**)" do - it "delegates kwargs" do - a = Class.new(DelegationSpecs::Target) - a.class_eval(<<-RUBY) - def delegate(**) - target(**) - end - RUBY - - a.new.delegate(a: 1) { |x| x }.should == [[], {a: 1}, nil] +describe "delegation with def(**)" do + it "delegates kwargs" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate(**) + target(**) end + RUBY - ruby_version_is "3.3" do - context "within a block that accepts anonymous kwargs within a method that accepts anonymous kwargs" do - it "does not allow delegating kwargs" do - -> { - eval "def m(**); proc { |**| n(**) } end" - }.should raise_error(SyntaxError, /anonymous keyword rest parameter is also used within block/) - end + a.new.delegate(a: 1) { |x| x }.should == [[], {a: 1}, nil] + end + + ruby_version_is "3.3" do + context "within a block that accepts anonymous kwargs within a method that accepts anonymous kwargs" do + it "does not allow delegating kwargs" do + -> { + eval "def m(**); proc { |**| n(**) } end" + }.should raise_error(SyntaxError, /anonymous keyword rest parameter is also used within block/) end end end end -ruby_version_is "3.1" do - describe "delegation with def(&)" do - it "delegates an anonymous block parameter" do - a = Class.new(DelegationSpecs::Target) - a.class_eval(<<-RUBY) - def delegate(&) - target(&) - end - RUBY - - block = proc {} - a.new.delegate(&block).should == [[], {}, block] +describe "delegation with def(&)" do + it "delegates an anonymous block parameter" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate(&) + target(&) end + RUBY - ruby_version_is "3.3" do - context "within a block that accepts anonymous block within a method that accepts anonymous block" do - it "does not allow delegating a block" do - -> { - eval "def m(&); proc { |&| n(&) } end" - }.should raise_error(SyntaxError, /anonymous block parameter is also used within block/) - end + block = proc {} + a.new.delegate(&block).should == [[], {}, block] + end + + ruby_version_is "3.3" do + context "within a block that accepts anonymous block within a method that accepts anonymous block" do + it "does not allow delegating a block" do + -> { + eval "def m(&); proc { |&| n(&) } end" + }.should raise_error(SyntaxError, /anonymous block parameter is also used within block/) end end end diff --git a/spec/ruby/language/ensure_spec.rb b/spec/ruby/language/ensure_spec.rb index 16e626b4d0..b76292c007 100644 --- a/spec/ruby/language/ensure_spec.rb +++ b/spec/ruby/language/ensure_spec.rb @@ -339,10 +339,8 @@ describe "An ensure block inside 'do end' block" do end end line = __LINE__ - foo.should == [ - "#{__FILE__}:#{line-3}:in 'foo'", - "#{__FILE__}:#{line+1}:in 'block (3 levels) in <top (required)>'" - ] + foo[0].should =~ /#{__FILE__}:#{line-3}:in 'foo'/ + foo[1].should =~ /#{__FILE__}:#{line+2}:in 'block/ end end end diff --git a/spec/ruby/language/fixtures/class_with_class_variable.rb b/spec/ruby/language/fixtures/class_with_class_variable.rb new file mode 100644 index 0000000000..0b07f16d30 --- /dev/null +++ b/spec/ruby/language/fixtures/class_with_class_variable.rb @@ -0,0 +1,9 @@ +module StringSpecs + class ClassWithClassVariable + @@a = "xxx" + + def foo + "#@@a" + end + end +end diff --git a/spec/ruby/language/fixtures/module.rb b/spec/ruby/language/fixtures/module.rb index 33d323846e..75eee77791 100644 --- a/spec/ruby/language/fixtures/module.rb +++ b/spec/ruby/language/fixtures/module.rb @@ -12,13 +12,4 @@ module ModuleSpecs module Anonymous end - - module IncludedInObject - module IncludedModuleSpecs - end - end -end - -class Object - include ModuleSpecs::IncludedInObject end diff --git a/spec/ruby/language/fixtures/send.rb b/spec/ruby/language/fixtures/send.rb index 5d1d9da214..4787abee5c 100644 --- a/spec/ruby/language/fixtures/send.rb +++ b/spec/ruby/language/fixtures/send.rb @@ -81,6 +81,16 @@ module LangSendSpecs end end + class RawToProc + def initialize(to_proc) + @to_proc = to_proc + end + + def to_proc + @to_proc + end + end + class ToAry def initialize(obj) @obj = obj diff --git a/spec/ruby/language/hash_spec.rb b/spec/ruby/language/hash_spec.rb index 068ac0f39c..668716e2e3 100644 --- a/spec/ruby/language/hash_spec.rb +++ b/spec/ruby/language/hash_spec.rb @@ -60,14 +60,12 @@ describe "Hash literal" do @h.should == {1000 => :foo} end - ruby_version_is "3.1" do - it "checks duplicated float keys on initialization" do - -> { - @h = eval "{1.0 => :bar, 1.0 => :foo}" - }.should complain(/key 1.0 is duplicated|duplicated key/) - @h.keys.size.should == 1 - @h.should == {1.0 => :foo} - end + it "checks duplicated float keys on initialization" do + -> { + @h = eval "{1.0 => :bar, 1.0 => :foo}" + }.should complain(/key 1.0 is duplicated|duplicated key/) + @h.keys.size.should == 1 + @h.should == {1.0 => :foo} end it "accepts a hanging comma" do @@ -77,9 +75,9 @@ describe "Hash literal" do end it "recognizes '=' at the end of the key" do - eval("{:a==>1}").should == {:"a=" => 1} - eval("{:a= =>1}").should == {:"a=" => 1} - eval("{:a= => 1}").should == {:"a=" => 1} + {:a==>1}.should == {:"a=" => 1} + {:a= =>1}.should == {:"a=" => 1} + {:a= => 1}.should == {:"a=" => 1} end it "with '==>' in the middle raises SyntaxError" do @@ -87,11 +85,11 @@ describe "Hash literal" do end it "recognizes '!' at the end of the key" do - eval("{:a! =>1}").should == {:"a!" => 1} - eval("{:a! => 1}").should == {:"a!" => 1} + {:a! =>1}.should == {:"a!" => 1} + {:a! => 1}.should == {:"a!" => 1} - eval("{a!:1}").should == {:"a!" => 1} - eval("{a!: 1}").should == {:"a!" => 1} + {a!:1}.should == {:"a!" => 1} + {a!: 1}.should == {:"a!" => 1} end it "raises a SyntaxError if there is no space between `!` and `=>`" do @@ -99,11 +97,11 @@ describe "Hash literal" do end it "recognizes '?' at the end of the key" do - eval("{:a? =>1}").should == {:"a?" => 1} - eval("{:a? => 1}").should == {:"a?" => 1} + {:a? =>1}.should == {:"a?" => 1} + {:a? => 1}.should == {:"a?" => 1} - eval("{a?:1}").should == {:"a?" => 1} - eval("{a?: 1}").should == {:"a?" => 1} + {a?:1}.should == {:"a?" => 1} + {a?: 1}.should == {:"a?" => 1} end it "raises a SyntaxError if there is no space between `?` and `=>`" do @@ -129,7 +127,7 @@ describe "Hash literal" do it "accepts mixed 'key: value', 'key => value' and '\"key\"': value' syntax" do h = {:a => 1, :b => 2, "c" => 3, :d => 4} - eval('{a: 1, :b => 2, "c" => 3, "d": 4}').should == h + {a: 1, :b => 2, "c" => 3, "d": 4}.should == h end it "expands an '**{}' element into the containing Hash literal initialization" do @@ -151,6 +149,26 @@ describe "Hash literal" do {a: 1, **h, c: 4}.should == {a: 1, b: 2, c: 4} end + ruby_version_is ""..."3.4" do + it "does not expand nil using ** into {} and raises TypeError" do + h = nil + -> { {a: 1, **h} }.should raise_error(TypeError, "no implicit conversion of nil into Hash") + + -> { {a: 1, **nil} }.should raise_error(TypeError, "no implicit conversion of nil into Hash") + end + end + + ruby_version_is "3.4" do + it "expands nil using ** into {}" do + h = nil + {**h}.should == {} + {a: 1, **h}.should == {a: 1} + + {**nil}.should == {} + {a: 1, **nil}.should == {a: 1} + end + end + it "expands an '**{}' or '**obj' element with the last key/value pair taking precedence" do -> { @h = eval "{a: 1, **{a: 2, b: 3, c: 1}, c: 3}" @@ -259,50 +277,48 @@ describe "The ** operator" do end end - ruby_version_is "3.1" do - describe "hash with omitted value" do - it "accepts short notation 'key' for 'key: value' syntax" do - a, b, c = 1, 2, 3 - h = eval('{a:}') - {a: 1}.should == h - h = eval('{a:, b:, c:}') - {a: 1, b: 2, c: 3}.should == h - end + describe "hash with omitted value" do + it "accepts short notation 'key' for 'key: value' syntax" do + a, b, c = 1, 2, 3 + h = {a:} + {a: 1}.should == h + h = {a:, b:, c:} + {a: 1, b: 2, c: 3}.should == h + end - it "ignores hanging comma on short notation" do - a, b, c = 1, 2, 3 - h = eval('{a:, b:, c:,}') - {a: 1, b: 2, c: 3}.should == h - end + it "ignores hanging comma on short notation" do + a, b, c = 1, 2, 3 + h = {a:, b:, c:,} + {a: 1, b: 2, c: 3}.should == h + end - it "accepts mixed syntax" do - a, e = 1, 5 - h = eval('{a:, b: 2, "c" => 3, :d => 4, e:}') - eval('{a: 1, :b => 2, "c" => 3, "d": 4, e: 5}').should == h - end + it "accepts mixed syntax" do + a, e = 1, 5 + h = {a:, b: 2, "c" => 3, :d => 4, e:} + {a: 1, :b => 2, "c" => 3, "d": 4, e: 5}.should == h + end - it "works with methods and local vars" do - a = Class.new - a.class_eval(<<-RUBY) - def bar - "baz" - end + it "works with methods and local vars" do + a = Class.new + a.class_eval(<<-RUBY) + def bar + "baz" + end - def foo(val) - {bar:, val:} - end - RUBY + def foo(val) + {bar:, val:} + end + RUBY - a.new.foo(1).should == {bar: "baz", val: 1} - end + a.new.foo(1).should == {bar: "baz", val: 1} + end - it "raises a SyntaxError when the hash key ends with `!`" do - -> { eval("{a!:}") }.should raise_error(SyntaxError, /identifier a! is not valid to get/) - end + it "raises a SyntaxError when the hash key ends with `!`" do + -> { eval("{a!:}") }.should raise_error(SyntaxError, /identifier a! is not valid to get/) + end - it "raises a SyntaxError when the hash key ends with `?`" do - -> { eval("{a?:}") }.should raise_error(SyntaxError, /identifier a\? is not valid to get/) - end + it "raises a SyntaxError when the hash key ends with `?`" do + -> { eval("{a?:}") }.should raise_error(SyntaxError, /identifier a\? is not valid to get/) end end end diff --git a/spec/ruby/language/it_parameter_spec.rb b/spec/ruby/language/it_parameter_spec.rb new file mode 100644 index 0000000000..72023180d9 --- /dev/null +++ b/spec/ruby/language/it_parameter_spec.rb @@ -0,0 +1,66 @@ +require_relative '../spec_helper' + +ruby_version_is "3.4" do + describe "The `it` parameter" do + it "provides it in a block" do + -> { it }.call("a").should == "a" + proc { it }.call("a").should == "a" + lambda { it }.call("a").should == "a" + ["a"].map { it }.should == ["a"] + end + + it "assigns nil to not passed parameters" do + proc { it }.call().should == nil + end + + it "can be used in both outer and nested blocks at the same time" do + -> { it + -> { it * it }.call(2) }.call(3).should == 7 + end + + it "is a regular local variable if there is already a 'it' local variable" do + it = 0 + proc { it }.call("a").should == 0 + end + + it "raises SyntaxError when block parameters are specified explicitly" do + -> { eval("-> () { it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + -> { eval("-> (x) { it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + + -> { eval("proc { || it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + -> { eval("proc { |x| it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + + -> { eval("lambda { || it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + -> { eval("lambda { |x| it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + + -> { eval("['a'].map { || it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + -> { eval("['a'].map { |x| it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + end + + it "affects block arity" do + -> {}.arity.should == 0 + -> { it }.arity.should == 1 + end + + it "affects block parameters" do + -> { it }.parameters.should == [[:req]] + + ruby_version_is ""..."4.0" do + proc { it }.parameters.should == [[:opt, nil]] + end + ruby_version_is "4.0" do + proc { it }.parameters.should == [[:opt]] + end + end + + it "does not affect binding local variables" do + -> { it; binding.local_variables }.call("a").should == [] + end + + it "does not work in methods" do + obj = Object.new + def obj.foo; it; end + + -> { obj.foo("a") }.should raise_error(ArgumentError, /wrong number of arguments/) + end + end +end diff --git a/spec/ruby/language/keyword_arguments_spec.rb b/spec/ruby/language/keyword_arguments_spec.rb index 8668799d26..4f6370d419 100644 --- a/spec/ruby/language/keyword_arguments_spec.rb +++ b/spec/ruby/language/keyword_arguments_spec.rb @@ -323,76 +323,36 @@ describe "Keyword arguments" do m({a: 1}).should == [[{a: 1}], {}] end - ruby_version_is "3.1" do - describe "omitted values" do - it "accepts short notation 'key' for 'key: value' syntax" do - def m(a:, b:) - [a, b] - end - - a = 1 - b = 2 - - eval('m(a:, b:).should == [1, 2]') - end - end - end - - ruby_version_is "3.2" do - it "does not work with call(*ruby2_keyword_args) with missing ruby2_keywords in between" do - class << self - def n(*args) # Note the missing ruby2_keywords here - target(*args) - end - - ruby2_keywords def m(*args) - n(*args) - end + describe "omitted values" do + it "accepts short notation 'key' for 'key: value' syntax" do + def m(a:, b:) + [a, b] end - empty = {} - m(**empty).should == [[], {}] - m(empty).should == [[{}], {}] + a = 1 + b = 2 - m(a: 1).should == [[{a: 1}], {}] - m({a: 1}).should == [[{a: 1}], {}] + m(a:, b:).should == [1, 2] end end - ruby_version_is ""..."3.2" do - # https://bugs.ruby-lang.org/issues/18625 - it "works with call(*ruby2_keyword_args) with missing ruby2_keywords in between due to CRuby bug #18625" do - class << self - def n(*args) # Note the missing ruby2_keywords here - target(*args) - end - - ruby2_keywords def m(*args) - n(*args) - end + it "does not work with call(*ruby2_keyword_args) with missing ruby2_keywords in between" do + class << self + def n(*args) # Note the missing ruby2_keywords here + target(*args) end - empty = {} - m(**empty).should == [[], {}] - Hash.ruby2_keywords_hash?(empty).should == false - m(empty).should == [[{}], {}] - Hash.ruby2_keywords_hash?(empty).should == false - - m(a: 1).should == [[], {a: 1}] - m({a: 1}).should == [[{a: 1}], {}] - - kw = {a: 1} + ruby2_keywords def m(*args) + n(*args) + end + end - m(**kw).should == [[], {a: 1}] - m(**kw)[1].should == kw - m(**kw)[1].should_not.equal?(kw) - Hash.ruby2_keywords_hash?(kw).should == false - Hash.ruby2_keywords_hash?(m(**kw)[1]).should == false + empty = {} + m(**empty).should == [[], {}] + m(empty).should == [[{}], {}] - m(kw).should == [[{a: 1}], {}] - m(kw)[0][0].should.equal?(kw) - Hash.ruby2_keywords_hash?(kw).should == false - end + m(a: 1).should == [[{a: 1}], {}] + m({a: 1}).should == [[{a: 1}], {}] end end diff --git a/spec/ruby/language/magic_comment_spec.rb b/spec/ruby/language/magic_comment_spec.rb index f2bf3a08e5..af9c9dbfd0 100644 --- a/spec/ruby/language/magic_comment_spec.rb +++ b/spec/ruby/language/magic_comment_spec.rb @@ -45,7 +45,8 @@ end describe "Magic comments" do describe "in stdin" do - it_behaves_like :magic_comments, :locale, -> file { + default = (platform_is :windows and ruby_version_is "4.0") ? :UTF8 : :locale + it_behaves_like :magic_comments, default, -> file { print_at_exit = fixture(__FILE__, "print_magic_comment_result_at_exit.rb") ruby_exe(nil, args: "< #{fixture(__FILE__, file)}", options: "-r#{print_at_exit}") } diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb index 9abe4cde20..8f72bd45ed 100644 --- a/spec/ruby/language/method_spec.rb +++ b/spec/ruby/language/method_spec.rb @@ -1175,6 +1175,31 @@ describe "A method" do end end +context "when passing **nil into a method that accepts keyword arguments" do + ruby_version_is ""..."3.4" do + it "raises TypeError" do + def m(**kw) kw; end + + h = nil + -> { m(a: 1, **h) }.should raise_error(TypeError, "no implicit conversion of nil into Hash") + -> { m(a: 1, **nil) }.should raise_error(TypeError, "no implicit conversion of nil into Hash") + end + end + + ruby_version_is "3.4" do + it "expands nil using ** into {}" do + def m(**kw) kw; end + + h = nil + m(**h).should == {} + m(a: 1, **h).should == {a: 1} + + m(**nil).should == {} + m(a: 1, **nil).should == {a: 1} + end + end +end + describe "A method call with a space between method name and parentheses" do before(:each) do def m(*args) @@ -1416,53 +1441,209 @@ describe "Keyword arguments are now separated from positional arguments" do end end -ruby_version_is "3.1" do - describe "kwarg with omitted value in a method call" do - context "accepts short notation 'kwarg' in method call" do - evaluate <<-ruby do - def call(*args, **kwargs) = [args, kwargs] - ruby +describe "kwarg with omitted value in a method call" do + context "accepts short notation 'kwarg' in method call" do + evaluate <<-ruby do + def call(*args, **kwargs) = [args, kwargs] + ruby + + a, b, c = 1, 2, 3 + arr, h = call(a:) + h.should == {a: 1} + arr.should == [] + + arr, h = call(a:, b:, c:) + h.should == {a: 1, b: 2, c: 3} + arr.should == [] + + arr, h = call(a:, b: 10, c:) + h.should == {a: 1, b: 10, c: 3} + arr.should == [] + end + end + + context "with methods and local variables" do + evaluate <<-ruby do + def call(*args, **kwargs) = [args, kwargs] + + def bar + "baz" + end + + def foo(val) + call bar:, val: + end + ruby - a, b, c = 1, 2, 3 - arr, h = eval('call a:') - h.should == {a: 1} - arr.should == [] + foo(1).should == [[], {bar: "baz", val: 1}] + end + end +end - arr, h = eval('call(a:, b:, c:)') - h.should == {a: 1, b: 2, c: 3} - arr.should == [] +describe "Inside 'endless' method definitions" do + it "allows method calls without parenthesis" do + def greet(person) = "Hi, ".dup.concat person - arr, h = eval('call(a:, b: 10, c:)') - h.should == {a: 1, b: 10, c: 3} - arr.should == [] + greet("Homer").should == "Hi, Homer" + end +end + +describe "warning about not used block argument" do + ruby_version_is "3.4" do + it "warns when passing a block argument to a method that never uses it" do + def m_that_does_not_use_block + 42 end + + -> { + m_that_does_not_use_block { } + }.should complain( + /#{__FILE__}:#{__LINE__ - 2}: warning: the block passed to 'm_that_does_not_use_block' defined at #{__FILE__}:#{__LINE__ - 7} may be ignored/, + verbose: true) end - context "with methods and local variables" do - evaluate <<-ruby do - def call(*args, **kwargs) = [args, kwargs] + it "does not warn when passing a block argument to a method that declares a block parameter" do + def m_with_block_parameter(&block) + 42 + end - def bar - "baz" - end + -> { m_with_block_parameter { } }.should_not complain(verbose: true) + end - def foo(val) - call bar:, val: - end - ruby + it "does not warn when passing a block argument to a method that declares an anonymous block parameter" do + def m_with_anonymous_block_parameter(&) + 42 + end + + -> { m_with_anonymous_block_parameter { } }.should_not complain(verbose: true) + end - foo(1).should == [[], {bar: "baz", val: 1}] + it "does not warn when passing a block argument to a method that yields an implicit block parameter" do + def m_with_yield + yield 42 end + + -> { m_with_yield { } }.should_not complain(verbose: true) end - end - describe "Inside 'endless' method definitions" do - it "allows method calls without parenthesis" do - eval <<-ruby - def greet(person) = "Hi, ".dup.concat person - ruby + it "warns when passing a block argument to a method that calls #block_given?" do + def m_with_block_given + block_given? + end + + -> { + m_with_block_given { } + }.should complain( + /#{__FILE__}:#{__LINE__ - 2}: warning: the block passed to 'm_with_block_given' defined at #{__FILE__}:#{__LINE__ - 7} may be ignored/, + verbose: true) + end + + it "does not warn when passing a block argument to a method that calls super" do + parent = Class.new do + def m + end + end + + child = Class.new(parent) do + def m + super + end + end + + obj = child.new + -> { obj.m { } }.should_not complain(verbose: true) + end + + it "does not warn when passing a block argument to a method that calls super(...)" do + parent = Class.new do + def m(a) + end + end + + child = Class.new(parent) do + def m(...) + super(...) + end + end + + obj = child.new + -> { obj.m(42) { } }.should_not complain(verbose: true) + end + + it "does not warn when called #initialize()" do + klass = Class.new do + def initialize + end + end + + -> { klass.new {} }.should_not complain(verbose: true) + end + + it "does not warn when passing a block argument to a method that calls super()" do + parent = Class.new do + def m + end + end - greet("Homer").should == "Hi, Homer" + child = Class.new(parent) do + def m + super() + end + end + + obj = child.new + -> { obj.m { } }.should_not complain(verbose: true) + end + + it "warns only once per call site" do + def m_that_does_not_use_block + 42 + end + + def call_m_that_does_not_use_block + m_that_does_not_use_block {} + end + + -> { + m_that_does_not_use_block { } + }.should complain(/the block passed to 'm_that_does_not_use_block' defined at .+ may be ignored/, verbose: true) + + -> { + m_that_does_not_use_block { } + }.should_not complain(verbose: true) + end + + it "can be disabled with :strict_unused_block warning category" do + def m_that_does_not_use_block + 42 + end + + # ensure that warning is emitted + -> { m_that_does_not_use_block { } }.should complain(verbose: true) + + warn_strict_unused_block = Warning[:strict_unused_block] + Warning[:strict_unused_block] = false + begin + -> { m_that_does_not_use_block { } }.should_not complain(verbose: true) + ensure + Warning[:strict_unused_block] = warn_strict_unused_block + end + end + + it "can be enabled with :strict_unused_block = true warning category in not verbose mode" do + def m_that_does_not_use_block + 42 + end + + warn_strict_unused_block = Warning[:strict_unused_block] + Warning[:strict_unused_block] = true + begin + -> { + m_that_does_not_use_block { } + }.should complain(/the block passed to 'm_that_does_not_use_block' defined at .+ may be ignored/) + ensure + Warning[:strict_unused_block] = warn_strict_unused_block + end end end end diff --git a/spec/ruby/language/module_spec.rb b/spec/ruby/language/module_spec.rb index fffcf9c90d..fba4aa8c6e 100644 --- a/spec/ruby/language/module_spec.rb +++ b/spec/ruby/language/module_spec.rb @@ -26,20 +26,39 @@ describe "The module keyword" do it "reopens an existing module" do module ModuleSpecs; Reopened = true; end ModuleSpecs::Reopened.should be_true - end - - ruby_version_is '3.2' do - it "does not reopen a module included in Object" do - module IncludedModuleSpecs; Reopened = true; end - ModuleSpecs::IncludedInObject::IncludedModuleSpecs.should_not == Object::IncludedModuleSpecs - end - end - - ruby_version_is ''...'3.2' do - it "reopens a module included in Object" do - module IncludedModuleSpecs; Reopened = true; end - ModuleSpecs::IncludedInObject::IncludedModuleSpecs::Reopened.should be_true - end + ensure + ModuleSpecs.send(:remove_const, :Reopened) + end + + it "does not reopen a module included in Object" do + ruby_exe(<<~RUBY).should == "false" + module IncludedInObject + module IncludedModule; end + end + class Object + include IncludedInObject + end + module IncludedModule; end + print IncludedInObject::IncludedModule == Object::IncludedModule + RUBY + end + + it "does not reopen a module included in non-Object modules" do + ruby_exe(<<~RUBY).should == "false/false" + module Included + module IncludedModule; end + end + module M + include Included + module IncludedModule; end + end + class C + include Included + module IncludedModule; end + end + print Included::IncludedModule == M::IncludedModule, "/", + Included::IncludedModule == C::IncludedModule + RUBY end it "raises a TypeError if the constant is a Class" do @@ -76,6 +95,8 @@ describe "Assigning an anonymous module to a constant" do ::ModuleSpecs_CS1 = mod mod.name.should == "ModuleSpecs_CS1" + ensure + Object.send(:remove_const, :ModuleSpecs_CS1) end it "sets the name of a module scoped by an anonymous module" do @@ -96,5 +117,7 @@ describe "Assigning an anonymous module to a constant" do b.name.should == "ModuleSpecs_CS2::B" c.name.should == "ModuleSpecs_CS2::B::C" d.name.should == "ModuleSpecs_CS2::D" + ensure + Object.send(:remove_const, :ModuleSpecs_CS2) end end diff --git a/spec/ruby/language/numbered_parameters_spec.rb b/spec/ruby/language/numbered_parameters_spec.rb index 06f9948c58..de532c326d 100644 --- a/spec/ruby/language/numbered_parameters_spec.rb +++ b/spec/ruby/language/numbered_parameters_spec.rb @@ -90,9 +90,18 @@ describe "Numbered parameters" do proc { _2 }.parameters.should == [[:opt, :_1], [:opt, :_2]] end - it "affects binding local variables" do - -> { _1; binding.local_variables }.call("a").should == [:_1] - -> { _2; binding.local_variables }.call("a", "b").should == [:_1, :_2] + ruby_version_is ""..."4.0" do + it "affects binding local variables" do + -> { _1; binding.local_variables }.call("a").should == [:_1] + -> { _2; binding.local_variables }.call("a", "b").should == [:_1, :_2] + end + end + + ruby_version_is "4.0" do + it "does not affect binding local variables" do + -> { _1; binding.local_variables }.call("a").should == [] + -> { _2; binding.local_variables }.call("a", "b").should == [] + end end it "does not work in methods" do diff --git a/spec/ruby/language/optional_assignments_spec.rb b/spec/ruby/language/optional_assignments_spec.rb index 2443cc6b79..5fe3e3671b 100644 --- a/spec/ruby/language/optional_assignments_spec.rb +++ b/spec/ruby/language/optional_assignments_spec.rb @@ -698,6 +698,8 @@ describe 'Optional constant assignment' do x.should == 1 y.should == 0 ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT3.should == nil + ensure + ConstantSpecs::ClassA.send(:remove_const, :NIL_OR_ASSIGNED_CONSTANT3) end end diff --git a/spec/ruby/language/pattern_matching/3.1.rb b/spec/ruby/language/pattern_matching/3.1.rb deleted file mode 100644 index 7a09084e41..0000000000 --- a/spec/ruby/language/pattern_matching/3.1.rb +++ /dev/null @@ -1,75 +0,0 @@ -describe "Pattern matching" do - before :each do - ScratchPad.record [] - end - - describe "Ruby 3.1 improvements" do - ruby_version_is "3.1" do - it "can omit parentheses in one line pattern matching" do - [1, 2] => a, b - [a, b].should == [1, 2] - - {a: 1} => a: - a.should == 1 - end - - it "supports pinning instance variables" do - @a = /a/ - case 'abc' - in ^@a - true - end.should == true - end - - it "supports pinning class variables" do - result = nil - Module.new do - result = module_eval(<<~RUBY) - @@a = 0..10 - - case 2 - in ^@@a - true - end - RUBY - end - - result.should == true - end - - it "supports pinning global variables" do - $a = /a/ - case 'abc' - in ^$a - true - end.should == true - end - - it "supports pinning expressions" do - case 'abc' - in ^(/a/) - true - end.should == true - - case 0 - in ^(0 + 0) - true - end.should == true - end - - it "supports pinning expressions in array pattern" do - case [3] - in [^(1 + 2)] - true - end.should == true - end - - it "supports pinning expressions in hash pattern" do - case {name: '2.6', released_at: Time.new(2018, 12, 25)} - in {released_at: ^(Time.new(2010)..Time.new(2020))} - true - end.should == true - end - end - end -end diff --git a/spec/ruby/language/pattern_matching_spec.rb b/spec/ruby/language/pattern_matching_spec.rb index 94432b1fa0..c1a6f0e4d6 100644 --- a/spec/ruby/language/pattern_matching_spec.rb +++ b/spec/ruby/language/pattern_matching_spec.rb @@ -164,16 +164,8 @@ describe "Pattern matching" do @src = '[0, 1] => [a, b]' end - ruby_version_is ""..."3.1" do - it "warns about pattern matching is experimental feature" do - -> { eval @src }.should complain(/pattern matching is experimental, and the behavior may change in future versions of Ruby!/i) - end - end - - ruby_version_is "3.1" do - it "does not warn about pattern matching is experimental feature" do - -> { eval @src }.should_not complain - end + it "does not warn about pattern matching is experimental feature" do + -> { eval @src }.should_not complain end end end @@ -501,7 +493,7 @@ describe "Pattern matching" do in [0, 0] | [0, a] end RUBY - }.should raise_error(SyntaxError, /illegal variable in alternative pattern/) + }.should raise_error(SyntaxError) end it "support underscore prefixed variables in alternation" do @@ -1220,8 +1212,99 @@ describe "Pattern matching" do result.should == true end end -end -ruby_version_is "3.1" do - require_relative 'pattern_matching/3.1' + describe "Ruby 3.1 improvements" do + it "can omit parentheses in one line pattern matching" do + [1, 2] => a, b + [a, b].should == [1, 2] + + {a: 1} => a: + a.should == 1 + end + + it "supports pinning instance variables" do + @a = /a/ + case 'abc' + in ^@a + true + end.should == true + end + + it "supports pinning class variables" do + result = nil + Module.new do + # avoid "class variable access from toplevel" runtime error with #module_eval + result = module_eval(<<~RUBY) + @@a = 0..10 + + case 2 + in ^@@a + true + end + RUBY + end + + result.should == true + end + + it "supports pinning global variables" do + $a = /a/ + case 'abc' + in ^$a + true + end.should == true + end + + it "supports pinning expressions" do + case 'abc' + in ^(/a/) + true + end.should == true + + case 0 + in ^(0 + 0) + true + end.should == true + end + + it "supports pinning expressions in array pattern" do + case [3] + in [^(1 + 2)] + true + end.should == true + end + + it "supports pinning expressions in hash pattern" do + case {name: '2.6', released_at: Time.new(2018, 12, 25)} + in {released_at: ^(Time.new(2010)..Time.new(2020))} + true + end.should == true + end + end + + describe "value in pattern" do + it "returns true if the pattern matches" do + (1 in 1).should == true + + (1 in Integer).should == true + + e = nil + ([1, 2] in [1, e]).should == true + e.should == 2 + + k = nil + ({k: 1} in {k:}).should == true + k.should == 1 + end + + it "returns false if the pattern does not match" do + (1 in 2).should == false + + (1 in Float).should == false + + ([1, 2] in [2, e]).should == false + + ({k: 1} in {k: 2}).should == false + end + end end diff --git a/spec/ruby/language/predefined_spec.rb b/spec/ruby/language/predefined_spec.rb index cc231e341e..fc1667a38f 100644 --- a/spec/ruby/language/predefined_spec.rb +++ b/spec/ruby/language/predefined_spec.rb @@ -1,4 +1,5 @@ require_relative '../spec_helper' +require_relative '../core/exception/shared/set_backtrace' require 'stringio' # The following tables are excerpted from Programming Ruby: The Pragmatic Programmer's Guide' @@ -71,7 +72,7 @@ describe "Predefined global $~" do match2.should_not == nil $~.should == match2 - eval 'match3 = /baz/.match("baz")' + match3 = /baz/.match("baz") match3.should_not == nil $~.should == match3 @@ -88,8 +89,8 @@ describe "Predefined global $~" do $~ = /foo/.match("foo") $~.should be_an_instance_of(MatchData) - -> { $~ = Object.new }.should raise_error(TypeError) - -> { $~ = 1 }.should raise_error(TypeError) + -> { $~ = Object.new }.should raise_error(TypeError, 'wrong argument type Object (expected MatchData)') + -> { $~ = 1 }.should raise_error(TypeError, 'wrong argument type Integer (expected MatchData)') end it "changes the value of derived capture globals when assigned" do @@ -136,6 +137,19 @@ describe "Predefined global $&" do "abc".dup.force_encoding(Encoding::EUC_JP) =~ /b/ $&.encoding.should equal(Encoding::EUC_JP) end + + it "is read-only" do + -> { + eval %q{$& = ""} + }.should raise_error(SyntaxError, /Can't set variable \$&/) + end + + it "is read-only when aliased" do + alias $predefined_spec_ampersand $& + -> { + $predefined_spec_ampersand = "" + }.should raise_error(NameError, '$predefined_spec_ampersand is a read-only variable') + end end describe "Predefined global $`" do @@ -154,6 +168,19 @@ describe "Predefined global $`" do "abc".dup.force_encoding(Encoding::ISO_8859_1) =~ /a/ $`.encoding.should equal(Encoding::ISO_8859_1) end + + it "is read-only" do + -> { + eval %q{$` = ""} + }.should raise_error(SyntaxError, /Can't set variable \$`/) + end + + it "is read-only when aliased" do + alias $predefined_spec_backquote $` + -> { + $predefined_spec_backquote = "" + }.should raise_error(NameError, '$predefined_spec_backquote is a read-only variable') + end end describe "Predefined global $'" do @@ -172,6 +199,19 @@ describe "Predefined global $'" do "abc".dup.force_encoding(Encoding::ISO_8859_1) =~ /c/ $'.encoding.should equal(Encoding::ISO_8859_1) end + + it "is read-only" do + -> { + eval %q{$' = ""} + }.should raise_error(SyntaxError, /Can't set variable \$'/) + end + + it "is read-only when aliased" do + alias $predefined_spec_single_quote $' + -> { + $predefined_spec_single_quote = "" + }.should raise_error(NameError, '$predefined_spec_single_quote is a read-only variable') + end end describe "Predefined global $+" do @@ -190,6 +230,19 @@ describe "Predefined global $+" do "abc".dup.force_encoding(Encoding::EUC_JP) =~ /(b)/ $+.encoding.should equal(Encoding::EUC_JP) end + + it "is read-only" do + -> { + eval %q{$+ = ""} + }.should raise_error(SyntaxError, /Can't set variable \$\+/) + end + + it "is read-only when aliased" do + alias $predefined_spec_plus $+ + -> { + $predefined_spec_plus = "" + }.should raise_error(NameError, '$predefined_spec_plus is a read-only variable') + end end describe "Predefined globals $1..N" do @@ -229,7 +282,7 @@ describe "Predefined global $stdout" do end it "raises TypeError error if assigned to nil" do - -> { $stdout = nil }.should raise_error(TypeError) + -> { $stdout = nil }.should raise_error(TypeError, '$stdout must have write method, NilClass given') end it "raises TypeError error if assigned to object that doesn't respond to #write" do @@ -253,6 +306,12 @@ describe "Predefined global $!" do $!.should == nil end + it "is read-only" do + -> { + $! = [] + }.should raise_error(NameError, '$! is a read-only variable') + end + # See http://jira.codehaus.org/browse/JRUBY-5550 it "remains nil after a failed core class \"checked\" coercion against a class that defines method_missing" do $!.should == nil @@ -512,6 +571,75 @@ describe "Predefined global $!" do end end +describe "Predefined global $@" do + it "is Fiber-local" do + Fiber.new do + raise "hi" + rescue + Fiber.yield + end.resume + + $@.should == nil + end + + it "is set to a backtrace of a rescued exception" do + begin + raise + rescue + $@.should be_an_instance_of(Array) + $@.should == $!.backtrace + end + end + + it "is cleared when an exception is rescued" do + begin + raise + rescue + end + + $@.should == nil + end + + it "is not set when there is no current exception" do + $@.should == nil + end + + it "is set to a backtrace of a rescued exception" do + begin + raise + rescue + $@.should be_an_instance_of(Array) + $@.should == $!.backtrace + end + end + + it "is not read-only" do + begin + raise + rescue + $@ = [] + $@.should == [] + end + end + + it_behaves_like :exception_set_backtrace, -> backtrace { + exception = nil + begin + raise + rescue + $@ = backtrace + exception = $! + end + exception + } + + it "cannot be assigned when there is no a rescued exception" do + -> { + $@ = [] + }.should raise_error(ArgumentError, '$! not set') + end +end + # Input/Output Variables # --------------------------------------------------------------------------------------------------- # @@ -559,12 +687,39 @@ describe "Predefined global $/" do $VERBOSE = @verbose end - it "can be assigned a String" do - str = "abc" - $/ = str - $/.should equal(str) + ruby_version_is ""..."4.0" do + it "can be assigned a String" do + str = +"abc" + $/ = str + $/.should equal(str) + end end + ruby_version_is "4.0" do + it "makes a new frozen String from the assigned String" do + string_subclass = Class.new(String) + str = string_subclass.new("abc") + str.instance_variable_set(:@ivar, 1) + $/ = str + $/.should.frozen? + $/.should be_an_instance_of(String) + $/.should_not.instance_variable_defined?(:@ivar) + $/.should == str + end + + it "makes a new frozen String if it's not frozen" do + str = +"abc" + $/ = str + $/.should.frozen? + $/.should == str + end + + it "assigns the given String if it's frozen and has no instance variables" do + str = "abc".freeze + $/ = str + $/.should equal(str) + end + end it "can be assigned nil" do $/ = nil $/.should be_nil @@ -583,15 +738,19 @@ describe "Predefined global $/" do obj = mock("$/ value") obj.should_not_receive(:to_str) - -> { $/ = obj }.should raise_error(TypeError) + -> { $/ = obj }.should raise_error(TypeError, 'value of $/ must be String') end it "raises a TypeError if assigned an Integer" do - -> { $/ = 1 }.should raise_error(TypeError) + -> { $/ = 1 }.should raise_error(TypeError, 'value of $/ must be String') end it "raises a TypeError if assigned a boolean" do - -> { $/ = true }.should raise_error(TypeError) + -> { $/ = true }.should raise_error(TypeError, 'value of $/ must be String') + end + + it "warns if assigned non-nil" do + -> { $/ = "_" }.should complain(/warning: (?:non-nil )?[`']\$\/' is deprecated/) end end @@ -608,10 +767,38 @@ describe "Predefined global $-0" do $VERBOSE = @verbose end - it "can be assigned a String" do - str = "abc" - $-0 = str - $-0.should equal(str) + ruby_version_is ""..."4.0" do + it "can be assigned a String" do + str = +"abc" + $-0 = str + $-0.should equal(str) + end + end + + ruby_version_is "4.0" do + it "makes a new frozen String from the assigned String" do + string_subclass = Class.new(String) + str = string_subclass.new("abc") + str.instance_variable_set(:@ivar, 1) + $-0 = str + $-0.should.frozen? + $-0.should be_an_instance_of(String) + $-0.should_not.instance_variable_defined?(:@ivar) + $-0.should == str + end + + it "makes a new frozen String if it's not frozen" do + str = +"abc" + $-0 = str + $-0.should.frozen? + $-0.should == str + end + + it "assigns the given String if it's frozen and has no instance variables" do + str = "abc".freeze + $-0 = str + $-0.should equal(str) + end end it "can be assigned nil" do @@ -632,15 +819,19 @@ describe "Predefined global $-0" do obj = mock("$-0 value") obj.should_not_receive(:to_str) - -> { $-0 = obj }.should raise_error(TypeError) + -> { $-0 = obj }.should raise_error(TypeError, 'value of $-0 must be String') end it "raises a TypeError if assigned an Integer" do - -> { $-0 = 1 }.should raise_error(TypeError) + -> { $-0 = 1 }.should raise_error(TypeError, 'value of $-0 must be String') end it "raises a TypeError if assigned a boolean" do - -> { $-0 = true }.should raise_error(TypeError) + -> { $-0 = true }.should raise_error(TypeError, 'value of $-0 must be String') + end + + it "warns if assigned non-nil" do + -> { $-0 = "_" }.should complain(/warning: (?:non-nil )?[`']\$-0' is deprecated/) end end @@ -674,12 +865,16 @@ describe "Predefined global $\\" do obj = mock("$\\ value") obj.should_not_receive(:to_str) - -> { $\ = obj }.should raise_error(TypeError) + -> { $\ = obj }.should raise_error(TypeError, 'value of $\ must be String') end it "raises a TypeError if assigned not String" do - -> { $\ = 1 }.should raise_error(TypeError) - -> { $\ = true }.should raise_error(TypeError) + -> { $\ = 1 }.should raise_error(TypeError, 'value of $\ must be String') + -> { $\ = true }.should raise_error(TypeError, 'value of $\ must be String') + end + + it "warns if assigned non-nil" do + -> { $\ = "_" }.should complain(/warning: (?:non-nil )?[`']\$\\' is deprecated/) end end @@ -693,11 +888,11 @@ describe "Predefined global $," do end it "raises TypeError if assigned a non-String" do - -> { $, = Object.new }.should raise_error(TypeError) + -> { $, = Object.new }.should raise_error(TypeError, 'value of $, must be String') end it "warns if assigned non-nil" do - -> { $, = "_" }.should complain(/warning: [`']\$,' is deprecated/) + -> { $, = "_" }.should complain(/warning: (?:non-nil )?[`']\$,' is deprecated/) end end @@ -734,7 +929,7 @@ describe "Predefined global $;" do end it "warns if assigned non-nil" do - -> { $; = "_" }.should complain(/warning: [`']\$;' is deprecated/) + -> { $; = "_" }.should complain(/warning: (?:non-nil )?[`']\$;' is deprecated/) end end @@ -768,7 +963,7 @@ describe "Predefined global $_" do match.should == "bar\n" $_.should == match - eval 'match = stdin.gets' + match = stdin.gets match.should == "baz\n" $_.should == match @@ -878,22 +1073,28 @@ describe "Execution variable $:" do it "is read-only" do -> { $: = [] - }.should raise_error(NameError) + }.should raise_error(NameError, '$: is a read-only variable') -> { $LOAD_PATH = [] - }.should raise_error(NameError) + }.should raise_error(NameError, '$LOAD_PATH is a read-only variable') -> { $-I = [] - }.should raise_error(NameError) + }.should raise_error(NameError, '$-I is a read-only variable') end it "default $LOAD_PATH entries until sitelibdir included have @gem_prelude_index set" do skip "no sense in ruby itself" if MSpecScript.instance_variable_defined?(:@testing_ruby) - $:.should.include?(RbConfig::CONFIG['sitelibdir']) - idx = $:.index(RbConfig::CONFIG['sitelibdir']) + if platform_is :windows + # See https://github.com/ruby/setup-ruby/pull/762#issuecomment-2917460440 + $:.should.find { |e| File.realdirpath(e) == RbConfig::CONFIG['sitelibdir'] } + idx = $:.index { |e| File.realdirpath(e) == RbConfig::CONFIG['sitelibdir'] } + else + $:.should.include?(RbConfig::CONFIG['sitelibdir']) + idx = $:.index(RbConfig::CONFIG['sitelibdir']) + end $:[idx..-1].all? { |p| p.instance_variable_defined?(:@gem_prelude_index) }.should be_true $:[0...idx].all? { |p| !p.instance_variable_defined?(:@gem_prelude_index) }.should be_true @@ -908,11 +1109,11 @@ describe "Global variable $\"" do it "is read-only" do -> { $" = [] - }.should raise_error(NameError) + }.should raise_error(NameError, '$" is a read-only variable') -> { $LOADED_FEATURES = [] - }.should raise_error(NameError) + }.should raise_error(NameError, '$LOADED_FEATURES is a read-only variable') end end @@ -920,7 +1121,7 @@ describe "Global variable $<" do it "is read-only" do -> { $< = nil - }.should raise_error(NameError) + }.should raise_error(NameError, '$< is a read-only variable') end end @@ -928,7 +1129,7 @@ describe "Global variable $FILENAME" do it "is read-only" do -> { $FILENAME = "-" - }.should raise_error(NameError) + }.should raise_error(NameError, '$FILENAME is a read-only variable') end end @@ -936,7 +1137,7 @@ describe "Global variable $?" do it "is read-only" do -> { $? = nil - }.should raise_error(NameError) + }.should raise_error(NameError, '$? is a read-only variable') end it "is thread-local" do @@ -947,19 +1148,19 @@ end describe "Global variable $-a" do it "is read-only" do - -> { $-a = true }.should raise_error(NameError) + -> { $-a = true }.should raise_error(NameError, '$-a is a read-only variable') end end describe "Global variable $-l" do it "is read-only" do - -> { $-l = true }.should raise_error(NameError) + -> { $-l = true }.should raise_error(NameError, '$-l is a read-only variable') end end describe "Global variable $-p" do it "is read-only" do - -> { $-p = true }.should raise_error(NameError) + -> { $-p = true }.should raise_error(NameError, '$-p is a read-only variable') end end @@ -1110,7 +1311,7 @@ describe "The predefined standard object nil" do end it "raises a SyntaxError if assigned to" do - -> { eval("nil = true") }.should raise_error(SyntaxError) + -> { eval("nil = true") }.should raise_error(SyntaxError, /Can't assign to nil/) end end @@ -1120,7 +1321,7 @@ describe "The predefined standard object true" do end it "raises a SyntaxError if assigned to" do - -> { eval("true = false") }.should raise_error(SyntaxError) + -> { eval("true = false") }.should raise_error(SyntaxError, /Can't assign to true/) end end @@ -1130,13 +1331,13 @@ describe "The predefined standard object false" do end it "raises a SyntaxError if assigned to" do - -> { eval("false = nil") }.should raise_error(SyntaxError) + -> { eval("false = nil") }.should raise_error(SyntaxError, /Can't assign to false/) end end describe "The self pseudo-variable" do it "raises a SyntaxError if assigned to" do - -> { eval("self = 1") }.should raise_error(SyntaxError) + -> { eval("self = 1") }.should raise_error(SyntaxError, /Can't change the value of self/) end end @@ -1327,9 +1528,9 @@ end describe "$LOAD_PATH.resolve_feature_path" do it "returns what will be loaded without actual loading, .rb file" do - extension, path = $LOAD_PATH.resolve_feature_path('set') + extension, path = $LOAD_PATH.resolve_feature_path('pp') extension.should == :rb - path.should.end_with?('/set.rb') + path.should.end_with?('/pp.rb') end it "returns what will be loaded without actual loading, .so file" do @@ -1341,16 +1542,8 @@ describe "$LOAD_PATH.resolve_feature_path" do path.should.end_with?("/etc.#{RbConfig::CONFIG['DLEXT']}") end - ruby_version_is ""..."3.1" do - it "raises LoadError if feature cannot be found" do - -> { $LOAD_PATH.resolve_feature_path('noop') }.should raise_error(LoadError) - end - end - - ruby_version_is "3.1" do - it "return nil if feature cannot be found" do - $LOAD_PATH.resolve_feature_path('noop').should be_nil - end + it "return nil if feature cannot be found" do + $LOAD_PATH.resolve_feature_path('noop').should be_nil end end diff --git a/spec/ruby/language/private_spec.rb b/spec/ruby/language/private_spec.rb index ddf185e6d2..b04aa25c9e 100644 --- a/spec/ruby/language/private_spec.rb +++ b/spec/ruby/language/private_spec.rb @@ -34,7 +34,7 @@ describe "The private keyword" do it "changes visibility of previously called method" do klass = Class.new do def foo - "foo" + "foo" end end f = klass.new diff --git a/spec/ruby/language/proc_spec.rb b/spec/ruby/language/proc_spec.rb index cc69b7799c..ca9a13aa61 100644 --- a/spec/ruby/language/proc_spec.rb +++ b/spec/ruby/language/proc_spec.rb @@ -104,7 +104,7 @@ describe "A Proc" do end it "assigns all passed values after the first to the rest argument" do - @l.call(1, 2, 3).should == [1, [2, 3]] + @l.call(1, 2, 3).should == [1, [2, 3]] end it "does not call #to_ary to convert a single passed object to an Array" do diff --git a/spec/ruby/language/regexp/anchors_spec.rb b/spec/ruby/language/regexp/anchors_spec.rb index 0129e255da..cdc06c0b4d 100644 --- a/spec/ruby/language/regexp/anchors_spec.rb +++ b/spec/ruby/language/regexp/anchors_spec.rb @@ -124,10 +124,10 @@ describe "Regexps with anchors" do /foo\b/.match("foo").to_a.should == ["foo"] /foo\b/.match("foo\n").to_a.should == ["foo"] LanguageSpecs.white_spaces.scan(/./).each do |c| - /foo\b/.match("foo" + c).to_a.should == ["foo"] + /foo\b/.match("foo" + c).to_a.should == ["foo"] end LanguageSpecs.non_alphanum_non_space.scan(/./).each do |c| - /foo\b/.match("foo" + c).to_a.should == ["foo"] + /foo\b/.match("foo" + c).to_a.should == ["foo"] end /foo\b/.match("foo\0").to_a.should == ["foo"] # Basic non-matching @@ -145,10 +145,10 @@ describe "Regexps with anchors" do /foo\B/.match("foo").should be_nil /foo\B/.match("foo\n").should be_nil LanguageSpecs.white_spaces.scan(/./).each do |c| - /foo\B/.match("foo" + c).should be_nil + /foo\B/.match("foo" + c).should be_nil end LanguageSpecs.non_alphanum_non_space.scan(/./).each do |c| - /foo\B/.match("foo" + c).should be_nil + /foo\B/.match("foo" + c).should be_nil end /foo\B/.match("foo\0").should be_nil end diff --git a/spec/ruby/language/regexp/character_classes_spec.rb b/spec/ruby/language/regexp/character_classes_spec.rb index 98d431a817..018757db41 100644 --- a/spec/ruby/language/regexp/character_classes_spec.rb +++ b/spec/ruby/language/regexp/character_classes_spec.rb @@ -113,7 +113,7 @@ describe "Regexp with character classes" do end it "doesn't matches Unicode marks with [[:alnum:]]" do - "\u{36F}".match(/[[:alnum:]]/).should be_nil + "\u{3099}".match(/[[:alnum:]]/).should be_nil end it "doesn't match Unicode control characters with [[:alnum:]]" do @@ -133,7 +133,7 @@ describe "Regexp with character classes" do end it "doesn't matches Unicode marks with [[:alpha:]]" do - "\u{36F}".match(/[[:alpha:]]/).should be_nil + "\u{3099}".match(/[[:alpha:]]/).should be_nil end it "doesn't match Unicode control characters with [[:alpha:]]" do @@ -226,7 +226,7 @@ describe "Regexp with character classes" do end it "matches Unicode letter characters with [[:graph:]]" do - "à".match(/[[:graph:]]/).to_a.should == ["à"] + "à".match(/[[:graph:]]/).to_a.should == ["à"] end it "matches Unicode digits with [[:graph:]]" do @@ -562,6 +562,13 @@ describe "Regexp with character classes" do "\u{16EE}".match(/[[:word:]]/).to_a.should == ["\u{16EE}"] end + ruby_bug "#19417", ""..."3.4.6" do + it "matches Unicode join control characters with [[:word:]]" do + "\u{200C}".match(/[[:word:]]/).to_a.should == ["\u{200C}"] + "\u{200D}".match(/[[:word:]]/).to_a.should == ["\u{200D}"] + end + end + it "doesn't match Unicode No characters with [[:word:]]" do "\u{17F0}".match(/[[:word:]]/).should be_nil end diff --git a/spec/ruby/language/regexp/encoding_spec.rb b/spec/ruby/language/regexp/encoding_spec.rb index 0571b2d3cf..ceb9cf823a 100644 --- a/spec/ruby/language/regexp/encoding_spec.rb +++ b/spec/ruby/language/regexp/encoding_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative '../fixtures/classes' @@ -39,7 +39,11 @@ describe "Regexps with encoding modifiers" do end it "warns when using /n with a match string with non-ASCII characters and an encoding other than ASCII-8BIT" do - -> { /./n.match("\303\251".dup.force_encoding('utf-8')) }.should complain(%r{historical binary regexp match /.../n against UTF-8 string}) + -> { + eval <<~RUBY + /./n.match("\303\251".dup.force_encoding('utf-8')) + RUBY + }.should complain(%r{historical binary regexp match /.../n against UTF-8 string}) end it 'uses US-ASCII as /n encoding if all chars are 7-bit' do diff --git a/spec/ruby/language/regexp/escapes_spec.rb b/spec/ruby/language/regexp/escapes_spec.rb index 16a4d8c23b..541998b937 100644 --- a/spec/ruby/language/regexp/escapes_spec.rb +++ b/spec/ruby/language/regexp/escapes_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative '../fixtures/classes' diff --git a/spec/ruby/language/regexp/grouping_spec.rb b/spec/ruby/language/regexp/grouping_spec.rb index 2f04a04018..313858f714 100644 --- a/spec/ruby/language/regexp/grouping_spec.rb +++ b/spec/ruby/language/regexp/grouping_spec.rb @@ -12,7 +12,7 @@ describe "Regexps with grouping" do end it "raises a SyntaxError when parentheses aren't balanced" do - -> { eval "/(hay(st)ack/" }.should raise_error(SyntaxError) + -> { eval "/(hay(st)ack/" }.should raise_error(SyntaxError) end it "supports (?: ) (non-capturing group)" do diff --git a/spec/ruby/language/regexp_spec.rb b/spec/ruby/language/regexp_spec.rb index 0cd9584549..ce344b5b05 100644 --- a/spec/ruby/language/regexp_spec.rb +++ b/spec/ruby/language/regexp_spec.rb @@ -112,7 +112,7 @@ describe "Literal Regexps" do /foo.(?<=\d)/.match("fooA foo1").to_a.should == ["foo1"] end - ruby_bug "#13671", ""..."3.6" do # https://bugs.ruby-lang.org/issues/13671 + ruby_bug "#13671", ""..."4.0" do # https://bugs.ruby-lang.org/issues/13671 it "handles a lookbehind with ss characters" do r = Regexp.new("(?<!dss)", Regexp::IGNORECASE) r.should =~ "✨" diff --git a/spec/ruby/language/rescue_spec.rb b/spec/ruby/language/rescue_spec.rb index 4dc25a5b45..6be3bfd023 100644 --- a/spec/ruby/language/rescue_spec.rb +++ b/spec/ruby/language/rescue_spec.rb @@ -136,10 +136,14 @@ describe "The rescue keyword" do it 'captures successfully at the top-level' do ScratchPad.record [] + loaded_features = $".dup + begin + require_relative 'fixtures/rescue/top_level' - require_relative 'fixtures/rescue/top_level' - - ScratchPad.recorded.should == ["message"] + ScratchPad.recorded.should == ["message"] + ensure + $".replace loaded_features + end end end @@ -573,10 +577,8 @@ describe "The rescue keyword" do end end line = __LINE__ - foo.should == [ - "#{__FILE__}:#{line-3}:in 'foo'", - "#{__FILE__}:#{line+1}:in 'block (3 levels) in <top (required)>'" - ] + foo[0].should =~ /#{__FILE__}:#{line-3}:in 'foo'/ + foo[1].should =~ /#{__FILE__}:#{line+2}:in 'block/ end end diff --git a/spec/ruby/language/reserved_keywords.rb b/spec/ruby/language/reserved_keywords.rb new file mode 100644 index 0000000000..6c40e34ccc --- /dev/null +++ b/spec/ruby/language/reserved_keywords.rb @@ -0,0 +1,149 @@ +require_relative '../spec_helper' + +describe "Ruby's reserved keywords" do + # Copied from https://github.com/ruby/ruby/blob/master/defs/keywords + keywords = %w[ + alias + and + begin + BEGIN + break + case + class + def + defined? + do + else + elsif + end + END + ensure + false + for + if + in + module + next + nil + not + or + redo + rescue + retry + return + self + super + then + true + undef + unless + until + when + while + yield + __ENCODING__ + __FILE__ + __LINE__ + ] + + keywords.each do |name| + describe "keyword '#{name}'" do + it "can't be used as local variable name" do + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) + #{name} = :local_variable + RUBY + end + + if name == "defined?" + it "can't be used as an instance variable name" do + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) + @#{name} = :instance_variable + RUBY + end + + it "can't be used as a class variable name" do + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) + class C + @@#{name} = :class_variable + end + RUBY + end + + it "can't be used as a global variable name" do + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) + $#{name} = :global_variable + RUBY + end + else + it "can be used as an instance variable name" do + result = eval <<~RUBY + @#{name} = :instance_variable + @#{name} + RUBY + + result.should == :instance_variable + end + + it "can be used as a class variable name" do + result = eval <<~RUBY + class C + @@#{name} = :class_variable + @@#{name} + end + RUBY + + result.should == :class_variable + end + + it "can be used as a global variable name" do + result = eval <<~RUBY + $#{name} = :global_variable + $#{name} + RUBY + + result.should == :global_variable + end + end + + it "can't be used as a positional parameter name" do + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) + def x(#{name}); end + RUBY + end + + invalid_kw_param_names = ["BEGIN","END","defined?"] + + if invalid_kw_param_names.include?(name) + it "can't be used a keyword parameter name" do + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) + def m(#{name}:); end + RUBY + end + else + it "can be used a keyword parameter name" do + result = instance_eval <<~RUBY + def m(#{name}:) + binding.local_variable_get(:#{name}) + end + + m(#{name}: :argument) + RUBY + + result.should == :argument + end + end + + it "can be used as a method name" do + result = instance_eval <<~RUBY + def #{name} + :method_return_value + end + + send(:#{name}) + RUBY + + result.should == :method_return_value + end + end + end +end diff --git a/spec/ruby/language/send_spec.rb b/spec/ruby/language/send_spec.rb index aaccdf0998..5d6340ffc5 100644 --- a/spec/ruby/language/send_spec.rb +++ b/spec/ruby/language/send_spec.rb @@ -106,6 +106,24 @@ describe "Invoking a method" do specs.yield_now(&o).should == :from_to_proc end + ruby_version_is "4.0" do + it "raises TypeError if 'to_proc' doesn't return a Proc" do + o = LangSendSpecs::RawToProc.new(42) + + -> { + specs.makeproc(&o) + }.should raise_error(TypeError, "can't convert LangSendSpecs::RawToProc to Proc (LangSendSpecs::RawToProc#to_proc gives Integer)") + end + + it "raises TypeError if block object isn't a Proc and doesn't respond to `to_proc`" do + o = Object.new + + -> { + specs.makeproc(&o) + }.should raise_error(TypeError, "no implicit conversion of Object into Proc") + end + end + it "raises a SyntaxError with both a literal block and an object as block" do -> { eval "specs.oneb(10, &l){ 42 }" diff --git a/spec/ruby/language/string_spec.rb b/spec/ruby/language/string_spec.rb index 083a7f5db5..f287731bed 100644 --- a/spec/ruby/language/string_spec.rb +++ b/spec/ruby/language/string_spec.rb @@ -1,6 +1,7 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../spec_helper' +require_relative 'fixtures/class_with_class_variable' # TODO: rewrite these horrid specs. it "are..." seriously?! @@ -27,6 +28,11 @@ describe "Ruby character strings" do "#$ip".should == 'xxx' end + it "interpolate class variables just with the # character" do + object = StringSpecs::ClassWithClassVariable.new + object.foo.should == 'xxx' + end + it "allows underscore as part of a variable name in a simple interpolation" do @my_ip = 'xxx' "#@my_ip".should == 'xxx' @@ -280,15 +286,15 @@ describe "Ruby String interpolation" do it "creates a non-frozen String" do code = <<~'RUBY' - "a#{6*7}c" + "a#{6*7}c" RUBY eval(code).should_not.frozen? end it "creates a non-frozen String when # frozen-string-literal: true is used" do code = <<~'RUBY' - # frozen-string-literal: true - "a#{6*7}c" + # frozen-string-literal: true + "a#{6*7}c" RUBY eval(code).should_not.frozen? end diff --git a/spec/ruby/language/undef_spec.rb b/spec/ruby/language/undef_spec.rb index 29dba4afb4..268c0b84c3 100644 --- a/spec/ruby/language/undef_spec.rb +++ b/spec/ruby/language/undef_spec.rb @@ -69,7 +69,7 @@ describe "The undef keyword" do it "raises a NameError when passed a missing name" do Class.new do -> { - undef not_exist + undef not_exist }.should raise_error(NameError) { |e| # a NameError and not a NoMethodError e.class.should == NameError diff --git a/spec/ruby/language/variables_spec.rb b/spec/ruby/language/variables_spec.rb index 01be61a9dc..e134271939 100644 --- a/spec/ruby/language/variables_spec.rb +++ b/spec/ruby/language/variables_spec.rb @@ -14,69 +14,34 @@ describe "Evaluation order during assignment" do end context "with multiple assignment" do - ruby_version_is ""..."3.1" do - it "does not evaluate from left to right" do - obj = VariablesSpecs::EvalOrder.new - - obj.instance_eval do - foo[0], bar.baz = a, b - end - - obj.order.should == ["a", "b", "foo", "foo[]=", "bar", "bar.baz="] + it "evaluates from left to right, receivers first then methods" do + obj = VariablesSpecs::EvalOrder.new + obj.instance_eval do + foo[0], bar.baz = a, b end - it "cannot be used to swap variables with nested method calls" do - node = VariablesSpecs::EvalOrder.new.node - - original_node = node - original_node_left = node.left - original_node_left_right = node.left.right - - node.left, node.left.right, node = node.left.right, node, node.left - # Should evaluate in the order of: - # RHS: node.left.right, node, node.left - # LHS: - # * node(original_node), original_node.left = original_node_left_right - # * node(original_node), node.left(changed in the previous assignment to original_node_left_right), - # original_node_left_right.right = original_node - # * node = original_node_left - - node.should == original_node_left - node.right.should_not == original_node - node.right.left.should_not == original_node_left_right - end + obj.order.should == ["foo", "bar", "a", "b", "foo[]=", "bar.baz="] end - ruby_version_is "3.1" do - it "evaluates from left to right, receivers first then methods" do - obj = VariablesSpecs::EvalOrder.new - obj.instance_eval do - foo[0], bar.baz = a, b - end + it "can be used to swap variables with nested method calls" do + node = VariablesSpecs::EvalOrder.new.node - obj.order.should == ["foo", "bar", "a", "b", "foo[]=", "bar.baz="] - end + original_node = node + original_node_left = node.left + original_node_left_right = node.left.right - it "can be used to swap variables with nested method calls" do - node = VariablesSpecs::EvalOrder.new.node - - original_node = node - original_node_left = node.left - original_node_left_right = node.left.right - - node.left, node.left.right, node = node.left.right, node, node.left - # Should evaluate in the order of: - # LHS: node, node.left(original_node_left) - # RHS: original_node_left_right, original_node, original_node_left - # Ops: - # * node(original_node), original_node.left = original_node_left_right - # * original_node_left.right = original_node - # * node = original_node_left - - node.should == original_node_left - node.right.should == original_node - node.right.left.should == original_node_left_right - end + node.left, node.left.right, node = node.left.right, node, node.left + # Should evaluate in the order of: + # LHS: node, node.left(original_node_left) + # RHS: original_node_left_right, original_node, original_node_left + # Ops: + # * node(original_node), original_node.left = original_node_left_right + # * original_node_left.right = original_node + # * node = original_node_left + + node.should == original_node_left + node.right.should == original_node + node.right.left.should == original_node_left_right end end end @@ -381,6 +346,9 @@ describe "Multiple assignment" do SINGLE_RHS_1, SINGLE_RHS_2 = 1 [SINGLE_RHS_1, SINGLE_RHS_2].should == [1, nil] end + ensure + VariableSpecs.send(:remove_const, :SINGLE_RHS_1) + VariableSpecs.send(:remove_const, :SINGLE_RHS_2) end end @@ -395,11 +363,22 @@ describe "Multiple assignment" do a.should == [] end - it "calls #to_a to convert nil to an empty Array" do - nil.should_receive(:to_a).and_return([]) + ruby_version_is "4.0" do + it "converts nil to empty array without calling a method" do + nil.should_not_receive(:to_a) - (*a = *nil).should == [] - a.should == [] + (*a = *nil).should == [] + a.should == [] + end + end + + ruby_version_is ""..."4.0" do + it "calls #to_a to convert nil to an empty Array" do + nil.should_receive(:to_a).and_return([]) + + (*a = *nil).should == [] + a.should == [] + end end it "does not call #to_a on an Array" do @@ -619,6 +598,8 @@ describe "Multiple assignment" do (*SINGLE_SPLATTED_RHS) = *1 SINGLE_SPLATTED_RHS.should == [1] end + ensure + VariableSpecs.send(:remove_const, :SINGLE_SPLATTED_RHS) end end @@ -818,6 +799,9 @@ describe "Multiple assignment" do MRHS_VALUES_1.should == 1 MRHS_VALUES_2.should == 2 end + ensure + VariableSpecs.send(:remove_const, :MRHS_VALUES_1) + VariableSpecs.send(:remove_const, :MRHS_VALUES_2) end it "assigns all RHS values as an array to a single LHS constant" do @@ -825,6 +809,8 @@ describe "Multiple assignment" do MRHS_VALUES = 1, 2, 3 MRHS_VALUES.should == [1, 2, 3] end + ensure + VariableSpecs.send(:remove_const, :MRHS_VALUES) end end |
