diff options
Diffstat (limited to 'spec/ruby/language')
97 files changed, 7833 insertions, 3989 deletions
diff --git a/spec/ruby/language/BEGIN_spec.rb b/spec/ruby/language/BEGIN_spec.rb index 5aef5a1d7c..25db32b96a 100644 --- a/spec/ruby/language/BEGIN_spec.rb +++ b/spec/ruby/language/BEGIN_spec.rb @@ -15,7 +15,7 @@ describe "The BEGIN keyword" do end it "must appear in a top-level context" do - -> { eval "1.times { BEGIN { 1 } }" }.should raise_error(SyntaxError) + -> { eval "1.times { BEGIN { 1 } }" }.should.raise(SyntaxError) end it "uses top-level for self" do diff --git a/spec/ruby/language/END_spec.rb b/spec/ruby/language/END_spec.rb index 762a8db0c0..c84f0cc9ac 100644 --- a/spec/ruby/language/END_spec.rb +++ b/spec/ruby/language/END_spec.rb @@ -1,15 +1,33 @@ require_relative '../spec_helper' +require_relative '../shared/kernel/at_exit' describe "The END keyword" do + it_behaves_like :kernel_at_exit, :END + it "runs only once for multiple calls" do ruby_exe("10.times { END { puts 'foo' }; } ").should == "foo\n" end - it "runs last in a given code unit" do - ruby_exe("END { puts 'bar' }; puts'foo'; ").should == "foo\nbar\n" + it "is affected by the toplevel assignment" do + ruby_exe("foo = 'foo'; END { puts foo }").should == "foo\n" + end + + it "warns when END is used in a method" do + ruby_exe(<<~ruby, args: "2>&1").should =~ /warning: END in method; use at_exit/ + def foo + END { } + end + ruby end - it "runs multiple ends in LIFO order" do - ruby_exe("END { puts 'foo' }; END { puts 'bar' }").should == "bar\nfoo\n" + context "END blocks and at_exit callbacks are mixed" do + it "runs them all in reverse order of registration" do + ruby_exe(<<~ruby).should == "at_exit#2\nEND#2\nat_exit#1\nEND#1\n" + END { puts 'END#1' } + at_exit { puts 'at_exit#1' } + END { puts 'END#2' } + at_exit { puts 'at_exit#2' } + ruby + end end end diff --git a/spec/ruby/language/alias_spec.rb b/spec/ruby/language/alias_spec.rb index d1d06e3fac..4b3d36d308 100644 --- a/spec/ruby/language/alias_spec.rb +++ b/spec/ruby/language/alias_spec.rb @@ -52,6 +52,15 @@ describe "The alias keyword" do @obj.a.should == 5 end + it "works with an interpolated symbol with non-literal embedded expression on the left-hand side" do + @meta.class_eval do + eval %Q{ + alias :"#{'a' + ''.to_s}" value + } + end + @obj.a.should == 5 + end + it "works with a simple symbol on the right-hand side" do @meta.class_eval do alias a :value @@ -80,6 +89,15 @@ describe "The alias keyword" do @obj.a.should == 5 end + it "works with an interpolated symbol with non-literal embedded expression on the right-hand side" do + @meta.class_eval do + eval %Q{ + alias a :"#{'value' + ''.to_s}" + } + end + @obj.a.should == 5 + end + it "adds the new method to the list of methods" do original_methods = @obj.methods @meta.class_eval do @@ -122,7 +140,7 @@ describe "The alias keyword" do end @obj.__value.should == 5 - -> { AliasObject.new.__value }.should raise_error(NoMethodError) + -> { AliasObject.new.__value }.should.raise(NoMethodError) end it "operates on the class/module metaclass when used in instance_eval" do @@ -131,7 +149,7 @@ describe "The alias keyword" do end AliasObject.__klass_method.should == 7 - -> { Object.__klass_method }.should raise_error(NoMethodError) + -> { Object.__klass_method }.should.raise(NoMethodError) end it "operates on the class/module metaclass when used in instance_exec" do @@ -140,7 +158,7 @@ describe "The alias keyword" do end AliasObject.__klass_method2.should == 7 - -> { Object.__klass_method2 }.should raise_error(NoMethodError) + -> { Object.__klass_method2 }.should.raise(NoMethodError) end it "operates on methods defined via attr, attr_reader, and attr_accessor" do @@ -223,26 +241,39 @@ describe "The alias keyword" do 1.instance_eval do alias :foo :to_s end - end.should raise_error(TypeError) + end.should.raise(TypeError) -> do :blah.instance_eval do alias :foo :to_s end - end.should raise_error(TypeError) + end.should.raise(TypeError) end it "on top level defines the alias on Object" do # because it defines on the default definee / current module - ruby_exe("def foo; end; alias bla foo; print method(:bla).owner", escape: true).should == "Object" + ruby_exe("def foo; end; alias bla foo; print method(:bla).owner").should == "Object" end it "raises a NameError when passed a missing name" do - -> { @meta.class_eval { alias undef_method not_exist } }.should raise_error(NameError) { |e| + -> { @meta.class_eval { alias undef_method not_exist } }.should.raise(NameError) { |e| # a NameError and not a NoMethodError e.class.should == NameError } end + + it "defines the method on the aliased class when the original method is from a parent class" do + parent = Class.new do + def parent_method + end + end + child = Class.new(parent) do + alias parent_method_alias parent_method + end + + child.instance_method(:parent_method_alias).owner.should == child + child.instance_methods(false).should.include?(:parent_method_alias) + end end describe "The alias keyword" do diff --git a/spec/ruby/language/and_spec.rb b/spec/ruby/language/and_spec.rb index 55a2a3103a..c5c255989b 100644 --- a/spec/ruby/language/and_spec.rb +++ b/spec/ruby/language/and_spec.rb @@ -5,7 +5,7 @@ describe "The '&&' statement" do it "short-circuits evaluation at the first condition to be false" do x = nil true && false && x = 1 - x.should be_nil + x.should == nil end it "evaluates to the first condition not to be true" do @@ -33,9 +33,9 @@ describe "The '&&' statement" do end it "treats empty expressions as nil" do - (() && true).should be_nil - (true && ()).should be_nil - (() && ()).should be_nil + (() && true).should == nil + (true && ()).should == nil + (() && ()).should == nil end end @@ -44,7 +44,7 @@ describe "The 'and' statement" do it "short-circuits evaluation at the first condition to be false" do x = nil true and false and x = 1 - x.should be_nil + x.should == nil end it "evaluates to the first condition not to be true" do @@ -72,9 +72,9 @@ describe "The 'and' statement" do end it "treats empty expressions as nil" do - (() and true).should be_nil - (true and ()).should be_nil - (() and ()).should be_nil + (() and true).should == nil + (true and ()).should == nil + (() and ()).should == nil end end diff --git a/spec/ruby/language/array_spec.rb b/spec/ruby/language/array_spec.rb index 2583cffbf7..3601eb0e00 100644 --- a/spec/ruby/language/array_spec.rb +++ b/spec/ruby/language/array_spec.rb @@ -4,7 +4,7 @@ require_relative 'fixtures/array' describe "Array literals" do it "[] should return a new array populated with the given elements" do array = [1, 'a', nil] - array.should be_kind_of(Array) + array.should.is_a?(Array) array[0].should == 1 array[1].should == 'a' array[2].should == nil @@ -12,7 +12,7 @@ describe "Array literals" do it "[] treats empty expressions as nil elements" do array = [0, (), 2, (), 4] - array.should be_kind_of(Array) + array.should.is_a?(Array) array[0].should == 0 array[1].should == nil array[2].should == 2 @@ -89,7 +89,7 @@ describe "The unpacking splat operator (*)" do it "returns a new array containing the same values when applied to an array inside an empty array" do splatted_array = [3, 4, 5] [*splatted_array].should == splatted_array - [*splatted_array].should_not equal(splatted_array) + [*splatted_array].should_not.equal?(splatted_array) end it "unpacks the start and count arguments in an array slice assignment" do @@ -135,7 +135,7 @@ describe "The unpacking splat operator (*)" do it "when applied to a non-Array value uses it unchanged if it does not respond_to?(:to_a)" do obj = Object.new - obj.should_not respond_to(:to_a) + obj.should_not.respond_to?(:to_a) [1, *obj].should == [1, obj] end diff --git a/spec/ruby/language/assignments_spec.rb b/spec/ruby/language/assignments_spec.rb new file mode 100644 index 0000000000..d621c9f0c6 --- /dev/null +++ b/spec/ruby/language/assignments_spec.rb @@ -0,0 +1,582 @@ +require_relative '../spec_helper' + +# Should be synchronized with spec/ruby/language/optional_assignments_spec.rb +# Some specs for assignments are located in language/variables_spec.rb +describe 'Assignments' do + describe 'using =' do + describe 'evaluation order' do + it 'evaluates expressions left to right when assignment with an accessor' do + object = Object.new + def object.a=(value) end + ScratchPad.record [] + + (ScratchPad << :receiver; object).a = (ScratchPad << :rhs; :value) + ScratchPad.recorded.should == [:receiver, :rhs] + end + + it 'evaluates expressions left to right when assignment with a #[]=' do + object = Object.new + def object.[]=(_, _) end + ScratchPad.record [] + + (ScratchPad << :receiver; object)[(ScratchPad << :argument; :a)] = (ScratchPad << :rhs; :value) + ScratchPad.recorded.should == [:receiver, :argument, :rhs] + end + + 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 + + it 'raises TypeError after evaluation of right-hand-side when compounded constant module is not a module' do + ScratchPad.record [] + + -> { + (:not_a_module)::A = (ScratchPad << :rhs; :value) + }.should.raise(TypeError) + + 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(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(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 + describe 'using an accessor' do + before do + klass = Class.new { attr_accessor :b } + @a = klass.new + end + + it 'does evaluate receiver only once when assigns' do + ScratchPad.record [] + @a.b = 1 + + (ScratchPad << :evaluated; @a).b += 2 + + ScratchPad.recorded.should == [:evaluated] + @a.b.should == 3 + end + + it 'ignores method visibility when receiver is self' do + klass_with_private_methods = Class.new do + def initialize(n) @a = n end + def public_method(n); self.a += n end + private + def a; @a end + def a=(n) @a = n; 42 end + end + + a = klass_with_private_methods.new(0) + a.public_method(2).should == 2 + end + end + + describe 'using a #[]' do + before do + klass = Class.new do + def [](k) + @hash ||= {} + @hash[k] + end + + def []=(k, v) + @hash ||= {} + @hash[k] = v + 7 + end + end + @b = klass.new + end + + it 'evaluates receiver only once when assigns' do + ScratchPad.record [] + a = {k: 1} + + (ScratchPad << :evaluated; a)[:k] += 2 + + ScratchPad.recorded.should == [:evaluated] + a[:k].should == 3 + end + + it 'ignores method visibility when receiver is self' do + klass_with_private_methods = Class.new do + def initialize(h) @a = h end + def public_method(k, n); self[k] += n end + private + def [](k) @a[k] end + def []=(k, v) @a[k] = v; 42 end + end + + a = klass_with_private_methods.new(k: 0) + 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(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.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(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 + (@b[*[:m]] += 10).should == 20 + @b[:m].should == 20 + + @b[:n] = 10 + (@b[*(1; [:n])] += 10).should == 20 + @b[:n].should == 20 + + @b[:k] = 10 + (@b[*begin 1; [:k] end] += 10).should == 20 + @b[:k].should == 20 + end + + it 'calls #to_a only once' do + k = Object.new + def k.to_a + ScratchPad << :to_a + [:k] + end + + ScratchPad.record [] + @b[:k] = 10 + (@b[*k] += 10).should == 20 + @b[:k].should == 20 + ScratchPad.recorded.should == [:to_a] + end + + it 'correctly handles a nested splatted argument' do + @b[:k] = 10 + (@b[*[*[:k]]] += 10).should == 20 + @b[:k].should == 20 + end + + it 'correctly handles multiple nested splatted arguments' do + klass_with_multiple_parameters = Class.new do + def [](k1, k2, k3) + @hash ||= {} + @hash[:"#{k1}#{k2}#{k3}"] + end + + def []=(k1, k2, k3, v) + @hash ||= {} + @hash[:"#{k1}#{k2}#{k3}"] = v + 7 + end + end + a = klass_with_multiple_parameters.new + + a[:a, :b, :c] = 10 + (a[*[:a], *[:b], *[:c]] += 10).should == 20 + a[:a, :b, :c].should == 20 + end + end + end + + describe 'using compounded constants' do + it 'causes side-effects of the module part to be applied only once (when assigns)' do + module ConstantSpecs + OpAssignTrue = 1 + end + + suppress_warning do # already initialized constant + x = 0 + (x += 1; ConstantSpecs)::OpAssignTrue += 2 + x.should == 1 + ConstantSpecs::OpAssignTrue.should == 3 + end + + ConstantSpecs.send :remove_const, :OpAssignTrue + end + end + end +end + +# generic cases +describe 'Multiple assignments' do + it 'assigns multiple targets when assignment with an accessor' do + object = Object.new + class << object + attr_accessor :a, :b + end + + object.a, object.b = :a, :b + + object.a.should == :a + object.b.should == :b + end + + it 'assigns multiple targets when assignment with a nested accessor' do + object = Object.new + class << object + attr_accessor :a, :b + end + + (object.a, object.b), c = [:a, :b], nil + + object.a.should == :a + object.b.should == :b + end + + it 'assigns multiple targets when assignment with a #[]=' do + object = Object.new + class << object + def []=(k, v) (@h ||= {})[k] = v; end + def [](k) (@h ||= {})[k]; end + end + + object[:a], object[:b] = :a, :b + + object[:a].should == :a + object[:b].should == :b + end + + it 'assigns multiple targets when assignment with a nested #[]=' do + object = Object.new + class << object + def []=(k, v) (@h ||= {})[k] = v; end + def [](k) (@h ||= {})[k]; end + end + + (object[:a], object[:b]), c = [:v1, :v2], nil + + object[:a].should == :v1 + object[:b].should == :v2 + end + + it 'assigns multiple targets when assignment with compounded constant' do + m = Module.new + + m::A, m::B = :a, :b + + m::A.should == :a + m::B.should == :b + end + + it 'assigns multiple targets when assignment with a nested compounded constant' do + m = Module.new + + (m::A, m::B), c = [:a, :b], nil + + m::A.should == :a + m::B.should == :b + end +end + +describe 'Multiple assignments' do + describe 'evaluation order' 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) + + ScratchPad.recorded.should == [:a, :b, :c, :d, :e, :f, :value] + end + + 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 + + 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 + + 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.recorded.should == [:ra, :aa, :rb, :ab, :rc, :ac, :rd, :ad, :re, :ae, :rf, :af, :value] + end + + 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 [] + + ((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 [] + + (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 + end + + context 'when assignment with method call and receiver is self' do + it 'assigns values correctly when assignment with accessor' do + object = Object.new + class << object + attr_accessor :a, :b + + def assign(v1, v2) + self.a, self.b = v1, v2 + end + end + + object.assign :v1, :v2 + object.a.should == :v1 + object.b.should == :v2 + end + + it 'evaluates expressions right to left when assignment with a nested accessor' do + object = Object.new + class << object + attr_accessor :a, :b + + def assign(v1, v2) + (self.a, self.b), c = [v1, v2], nil + end + end + + object.assign :v1, :v2 + object.a.should == :v1 + object.b.should == :v2 + end + + it 'assigns values correctly when assignment with a #[]=' do + object = Object.new + class << object + def []=(key, v) + @h ||= {} + @h[key] = v + end + + def [](key) + (@h || {})[key] + end + + def assign(k1, v1, k2, v2) + self[k1], self[k2] = v1, v2 + end + end + + object.assign :k1, :v1, :k2, :v2 + object[:k1].should == :v1 + object[:k2].should == :v2 + end + + it 'assigns values correctly when assignment with a nested #[]=' do + object = Object.new + class << object + def []=(key, v) + @h ||= {} + @h[key] = v + end + + def [](key) + (@h || {})[key] + end + + def assign(k1, v1, k2, v2) + (self[k1], self[k2]), c = [v1, v2], nil + end + end + + object.assign :k1, :v1, :k2, :v2 + object[:k1].should == :v1 + object[:k2].should == :v2 + end + + it 'assigns values correctly when assignment with compounded constant' do + m = Module.new + m.module_exec do + self::A, self::B = :v1, :v2 + end + + m::A.should == :v1 + m::B.should == :v2 + end + + it 'assigns values correctly when assignment with a nested compounded constant' do + m = Module.new + m.module_exec do + (self::A, self::B), c = [:v1, :v2], nil + end + + m::A.should == :v1 + m::B.should == :v2 + end + end +end diff --git a/spec/ruby/language/block_spec.rb b/spec/ruby/language/block_spec.rb index b2a3cc84c4..5bdb993aea 100644 --- a/spec/ruby/language/block_spec.rb +++ b/spec/ruby/language/block_spec.rb @@ -13,7 +13,7 @@ describe "A block yielded a single" do it "receives the identical Array object" do ary = [1, 2] - m(ary) { |a| a }.should equal(ary) + m(ary) { |a| a }.should.equal?(ary) end it "assigns the Array to a single rest argument" do @@ -40,80 +40,54 @@ describe "A block yielded a single" do m([1, 2]) { |a=5, b, c, d| [a, b, c, d] }.should == [5, 1, 2, nil] end - it "assigns elements to required arguments when a keyword rest argument is present" do - m([1, 2]) { |a, **k| [a, k] }.should == [1, {}] + it "assigns elements to pre arguments" do + m([1, 2]) { |a, b, c, d=5| [a, b, c, d] }.should == [1, 2, nil, 5] end - ruby_version_is ''..."3.0" do - it "assigns elements to mixed argument types" do - suppress_keyword_warning do - result = m([1, 2, 3, {x: 9}]) { |a, b=5, *c, d, e: 2, **k| [a, b, c, d, e, k] } - result.should == [1, 2, [], 3, 2, {x: 9}] - end - end - - it "assigns symbol keys from a Hash to keyword arguments" do - suppress_keyword_warning do - result = m(["a" => 1, a: 10]) { |a=nil, **b| [a, b] } - result.should == [{"a" => 1}, a: 10] - end - end - - it "assigns symbol keys from a Hash returned by #to_hash to keyword arguments" do - suppress_keyword_warning do - obj = mock("coerce block keyword arguments") - obj.should_receive(:to_hash).and_return({"a" => 1, b: 2}) - - result = m([obj]) { |a=nil, **b| [a, b] } - result.should == [{"a" => 1}, b: 2] - end - end + it "assigns elements to pre and post arguments" do + m([1 ]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 5, 6, nil, nil] + m([1, 2 ]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 5, 6, 2, nil] + m([1, 2, 3 ]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 5, 6, 2, 3] + m([1, 2, 3, 4 ]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 2, 6, 3, 4] + m([1, 2, 3, 4, 5 ]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 2, 3, 4, 5] + m([1, 2, 3, 4, 5, 6]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 2, 3, 4, 5] end - ruby_version_is "3.0" do - it "assigns elements to mixed argument types" do - result = m([1, 2, 3, {x: 9}]) { |a, b=5, *c, d, e: 2, **k| [a, b, c, d, e, k] } - result.should == [1, 2, [3], {x: 9}, 2, {}] - end - - it "does not treat final Hash as keyword arguments and does not autosplat" do - result = m(["a" => 1, a: 10]) { |a=nil, **b| [a, b] } - result.should == [[{"a" => 1, a: 10}], {}] - end - - it "does not call #to_hash on final argument to get keyword arguments and does not autosplat" do - suppress_keyword_warning do - obj = mock("coerce block keyword arguments") - obj.should_not_receive(:to_hash) + it "assigns elements to pre and post arguments when *rest is present" do + m([1 ]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 5, 6, [], nil, nil] + m([1, 2 ]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 5, 6, [], 2, nil] + m([1, 2, 3 ]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 5, 6, [], 2, 3] + m([1, 2, 3, 4 ]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 2, 6, [], 3, 4] + m([1, 2, 3, 4, 5 ]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 2, 3, [], 4, 5] + 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 - result = m([obj]) { |a=nil, **b| [a, b] } - result.should == [[obj], {}] - end - 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 ""..."2.7" do - it "calls #to_hash on the argument and uses resulting hash as first argument when optional argument and keyword argument accepted" do - obj = mock("coerce block keyword arguments") - obj.should_receive(:to_hash).and_return({"a" => 1, "b" => 2}) + 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 - result = m([obj]) { |a=nil, **b| [a, b] } - result.should == [{"a" => 1, "b" => 2}, {}] - end + it "raises error when required keyword arguments are present" do + -> { + m([1, 2]) { |a, b:, c:| [a, b, c] } + }.should.raise(ArgumentError, "missing keywords: :b, :c") end - ruby_version_is "2.7"...'3.0' do - it "calls #to_hash on the argument but ignores result when optional argument and keyword argument accepted" do - obj = mock("coerce block keyword arguments") - obj.should_receive(:to_hash).and_return({"a" => 1, "b" => 2}) + it "assigns elements to mixed argument types" do + result = m([1, 2, 3, {x: 9}]) { |a, b=5, *c, d, e: 2, **k| [a, b, c, d, e, k] } + result.should == [1, 2, [3], {x: 9}, 2, {}] + end - result = m([obj]) { |a=nil, **b| [a, b] } - result.should == [obj, {}] - end + it "does not treat final Hash as keyword arguments and does not autosplat" do + result = m(["a" => 1, a: 10]) { |a=nil, **b| [a, b] } + result.should == [[{"a" => 1, a: 10}], {}] end - ruby_version_is "3.0" do - it "does not call #to_hash on the argument when optional argument and keyword argument accepted and does not autosplat" do + it "does not call #to_hash on final argument to get keyword arguments and does not autosplat" do + suppress_keyword_warning do obj = mock("coerce block keyword arguments") obj.should_not_receive(:to_hash) @@ -122,102 +96,42 @@ describe "A block yielded a single" do end end - describe "when non-symbol keys are in a keyword arguments Hash" do - ruby_version_is ""..."3.0" do - it "separates non-symbol keys and symbol keys" do - suppress_keyword_warning do - result = m(["a" => 10, b: 2]) { |a=nil, **b| [a, b] } - result.should == [{"a" => 10}, {b: 2}] - end - end - end - ruby_version_is "3.0" do - it "does not separate non-symbol keys and symbol keys and does not autosplat" do - suppress_keyword_warning do - result = m(["a" => 10, b: 2]) { |a=nil, **b| [a, b] } - result.should == [[{"a" => 10, b: 2}], {}] - end - end - end - end + it "does not call #to_hash on the argument when optional argument and keyword argument accepted and does not autosplat" do + obj = mock("coerce block keyword arguments") + obj.should_not_receive(:to_hash) - ruby_version_is ""..."3.0" do - it "does not treat hashes with string keys as keyword arguments" do - result = m(["a" => 10]) { |a = nil, **b| [a, b] } - result.should == [{"a" => 10}, {}] - end - end - - ruby_version_is "3.0" do - it "does not treat hashes with string keys as keyword arguments and does not autosplat" do - result = m(["a" => 10]) { |a = nil, **b| [a, b] } - result.should == [[{"a" => 10}], {}] - end + result = m([obj]) { |a=nil, **b| [a, b] } + result.should == [[obj], {}] end - ruby_version_is ''...'3.0' do - it "calls #to_hash on the last element if keyword arguments are present" do - suppress_keyword_warning do - obj = mock("destructure block keyword arguments") - obj.should_receive(:to_hash).and_return({x: 9}) - - result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] } - result.should == [1, [2], 3, {x: 9}] - end - end - - it "assigns the last element to a non-keyword argument if #to_hash returns nil" do + describe "when non-symbol keys are in a keyword arguments Hash" do + it "does not separate non-symbol keys and symbol keys and does not autosplat" do suppress_keyword_warning do - obj = mock("destructure block keyword arguments") - obj.should_receive(:to_hash).and_return(nil) - - result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] } - result.should == [1, [2, 3], obj, {}] + result = m(["a" => 10, b: 2]) { |a=nil, **b| [a, b] } + result.should == [[{"a" => 10, b: 2}], {}] end end + end - it "calls #to_hash on the last element when there are more arguments than parameters" do - suppress_keyword_warning do - x = mock("destructure matching block keyword argument") - x.should_receive(:to_hash).and_return({x: 9}) - - result = m([1, 2, 3, {y: 9}, 4, 5, x]) { |a, b=5, c, **k| [a, b, c, k] } - result.should == [1, 2, 3, {x: 9}] - end - end - - it "raises a TypeError if #to_hash does not return a Hash" do - obj = mock("destructure block keyword arguments") - obj.should_receive(:to_hash).and_return(1) - - -> { m([1, 2, 3, obj]) { |a, *b, c, **k| } }.should raise_error(TypeError) - end - - it "raises the error raised inside #to_hash" do - obj = mock("destructure block keyword arguments") - error = RuntimeError.new("error while converting to a hash") - obj.should_receive(:to_hash).and_raise(error) - - -> { m([1, 2, 3, obj]) { |a, *b, c, **k| } }.should raise_error(error) - end + it "does not treat hashes with string keys as keyword arguments and does not autosplat" do + result = m(["a" => 10]) { |a = nil, **b| [a, b] } + result.should == [[{"a" => 10}], {}] end - ruby_version_is '3.0' do - it "does not call #to_hash on the last element if keyword arguments are present" do - obj = mock("destructure block keyword arguments") - obj.should_not_receive(:to_hash) + it "does not call #to_hash on the last element if keyword arguments are present" do + obj = mock("destructure block keyword arguments") + obj.should_not_receive(:to_hash) - result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] } - result.should == [1, [2, 3], obj, {}] - end + result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] } + result.should == [1, [2, 3], obj, {}] + end - it "does not call #to_hash on the last element when there are more arguments than parameters" do - x = mock("destructure matching block keyword argument") - x.should_not_receive(:to_hash) + it "does not call #to_hash on the last element when there are more arguments than parameters" do + x = mock("destructure matching block keyword argument") + x.should_not_receive(:to_hash) - result = m([1, 2, 3, {y: 9}, 4, 5, x]) { |a, b=5, c, **k| [a, b, c, k] } - result.should == [1, 2, 3, {}] - end + result = m([1, 2, 3, {y: 9}, 4, 5, x]) { |a, b=5, c, **k| [a, b, c, k] } + result.should == [1, 2, 3, {}] end it "does not call #to_ary on the Array" do @@ -264,11 +178,70 @@ describe "A block yielded a single" do m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil] end + it "receives the object if it does not respond to #to_ary" do + obj = Object.new + + m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil] + end + + it "calls #respond_to? to check if object has method #to_ary" do + obj = mock("destructure block arguments") + obj.should_receive(:respond_to?).with(:to_ary, true).and_return(true) + obj.should_receive(:to_ary).and_return([1, 2]) + + m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil] + end + + it "calls #respond_to? on a BasicObject to check if object has method #to_ary" do + ScratchPad.record [] + obj = BasicObject.new + def obj.respond_to?(name, *) + ScratchPad << [:respond_to?, name] + name == :to_ary ? true : super + end + def obj.to_ary + ScratchPad << :to_ary + [1, 2] + end + + m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil] + ScratchPad.recorded.should == [[:respond_to?, :to_ary], :to_ary] + end + + it "receives the object if it does not respond to #respond_to?" do + obj = BasicObject.new + + m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil] + end + + it "calls #to_ary on the object when it is defined dynamically" do + obj = Object.new + def obj.method_missing(name, *args, &block) + if name == :to_ary + [1, 2] + else + super + end + end + def obj.respond_to_missing?(name, include_private) + name == :to_ary + end + + m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil] + end + it "raises a TypeError if #to_ary does not return an Array" do obj = mock("destructure block arguments") obj.should_receive(:to_ary).and_return(1) - -> { m(obj) { |a, b| } }.should raise_error(TypeError) + -> { m(obj) { |a, b| } }.should.raise(TypeError) + end + + it "raises error transparently if #to_ary raises error on its own" do + obj = Object.new + def obj.to_ary; raise "Exception raised in #to_ary" end + + -> { m(obj) { |a, b| } }.should.raise(RuntimeError, "Exception raised in #to_ary") end end end @@ -318,7 +291,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 @@ -332,13 +305,13 @@ 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 describe "taking |a| arguments" do it "assigns nil to the argument when no values are yielded" do - @y.z { |a| a }.should be_nil + @y.z { |a| a }.should == nil end it "assigns the value yielded to the argument" do @@ -349,7 +322,7 @@ describe "A block" do obj = mock("block yield to_ary") obj.should_not_receive(:to_ary) - @y.s(obj) { |a| a }.should equal(obj) + @y.s(obj) { |a| a }.should.equal?(obj) end it "assigns the first value yielded to the argument" do @@ -361,7 +334,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 @@ -425,16 +398,15 @@ describe "A block" do obj = mock("block yield to_ary invalid") obj.should_receive(:to_ary).and_return(1) - -> { @y.s(obj) { |a, b| } }.should raise_error(TypeError) + -> { @y.s(obj) { |a, b| } }.should.raise(TypeError) end it "raises the original exception if #to_ary raises an exception" do obj = mock("block yield to_ary raising an exception") obj.should_receive(:to_ary).and_raise(ZeroDivisionError) - -> { @y.s(obj) { |a, b| } }.should raise_error(ZeroDivisionError) + -> { @y.s(obj) { |a, b| } }.should.raise(ZeroDivisionError) end - end describe "taking |a, *b| arguments" do @@ -489,7 +461,7 @@ describe "A block" do obj = mock("block yield to_ary invalid") obj.should_receive(:to_ary).and_return(1) - -> { @y.s(obj) { |a, *b| } }.should raise_error(TypeError) + -> { @y.s(obj) { |a, *b| } }.should.raise(TypeError) end end @@ -567,7 +539,7 @@ describe "A block" do describe "taking |a, | arguments" do it "assigns nil to the argument when no values are yielded" do - @y.z { |a, | a }.should be_nil + @y.z { |a, | a }.should == nil end it "assigns the argument a single value yielded" do @@ -583,7 +555,7 @@ describe "A block" do end it "assigns nil to the argument when passed an empty Array" do - @y.s([]) { |a, | a }.should be_nil + @y.s([]) { |a, | a }.should == nil end it "assigns the argument the first element of the Array when passed a single Array" do @@ -614,7 +586,7 @@ describe "A block" do obj = mock("block yield to_ary invalid") obj.should_receive(:to_ary).and_return(1) - -> { @y.s(obj) { |a, | } }.should raise_error(TypeError) + -> { @y.s(obj) { |a, | } }.should.raise(TypeError) end end @@ -656,7 +628,7 @@ describe "A block" do obj = mock("block yield to_ary invalid") obj.should_receive(:to_ary).and_return(1) - -> { @y.s(obj) { |(a, b)| } }.should raise_error(TypeError) + -> { @y.s(obj) { |(a, b)| } }.should.raise(TypeError) end end @@ -697,7 +669,7 @@ describe "A block" do obj = mock("block yield to_ary invalid") obj.should_receive(:to_ary).and_return(1) - -> { @y.s(obj) { |(a, b), c| } }.should raise_error(TypeError) + -> { @y.s(obj) { |(a, b), c| } }.should.raise(TypeError) end end @@ -756,15 +728,51 @@ describe "A block" do describe "taking identically-named arguments" do it "raises a SyntaxError for standard arguments" do - -> { eval "lambda { |x,x| }" }.should raise_error(SyntaxError) - -> { eval "->(x,x) {}" }.should raise_error(SyntaxError) - -> { eval "Proc.new { |x,x| }" }.should raise_error(SyntaxError) + -> { eval "lambda { |x,x| }" }.should.raise(SyntaxError) + -> { eval "->(x,x) {}" }.should.raise(SyntaxError) + -> { eval "Proc.new { |x,x| }" }.should.raise(SyntaxError) 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.instance_of?(Proc) # rubocop:disable Style/Lambda + -> _,_ {}.should.instance_of?(Proc) + Proc.new { |_,_| }.should.instance_of?(Proc) + end + end + + describe 'pre and post parameters' do + it "assigns nil to unassigned required arguments" do + proc { |a, *b, c, d| [a, b, c, d] }.call(1, 2).should == [1, [], 2, nil] + end + + it "assigns elements to optional arguments" do + proc { |a=5, b=4, c=3| [a, b, c] }.call(1, 2).should == [1, 2, 3] + end + + it "assigns elements to post arguments" do + proc { |a=5, b, c, d| [a, b, c, d] }.call(1, 2).should == [5, 1, 2, nil] + end + + it "assigns elements to pre arguments" do + proc { |a, b, c, d=5| [a, b, c, d] }.call(1, 2).should == [1, 2, nil, 5] + end + + it "assigns elements to pre and post arguments" do + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1 ).should == [1, 5, 6, nil, nil] + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1, 2 ).should == [1, 5, 6, 2, nil] + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1, 2, 3 ).should == [1, 5, 6, 2, 3] + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1, 2, 3, 4 ).should == [1, 2, 6, 3, 4] + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1, 2, 3, 4, 5 ).should == [1, 2, 3, 4, 5] + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1, 2, 3, 4, 5, 6).should == [1, 2, 3, 4, 5] + end + + it "assigns elements to pre and post arguments when *rest is present" do + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1 ).should == [1, 5, 6, [], nil, nil] + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1, 2 ).should == [1, 5, 6, [], 2, nil] + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1, 2, 3 ).should == [1, 5, 6, [], 2, 3] + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1, 2, 3, 4 ).should == [1, 2, 6, [], 3, 4] + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1, 2, 3, 4, 5 ).should == [1, 2, 3, [], 4, 5] + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1, 2, 3, 4, 5, 6).should == [1, 2, 3, [4], 5, 6] end end end @@ -779,29 +787,29 @@ describe "Block-local variables" do end it "can not have the same name as one of the standard parameters" do - -> { eval "[1].each {|foo; foo| }" }.should raise_error(SyntaxError) - -> { eval "[1].each {|foo, bar; glark, bar| }" }.should raise_error(SyntaxError) + -> { eval "[1].each {|foo; foo| }" }.should.raise(SyntaxError) + -> { eval "[1].each {|foo, bar; glark, bar| }" }.should.raise(SyntaxError) end it "can not be prefixed with an asterisk" do - -> { eval "[1].each {|foo; *bar| }" }.should raise_error(SyntaxError) + -> { eval "[1].each {|foo; *bar| }" }.should.raise(SyntaxError) -> do eval "[1].each {|foo, bar; glark, *fnord| }" - end.should raise_error(SyntaxError) + end.should.raise(SyntaxError) end it "can not be prefixed with an ampersand" do - -> { eval "[1].each {|foo; &bar| }" }.should raise_error(SyntaxError) + -> { eval "[1].each {|foo; &bar| }" }.should.raise(SyntaxError) -> do eval "[1].each {|foo, bar; glark, &fnord| }" - end.should raise_error(SyntaxError) + end.should.raise(SyntaxError) end it "can not be assigned default values" do - -> { eval "[1].each {|foo; bar=1| }" }.should raise_error(SyntaxError) + -> { eval "[1].each {|foo; bar=1| }" }.should.raise(SyntaxError) -> do eval "[1].each {|foo, bar; glark, fnord=:fnord| }" - end.should raise_error(SyntaxError) + end.should.raise(SyntaxError) end it "need not be preceded by standard parameters" do @@ -810,8 +818,8 @@ describe "Block-local variables" do end it "only allow a single semi-colon in the parameter list" do - -> { eval "[1].each {|foo; bar; glark| }" }.should raise_error(SyntaxError) - -> { eval "[1].each {|; bar; glark| }" }.should raise_error(SyntaxError) + -> { eval "[1].each {|foo; bar; glark| }" }.should.raise(SyntaxError) + -> { eval "[1].each {|; bar; glark| }" }.should.raise(SyntaxError) end it "override shadowed variables from the outer scope" do @@ -836,21 +844,21 @@ describe "Block-local variables" do end it "are not automatically instantiated in the outer scope" do - defined?(glark).should be_nil + defined?(glark).should == nil [1].each {|;glark| 1} - defined?(glark).should be_nil + defined?(glark).should == nil end it "are automatically instantiated in the block" do [1].each do |;glark| - glark.should be_nil + glark.should == nil end end it "are visible in deeper scopes before initialization" do [1].each {|;glark| [1].each { - defined?(glark).should_not be_nil + defined?(glark).should_not == nil glark = 1 } glark.should == 1 @@ -878,7 +886,7 @@ describe "Post-args" do -> *a, b do [a, b] end.call - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end it "are assigned to nil when not enough arguments are given to a proc" do @@ -949,44 +957,27 @@ describe "Post-args" do end describe "with a circular argument reference" do - ruby_version_is ''...'2.7' do - it "warns and uses a nil value when there is an existing local variable with same name" do + ruby_version_is ""..."3.4" do + it "raises a SyntaxError if using the argument in its default value" do a = 1 -> { - @proc = eval "proc { |a=a| a }" - }.should complain(/circular argument reference/) - @proc.call.should == nil - end - - it "warns and uses a nil value when there is an existing method with same name" do - def a; 1; end - -> { - @proc = eval "proc { |a=a| a }" - }.should complain(/circular argument reference/) - @proc.call.should == nil + eval "proc { |a=a| a }" + }.should.raise(SyntaxError) end end - ruby_version_is '2.7' do - it "raises a SyntaxError if using an existing local with the same name as the argument" do - a = 1 + ruby_version_is "3.4" do + it "is nil if using the argument in its default value" do -> { - @proc = eval "proc { |a=a| a }" - }.should raise_error(SyntaxError) - end - - it "raises a SyntaxError if there is an existing method with the same name as the argument" do - def a; 1; end - -> { - @proc = eval "proc { |a=a| a }" - }.should raise_error(SyntaxError) + eval "proc { |a=a| a }.call" + }.call.should == nil end end + end - it "calls an existing method with the same name as the argument if explicitly using ()" do - def a; 1; end - proc { |a=a()| a }.call.should == 1 - end + it "calls an existing method with the same name as the argument if explicitly using ()" do + def a; 1; end + proc { |a=a()| a }.call.should == 1 end end @@ -1004,3 +995,154 @@ describe "Post-args" do end end end + +# tested more thoroughly in language/delegation_spec.rb +describe "Anonymous block forwarding" do + 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 + + 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(SyntaxError) + end + + 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 + + it "works alongside positional parameters" do + def inner; yield end + def pos(arg1, &); inner(&) end + + pos(:a) { 1 }.should == 1 + end + + 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 + + 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 + + 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 + +describe "`it` calls without arguments in a block" do + ruby_version_is ""..."3.4" do + it "emits a deprecation warning" do + -> { + eval "proc { it }" + }.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 + -> { eval "proc { |*| it }" }.should_not complain + -> { eval "proc { |a:, b:| it }" }.should_not complain + -> { eval "proc { |**kw| it }" }.should_not complain + -> { 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 "4.1" do + it "works alongside disallowed block argument" do + no_block = eval <<-EOF + proc {|arg1, &nil| arg1} + EOF + + no_block.call(:a).should == :a + -> { no_block.call(:a) {} }.should.raise(ArgumentError, 'no block accepted') + end + end +end + +# Duplicates specs in language/it_parameter_spec.rb +# Need them here to run on Ruby versions prior 3.4 +# TODO: remove when the minimal supported Ruby version is 3.4 +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 + +describe "Block-parameter destructuring" do + it "does not warn about unused inner names in verbose mode" do + -> { + eval <<~RUBY, binding, __FILE__, __LINE__ + 1 + proc { |key, (val1, val2)| [key, val2] } + RUBY + }.should_not complain(verbose: true) + end +end diff --git a/spec/ruby/language/break_spec.rb b/spec/ruby/language/break_spec.rb index 627cb4a071..5c9b8060c3 100644 --- a/spec/ruby/language/break_spec.rb +++ b/spec/ruby/language/break_spec.rb @@ -52,29 +52,29 @@ describe "The break statement in a captured block" do describe "when the invocation of the scope creating the block is still active" do it "raises a LocalJumpError when invoking the block from the scope creating the block" do - -> { @program.break_in_method }.should raise_error(LocalJumpError) + -> { @program.break_in_method }.should.raise(LocalJumpError) ScratchPad.recorded.should == [:a, :xa, :d, :b] end it "raises a LocalJumpError when invoking the block from a method" do - -> { @program.break_in_nested_method }.should raise_error(LocalJumpError) + -> { @program.break_in_nested_method }.should.raise(LocalJumpError) ScratchPad.recorded.should == [:a, :xa, :cc, :aa, :b] end it "raises a LocalJumpError when yielding to the block" do - -> { @program.break_in_yielding_method }.should raise_error(LocalJumpError) + -> { @program.break_in_yielding_method }.should.raise(LocalJumpError) ScratchPad.recorded.should == [:a, :xa, :cc, :aa, :b] end end describe "from a scope that has returned" do it "raises a LocalJumpError when calling the block from a method" do - -> { @program.break_in_method_captured }.should raise_error(LocalJumpError) + -> { @program.break_in_method_captured }.should.raise(LocalJumpError) ScratchPad.recorded.should == [:a, :za, :xa, :zd, :zb] end it "raises a LocalJumpError when yielding to the block" do - -> { @program.break_in_yield_captured }.should raise_error(LocalJumpError) + -> { @program.break_in_yield_captured }.should.raise(LocalJumpError) ScratchPad.recorded.should == [:a, :za, :xa, :zd, :aa, :zb] end end @@ -88,7 +88,7 @@ describe "The break statement in a captured block" do e end end - thread_with_break.value.should be_an_instance_of(LocalJumpError) + thread_with_break.value.should.instance_of?(LocalJumpError) end end end @@ -252,6 +252,25 @@ describe "Break inside a while loop" do end end +describe "The break statement in a method" do + it "is invalid and raises a SyntaxError" do + -> { + eval("def m; break; end") + }.should.raise(SyntaxError) + end +end + +describe "The break statement in a module literal" do + it "is invalid and raises a SyntaxError" do + code = <<~RUBY + module BreakSpecs:ModuleWithBreak + break + end + RUBY + + -> { eval(code) }.should.raise(SyntaxError) + end +end # TODO: Rewrite all the specs from here to the end of the file in the style # above. @@ -369,15 +388,15 @@ describe "Executing break from within a block" do -> do cls2.new.foo.should == 1 - end.should_not raise_error + end.should_not.raise end - it "raises LocalJumpError when converted into a proc during a a super call" do + it "raises LocalJumpError when converted into a proc during a super call" do cls1 = Class.new { def foo(&b); b; end } cls2 = Class.new(cls1) { def foo; super { break 1 }.call; end } -> do cls2.new.foo - end.should raise_error(LocalJumpError) + end.should.raise(LocalJumpError) end end diff --git a/spec/ruby/language/case_spec.rb b/spec/ruby/language/case_spec.rb index 410cb9afb1..8355062e44 100644 --- a/spec/ruby/language/case_spec.rb +++ b/spec/ruby/language/case_spec.rb @@ -94,38 +94,38 @@ describe "The 'case'-construct" do it "tests with matching regexps and sets $~ and captures" do case "foo42" when /oo(\d+)/ - $~.should be_kind_of(MatchData) + $~.should.is_a?(MatchData) $1.should == "42" else flunk end - $~.should be_kind_of(MatchData) + $~.should.is_a?(MatchData) $1.should == "42" end - it "tests with a regexp interpolated within another regexp" do + it "tests with a string interpolated in a regexp" do digits = '\d+' case "foo44" when /oo(#{digits})/ - $~.should be_kind_of(MatchData) + $~.should.is_a?(MatchData) $1.should == "44" else flunk end - $~.should be_kind_of(MatchData) + $~.should.is_a?(MatchData) $1.should == "44" end - it "tests with a string interpolated in a regexp" do + it "tests with a regexp interpolated within another regexp" do digits_regexp = /\d+/ case "foo43" when /oo(#{digits_regexp})/ - $~.should be_kind_of(MatchData) + $~.should.is_a?(MatchData) $1.should == "43" else flunk end - $~.should be_kind_of(MatchData) + $~.should.is_a?(MatchData) $1.should == "43" end @@ -156,6 +156,15 @@ describe "The 'case'-construct" do end.should == "foo" end + it "tests an empty array" do + case [] + when [] + 'foo' + else + 'bar' + end.should == 'foo' + end + it "expands arrays to lists of values" do case 'z' when *['a', 'b', 'c', 'd'] @@ -259,7 +268,7 @@ describe "The 'case'-construct" do true end CODE - }.should raise_error(SyntaxError) + }.should.raise(SyntaxError) end it "raises a SyntaxError when 'else' is used before a 'when' was given" do @@ -271,7 +280,7 @@ describe "The 'case'-construct" do when 4; false end CODE - }.should raise_error(SyntaxError) + }.should.raise(SyntaxError) end it "supports nested case statements" do @@ -320,49 +329,6 @@ describe "The 'case'-construct" do 100 end.should == 100 end -end - -describe "The 'case'-construct with no target expression" do - it "evaluates the body of the first clause when at least one of its condition expressions is true" do - case - when true, false; 'foo' - end.should == 'foo' - end - - it "evaluates the body of the first when clause that is not false/nil" do - case - when false; 'foo' - when 2; 'bar' - when 1 == 1; 'baz' - end.should == 'bar' - - case - when false; 'foo' - when nil; 'foo' - when 1 == 1; 'bar' - end.should == 'bar' - end - - it "evaluates the body of the else clause if all when clauses are false/nil" do - case - when false; 'foo' - when nil; 'foo' - when 1 == 2; 'bar' - else 'baz' - end.should == 'baz' - end - - it "evaluates multiple conditional expressions as a boolean disjunction" do - case - when true, false; 'foo' - else 'bar' - end.should == 'foo' - - case - when false, true; 'foo' - else 'bar' - end.should == 'foo' - end it "evaluates true as only 'true' when true is the first clause" do case 1 @@ -425,6 +391,104 @@ describe "The 'case'-construct with no target expression" do end.should == :called end + it "only matches last value in complex expressions within ()" do + case 'a' + when ('a'; 'b') + :wrong_called + when ('b'; 'a') + :called + end.should == :called + end + + it "supports declaring variables in the case target expression" do + def test(v) + case new_variable_in_expression = v + when true + # This extra block is a test that `new_variable_in_expression` is declared outside of it and not inside + self.then { new_variable_in_expression } + else + # Same + self.then { new_variable_in_expression.casecmp?("foo") } + end + end + + self.test("bar").should == false + self.test(true).should == true + end + + ruby_version_is ""..."3.4" do + it "warns if there are identical when clauses" do + -> { + eval <<~RUBY + case 1 + when 2 + :foo + when 2 + :bar + end + RUBY + }.should complain(/warning: (duplicated .when' clause with line \d+ is ignored|'when' clause on line \d+ duplicates 'when' clause on line \d+ and is ignored)/, verbose: true) + end + end + + ruby_version_is "3.4" do + it "warns if there are identical when clauses" do + -> { + eval <<~RUBY + case 1 + when 2 + :foo + when 2 + :bar + end + RUBY + }.should complain(/warning: 'when' clause on line \d+ duplicates 'when' clause on line \d+ and is ignored/, verbose: true) + end + end +end + +describe "The 'case'-construct with no target expression" do + it "evaluates the body of the first clause when at least one of its condition expressions is true" do + case + when true, false; 'foo' + end.should == 'foo' + end + + it "evaluates the body of the first when clause that is not false/nil" do + case + when false; 'foo' + when 2; 'bar' + when 1 == 1; 'baz' + end.should == 'bar' + + case + when false; 'foo' + when nil; 'foo' + when 1 == 1; 'bar' + end.should == 'bar' + end + + it "evaluates the body of the else clause if all when clauses are false/nil" do + case + when false; 'foo' + when nil; 'foo' + when 1 == 2; 'bar' + else 'baz' + end.should == 'baz' + end + + it "evaluates multiple conditional expressions as a boolean disjunction" do + case + when true, false; 'foo' + else 'bar' + end.should == 'foo' + + case + when false, true; 'foo' + else 'bar' + end.should == 'foo' + end + # Homogeneous cases are often optimized to avoid === using a jump table, and should be tested separately. # See https://github.com/jruby/jruby/issues/6440 it "handles homogeneous cases" do @@ -433,4 +497,13 @@ describe "The 'case'-construct with no target expression" do when 2; 'bar' end.should == 'foo' end + + it "expands arrays to lists of values" do + case + when *[false] + "foo" + when *[true] + "bar" + end.should == "bar" + end end diff --git a/spec/ruby/language/class_spec.rb b/spec/ruby/language/class_spec.rb index 83db164e1a..7ea4857514 100644 --- a/spec/ruby/language/class_spec.rb +++ b/spec/ruby/language/class_spec.rb @@ -10,19 +10,32 @@ end describe "The class keyword" do it "creates a new class with semicolon" do class ClassSpecsKeywordWithSemicolon; end - ClassSpecsKeywordWithSemicolon.should be_an_instance_of(Class) + ClassSpecsKeywordWithSemicolon.should.instance_of?(Class) end it "does not raise a SyntaxError when opening a class without a semicolon" do eval "class ClassSpecsKeywordWithoutSemicolon end" - ClassSpecsKeywordWithoutSemicolon.should be_an_instance_of(Class) + ClassSpecsKeywordWithoutSemicolon.should.instance_of?(Class) + end + + it "can redefine a class when called from a block" do + ClassSpecs::DEFINE_CLASS.call + A.should.instance_of?(Class) + + Object.send(:remove_const, :A) + defined?(A).should == nil + + ClassSpecs::DEFINE_CLASS.call + A.should.instance_of?(Class) + ensure + Object.send(:remove_const, :A) if defined?(::A) end end describe "A class definition" do it "creates a new class" do - ClassSpecs::A.should be_kind_of(Class) - ClassSpecs::A.new.should be_kind_of(ClassSpecs::A) + ClassSpecs::A.should.is_a?(Class) + ClassSpecs::A.new.should.is_a?(ClassSpecs::A) end it "has no class variables" do @@ -33,7 +46,14 @@ describe "A class definition" do -> { class ClassSpecsNumber end - }.should raise_error(TypeError) + }.should.raise(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(TypeError, /\AClassSpecs is not a class/) end # test case known to be detecting bugs (JRuby, MRI) @@ -41,19 +61,19 @@ describe "A class definition" do -> { class nil::Foo end - }.should raise_error(TypeError) + }.should.raise(TypeError) end it "raises TypeError if any constant qualifying the class is not a Module" do -> { class ClassSpecs::Number::MyClass end - }.should raise_error(TypeError) + }.should.raise(TypeError) -> { class ClassSpecsNumber::MyClass end - }.should raise_error(TypeError) + }.should.raise(TypeError) end it "inherits from Object by default" do @@ -67,7 +87,7 @@ describe "A class definition" do -> { class SuperclassResetToSubclass < M end - }.should raise_error(TypeError, /superclass mismatch/) + }.should.raise(TypeError, /superclass mismatch/) end end @@ -80,7 +100,7 @@ describe "A class definition" do -> { class SuperclassReopenedBasicObject < BasicObject end - }.should raise_error(TypeError, /superclass mismatch/) + }.should.raise(TypeError, /superclass mismatch/) SuperclassReopenedBasicObject.superclass.should == A end end @@ -95,7 +115,7 @@ describe "A class definition" do -> { class SuperclassReopenedObject < Object end - }.should raise_error(TypeError, /superclass mismatch/) + }.should.raise(TypeError, /superclass mismatch/) SuperclassReopenedObject.superclass.should == A end end @@ -120,7 +140,7 @@ describe "A class definition" do -> { class NoSuperclassSet < String end - }.should raise_error(TypeError, /superclass mismatch/) + }.should.raise(TypeError, /superclass mismatch/) end end @@ -129,7 +149,7 @@ describe "A class definition" do -> { class ShouldNotWork < self; end - }.should raise_error(TypeError) + }.should.raise(TypeError) end it "first evaluates the superclass before checking if the class already exists" do @@ -148,7 +168,7 @@ describe "A class definition" do it "raises a TypeError if inheriting from a metaclass" do obj = mock("metaclass super") meta = obj.singleton_class - -> { class ClassSpecs::MetaclassSuper < meta; end }.should raise_error(TypeError) + -> { class ClassSpecs::MetaclassSuper < meta; end }.should.raise(TypeError) end it "allows the declaration of class variables in the body" do @@ -157,7 +177,7 @@ describe "A class definition" do end it "stores instance variables defined in the class body in the class object" do - ClassSpecs.string_instance_variables(ClassSpecs::B).should include("@ivar") + ClassSpecs.string_instance_variables(ClassSpecs::B).should.include?("@ivar") ClassSpecs::B.instance_variable_get(:@ivar).should == :ivar end @@ -169,9 +189,9 @@ describe "A class definition" do end it "allows the definition of class-level instance variables in a class method" do - ClassSpecs.string_instance_variables(ClassSpecs::C).should_not include("@civ") + ClassSpecs.string_instance_variables(ClassSpecs::C).should_not.include?("@civ") ClassSpecs::C.make_class_instance_variable - ClassSpecs.string_instance_variables(ClassSpecs::C).should include("@civ") + ClassSpecs.string_instance_variables(ClassSpecs::C).should.include?("@civ") ClassSpecs::C.remove_instance_variable :@civ end @@ -258,13 +278,16 @@ describe "A class definition" do AnonWithConstant.name.should == 'AnonWithConstant' klass.get_class_name.should == 'AnonWithConstant' + ensure + Object.send(:remove_const, :AnonWithConstant) end end end describe "An outer class definition" do it "contains the inner classes" do - ClassSpecs::Container.constants.should include(:A, :B) + ClassSpecs::Container.constants.should.include?(:A) + ClassSpecs::Container.constants.should.include?(:B) end end @@ -282,33 +305,23 @@ describe "A class definition extending an object (sclass)" do end end CODE - }.should raise_error(TypeError) + }.should.raise(TypeError) end it "raises a TypeError when trying to extend non-Class" do error_msg = /superclass must be a.* Class/ - -> { class TestClass < ""; end }.should raise_error(TypeError, error_msg) - -> { class TestClass < 1; end }.should raise_error(TypeError, error_msg) - -> { class TestClass < :symbol; end }.should raise_error(TypeError, error_msg) - -> { class TestClass < mock('o'); end }.should raise_error(TypeError, error_msg) - -> { class TestClass < Module.new; end }.should raise_error(TypeError, error_msg) - -> { class TestClass < BasicObject.new; end }.should raise_error(TypeError, error_msg) - end - - ruby_version_is ""..."3.0" do - it "allows accessing the block of the original scope" do - suppress_warning do - ClassSpecs.sclass_with_block { 123 }.should == 123 - end - end + -> { class TestClass < ""; end }.should.raise(TypeError, error_msg) + -> { class TestClass < 1; end }.should.raise(TypeError, error_msg) + -> { class TestClass < :symbol; end }.should.raise(TypeError, error_msg) + -> { class TestClass < mock('o'); end }.should.raise(TypeError, error_msg) + -> { class TestClass < Module.new; end }.should.raise(TypeError, error_msg) + -> { class TestClass < BasicObject.new; end }.should.raise(TypeError, error_msg) end - ruby_version_is "3.0" do - it "does not allow accessing the block of the original scope" do - -> { - ClassSpecs.sclass_with_block { 123 } - }.should raise_error(SyntaxError) - end + it "does not allow accessing the block of the original scope" do + -> { + ClassSpecs.sclass_with_block { 123 } + }.should.raise(SyntaxError) end it "can use return to cause the enclosing method to return" do @@ -328,11 +341,11 @@ describe "Reopening a class" do end it "raises a TypeError when superclasses mismatch" do - -> { class ClassSpecs::A < Array; end }.should raise_error(TypeError) + -> { class ClassSpecs::A < Array; end }.should.raise(TypeError) end it "adds new methods to subclasses" do - -> { ClassSpecs::M.m }.should raise_error(NoMethodError) + -> { ClassSpecs::M.m }.should.raise(NoMethodError) class ClassSpecs::L def self.m 1 @@ -341,6 +354,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/class_variable_spec.rb b/spec/ruby/language/class_variable_spec.rb index dffab47a6b..84a7684c6d 100644 --- a/spec/ruby/language/class_variable_spec.rb +++ b/spec/ruby/language/class_variable_spec.rb @@ -30,19 +30,19 @@ describe "A class variable defined in a module" do end it "is not defined in these classes" do - ClassVariablesSpec::ClassC.cvar_defined?.should be_false + ClassVariablesSpec::ClassC.cvar_defined?.should == false end it "is only updated in the module a method defined in the module is used" do ClassVariablesSpec::ClassC.cvar_m = "new value" ClassVariablesSpec::ClassC.cvar_m.should == "new value" - ClassVariablesSpec::ClassC.cvar_defined?.should be_false + ClassVariablesSpec::ClassC.cvar_defined?.should == false end it "is updated in the class when a Method defined in the class is used" do ClassVariablesSpec::ClassC.cvar_c = "new value" - ClassVariablesSpec::ClassC.cvar_defined?.should be_true + ClassVariablesSpec::ClassC.cvar_defined?.should == true end it "can be accessed inside the class using the module methods" do @@ -55,11 +55,11 @@ describe "A class variable defined in a module" do end it "is defined in the extended module" do - ClassVariablesSpec::ModuleN.class_variable_defined?(:@@cvar_n).should be_true + ClassVariablesSpec::ModuleN.class_variable_defined?(:@@cvar_n).should == true end it "is not defined in the extending module" do - ClassVariablesSpec::ModuleO.class_variable_defined?(:@@cvar_n).should be_false + ClassVariablesSpec::ModuleO.class_variable_defined?(:@@cvar_n).should == false end end @@ -70,15 +70,45 @@ describe 'A class variable definition' do c = Class.new(b) b.class_variable_set(:@@cv, :value) - -> { a.class_variable_get(:@@cv) }.should raise_error(NameError) + -> { a.class_variable_get(:@@cv) }.should.raise(NameError) b.class_variable_get(:@@cv).should == :value c.class_variable_get(:@@cv).should == :value # updates the same variable c.class_variable_set(:@@cv, :next) - -> { a.class_variable_get(:@@cv) }.should raise_error(NameError) + -> { a.class_variable_get(:@@cv) }.should.raise(NameError) b.class_variable_get(:@@cv).should == :next c.class_variable_get(:@@cv).should == :next end end + +describe 'Accessing a class variable' do + it "raises a RuntimeError when accessed from the toplevel scope (not in some module or class)" do + -> { + eval "@@cvar_toplevel1" + }.should.raise(RuntimeError, 'class variable access from toplevel') + -> { + eval "@@cvar_toplevel2 = 2" + }.should.raise(RuntimeError, 'class variable access from toplevel') + end + + it "does not raise an error when checking if defined from the toplevel scope" do + -> { + eval "defined?(@@cvar_toplevel1)" + }.should_not.raise + end + + it "raises a RuntimeError when a class variable is overtaken in an ancestor class" do + parent = Class.new() + subclass = Class.new(parent) + subclass.class_variable_set(:@@cvar_overtaken, :subclass) + parent.class_variable_set(:@@cvar_overtaken, :parent) + + -> { + subclass.class_variable_get(:@@cvar_overtaken) + }.should.raise(RuntimeError, /class variable @@cvar_overtaken of .+ is overtaken by .+/) + + parent.class_variable_get(:@@cvar_overtaken).should == :parent + end +end diff --git a/spec/ruby/language/comment_spec.rb b/spec/ruby/language/comment_spec.rb index 690e844dc0..dd788e681c 100644 --- a/spec/ruby/language/comment_spec.rb +++ b/spec/ruby/language/comment_spec.rb @@ -1,15 +1,13 @@ require_relative '../spec_helper' describe "The comment" do - ruby_version_is "2.7" do - it "can be placed between fluent dot now" do - code = <<~CODE - 10 - # some comment - .to_s - CODE + it "can be placed between fluent dot now" do + code = <<~CODE + 10 + # some comment + .to_s + CODE - eval(code).should == '10' - end + eval(code).should == '10' end end diff --git a/spec/ruby/language/constants_spec.rb b/spec/ruby/language/constants_spec.rb index 4d46cf2f84..0880230a36 100644 --- a/spec/ruby/language/constants_spec.rb +++ b/spec/ruby/language/constants_spec.rb @@ -51,8 +51,8 @@ describe "Literal (A::X) constant resolution" do it "does not search the singleton class of the class or module" do -> do ConstantSpecs::ContainerA::ChildA::CS_CONST14 - end.should raise_error(NameError) - -> { ConstantSpecs::CS_CONST14 }.should raise_error(NameError) + end.should.raise(NameError) + -> { ConstantSpecs::CS_CONST14 }.should.raise(NameError) end end @@ -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 @@ -114,7 +135,7 @@ describe "Literal (A::X) constant resolution" do -> do ConstantSpecs::ContainerB::ChildB::CS_CONST108 - end.should raise_error(NameError) + end.should.raise(NameError) module ConstantSpecs class << self @@ -122,7 +143,10 @@ describe "Literal (A::X) constant resolution" do end end - -> { ConstantSpecs::CS_CONST108 }.should raise_error(NameError) + -> { ConstantSpecs::CS_CONST108 }.should.raise(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,55 +157,53 @@ 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 - it "evaluates the right hand side before evaluating a constant path" do + it "evaluates left-to-right" do mod = Module.new mod.module_eval <<-EOC - ConstantSpecsRHS::B = begin - module ConstantSpecsRHS; end - - "hello" - end + order = [] + ConstantSpecsRHS = Module.new + (order << :lhs; ConstantSpecsRHS)::B = (order << :rhs) EOC - mod::ConstantSpecsRHS::B.should == 'hello' + mod::ConstantSpecsRHS::B.should == [:lhs, :rhs] end end it "raises a NameError if no constant is defined in the search path" do - -> { ConstantSpecs::ParentA::CS_CONSTX }.should raise_error(NameError) + -> { ConstantSpecs::ParentA::CS_CONSTX }.should.raise(NameError) end - ruby_version_is "3.0" do - it "uses the module or class #name to craft the error message" do - mod = Module.new do - def self.name - "ModuleName" - end - - def self.inspect - "<unusable info>" - end + it "uses the module or class #name to craft the error message" do + mod = Module.new do + def self.name + "ModuleName" end - -> { mod::DOES_NOT_EXIST }.should raise_error(NameError, /uninitialized constant ModuleName::DOES_NOT_EXIST/) + def self.inspect + "<unusable info>" + end end - it "uses the module or class #inspect to craft the error message if they are anonymous" do - mod = Module.new do - def self.name - nil - end + -> { mod::DOES_NOT_EXIST }.should.raise(NameError, /uninitialized constant ModuleName::DOES_NOT_EXIST/) + end - def self.inspect - "<unusable info>" - end + it "uses the module or class #inspect to craft the error message if they are anonymous" do + mod = Module.new do + def self.name + nil end - -> { mod::DOES_NOT_EXIST }.should raise_error(NameError, /uninitialized constant <unusable info>::DOES_NOT_EXIST/) + def self.inspect + "<unusable info>" + end end + + -> { mod::DOES_NOT_EXIST }.should.raise(NameError, /uninitialized constant <unusable info>::DOES_NOT_EXIST/) end it "sends #const_missing to the original class or module scope" do @@ -193,10 +215,10 @@ describe "Literal (A::X) constant resolution" do end it "raises a TypeError if a non-class or non-module qualifier is given" do - -> { CS_CONST1::CS_CONST }.should raise_error(TypeError) - -> { 1::CS_CONST }.should raise_error(TypeError) - -> { "mod"::CS_CONST }.should raise_error(TypeError) - -> { false::CS_CONST }.should raise_error(TypeError) + -> { CS_CONST1::CS_CONST }.should.raise(TypeError) + -> { 1::CS_CONST }.should.raise(TypeError) + -> { "mod"::CS_CONST }.should.raise(TypeError) + -> { false::CS_CONST }.should.raise(TypeError) end end @@ -242,7 +264,7 @@ describe "Constant resolution within methods" do end it "does not search the lexical scope of the caller" do - -> { ConstantSpecs::ClassA.const16 }.should raise_error(NameError) + -> { ConstantSpecs::ClassA.const16 }.should.raise(NameError) end it "searches the lexical scope of a block" do @@ -257,7 +279,7 @@ describe "Constant resolution within methods" do it "does not search the lexical scope of qualifying modules" do -> do ConstantSpecs::ContainerA::ChildA.const23 - end.should raise_error(NameError) + end.should.raise(NameError) end end @@ -278,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 @@ -286,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 @@ -294,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 @@ -302,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 @@ -309,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 @@ -320,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 @@ -327,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) + -> { ConstantSpecs::ClassB.const209 }.should.raise(NameError) + ensure + ConstantSpecs::ClassB.send(:remove_const, :CS_CONST209) end it "searches the lexical scope of a block" do @@ -340,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 @@ -350,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 @@ -362,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 @@ -369,64 +426,20 @@ describe "Constant resolution within methods" do -> do ConstantSpecs::ContainerB::ChildB.const214 - end.should raise_error(NameError) + end.should.raise(NameError) + ensure + ConstantSpecs::ContainerB.send(:remove_const, :CS_CONST214) end end it "raises a NameError if no constant is defined in the search path" do - -> { ConstantSpecs::ParentA.constx }.should raise_error(NameError) + -> { ConstantSpecs::ParentA.constx }.should.raise(NameError) end it "sends #const_missing to the original class or module scope" do ConstantSpecs::ClassA.constx.should == :CS_CONSTX ConstantSpecs::ClassA.new.constx.should == :CS_CONSTX end - - describe "with ||=" do - it "assigns a scoped constant if previously undefined" do - ConstantSpecs.should_not have_constant(:OpAssignUndefined) - module ConstantSpecs - OpAssignUndefined ||= 42 - end - ConstantSpecs::OpAssignUndefined.should == 42 - ConstantSpecs::OpAssignUndefinedOutside ||= 42 - ConstantSpecs::OpAssignUndefinedOutside.should == 42 - ConstantSpecs.send(:remove_const, :OpAssignUndefined) - ConstantSpecs.send(:remove_const, :OpAssignUndefinedOutside) - end - - it "assigns a global constant if previously undefined" do - OpAssignGlobalUndefined ||= 42 - ::OpAssignGlobalUndefinedExplicitScope ||= 42 - OpAssignGlobalUndefined.should == 42 - ::OpAssignGlobalUndefinedExplicitScope.should == 42 - Object.send :remove_const, :OpAssignGlobalUndefined - Object.send :remove_const, :OpAssignGlobalUndefinedExplicitScope - end - - end - - describe "with &&=" do - it "re-assigns a scoped constant if already true" do - module ConstantSpecs - OpAssignTrue = true - end - suppress_warning do - ConstantSpecs::OpAssignTrue &&= 1 - end - ConstantSpecs::OpAssignTrue.should == 1 - ConstantSpecs.send :remove_const, :OpAssignTrue - end - - it "leaves scoped constant if not true" do - module ConstantSpecs - OpAssignFalse = false - end - ConstantSpecs::OpAssignFalse &&= 1 - ConstantSpecs::OpAssignFalse.should == false - ConstantSpecs.send :remove_const, :OpAssignFalse - end - end end describe "Constant resolution within a singleton class (class << obj)" do @@ -443,7 +456,7 @@ describe "Constant resolution within a singleton class (class << obj)" do it "uses its own namespace for nested modules" do a = ConstantSpecs::CS_SINGLETON3[0].x b = ConstantSpecs::CS_SINGLETON3[1].x - a.should_not equal(b) + a.should_not.equal?(b) end it "allows nested modules to have proper resolution" do @@ -456,12 +469,12 @@ end describe "top-level constant lookup" do context "on a class" do it "does not search Object after searching other scopes" do - -> { String::Hash }.should raise_error(NameError) + -> { String::Hash }.should.raise(NameError) end end it "searches Object unsuccessfully when searches on a module" do - -> { Enumerable::Hash }.should raise_error(NameError) + -> { Enumerable::Hash }.should.raise(NameError) end end @@ -475,39 +488,37 @@ describe "Module#private_constant marked constants" do mod.const_set :Foo, false }.should complain(/already initialized constant/) - -> {mod::Foo}.should raise_error(NameError) + -> {mod::Foo}.should.raise(NameError) end - ruby_version_is "2.6" do - it "sends #const_missing to the original class or module" do - mod = Module.new - mod.const_set :Foo, true - mod.send :private_constant, :Foo - def mod.const_missing(name) - name == :Foo ? name : super - end - - mod::Foo.should == :Foo + it "sends #const_missing to the original class or module" do + mod = Module.new + mod.const_set :Foo, true + mod.send :private_constant, :Foo + def mod.const_missing(name) + name == :Foo ? name : super end + + mod::Foo.should == :Foo end describe "in a module" do it "cannot be accessed from outside the module" do -> do ConstantVisibility::PrivConstModule::PRIVATE_CONSTANT_MODULE - end.should raise_error(NameError) + end.should.raise(NameError) end it "cannot be reopened as a module from scope where constant would be private" do -> do module ConstantVisibility::ModuleContainer::PrivateModule; end - end.should raise_error(NameError) + end.should.raise(NameError) end it "cannot be reopened as a class from scope where constant would be private" do -> do class ConstantVisibility::ModuleContainer::PrivateClass; end - end.should raise_error(NameError) + end.should.raise(NameError) end it "can be reopened as a module where constant is not private" do @@ -518,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 @@ -528,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 @@ -535,7 +554,7 @@ describe "Module#private_constant marked constants" do end it "can be accessed from the module itself" do - ConstantVisibility::PrivConstModule.private_constant_from_self.should be_true + ConstantVisibility::PrivConstModule.private_constant_from_self.should == true end it "is defined? from the module itself" do @@ -543,7 +562,7 @@ describe "Module#private_constant marked constants" do end it "can be accessed from lexical scope" do - ConstantVisibility::PrivConstModule::Nested.private_constant_from_scope.should be_true + ConstantVisibility::PrivConstModule::Nested.private_constant_from_scope.should == true end it "is defined? from lexical scope" do @@ -551,11 +570,24 @@ describe "Module#private_constant marked constants" do end it "can be accessed from classes that include the module" do - ConstantVisibility::PrivConstModuleChild.new.private_constant_from_include.should be_true + ConstantVisibility::ClassIncludingPrivConstModule.new.private_constant_from_include.should == true + end + + it "can be accessed from modules that include the module" do + ConstantVisibility::ModuleIncludingPrivConstModule.private_constant_from_include.should == true + end + + it "raises a NameError when accessed directly from modules that include the module" do + -> do + ConstantVisibility::ModuleIncludingPrivConstModule.private_constant_self_from_include + end.should.raise(NameError) + -> do + ConstantVisibility::ModuleIncludingPrivConstModule.private_constant_named_from_include + end.should.raise(NameError) end it "is defined? from classes that include the module" do - ConstantVisibility::PrivConstModuleChild.new.defined_from_include.should == "constant" + ConstantVisibility::ClassIncludingPrivConstModule.new.defined_from_include.should == "constant" end end @@ -563,19 +595,19 @@ describe "Module#private_constant marked constants" do it "cannot be accessed from outside the class" do -> do ConstantVisibility::PrivConstClass::PRIVATE_CONSTANT_CLASS - end.should raise_error(NameError) + end.should.raise(NameError) end it "cannot be reopened as a module" do -> do module ConstantVisibility::ClassContainer::PrivateModule; end - end.should raise_error(NameError) + end.should.raise(NameError) end it "cannot be reopened as a class" do -> do class ConstantVisibility::ClassContainer::PrivateClass; end - end.should raise_error(NameError) + end.should.raise(NameError) end it "can be reopened as a module where constant is not private" do @@ -586,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 @@ -596,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 @@ -603,7 +643,7 @@ describe "Module#private_constant marked constants" do end it "can be accessed from the class itself" do - ConstantVisibility::PrivConstClass.private_constant_from_self.should be_true + ConstantVisibility::PrivConstClass.private_constant_from_self.should == true end it "is defined? from the class itself" do @@ -611,7 +651,7 @@ describe "Module#private_constant marked constants" do end it "can be accessed from lexical scope" do - ConstantVisibility::PrivConstClass::Nested.private_constant_from_scope.should be_true + ConstantVisibility::PrivConstClass::Nested.private_constant_from_scope.should == true end it "is defined? from lexical scope" do @@ -619,7 +659,7 @@ describe "Module#private_constant marked constants" do end it "can be accessed from subclasses" do - ConstantVisibility::PrivConstClassChild.new.private_constant_from_subclass.should be_true + ConstantVisibility::PrivConstClassChild.new.private_constant_from_subclass.should == true end it "is defined? from subclasses" do @@ -631,7 +671,7 @@ describe "Module#private_constant marked constants" do it "cannot be accessed using ::Const form" do -> do ::PRIVATE_CONSTANT_IN_OBJECT - end.should raise_error(NameError) + end.should.raise(NameError) end it "is not defined? using ::Const form" do @@ -651,14 +691,14 @@ describe "Module#private_constant marked constants" do it "has :receiver and :name attributes" do -> do ConstantVisibility::PrivConstClass::PRIVATE_CONSTANT_CLASS - end.should raise_error(NameError) {|e| + end.should.raise(NameError) {|e| e.receiver.should == ConstantVisibility::PrivConstClass e.name.should == :PRIVATE_CONSTANT_CLASS } -> do ConstantVisibility::PrivConstModule::PRIVATE_CONSTANT_MODULE - end.should raise_error(NameError) {|e| + end.should.raise(NameError) {|e| e.receiver.should == ConstantVisibility::PrivConstModule e.name.should == :PRIVATE_CONSTANT_MODULE } @@ -667,14 +707,14 @@ describe "Module#private_constant marked constants" do it "has the defined class as the :name attribute" do -> do ConstantVisibility::PrivConstClassChild::PRIVATE_CONSTANT_CLASS - end.should raise_error(NameError) {|e| + end.should.raise(NameError) {|e| e.receiver.should == ConstantVisibility::PrivConstClass e.name.should == :PRIVATE_CONSTANT_CLASS } -> do - ConstantVisibility::PrivConstModuleChild::PRIVATE_CONSTANT_MODULE - end.should raise_error(NameError) {|e| + ConstantVisibility::ClassIncludingPrivConstModule::PRIVATE_CONSTANT_MODULE + end.should.raise(NameError) {|e| e.receiver.should == ConstantVisibility::PrivConstModule e.name.should == :PRIVATE_CONSTANT_MODULE } @@ -743,23 +783,27 @@ describe 'Allowed characters' do it 'does not allow not ASCII characters that cannot be upcased or lowercased at the beginning' do -> do Module.new.const_set("થBB", 1) - end.should raise_error(NameError, /wrong constant name/) + end.should.raise(NameError, /wrong constant name/) end - ruby_version_is ""..."2.6" do - it 'does not allow not ASCII upcased characters at the beginning' do - -> do - Module.new.const_set("ἍBB", 1) - end.should raise_error(NameError, /wrong constant name/) - end - end + it 'allows not ASCII upcased characters at the beginning' do + mod = Module.new + mod.const_set("ἍBB", 1) - ruby_version_is "2.6" do - it 'allows not ASCII upcased characters at the beginning' do - mod = Module.new - mod.const_set("ἍBB", 1) + eval("mod::ἍBB").should == 1 + end +end - eval("mod::ἍBB").should == 1 +describe 'Assignment' do + context 'dynamic assignment' do + it 'raises SyntaxError' do + -> do + eval <<-CODE + def test + B = 1 + end + CODE + end.should.raise(SyntaxError, /dynamic constant assignment/) end end end diff --git a/spec/ruby/language/def_spec.rb b/spec/ruby/language/def_spec.rb index 6b0be19d90..82c89a0d08 100644 --- a/spec/ruby/language/def_spec.rb +++ b/spec/ruby/language/def_spec.rb @@ -14,11 +14,11 @@ end describe "Defining a method at the top-level" do it "defines it on Object with private visibility by default" do - Object.should have_private_instance_method(:some_toplevel_method, false) + Object.private_instance_methods(false).should.include?(:some_toplevel_method) end it "defines it on Object with public visibility after calling public" do - Object.should have_public_instance_method(:public_toplevel_method, false) + Object.public_instance_methods(false).should.include?(:public_toplevel_method) end end @@ -28,7 +28,7 @@ describe "Defining an 'initialize' method" do def initialize end end - DefInitializeSpec.should have_private_instance_method(:initialize, false) + DefInitializeSpec.private_instance_methods(false).should.include?(:initialize) end end @@ -38,7 +38,7 @@ describe "Defining an 'initialize_copy' method" do def initialize_copy end end - DefInitializeCopySpec.should have_private_instance_method(:initialize_copy, false) + DefInitializeCopySpec.private_instance_methods(false).should.include?(:initialize_copy) end end @@ -48,7 +48,7 @@ describe "Defining an 'initialize_dup' method" do def initialize_dup end end - DefInitializeDupSpec.should have_private_instance_method(:initialize_dup, false) + DefInitializeDupSpec.private_instance_methods(false).should.include?(:initialize_dup) end end @@ -58,7 +58,7 @@ describe "Defining an 'initialize_clone' method" do def initialize_clone end end - DefInitializeCloneSpec.should have_private_instance_method(:initialize_clone, false) + DefInitializeCloneSpec.private_instance_methods(false).should.include?(:initialize_clone) end end @@ -68,7 +68,7 @@ describe "Defining a 'respond_to_missing?' method" do def respond_to_missing? end end - DefRespondToMissingPSpec.should have_private_instance_method(:respond_to_missing?, false) + DefRespondToMissingPSpec.private_instance_methods(false).should.include?(:respond_to_missing?) end end @@ -82,12 +82,12 @@ end describe "An instance method" do it "raises an error with too few arguments" do def foo(a, b); end - -> { foo 1 }.should raise_error(ArgumentError, 'wrong number of arguments (given 1, expected 2)') + -> { foo 1 }.should.raise(ArgumentError, 'wrong number of arguments (given 1, expected 2)') end it "raises an error with too many arguments" do def foo(a); end - -> { foo 1, 2 }.should raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 1)') + -> { foo 1, 2 }.should.raise(ArgumentError, 'wrong number of arguments (given 2, expected 1)') end it "raises FrozenError with the correct class name" do @@ -96,8 +96,9 @@ describe "An instance method" do self.freeze def foo; end end - }.should raise_error(FrozenError) { |e| - e.message.should.start_with? "can't modify frozen module" + }.should.raise(FrozenError) { |e| + msg_class = ruby_version_is("4.0") ? "Module" : "module" + e.message.should == "can't modify frozen #{msg_class}: #{e.receiver}" } -> { @@ -105,8 +106,9 @@ describe "An instance method" do self.freeze def foo; end end - }.should raise_error(FrozenError){ |e| - e.message.should.start_with? "can't modify frozen class" + }.should.raise(FrozenError){ |e| + msg_class = ruby_version_is("4.0") ? "Class" : "class" + e.message.should == "can't modify frozen #{msg_class}: #{e.receiver}" } end end @@ -133,12 +135,12 @@ describe "An instance method definition with a splat" do end it "allows only a single * argument" do - -> { eval 'def foo(a, *b, *c); end' }.should raise_error(SyntaxError) + -> { eval 'def foo(a, *b, *c); end' }.should.raise(SyntaxError) end it "requires the presence of any arguments that precede the *" do def foo(a, b, *c); end - -> { foo 1 }.should raise_error(ArgumentError, 'wrong number of arguments (given 1, expected 2+)') + -> { foo 1 }.should.raise(ArgumentError, 'wrong number of arguments (given 1, expected 2+)') end end @@ -171,7 +173,7 @@ describe "An instance method with a default argument" do def foo(a, b = 2) [a,b] end - -> { foo }.should raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1..2)') + -> { foo }.should.raise(ArgumentError, 'wrong number of arguments (given 0, expected 1..2)') foo(1).should == [1, 2] end @@ -179,7 +181,7 @@ describe "An instance method with a default argument" do def foo(a, b = 2, *c) [a,b,c] end - -> { foo }.should raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1+)') + -> { foo }.should.raise(ArgumentError, 'wrong number of arguments (given 0, expected 1+)') foo(1).should == [1,2,[]] end @@ -197,31 +199,24 @@ describe "An instance method with a default argument" do foo(2,3,3).should == [2,3,[3]] end - ruby_version_is ''...'2.7' do - it "warns and uses a nil value when there is an existing local method with same name" do - def bar - 1 - end + ruby_version_is ""..."3.4" do + it "raises a SyntaxError if using the argument in its default value" do -> { eval "def foo(bar = bar) bar end" - }.should complain(/circular argument reference/) - foo.should == nil - foo(2).should == 2 + }.should.raise(SyntaxError) end end - ruby_version_is '2.7' do - it "raises a syntaxError an existing method with the same name as the local variable" do - def bar - 1 - end + ruby_version_is "3.4" do + it "is nil if using the argument in its default value" do -> { eval "def foo(bar = bar) bar - end" - }.should raise_error(SyntaxError) + end + foo" + }.call.should == nil end end @@ -255,7 +250,7 @@ describe "A singleton method definition" do end it "can be declared for a global variable" do - $__a__ = "hi" + $__a__ = +"hi" def $__a__.foo 7 end @@ -284,26 +279,27 @@ describe "A singleton method definition" do it "raises FrozenError if frozen" do obj = Object.new obj.freeze - -> { def obj.foo; end }.should raise_error(FrozenError) + -> { def obj.foo; end }.should.raise(FrozenError) end 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(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(FrozenError, "can't modify frozen Class: #{c}") + + c = Class.new + c.freeze + -> { def c.foo; end }.should.raise(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(FrozenError, "can't modify frozen Module: #{m}") end end @@ -438,7 +434,7 @@ describe "A method definition inside a metaclass scope" do end DefSpecSingleton.a_class_method.should == DefSpecSingleton - -> { Object.a_class_method }.should raise_error(NoMethodError) + -> { Object.a_class_method }.should.raise(NoMethodError) end it "can create a singleton method" do @@ -448,7 +444,7 @@ describe "A method definition inside a metaclass scope" do end obj.a_singleton_method.should == obj - -> { Object.new.a_singleton_method }.should raise_error(NoMethodError) + -> { Object.new.a_singleton_method }.should.raise(NoMethodError) end it "raises FrozenError if frozen" do @@ -456,7 +452,7 @@ describe "A method definition inside a metaclass scope" do obj.freeze class << obj - -> { def foo; end }.should raise_error(FrozenError) + -> { def foo; end }.should.raise(FrozenError) end end end @@ -477,7 +473,7 @@ describe "A nested method definition" do other = DefSpecNested.new other.an_instance_method.should == other - DefSpecNested.should have_instance_method(:an_instance_method) + DefSpecNested.should.method_defined?(:an_instance_method, false) end it "creates a class method when evaluated in a class method" do @@ -492,11 +488,11 @@ describe "A nested method definition" do end end - -> { DefSpecNested.a_class_method }.should raise_error(NoMethodError) + -> { DefSpecNested.a_class_method }.should.raise(NoMethodError) DefSpecNested.create_class_method.should == DefSpecNested DefSpecNested.a_class_method.should == DefSpecNested - -> { Object.a_class_method }.should raise_error(NoMethodError) - -> { DefSpecNested.new.a_class_method }.should raise_error(NoMethodError) + -> { Object.a_class_method }.should.raise(NoMethodError) + -> { DefSpecNested.new.a_class_method }.should.raise(NoMethodError) end it "creates a singleton method when evaluated in the metaclass of an instance" do @@ -514,7 +510,7 @@ describe "A nested method definition" do obj.a_singleton_method.should == obj other = DefSpecNested.new - -> { other.a_singleton_method }.should raise_error(NoMethodError) + -> { other.a_singleton_method }.should.raise(NoMethodError) end it "creates a method in the surrounding context when evaluated in a def expr.method" do @@ -526,11 +522,13 @@ describe "A nested method definition" do end DefSpecNested::TARGET.defs_method - DefSpecNested.should have_instance_method :inherited_method - DefSpecNested::TARGET.should_not have_method :inherited_method + DefSpecNested.should.method_defined?(:inherited_method, false) + DefSpecNested::TARGET.should_not.respond_to? :inherited_method obj = DefSpecNested.new obj.inherited_method.should == obj + ensure + DefSpecNested.send(:remove_const, :TARGET) end # See http://yugui.jp/articles/846#label-3 @@ -547,11 +545,13 @@ describe "A nested method definition" do obj = DefSpecNested::OBJ obj.create_method_in_instance_eval - obj.should have_method :arg_method - obj.should have_method :body_method + obj.should.respond_to? :arg_method + obj.should.respond_to? :body_method - DefSpecNested.should_not have_instance_method :arg_method - DefSpecNested.should_not have_instance_method :body_method + DefSpecNested.should_not.method_defined? :arg_method + DefSpecNested.should_not.method_defined? :body_method + ensure + DefSpecNested.send(:remove_const, :OBJ) end it "creates an instance method inside Class.new" do @@ -569,7 +569,7 @@ describe "A nested method definition" do cls.new.new_def.should == 1 - -> { Object.new.new_def }.should raise_error(NoMethodError) + -> { Object.new.new_def }.should.raise(NoMethodError) end end @@ -585,18 +585,18 @@ describe "A method definition always resets the visibility to public for nested end obj = cls.new - -> { obj.do_def }.should raise_error(NoMethodError, /private/) + -> { obj.do_def }.should.raise(NoMethodError, /private/) obj.send :do_def obj.new_def.should == 1 cls.new.new_def.should == 1 - -> { Object.new.new_def }.should raise_error(NoMethodError) + -> { Object.new.new_def }.should.raise(NoMethodError) end it "at the toplevel" do obj = Object.new - -> { obj.toplevel_define_other_method }.should raise_error(NoMethodError, /private/) + -> { obj.toplevel_define_other_method }.should.raise(NoMethodError, /private/) toplevel_define_other_method nested_method_in_toplevel_method.should == 42 @@ -613,7 +613,7 @@ describe "A method definition inside an instance_eval" do obj.an_instance_eval_method.should == obj other = Object.new - -> { other.an_instance_eval_method }.should raise_error(NoMethodError) + -> { other.an_instance_eval_method }.should.raise(NoMethodError) end it "creates a singleton method when evaluated inside a metaclass" do @@ -626,7 +626,7 @@ describe "A method definition inside an instance_eval" do obj.a_metaclass_eval_method.should == obj other = Object.new - -> { other.a_metaclass_eval_method }.should raise_error(NoMethodError) + -> { other.a_metaclass_eval_method }.should.raise(NoMethodError) end it "creates a class method when the receiver is a class" do @@ -635,7 +635,7 @@ describe "A method definition inside an instance_eval" do end DefSpecNested.an_instance_eval_class_method.should == DefSpecNested - -> { Object.an_instance_eval_class_method }.should raise_error(NoMethodError) + -> { Object.an_instance_eval_class_method }.should.raise(NoMethodError) end it "creates a class method when the receiver is an anonymous class" do @@ -647,7 +647,7 @@ describe "A method definition inside an instance_eval" do end m.klass_method.should == :test - -> { Object.klass_method }.should raise_error(NoMethodError) + -> { Object.klass_method }.should.raise(NoMethodError) end it "creates a class method when instance_eval is within class" do @@ -660,7 +660,7 @@ describe "A method definition inside an instance_eval" do end m.klass_method.should == :test - -> { Object.klass_method }.should raise_error(NoMethodError) + -> { Object.klass_method }.should.raise(NoMethodError) end end @@ -673,7 +673,7 @@ describe "A method definition inside an instance_exec" do end DefSpecNested.an_instance_exec_class_method.should == 1 - -> { Object.an_instance_exec_class_method }.should raise_error(NoMethodError) + -> { Object.an_instance_exec_class_method }.should.raise(NoMethodError) end it "creates a class method when the receiver is an anonymous class" do @@ -687,7 +687,7 @@ describe "A method definition inside an instance_exec" do end m.klass_method.should == 1 - -> { Object.klass_method }.should raise_error(NoMethodError) + -> { Object.klass_method }.should.raise(NoMethodError) end it "creates a class method when instance_exec is within class" do @@ -702,7 +702,7 @@ describe "A method definition inside an instance_exec" do end m.klass_method.should == 2 - -> { Object.klass_method }.should raise_error(NoMethodError) + -> { Object.klass_method }.should.raise(NoMethodError) end end @@ -722,7 +722,7 @@ describe "A method definition in an eval" do other = DefSpecNested.new other.an_eval_instance_method.should == other - -> { Object.new.an_eval_instance_method }.should raise_error(NoMethodError) + -> { Object.new.an_eval_instance_method }.should.raise(NoMethodError) end it "creates a class method" do @@ -738,8 +738,8 @@ describe "A method definition in an eval" do DefSpecNestedB.eval_class_method.should == DefSpecNestedB DefSpecNestedB.an_eval_class_method.should == DefSpecNestedB - -> { Object.an_eval_class_method }.should raise_error(NoMethodError) - -> { DefSpecNestedB.new.an_eval_class_method}.should raise_error(NoMethodError) + -> { Object.an_eval_class_method }.should.raise(NoMethodError) + -> { DefSpecNestedB.new.an_eval_class_method}.should.raise(NoMethodError) end it "creates a singleton method" do @@ -757,7 +757,7 @@ describe "A method definition in an eval" do obj.an_eval_singleton_method.should == obj other = DefSpecNested.new - -> { other.an_eval_singleton_method }.should raise_error(NoMethodError) + -> { other.an_eval_singleton_method }.should.raise(NoMethodError) end end @@ -768,8 +768,8 @@ describe "a method definition that sets more than one default parameter all to t it "assigns them all the same object by default" do foo.should == [{},{},{}] a, b, c = foo - a.should eql(b) - a.should eql(c) + a.should.eql?(b) + a.should.eql?(c) end it "allows the first argument to be given, and sets the rest to null" do @@ -779,11 +779,11 @@ describe "a method definition that sets more than one default parameter all to t it "assigns the parameters different objects across different default calls" do a, _b, _c = foo d, _e, _f = foo - a.should_not equal(d) + a.should_not.equal?(d) end it "only allows overriding the default value of the first such parameter in each set" do - -> { foo(1,2) }.should raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 0..1)') + -> { foo(1,2) }.should.raise(ArgumentError, 'wrong number of arguments (given 2, expected 0..1)') end def bar(a=b=c=1,d=2) @@ -794,7 +794,7 @@ describe "a method definition that sets more than one default parameter all to t bar.should == [1,1,1,2] bar(3).should == [3,nil,nil,2] bar(3,4).should == [3,nil,nil,4] - -> { bar(3,4,5) }.should raise_error(ArgumentError, 'wrong number of arguments (given 3, expected 0..2)') + -> { bar(3,4,5) }.should.raise(ArgumentError, 'wrong number of arguments (given 3, expected 0..2)') end end @@ -809,7 +809,7 @@ describe "The def keyword" do }.call end - DefSpecsLambdaVisibility.should have_private_instance_method("some_method") + DefSpecsLambdaVisibility.private_instance_methods(false).should.include?(:some_method) end end end diff --git a/spec/ruby/language/defined_spec.rb b/spec/ruby/language/defined_spec.rb index f2da8a9293..3fd611d09e 100644 --- a/spec/ruby/language/defined_spec.rb +++ b/spec/ruby/language/defined_spec.rb @@ -5,21 +5,25 @@ describe "The defined? keyword for literals" do it "returns 'self' for self" do ret = defined?(self) ret.should == "self" + ret.frozen?.should == true end it "returns 'nil' for nil" do ret = defined?(nil) ret.should == "nil" + ret.frozen?.should == true end it "returns 'true' for true" do ret = defined?(true) ret.should == "true" + ret.frozen?.should == true end it "returns 'false' for false" do ret = defined?(false) ret.should == "false" + ret.frozen?.should == true end describe "for a literal Array" do @@ -27,6 +31,7 @@ describe "The defined? keyword for literals" do it "returns 'expression' if each element is defined" do ret = defined?([Object, Array]) ret.should == "expression" + ret.frozen?.should == true end it "returns nil if one element is not defined" do @@ -43,13 +48,29 @@ describe "The defined? keyword for literals" do end describe "The defined? keyword when called with a method name" do + before :each do + ScratchPad.clear + end + + it "does not call the method" do + defined?(DefinedSpecs.side_effects).should == "method" + ScratchPad.recorded.should != :defined_specs_side_effects + end + + it "does not execute the arguments" do + defined?(DefinedSpecs.any_args(DefinedSpecs.side_effects)).should == "method" + ScratchPad.recorded.should != :defined_specs_side_effects + end + describe "without a receiver" do it "returns 'method' if the method is defined" do - defined?(puts).should == "method" + ret = defined?(puts) + ret.should == "method" + ret.frozen?.should == true end it "returns nil if the method is not defined" do - defined?(defined_specs_undefined_method).should be_nil + defined?(defined_specs_undefined_method).should == nil end it "returns 'method' if the method is defined and private" do @@ -69,23 +90,23 @@ describe "The defined? keyword when called with a method name" do end it "returns nil if the method is private" do - defined?(Object.print).should be_nil + defined?(Object.print).should == nil end it "returns nil if the method is protected" do - defined?(DefinedSpecs::Basic.new.protected_method).should be_nil + defined?(DefinedSpecs::Basic.new.protected_method).should == nil end it "returns nil if the method is not defined" do - defined?(Kernel.defined_specs_undefined_method).should be_nil + defined?(Kernel.defined_specs_undefined_method).should == nil end it "returns nil if the class is not defined" do - defined?(DefinedSpecsUndefined.puts).should be_nil + defined?(DefinedSpecsUndefined.puts).should == nil end it "returns nil if the subclass is not defined" do - defined?(DefinedSpecs::Undefined.puts).should be_nil + defined?(DefinedSpecs::Undefined.puts).should == nil end end @@ -95,13 +116,18 @@ describe "The defined? keyword when called with a method name" do defined?(obj.a_defined_method).should == "method" end + it "returns 'method' for []=" do + a = [] + defined?(a[0] = 1).should == "method" + end + it "returns nil if the method is not defined" do obj = DefinedSpecs::Basic.new - defined?(obj.an_undefined_method).should be_nil + defined?(obj.an_undefined_method).should == nil end it "returns nil if the variable does not exist" do - defined?(nonexistent_local_variable.some_method).should be_nil + defined?(nonexistent_local_variable.some_method).should == nil end it "calls #respond_to_missing?" do @@ -119,11 +145,11 @@ describe "The defined? keyword when called with a method name" do it "returns nil if the method is not defined" do @defined_specs_obj = DefinedSpecs::Basic.new - defined?(@defined_specs_obj.an_undefined_method).should be_nil + defined?(@defined_specs_obj.an_undefined_method).should == nil end it "returns nil if the variable does not exist" do - defined?(@nonexistent_instance_variable.some_method).should be_nil + defined?(@nonexistent_instance_variable.some_method).should == nil end end @@ -135,22 +161,22 @@ describe "The defined? keyword when called with a method name" do it "returns nil if the method is not defined" do $defined_specs_obj = DefinedSpecs::Basic.new - defined?($defined_specs_obj.an_undefined_method).should be_nil + defined?($defined_specs_obj.an_undefined_method).should == nil end it "returns nil if the variable does not exist" do - defined?($nonexistent_global_variable.some_method).should be_nil + defined?($nonexistent_global_variable.some_method).should == nil end end describe "having a method call as a receiver" do it "returns nil if evaluating the receiver raises an exception" do - defined?(DefinedSpecs.exception_method / 2).should be_nil + defined?(DefinedSpecs.exception_method / 2).should == nil ScratchPad.recorded.should == :defined_specs_exception end it "returns nil if the method is not defined on the object the receiver returns" do - defined?(DefinedSpecs.side_effects / 2).should be_nil + defined?(DefinedSpecs.side_effects / 2).should == nil ScratchPad.recorded.should == :defined_specs_side_effects end @@ -159,6 +185,32 @@ describe "The defined? keyword when called with a method name" do ScratchPad.recorded.should == :defined_specs_fixnum_method end end + + describe "having a throw in the receiver" do + it "escapes defined? and performs the throw semantics as normal" do + defined_returned = false + catch(:out) { + # NOTE: defined? behaves differently if it is called in a void context, see below + defined?(throw(:out, 42).foo).should == :unreachable + defined_returned = true + }.should == 42 + defined_returned.should == false + end + end + + describe "in a void context" do + it "does not execute the receiver" do + ScratchPad.record :not_executed + defined?(DefinedSpecs.side_effects / 2) + ScratchPad.recorded.should == :not_executed + end + + it "warns about the void context when parsing it" do + -> { + eval "defined?(DefinedSpecs.side_effects / 2); 42" + }.should complain(/warning: possibly useless use of defined\? in void context/, verbose: true) + end + end end describe "The defined? keyword for an expression" do @@ -167,7 +219,9 @@ describe "The defined? keyword for an expression" do end it "returns 'assignment' for assigning a local variable" do - defined?(x = 2).should == "assignment" + ret = defined?(x = 2) + ret.should == "assignment" + ret.frozen?.should == true end it "returns 'assignment' for assigning an instance variable" do @@ -182,6 +236,14 @@ describe "The defined? keyword for an expression" do defined?(@@defined_specs_x = 2).should == "assignment" end + it "returns 'assignment' for assigning a constant" do + defined?(A = 2).should == "assignment" + end + + it "returns 'assignment' for assigning a fully qualified constant" do + defined?(Object::A = 2).should == "assignment" + end + it "returns 'assignment' for assigning multiple variables" do defined?((a, b = 1, 2)).should == "assignment" end @@ -199,7 +261,27 @@ describe "The defined? keyword for an expression" do end it "returns 'assignment' for an expression with '+='" do - defined?(x += 2).should == "assignment" + defined?(a += 1).should == "assignment" + defined?(@a += 1).should == "assignment" + defined?(@@a += 1).should == "assignment" + defined?($a += 1).should == "assignment" + defined?(A += 1).should == "assignment" + # fully qualified constant check is moved out into a separate test case + defined?(a.b += 1).should == "assignment" + defined?(a[:b] += 1).should == "assignment" + end + + # https://bugs.ruby-lang.org/issues/20111 + ruby_version_is ""..."3.4" do + it "returns 'expression' for an assigning a fully qualified constant with '+='" do + defined?(Object::A += 1).should == "expression" + end + end + + ruby_version_is "3.4" do + it "returns 'assignment' for an assigning a fully qualified constant with '+='" do + defined?(Object::A += 1).should == "assignment" + end end it "returns 'assignment' for an expression with '*='" do @@ -230,12 +312,90 @@ describe "The defined? keyword for an expression" do defined?(x >>= 2).should == "assignment" end - it "returns 'assignment' for an expression with '||='" do - defined?(x ||= 2).should == "assignment" + context "||=" do + it "returns 'assignment' for assigning a local variable with '||='" do + defined?(a ||= true).should == "assignment" + end + + it "returns 'assignment' for assigning an instance variable with '||='" do + defined?(@a ||= true).should == "assignment" + end + + it "returns 'assignment' for assigning a class variable with '||='" do + defined?(@@a ||= true).should == "assignment" + end + + it "returns 'assignment' for assigning a global variable with '||='" do + defined?($a ||= true).should == "assignment" + end + + it "returns 'assignment' for assigning a constant with '||='" do + defined?(A ||= true).should == "assignment" + end + + # https://bugs.ruby-lang.org/issues/20111 + ruby_version_is ""..."3.4" do + it "returns 'expression' for assigning a fully qualified constant with '||='" do + defined?(Object::A ||= true).should == "expression" + end + end + + ruby_version_is "3.4" do + it "returns 'assignment' for assigning a fully qualified constant with '||='" do + defined?(Object::A ||= true).should == "assignment" + end + end + + it "returns 'assignment' for assigning an attribute with '||='" do + defined?(a.b ||= true).should == "assignment" + end + + it "returns 'assignment' for assigning a referenced element with '||='" do + defined?(a[:b] ||= true).should == "assignment" + end end - it "returns 'assignment' for an expression with '&&='" do - defined?(x &&= 2).should == "assignment" + context "&&=" do + it "returns 'assignment' for assigning a local variable with '&&='" do + defined?(a &&= true).should == "assignment" + end + + it "returns 'assignment' for assigning an instance variable with '&&='" do + defined?(@a &&= true).should == "assignment" + end + + it "returns 'assignment' for assigning a class variable with '&&='" do + defined?(@@a &&= true).should == "assignment" + end + + it "returns 'assignment' for assigning a global variable with '&&='" do + defined?($a &&= true).should == "assignment" + end + + it "returns 'assignment' for assigning a constant with '&&='" do + defined?(A &&= true).should == "assignment" + end + + # https://bugs.ruby-lang.org/issues/20111 + ruby_version_is ""..."3.4" do + it "returns 'expression' for assigning a fully qualified constant with '&&='" do + defined?(Object::A &&= true).should == "expression" + end + end + + ruby_version_is "3.4" do + it "returns 'assignment' for assigning a fully qualified constant with '&&='" do + defined?(Object::A &&= true).should == "assignment" + end + end + + it "returns 'assignment' for assigning an attribute with '&&='" do + defined?(a.b &&= true).should == "assignment" + end + + it "returns 'assignment' for assigning a referenced element with '&&='" do + defined?(a[:b] &&= true).should == "assignment" + end end it "returns 'assignment' for an expression with '**='" do @@ -243,15 +403,15 @@ describe "The defined? keyword for an expression" do end it "returns nil for an expression with == and an undefined method" do - defined?(defined_specs_undefined_method == 2).should be_nil + defined?(defined_specs_undefined_method == 2).should == nil end it "returns nil for an expression with != and an undefined method" do - defined?(defined_specs_undefined_method != 2).should be_nil + defined?(defined_specs_undefined_method != 2).should == nil end it "returns nil for an expression with !~ and an undefined method" do - defined?(defined_specs_undefined_method !~ 2).should be_nil + defined?(defined_specs_undefined_method !~ 2).should == nil end it "returns 'method' for an expression with '=='" do @@ -271,25 +431,25 @@ describe "The defined? keyword for an expression" do describe "with logical connectives" do it "returns nil for an expression with '!' and an undefined method" do - defined?(!defined_specs_undefined_method).should be_nil + defined?(!defined_specs_undefined_method).should == nil end it "returns nil for an expression with '!' and an unset class variable" do @result = eval("class singleton_class::A; defined?(!@@doesnt_exist) end", binding, __FILE__, __LINE__) - @result.should be_nil + @result.should == nil end it "returns nil for an expression with 'not' and an undefined method" do - defined?(not defined_specs_undefined_method).should be_nil + defined?(not defined_specs_undefined_method).should == nil end it "returns nil for an expression with 'not' and an unset class variable" do @result = eval("class singleton_class::A; defined?(not @@doesnt_exist) end", binding, __FILE__, __LINE__) - @result.should be_nil + @result.should == nil end it "does not propagate an exception raised by a method in a 'not' expression" do - defined?(not DefinedSpecs.exception_method).should be_nil + defined?(not DefinedSpecs.exception_method).should == nil ScratchPad.recorded.should == :defined_specs_exception end @@ -328,11 +488,11 @@ describe "The defined? keyword for an expression" do end it "returns nil for an expression with '!' and an unset global variable" do - defined?(!$defined_specs_undefined_global_variable).should be_nil + defined?(!$defined_specs_undefined_global_variable).should == nil end it "returns nil for an expression with '!' and an unset instance variable" do - defined?(!@defined_specs_undefined_instance_variable).should be_nil + defined?(!@defined_specs_undefined_instance_variable).should == nil end it "returns 'method' for a 'not' expression with a method" do @@ -345,11 +505,11 @@ describe "The defined? keyword for an expression" do end it "returns nil for an expression with 'not' and an unset global variable" do - defined?(not $defined_specs_undefined_global_variable).should be_nil + defined?(not $defined_specs_undefined_global_variable).should == nil end it "returns nil for an expression with 'not' and an unset instance variable" do - defined?(not @defined_specs_undefined_instance_variable).should be_nil + defined?(not @defined_specs_undefined_instance_variable).should == nil end it "returns 'expression' for an expression with '&&/and' and an undefined method" do @@ -364,12 +524,12 @@ describe "The defined? keyword for an expression" do it "does not call a method in an '&&' expression and returns 'expression'" do defined?(DefinedSpecs.side_effects && true).should == "expression" - ScratchPad.recorded.should be_nil + ScratchPad.recorded.should == nil end it "does not call a method in an 'and' expression and returns 'expression'" do defined?(DefinedSpecs.side_effects and true).should == "expression" - ScratchPad.recorded.should be_nil + ScratchPad.recorded.should == nil end it "returns 'expression' for an expression with '||/or' and an undefined method" do @@ -384,12 +544,12 @@ describe "The defined? keyword for an expression" do it "does not call a method in an '||' expression and returns 'expression'" do defined?(DefinedSpecs.side_effects || true).should == "expression" - ScratchPad.recorded.should be_nil + ScratchPad.recorded.should == nil end it "does not call a method in an 'or' expression and returns 'expression'" do defined?(DefinedSpecs.side_effects or true).should == "expression" - ScratchPad.recorded.should be_nil + ScratchPad.recorded.should == nil end end @@ -412,7 +572,7 @@ describe "The defined? keyword for an expression" do it "does not call the method in the String" do defined?("garble #{DefinedSpecs.dynamic_string}").should == "expression" - ScratchPad.recorded.should be_nil + ScratchPad.recorded.should == nil end end @@ -431,7 +591,7 @@ describe "The defined? keyword for an expression" do it "does not call the method in the Regexp" do defined?(/garble #{DefinedSpecs.dynamic_string}/).should == "expression" - ScratchPad.recorded.should be_nil + ScratchPad.recorded.should == nil end end @@ -470,7 +630,9 @@ end describe "The defined? keyword for variables" do it "returns 'local-variable' when called with the name of a local variable" do - DefinedSpecs::Basic.new.local_variable_defined.should == "local-variable" + ret = DefinedSpecs::Basic.new.local_variable_defined + ret.should == "local-variable" + ret.frozen?.should == true end it "returns 'local-variable' when called with the name of a local variable assigned to nil" do @@ -478,15 +640,17 @@ describe "The defined? keyword for variables" do end it "returns nil for an instance variable that has not been read" do - DefinedSpecs::Basic.new.instance_variable_undefined.should be_nil + DefinedSpecs::Basic.new.instance_variable_undefined.should == nil end it "returns nil for an instance variable that has been read but not assigned to" do - DefinedSpecs::Basic.new.instance_variable_read.should be_nil + DefinedSpecs::Basic.new.instance_variable_read.should == nil end it "returns 'instance-variable' for an instance variable that has been assigned" do - DefinedSpecs::Basic.new.instance_variable_defined.should == "instance-variable" + ret = DefinedSpecs::Basic.new.instance_variable_defined + ret.should == "instance-variable" + ret.frozen?.should == true end it "returns 'instance-variable' for an instance variable that has been assigned to nil" do @@ -494,15 +658,17 @@ describe "The defined? keyword for variables" do end it "returns nil for a global variable that has not been read" do - DefinedSpecs::Basic.new.global_variable_undefined.should be_nil + DefinedSpecs::Basic.new.global_variable_undefined.should == nil end it "returns nil for a global variable that has been read but not assigned to" do - DefinedSpecs::Basic.new.global_variable_read.should be_nil + DefinedSpecs::Basic.new.global_variable_read.should == nil end it "returns 'global-variable' for a global variable that has been assigned nil" do - DefinedSpecs::Basic.new.global_variable_defined_as_nil.should == "global-variable" + ret = DefinedSpecs::Basic.new.global_variable_defined_as_nil + ret.should == "global-variable" + ret.frozen?.should == true end # MRI appears to special case defined? for $! and $~ in that it returns @@ -528,27 +694,27 @@ describe "The defined? keyword for variables" do end it "returns nil for $&" do - defined?($&).should be_nil + defined?($&).should == nil end it "returns nil for $`" do - defined?($`).should be_nil + defined?($`).should == nil end it "returns nil for $'" do - defined?($').should be_nil + defined?($').should == nil end it "returns nil for $+" do - defined?($+).should be_nil + defined?($+).should == nil end it "returns nil for any last match global" do - defined?($1).should be_nil - defined?($4).should be_nil - defined?($7).should be_nil - defined?($10).should be_nil - defined?($200).should be_nil + defined?($1).should == nil + defined?($4).should == nil + defined?($7).should == nil + defined?($10).should == nil + defined?($200).should == nil end end @@ -583,10 +749,10 @@ describe "The defined? keyword for variables" do end it "returns nil for non-captures" do - defined?($4).should be_nil - defined?($7).should be_nil - defined?($10).should be_nil - defined?($200).should be_nil + defined?($4).should == nil + defined?($7).should == nil + defined?($10).should == nil + defined?($200).should == nil end end @@ -600,27 +766,27 @@ describe "The defined? keyword for variables" do end it "returns nil for $&" do - defined?($&).should be_nil + defined?($&).should == nil end it "returns nil for $`" do - defined?($`).should be_nil + defined?($`).should == nil end it "returns nil for $'" do - defined?($').should be_nil + defined?($').should == nil end it "returns nil for $+" do - defined?($+).should be_nil + defined?($+).should == nil end it "returns nil for any last match global" do - defined?($1).should be_nil - defined?($4).should be_nil - defined?($7).should be_nil - defined?($10).should be_nil - defined?($200).should be_nil + defined?($1).should == nil + defined?($4).should == nil + defined?($7).should == nil + defined?($10).should == nil + defined?($200).should == nil end end @@ -655,10 +821,10 @@ describe "The defined? keyword for variables" do end it "returns nil for non-captures" do - defined?($4).should be_nil - defined?($7).should be_nil - defined?($10).should be_nil - defined?($200).should be_nil + defined?($4).should == nil + defined?($7).should == nil + defined?($10).should == nil + defined?($200).should == nil end end it "returns 'global-variable' for a global variable that has been assigned" do @@ -666,7 +832,7 @@ describe "The defined? keyword for variables" do end it "returns nil for a class variable that has not been read" do - DefinedSpecs::Basic.new.class_variable_undefined.should be_nil + DefinedSpecs::Basic.new.class_variable_undefined.should == nil end # There is no spec for a class variable that is read before being assigned @@ -674,7 +840,9 @@ describe "The defined? keyword for variables" do # get to the defined? call so it really has nothing to do with 'defined?'. it "returns 'class variable' when called with the name of a class variable" do - DefinedSpecs::Basic.new.class_variable_defined.should == "class variable" + ret = DefinedSpecs::Basic.new.class_variable_defined + ret.should == "class variable" + ret.frozen?.should == true end it "returns 'local-variable' when called with the name of a block local" do @@ -685,16 +853,18 @@ end describe "The defined? keyword for a simple constant" do it "returns 'constant' when the constant is defined" do - defined?(DefinedSpecs).should == "constant" + ret = defined?(DefinedSpecs) + ret.should == "constant" + ret.frozen?.should == true end it "returns nil when the constant is not defined" do - defined?(DefinedSpecsUndefined).should be_nil + defined?(DefinedSpecsUndefined).should == nil end it "does not call Object.const_missing if the constant is not defined" do Object.should_not_receive(:const_missing) - defined?(DefinedSpecsUndefined).should be_nil + defined?(DefinedSpecsUndefined).should == nil end it "returns 'constant' for an included module" do @@ -712,12 +882,12 @@ describe "The defined? keyword for a top-level constant" do end it "returns nil if the constant is not defined" do - defined?(::DefinedSpecsUndefined).should be_nil + defined?(::DefinedSpecsUndefined).should == nil end it "does not call Object.const_missing if the constant is not defined" do Object.should_not_receive(:const_missing) - defined?(::DefinedSpecsUndefined).should be_nil + defined?(::DefinedSpecsUndefined).should == nil end end @@ -727,33 +897,37 @@ describe "The defined? keyword for a scoped constant" do end it "returns nil when the scoped constant is not defined" do - defined?(DefinedSpecs::Undefined).should be_nil + defined?(DefinedSpecs::Undefined).should == nil + end + + it "returns nil when the constant is not defined and the outer module implements .const_missing" do + defined?(DefinedSpecs::ModuleWithConstMissing::Undefined).should == nil end it "does not call .const_missing if the constant is not defined" do DefinedSpecs.should_not_receive(:const_missing) - defined?(DefinedSpecs::UnknownChild).should be_nil + defined?(DefinedSpecs::UnknownChild).should == nil end it "returns nil when an undefined constant is scoped to a defined constant" do - defined?(DefinedSpecs::Child::Undefined).should be_nil + defined?(DefinedSpecs::Child::Undefined).should == nil end it "returns nil when a constant is scoped to an undefined constant" do Object.should_not_receive(:const_missing) - defined?(Undefined::Object).should be_nil + defined?(Undefined::Object).should == nil end it "returns nil when the undefined constant is scoped to an undefined constant" do - defined?(DefinedSpecs::Undefined::Undefined).should be_nil + defined?(DefinedSpecs::Undefined::Undefined).should == nil end it "returns nil when a constant is defined on top-level but not on the module" do - defined?(DefinedSpecs::String).should be_nil + defined?(DefinedSpecs::String).should == nil end it "returns nil when a constant is defined on top-level but not on the class" do - defined?(DefinedSpecs::Basic::String).should be_nil + defined?(DefinedSpecs::Basic::String).should == nil end it "returns 'constant' if the scoped-scoped constant is defined" do @@ -767,15 +941,15 @@ describe "The defined? keyword for a top-level scoped constant" do end it "returns nil when the scoped constant is not defined" do - defined?(::DefinedSpecs::Undefined).should be_nil + defined?(::DefinedSpecs::Undefined).should == nil end it "returns nil when an undefined constant is scoped to a defined constant" do - defined?(::DefinedSpecs::Child::Undefined).should be_nil + defined?(::DefinedSpecs::Child::Undefined).should == nil end it "returns nil when the undefined constant is scoped to an undefined constant" do - defined?(::DefinedSpecs::Undefined::Undefined).should be_nil + defined?(::DefinedSpecs::Undefined::Undefined).should == nil end it "returns 'constant' if the scoped-scoped constant is defined" do @@ -785,7 +959,7 @@ end describe "The defined? keyword for a self-send method call scoped constant" do it "returns nil if the constant is not defined in the scope of the method's value" do - defined?(defined_specs_method::Undefined).should be_nil + defined?(defined_specs_method::Undefined).should == nil end it "returns 'constant' if the constant is defined in the scope of the method's value" do @@ -793,11 +967,11 @@ describe "The defined? keyword for a self-send method call scoped constant" do end it "returns nil if the last constant is not defined in the scope chain" do - defined?(defined_specs_method::Basic::Undefined).should be_nil + defined?(defined_specs_method::Basic::Undefined).should == nil end it "returns nil if the middle constant is not defined in the scope chain" do - defined?(defined_specs_method::Undefined::Undefined).should be_nil + defined?(defined_specs_method::Undefined::Undefined).should == nil end it "returns 'constant' if all the constants in the scope chain are defined" do @@ -807,7 +981,7 @@ end describe "The defined? keyword for a receiver method call scoped constant" do it "returns nil if the constant is not defined in the scope of the method's value" do - defined?(defined_specs_receiver.defined_method::Undefined).should be_nil + defined?(defined_specs_receiver.defined_method::Undefined).should == nil end it "returns 'constant' if the constant is defined in the scope of the method's value" do @@ -815,11 +989,11 @@ describe "The defined? keyword for a receiver method call scoped constant" do end it "returns nil if the last constant is not defined in the scope chain" do - defined?(defined_specs_receiver.defined_method::Basic::Undefined).should be_nil + defined?(defined_specs_receiver.defined_method::Basic::Undefined).should == nil end it "returns nil if the middle constant is not defined in the scope chain" do - defined?(defined_specs_receiver.defined_method::Undefined::Undefined).should be_nil + defined?(defined_specs_receiver.defined_method::Undefined::Undefined).should == nil end it "returns 'constant' if all the constants in the scope chain are defined" do @@ -829,7 +1003,7 @@ end describe "The defined? keyword for a module method call scoped constant" do it "returns nil if the constant is not defined in the scope of the method's value" do - defined?(DefinedSpecs.defined_method::Undefined).should be_nil + defined?(DefinedSpecs.defined_method::Undefined).should == nil end it "returns 'constant' if the constant scoped by the method's value is defined" do @@ -837,11 +1011,11 @@ describe "The defined? keyword for a module method call scoped constant" do end it "returns nil if the last constant in the scope chain is not defined" do - defined?(DefinedSpecs.defined_method::Basic::Undefined).should be_nil + defined?(DefinedSpecs.defined_method::Basic::Undefined).should == nil end it "returns nil if the middle constant in the scope chain is not defined" do - defined?(DefinedSpecs.defined_method::Undefined::Undefined).should be_nil + defined?(DefinedSpecs.defined_method::Undefined::Undefined).should == nil end it "returns 'constant' if all the constants in the scope chain are defined" do @@ -849,11 +1023,11 @@ describe "The defined? keyword for a module method call scoped constant" do end it "returns nil if the outer scope constant in the receiver is not defined" do - defined?(Undefined::DefinedSpecs.defined_method::Basic).should be_nil + defined?(Undefined::DefinedSpecs.defined_method::Basic).should == nil end it "returns nil if the scoped constant in the receiver is not defined" do - defined?(DefinedSpecs::Undefined.defined_method::Basic).should be_nil + defined?(DefinedSpecs::Undefined.defined_method::Basic).should == nil end it "returns 'constant' if all the constants in the receiver are defined" do @@ -874,7 +1048,7 @@ describe "The defined? keyword for a variable scoped constant" do it "returns nil if the instance scoped constant is not defined" do @defined_specs_obj = DefinedSpecs::Basic - defined?(@defined_specs_obj::Undefined).should be_nil + defined?(@defined_specs_obj::Undefined).should == nil end it "returns 'constant' if the constant is defined in the scope of the instance variable" do @@ -884,7 +1058,7 @@ describe "The defined? keyword for a variable scoped constant" do it "returns nil if the global scoped constant is not defined" do $defined_specs_obj = DefinedSpecs::Basic - defined?($defined_specs_obj::Undefined).should be_nil + defined?($defined_specs_obj::Undefined).should == nil end it "returns 'constant' if the constant is defined in the scope of the global variable" do @@ -896,7 +1070,7 @@ describe "The defined? keyword for a variable scoped constant" do eval(<<-END, binding, __FILE__, __LINE__) class singleton_class::A @@defined_specs_obj = DefinedSpecs::Basic - defined?(@@defined_specs_obj::Undefined).should be_nil + defined?(@@defined_specs_obj::Undefined).should == nil end END end @@ -912,7 +1086,7 @@ describe "The defined? keyword for a variable scoped constant" do it "returns nil if the local scoped constant is not defined" do defined_specs_obj = DefinedSpecs::Basic - defined?(defined_specs_obj::Undefined).should be_nil + defined?(defined_specs_obj::Undefined).should == nil end it "returns 'constant' if the constant is defined in the scope of the local variable" do @@ -933,46 +1107,62 @@ end describe "The defined? keyword for yield" do it "returns nil if no block is passed to a method not taking a block parameter" do - DefinedSpecs::Basic.new.no_yield_block.should be_nil + DefinedSpecs::Basic.new.no_yield_block.should == nil end it "returns nil if no block is passed to a method taking a block parameter" do - DefinedSpecs::Basic.new.no_yield_block_parameter.should be_nil + DefinedSpecs::Basic.new.no_yield_block_parameter.should == nil end it "returns 'yield' if a block is passed to a method not taking a block parameter" do - DefinedSpecs::Basic.new.yield_block.should == "yield" + ret = DefinedSpecs::Basic.new.yield_block + ret.should == "yield" + ret.frozen?.should == true end it "returns 'yield' if a block is passed to a method taking a block parameter" do DefinedSpecs::Basic.new.yield_block_parameter.should == "yield" end + + it "returns 'yield' when called within a block" do + def yielder + yield + end + + def call_defined + yielder { defined?(yield) } + end + + call_defined() { }.should == "yield" + end end describe "The defined? keyword for super" do it "returns nil when a superclass undef's the method" do - DefinedSpecs::ClassWithoutMethod.new.test.should be_nil + DefinedSpecs::ClassWithoutMethod.new.test.should == nil end describe "for a method taking no arguments" do it "returns nil when no superclass method exists" do - DefinedSpecs::Super.new.no_super_method_no_args.should be_nil + DefinedSpecs::Super.new.no_super_method_no_args.should == nil end it "returns nil from a block when no superclass method exists" do - DefinedSpecs::Super.new.no_super_method_block_no_args.should be_nil + DefinedSpecs::Super.new.no_super_method_block_no_args.should == nil end it "returns nil from a #define_method when no superclass method exists" do - DefinedSpecs::Super.new.no_super_define_method_no_args.should be_nil + DefinedSpecs::Super.new.no_super_define_method_no_args.should == nil end it "returns nil from a block in a #define_method when no superclass method exists" do - DefinedSpecs::Super.new.no_super_define_method_block_no_args.should be_nil + DefinedSpecs::Super.new.no_super_define_method_block_no_args.should == nil end it "returns 'super' when a superclass method exists" do - DefinedSpecs::Super.new.method_no_args.should == "super" + ret = DefinedSpecs::Super.new.method_no_args + ret.should == "super" + ret.frozen?.should == true end it "returns 'super' from a block when a superclass method exists" do @@ -994,19 +1184,19 @@ describe "The defined? keyword for super" do describe "for a method taking arguments" do it "returns nil when no superclass method exists" do - DefinedSpecs::Super.new.no_super_method_args.should be_nil + DefinedSpecs::Super.new.no_super_method_args.should == nil end it "returns nil from a block when no superclass method exists" do - DefinedSpecs::Super.new.no_super_method_block_args.should be_nil + DefinedSpecs::Super.new.no_super_method_block_args.should == nil end it "returns nil from a #define_method when no superclass method exists" do - DefinedSpecs::Super.new.no_super_define_method_args.should be_nil + DefinedSpecs::Super.new.no_super_define_method_args.should == nil end it "returns nil from a block in a #define_method when no superclass method exists" do - DefinedSpecs::Super.new.no_super_define_method_block_args.should be_nil + DefinedSpecs::Super.new.no_super_define_method_block_args.should == nil end it "returns 'super' when a superclass method exists" do @@ -1041,7 +1231,7 @@ describe "The defined? keyword for instance variables" do end it "returns nil if not assigned" do - defined?(@unassigned_ivar).should be_nil + defined?(@unassigned_ivar).should == nil end end diff --git a/spec/ruby/language/delegation_spec.rb b/spec/ruby/language/delegation_spec.rb index ac7b511f65..3d917993f5 100644 --- a/spec/ruby/language/delegation_spec.rb +++ b/spec/ruby/language/delegation_spec.rb @@ -1,41 +1,162 @@ require_relative '../spec_helper' require_relative 'fixtures/delegation' -ruby_version_is "2.7" do - describe "delegation with def(...)" do - it "delegates rest and kwargs" do - a = Class.new(DelegationSpecs::Target) +# Forwarding anonymous parameters +describe "delegation with def(...)" do + it "delegates rest and kwargs" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate(...) + target(...) + end + RUBY + + a.new.delegate(1, b: 2).should == [[1], {b: 2}, nil] + end + + it "delegates a block literal" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate_block(...) + target_block(...) + end + RUBY + + a.new.delegate_block(1, b: 2) { |x| x }.should == [{b: 2}, [1]] + end + + it "delegates a block argument" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate(...) + target(...) + end + RUBY + + block = proc {} + a.new.delegate(1, b: 2, &block).should == [[1], {b: 2}, block] + end + + it "delegates with additional arguments" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate(...) + target(:first, :second, ...) + end + RUBY + a.new.delegate(1, b: 2).should == [[:first, :second, 1], {b: 2}, nil] + end + + it "parses as open endless Range when brackets are omitted" do + a = Class.new(DelegationSpecs::Target) + suppress_warning do a.class_eval(<<-RUBY) def delegate(...) - target(...) + target ... end RUBY + end + + a.new.delegate(1, b: 2).should == Range.new([[], {}, nil], nil, true) + end +end + +describe "delegation with def(x, ...)" do + it "delegates rest and kwargs" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate(x, ...) + target(...) + end + RUBY + + a.new.delegate(0, 1, b: 2).should == [[1], {b: 2}, nil] + end + + it "delegates a block literal" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate_block(x, ...) + target_block(...) + end + RUBY + + a.new.delegate_block(0, 1, b: 2) { |x| x }.should == [{b: 2}, [1]] + end + + it "delegates a block argument" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate(...) + target(...) + end + RUBY - a.new.delegate(1, b: 2).should == [[1], {b: 2}] + block = proc {} + a.new.delegate(1, b: 2, &block).should == [[1], {b: 2}, block] + end +end + +describe "delegation with def(*)" do + it "delegates rest" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate(*) + target(*) end + RUBY - it "delegates block" do - a = Class.new(DelegationSpecs::Target) - a.class_eval(<<-RUBY) - def delegate_block(...) - target_block(...) - end - RUBY + a.new.delegate(0, 1).should == [[0, 1], {}, nil] + end + + 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(SyntaxError, /anonymous rest parameter is also used within block/) + end + end +end - a.new.delegate_block(1, b: 2) { |x| x }.should == [{b: 2}, [1]] +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] + end + + 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(SyntaxError, /anonymous keyword rest parameter is also used within block/) + end + end +end + +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] + end - it "parses as open endless Range when brackets are omitted" do - a = Class.new(DelegationSpecs::Target) - suppress_warning do - a.class_eval(<<-RUBY) - def delegate(...) - target ... - end - RUBY - end - - a.new.delegate(1, b: 2).should == Range.new([[], {}], nil, true) + 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(SyntaxError, /anonymous block parameter is also used within block/) end end end diff --git a/spec/ruby/language/encoding_spec.rb b/spec/ruby/language/encoding_spec.rb index 5430c9cb98..116f53a77d 100644 --- a/spec/ruby/language/encoding_spec.rb +++ b/spec/ruby/language/encoding_spec.rb @@ -5,7 +5,7 @@ require_relative 'fixtures/coding_utf_8' describe "The __ENCODING__ pseudo-variable" do it "is an instance of Encoding" do - __ENCODING__.should be_kind_of(Encoding) + __ENCODING__.should.is_a?(Encoding) end it "is US-ASCII by default" do @@ -13,15 +13,15 @@ describe "The __ENCODING__ pseudo-variable" do end it "is the evaluated strings's one inside an eval" do - eval("__ENCODING__".force_encoding("US-ASCII")).should == Encoding::US_ASCII - eval("__ENCODING__".force_encoding("BINARY")).should == Encoding::BINARY + eval("__ENCODING__".dup.force_encoding("US-ASCII")).should == Encoding::US_ASCII + eval("__ENCODING__".dup.force_encoding("BINARY")).should == Encoding::BINARY end it "is the encoding specified by a magic comment inside an eval" do - code = "# encoding: BINARY\n__ENCODING__".force_encoding("US-ASCII") + code = "# encoding: BINARY\n__ENCODING__".dup.force_encoding("US-ASCII") eval(code).should == Encoding::BINARY - code = "# encoding: us-ascii\n__ENCODING__".force_encoding("BINARY") + code = "# encoding: us-ascii\n__ENCODING__".dup.force_encoding("BINARY") eval(code).should == Encoding::US_ASCII end @@ -31,6 +31,6 @@ describe "The __ENCODING__ pseudo-variable" do end it "raises a SyntaxError if assigned to" do - -> { eval("__ENCODING__ = 1") }.should raise_error(SyntaxError) + -> { eval("__ENCODING__ = 1") }.should.raise(SyntaxError) end end diff --git a/spec/ruby/language/ensure_spec.rb b/spec/ruby/language/ensure_spec.rb index e94c523e82..04ff0305ab 100644 --- a/spec/ruby/language/ensure_spec.rb +++ b/spec/ruby/language/ensure_spec.rb @@ -6,7 +6,7 @@ describe "An ensure block inside a begin block" do ScratchPad.record [] end - it "is executed when an exception is raised in it's corresponding begin block" do + it "is executed when an exception is raised in its corresponding begin block" do -> { begin ScratchPad << :begin @@ -14,12 +14,12 @@ describe "An ensure block inside a begin block" do ensure ScratchPad << :ensure end - }.should raise_error(EnsureSpec::Error) + }.should.raise(EnsureSpec::Error) ScratchPad.recorded.should == [:begin, :ensure] end - it "is executed when an exception is raised and rescued in it's corresponding begin block" do + it "is executed when an exception is raised and rescued in its corresponding begin block" do begin ScratchPad << :begin raise "An exception occurred!" @@ -32,7 +32,7 @@ describe "An ensure block inside a begin block" do ScratchPad.recorded.should == [:begin, :rescue, :ensure] end - it "is executed even when a symbol is thrown in it's corresponding begin block" do + it "is executed even when a symbol is thrown in its corresponding begin block" do catch(:symbol) do begin ScratchPad << :begin @@ -47,7 +47,7 @@ describe "An ensure block inside a begin block" do ScratchPad.recorded.should == [:begin, :ensure] end - it "is executed when nothing is raised or thrown in it's corresponding begin block" do + it "is executed when nothing is raised or thrown in its corresponding begin block" do begin ScratchPad << :begin rescue @@ -74,9 +74,9 @@ describe "An ensure block inside a begin block" do ensure raise "from ensure" end - }.should raise_error(RuntimeError, "from ensure") do |e| + }.should.raise(RuntimeError, "from ensure") { |e| e.cause.message.should == "from block" - end + } end end @@ -108,7 +108,7 @@ describe "An ensure block inside a method" do end it "is executed when an exception is raised in the method" do - -> { @obj.raise_in_method_with_ensure }.should raise_error(EnsureSpec::Error) + -> { @obj.raise_in_method_with_ensure }.should.raise(EnsureSpec::Error) @obj.executed.should == [:method, :ensure] end @@ -149,13 +149,13 @@ describe "An ensure block inside a method" do it "overrides exception raised in rescue if raises exception itself" do -> { @obj.raise_in_rescue_and_raise_in_ensure - }.should raise_error(RuntimeError, "raised in ensure") + }.should.raise(RuntimeError, "raised in ensure") end it "suppresses exception raised in method if raises exception itself" do -> { @obj.raise_in_method_and_raise_in_ensure - }.should raise_error(RuntimeError, "raised in ensure") + }.should.raise(RuntimeError, "raised in ensure") end end @@ -174,7 +174,7 @@ describe "An ensure block inside a class" do ScratchPad << :ensure end ruby - }.should raise_error(EnsureSpec::Error) + }.should.raise(EnsureSpec::Error) ScratchPad.recorded.should == [:class, :ensure] end @@ -247,7 +247,7 @@ describe "An ensure block inside {} block" do ensure } ruby - }.should raise_error(SyntaxError) + }.should.raise(SyntaxError) end end @@ -256,7 +256,7 @@ describe "An ensure block inside 'do end' block" do ScratchPad.record [] end - it "is executed when an exception is raised in it's corresponding begin block" do + it "is executed when an exception is raised in its corresponding begin block" do -> { eval(<<-ruby).call lambda do @@ -266,12 +266,12 @@ describe "An ensure block inside 'do end' block" do ScratchPad << :ensure end ruby - }.should raise_error(EnsureSpec::Error) + }.should.raise(EnsureSpec::Error) ScratchPad.recorded.should == [:begin, :ensure] end - it "is executed when an exception is raised and rescued in it's corresponding begin block" do + it "is executed when an exception is raised and rescued in its corresponding begin block" do eval(<<-ruby).call lambda do ScratchPad << :begin @@ -286,7 +286,7 @@ describe "An ensure block inside 'do end' block" do ScratchPad.recorded.should == [:begin, :rescue, :ensure] end - it "is executed even when a symbol is thrown in it's corresponding begin block" do + it "is executed even when a symbol is thrown in its corresponding begin block" do catch(:symbol) do eval(<<-ruby).call lambda do @@ -303,7 +303,7 @@ describe "An ensure block inside 'do end' block" do ScratchPad.recorded.should == [:begin, :ensure] end - it "is executed when nothing is raised or thrown in it's corresponding begin block" do + it "is executed when nothing is raised or thrown in its corresponding begin block" do eval(<<-ruby).call lambda do ScratchPad << :begin @@ -328,4 +328,19 @@ describe "An ensure block inside 'do end' block" do result.should == :begin end + + ruby_version_is "3.4" do + it "does not introduce extra backtrace entries" do + def foo + begin + raise "oops" + ensure + return caller(0, 2) # rubocop:disable Lint/EnsureReturn + end + end + line = __LINE__ + 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/execution_spec.rb b/spec/ruby/language/execution_spec.rb index 4e0310946d..51bcde62e8 100644 --- a/spec/ruby/language/execution_spec.rb +++ b/spec/ruby/language/execution_spec.rb @@ -5,6 +5,45 @@ describe "``" do ip = 'world' `echo disc #{ip}`.should == "disc world\n" end + + it "can be redefined and receive a frozen string as argument" do + called = false + runner = Object.new + + runner.singleton_class.define_method(:`) do |str| + called = true + + str.should == "test command" + str.frozen?.should == true + end + + runner.instance_exec do + `test command` + end + + called.should == true + end + + it "the argument isn't frozen if it contains interpolation" do + called = false + runner = Object.new + + runner.singleton_class.define_method(:`) do |str| + called = true + + str.should == "test command" + str.frozen?.should == false + str << "mutated" + end + + 2.times do + runner.instance_exec do + `test #{:command}` # rubocop:disable Lint/LiteralInInterpolation + end + end + + called.should == true + end end describe "%x" do @@ -12,4 +51,43 @@ describe "%x" do ip = 'world' %x(echo disc #{ip}).should == "disc world\n" end + + it "can be redefined and receive a frozen string as argument" do + called = false + runner = Object.new + + runner.singleton_class.define_method(:`) do |str| + called = true + + str.should == "test command" + str.frozen?.should == true + end + + runner.instance_exec do + %x{test command} + end + + called.should == true + end + + it "the argument isn't frozen if it contains interpolation" do + called = false + runner = Object.new + + runner.singleton_class.define_method(:`) do |str| + called = true + + str.should == "test command" + str.frozen?.should == false + str << "mutated" + end + + 2.times do + runner.instance_exec do + %x{test #{:command}} # rubocop:disable Lint/LiteralInInterpolation + end + end + + called.should == true + end end diff --git a/spec/ruby/language/file_spec.rb b/spec/ruby/language/file_spec.rb index 729dee1008..dd89ce2385 100644 --- a/spec/ruby/language/file_spec.rb +++ b/spec/ruby/language/file_spec.rb @@ -4,26 +4,18 @@ require_relative 'shared/__FILE__' describe "The __FILE__ pseudo-variable" do it "raises a SyntaxError if assigned to" do - -> { eval("__FILE__ = 1") }.should raise_error(SyntaxError) + -> { eval("__FILE__ = 1") }.should.raise(SyntaxError) end - it "equals (eval) inside an eval" do - eval("__FILE__").should == "(eval)" + it "equals (eval at __FILE__:__LINE__) inside an eval" do + eval("__FILE__").should == "(eval at #{__FILE__}:#{__LINE__})" end end -describe "The __FILE__ pseudo-variable" do - it_behaves_like :language___FILE__, :require, CodeLoadingSpecs::Method.new -end - -describe "The __FILE__ pseudo-variable" do +describe "The __FILE__ pseudo-variable with require" do it_behaves_like :language___FILE__, :require, Kernel end -describe "The __FILE__ pseudo-variable" do - it_behaves_like :language___FILE__, :load, CodeLoadingSpecs::Method.new -end - -describe "The __FILE__ pseudo-variable" do +describe "The __FILE__ pseudo-variable with load" do it_behaves_like :language___FILE__, :load, Kernel 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/constant_visibility.rb b/spec/ruby/language/fixtures/constant_visibility.rb index 022554430e..af38b2d8f2 100644 --- a/spec/ruby/language/fixtures/constant_visibility.rb +++ b/spec/ruby/language/fixtures/constant_visibility.rb @@ -65,7 +65,7 @@ module ConstantVisibility end end - class PrivConstModuleChild + class ClassIncludingPrivConstModule include PrivConstModule def private_constant_from_include @@ -77,6 +77,22 @@ module ConstantVisibility end end + module ModuleIncludingPrivConstModule + include PrivConstModule + + def self.private_constant_from_include + PRIVATE_CONSTANT_MODULE + end + + def self.private_constant_self_from_include + self::PRIVATE_CONSTANT_MODULE + end + + def self.private_constant_named_from_include + PrivConstModule::PRIVATE_CONSTANT_MODULE + end + end + class PrivConstClassChild < PrivConstClass def private_constant_from_subclass PRIVATE_CONSTANT_CLASS diff --git a/spec/ruby/language/fixtures/defined.rb b/spec/ruby/language/fixtures/defined.rb index 8b6004c19f..3761cfa5bd 100644 --- a/spec/ruby/language/fixtures/defined.rb +++ b/spec/ruby/language/fixtures/defined.rb @@ -19,6 +19,9 @@ module DefinedSpecs DefinedSpecs end + def self.any_args(*) + end + class Basic A = 42 @@ -282,6 +285,12 @@ module DefinedSpecs end end + module ModuleWithConstMissing + def self.const_missing(const) + const + end + end + class SuperWithIntermediateModules include IntermediateModule1 include IntermediateModule2 diff --git a/spec/ruby/language/fixtures/delegation.rb b/spec/ruby/language/fixtures/delegation.rb index 527d928390..da2b024791 100644 --- a/spec/ruby/language/fixtures/delegation.rb +++ b/spec/ruby/language/fixtures/delegation.rb @@ -1,7 +1,7 @@ module DelegationSpecs class Target - def target(*args, **kwargs) - [args, kwargs] + def target(*args, **kwargs, &block) + [args, kwargs, block] end def target_block(*args, **kwargs) diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_across_files.rb b/spec/ruby/language/fixtures/freeze_magic_comment_across_files.rb index 3aed2f29b6..f3ef666a3c 100644 --- a/spec/ruby/language/fixtures/freeze_magic_comment_across_files.rb +++ b/spec/ruby/language/fixtures/freeze_magic_comment_across_files.rb @@ -2,4 +2,5 @@ require_relative 'freeze_magic_comment_required' -p "abc".object_id == $second_literal_id +p "abc".equal?($second_literal) +$second_literal = nil diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb b/spec/ruby/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb index 53ef959970..e9ca35e7c8 100644 --- a/spec/ruby/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb +++ b/spec/ruby/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb @@ -2,4 +2,5 @@ require_relative 'freeze_magic_comment_required_diff_enc' -p "abc".object_id != $second_literal_id +p !"abc".equal?($second_literal) +$second_literal = nil diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_across_files_no_comment.rb b/spec/ruby/language/fixtures/freeze_magic_comment_across_files_no_comment.rb index fc6cd5bf82..c9eaab46a2 100644 --- a/spec/ruby/language/fixtures/freeze_magic_comment_across_files_no_comment.rb +++ b/spec/ruby/language/fixtures/freeze_magic_comment_across_files_no_comment.rb @@ -2,4 +2,5 @@ require_relative 'freeze_magic_comment_required_no_comment' -p "abc".object_id != $second_literal_id +p !"abc".equal?($second_literal) +$second_literal = nil diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_one_literal.rb b/spec/ruby/language/fixtures/freeze_magic_comment_one_literal.rb index d35905b332..c175b2b7a2 100644 --- a/spec/ruby/language/fixtures/freeze_magic_comment_one_literal.rb +++ b/spec/ruby/language/fixtures/freeze_magic_comment_one_literal.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true -ids = Array.new(2) { "abc".object_id } -p ids.first == ids.last +objs = Array.new(2) { "abc" } +p objs.first.equal?(objs.last) diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_required.rb b/spec/ruby/language/fixtures/freeze_magic_comment_required.rb index a4ff4459b1..f75acb2ce3 100644 --- a/spec/ruby/language/fixtures/freeze_magic_comment_required.rb +++ b/spec/ruby/language/fixtures/freeze_magic_comment_required.rb @@ -1,3 +1,3 @@ # frozen_string_literal: true -$second_literal_id = "abc".object_id +$second_literal = "abc" diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb b/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb Binary files differindex d0558a2251..739e96e99a 100644 --- a/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb +++ b/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_required_no_comment.rb b/spec/ruby/language/fixtures/freeze_magic_comment_required_no_comment.rb index e09232a5f4..6fbe175b42 100644 --- a/spec/ruby/language/fixtures/freeze_magic_comment_required_no_comment.rb +++ b/spec/ruby/language/fixtures/freeze_magic_comment_required_no_comment.rb @@ -1 +1 @@ -$second_literal_id = "abc".object_id +$second_literal = "abc" diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb b/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb index a4d655ad02..cccc5969bd 100644 --- a/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb +++ b/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb @@ -1,3 +1,3 @@ # frozen_string_literal: true -p "abc".object_id == "abc".object_id +p "abc".equal?("abc") 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/private.rb b/spec/ruby/language/fixtures/private.rb index 96f73cea3f..da3e0a97f9 100644 --- a/spec/ruby/language/fixtures/private.rb +++ b/spec/ruby/language/fixtures/private.rb @@ -43,17 +43,17 @@ module Private end end - class E - include D - end - - class G - def foo - "foo" - end - end - - class H < A - private :foo - end + class E + include D + end + + class G + def foo + "foo" + end + end + + class H < A + private :foo + end end diff --git a/spec/ruby/language/fixtures/rescue/top_level.rb b/spec/ruby/language/fixtures/rescue/top_level.rb new file mode 100644 index 0000000000..59e78ef1d6 --- /dev/null +++ b/spec/ruby/language/fixtures/rescue/top_level.rb @@ -0,0 +1,7 @@ +# capturing in local variable at top-level + +begin + raise "message" +rescue => e + ScratchPad << e.message +end diff --git a/spec/ruby/language/fixtures/return.rb b/spec/ruby/language/fixtures/return.rb index 0414c356e8..f6b143f3fa 100644 --- a/spec/ruby/language/fixtures/return.rb +++ b/spec/ruby/language/fixtures/return.rb @@ -101,18 +101,14 @@ module ReturnSpecs # return value will go into val before we run the ensure. # # If lamb's return keeps unwinding incorrectly, val will still - # have it's old value. + # have its old value. # # We can therefore use val to figure out what happened. begin val = foo() ensure - if val != :good - return :bad - end + return val end - - return val end end diff --git a/spec/ruby/language/fixtures/send.rb b/spec/ruby/language/fixtures/send.rb index 918241e171..4787abee5c 100644 --- a/spec/ruby/language/fixtures/send.rb +++ b/spec/ruby/language/fixtures/send.rb @@ -43,9 +43,9 @@ module LangSendSpecs attr_writer :foo private :foo= - def call_self_foo_equals(value) - self.foo = value - end + def call_self_foo_equals(value) + self.foo = value + end def call_self_foo_equals_masgn(value) a, self.foo = 1, value @@ -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/fixtures/squiggly_heredoc.rb b/spec/ruby/language/fixtures/squiggly_heredoc.rb index d223966b93..984a629e5b 100644 --- a/spec/ruby/language/fixtures/squiggly_heredoc.rb +++ b/spec/ruby/language/fixtures/squiggly_heredoc.rb @@ -29,6 +29,14 @@ module SquigglyHeredocSpecs HERE end + def self.backslash + <<~HERE + a + b\ + c + HERE + end + def self.least_indented_on_the_first_line <<~HERE a diff --git a/spec/ruby/language/fixtures/super.rb b/spec/ruby/language/fixtures/super.rb index 218f1e4970..b6d4218b03 100644 --- a/spec/ruby/language/fixtures/super.rb +++ b/spec/ruby/language/fixtures/super.rb @@ -266,7 +266,7 @@ module SuperSpecs # Use this so that we can see collect all supers that we see. # One bug that arises is that we call Alias2#name from Alias2#name - # as it's superclass. In that case, either we get a runaway recursion + # as its superclass. In that case, either we get a runaway recursion # super OR we get the return value being [:alias2, :alias2, :alias1] # rather than [:alias2, :alias1]. # @@ -539,6 +539,30 @@ module SuperSpecs args end + def m3(*args) + args + end + + def m4(*args) + args + end + + def m_default(*args) + args + end + + def m_rest(*args) + args + end + + def m_pre_default_rest_post(*args) + args + end + + def m_kwrest(**kw) + kw + end + def m_modified(*args) args end @@ -549,6 +573,30 @@ module SuperSpecs super end + def m3(_, _, _) + super + end + + def m4(_, _, _, _) + super + end + + def m_default(_ = 0) + super + end + + def m_rest(*_) + super + end + + def m_pre_default_rest_post(_, _, _=:a, _=:b, *_, _, _) + super + end + + def m_kwrest(**_) + super + end + def m_modified(_, _) _ = 14 super @@ -556,6 +604,20 @@ module SuperSpecs end end + module ZSuperInBlock + class A + def m(arg:) + arg + end + end + + class B < A + def m(arg:) + proc { super }.call + end + end + end + module Keywords class Arguments def foo(**args) diff --git a/spec/ruby/language/fixtures/variables.rb b/spec/ruby/language/fixtures/variables.rb index 07265dbb2b..527caa7a78 100644 --- a/spec/ruby/language/fixtures/variables.rb +++ b/spec/ruby/language/fixtures/variables.rb @@ -82,4 +82,76 @@ module VariablesSpecs def self.false false end + + class EvalOrder + attr_reader :order + + def initialize + @order = [] + end + + def reset + @order = [] + end + + def foo + self << "foo" + FooClass.new(self) + end + + def bar + self << "bar" + BarClass.new(self) + end + + def a + self << "a" + end + + def b + self << "b" + end + + def node + self << "node" + + node = Node.new + node.left = Node.new + node.left.right = Node.new + + node + end + + def <<(value) + order << value + end + + class FooClass + attr_reader :evaluator + + def initialize(evaluator) + @evaluator = evaluator + end + + def []=(_index, _value) + evaluator << "foo[]=" + end + end + + class BarClass + attr_reader :evaluator + + def initialize(evaluator) + @evaluator = evaluator + end + + def baz=(_value) + evaluator << "bar.baz=" + end + end + + class Node + attr_accessor :left, :right + end + end end diff --git a/spec/ruby/language/for_spec.rb b/spec/ruby/language/for_spec.rb index 0ad5ea88af..b0f3aef405 100644 --- a/spec/ruby/language/for_spec.rb +++ b/spec/ruby/language/for_spec.rb @@ -19,6 +19,27 @@ describe "The for expression" do end end + it "iterates over a list of arrays and destructures with an empty splat" do + for i, * in [[1,2]] + i.should == 1 + end + end + + it "iterates over a list of arrays and destructures with a splat" do + for i, *j in [[1,2]] + i.should == 1 + j.should == [2] + end + end + + it "iterates over a list of arrays and destructures with a splat and additional targets" do + for i, *j, k in [[1,2,3,4]] + i.should == 1 + j.should == [2,3] + k.should == 4 + end + end + it "iterates over an Hash passing each key-value pair to the block" do k = 0 l = 0 @@ -81,6 +102,85 @@ describe "The for expression" do end end + it "allows a global variable as an iterator name" do + old_global_var = $var + m = [1,2,3] + n = 0 + for $var in m + n += 1 + end + $var.should == 3 + n.should == 3 + $var = old_global_var + end + + it "allows an attribute as an iterator name" do + class OFor + attr_accessor :target + end + + ofor = OFor.new + m = [1,2,3] + n = 0 + for ofor.target in m + n += 1 + end + ofor.target.should == 3 + n.should == 3 + end + + it "allows an attribute with safe navigation as an iterator name" do + class OFor + attr_accessor :target + end + + ofor = OFor.new + m = [1,2,3] + n = 0 + eval <<~RUBY + for ofor&.target in m + n += 1 + end + RUBY + ofor.target.should == 3 + n.should == 3 + end + + it "allows an attribute with safe navigation on a nil base as an iterator name" do + ofor = nil + m = [1,2,3] + n = 0 + eval <<~RUBY + for ofor&.target in m + n += 1 + end + RUBY + ofor.should == nil + n.should == 3 + end + + it "allows an array index writer as an iterator name" do + arr = [:a, :b, :c] + m = [1,2,3] + n = 0 + for arr[1] in m + n += 1 + end + arr.should == [:a, 3, :c] + n.should == 3 + end + + it "allows a hash index writer as an iterator name" do + hash = { a: 10, b: 20, c: 30 } + m = [1,2,3] + n = 0 + for hash[:b] in m + n += 1 + end + hash.should == { a: 10, b: 3, c: 30 } + n.should == 3 + end + # 1.9 behaviour verified by nobu in # http://redmine.ruby-lang.org/issues/show/2053 it "yields only as many values as there are arguments" do @@ -115,7 +215,15 @@ describe "The for expression" do j.should == 6 end - it "executes code in containing variable scope" do + it "declares iteration variables in the surrounding variable scope" do + for a, b in [[1,2]] + end + + a.should == 1 + b.should == 2 + end + + it "declares variables in the body in the surrounding variable scope" do for i in 1..2 a = 123 end @@ -123,7 +231,7 @@ describe "The for expression" do a.should == 123 end - it "executes code in containing variable scope with 'do'" do + it "declares variables in the body in the surrounding variable scope with 'do'" do for i in 1..2 do a = 123 end @@ -131,6 +239,96 @@ describe "The for expression" do a.should == 123 end + it "declares variables inside a block as normal" do + for i in 1..2 do + proc { + inside_proc = 42 + }.call + end + local_variables.should == [:i] + end + + it "declares variables inside a lambda as normal" do + for i in 1..2 do + -> { + inside_proc = 42 + }.call + end + local_variables.should == [:i] + end + + it "can be nested" do + for a in [6] + for b in [7] + c = a * b + end + end + local_variables.sort.should == [:a, :b, :c] + c.should == 42 + end + + it "can be nested with blocks in between" do + # This is an edge case spec for Ruby implementations which have + # their own runtime scope per for loop body (like YARV and TruffleRuby) + for a in [1] + a1 = a + a1.should == a + for b in [2] + b1 = b + a1.should == a + b1.should == b + proc { + inside_proc = 42 + + a1.should == a + b1.should == b + inside_proc.should == 42 + + for c in [3].map { |enum_var| + a1.should == a + b1.should == b + inside_proc.should == 42 + enum_var + } + c1 = c + + a1.should == a + b1.should == b + c1.should == c + inside_proc.should == 42 + + for d in [4] + d1 = d + + a1.should == a + b1.should == b + c1.should == c + d1.should == d + inside_proc.should == 42 + end + end + local_variables.sort.should == [:a, :a1, :b, :b1, :c, :c1, :d, :d1, :inside_proc] + }.call + end + end + local_variables.sort.should == [:a, :a1, :b, :b1] + end + + it "can be nested with forward arguments" do + def bar(*args) + args + end + + def foo(...) + for a in [1] + r = bar(...) + end + r + end + + foo(2, 3).should == [2, 3] + end + it "does not try to access variables outside the method" do ForSpecs::ForInClassMethod.foo.should == [:bar, :baz] ForSpecs::ForInClassMethod::READER.call.should == :same_variable_set_outside diff --git a/spec/ruby/language/hash_spec.rb b/spec/ruby/language/hash_spec.rb index d6600ddb4a..7a4a2e37c9 100644 --- a/spec/ruby/language/hash_spec.rb +++ b/spec/ruby/language/hash_spec.rb @@ -33,7 +33,7 @@ describe "Hash literal" do end it "freezes string keys on initialization" do - key = "foo" + key = +"foo" h = {key => "bar"} key.reverse! h["foo"].should == "bar" @@ -48,6 +48,24 @@ describe "Hash literal" do }.should complain(/key :foo is duplicated|duplicated key/) @h.keys.size.should == 1 @h.should == {foo: :foo} + -> { + @h = eval "{%q{a} => :bar, %q{a} => :foo}" + }.should complain(/key "a" is duplicated|duplicated key/) + @h.keys.size.should == 1 + @h.should == {%q{a} => :foo} + -> { + @h = eval "{1000 => :bar, 1000 => :foo}" + }.should complain(/key 1000 is duplicated|duplicated key/) + @h.keys.size.should == 1 + @h.should == {1000 => :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 @@ -57,13 +75,37 @@ 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 - -> { eval("{:a ==> 1}") }.should raise_error(SyntaxError) + -> { eval("{:a ==> 1}") }.should.raise(SyntaxError) + end + + it "recognizes '!' at the end of the key" do + {:a! =>1}.should == {:"a!" => 1} + {: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 + -> { eval("{:a!=> 1}") }.should.raise(SyntaxError) + end + + it "recognizes '?' at the end of the key" do + {:a? =>1}.should == {:"a?" => 1} + {: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 + -> { eval("{:a?=> 1}") }.should.raise(SyntaxError) end it "constructs a new hash with the given elements" do @@ -85,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 @@ -107,11 +149,55 @@ describe "Hash literal" do {a: 1, **h, c: 4}.should == {a: 1, b: 2, c: 4} end - it "expands an '**{}' element with the last key/value pair taking precedence" do + ruby_version_is ""..."3.4" do + it "does not expand nil using ** into {} and raises TypeError" do + h = nil + -> { {a: 1, **h} }.should.raise(TypeError, "no implicit conversion of nil into Hash") + + -> { {a: 1, **nil} }.should.raise(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 + + it "expands nil using ** into {} and provides a copy to the callable" do + ScratchPad.record [] + insert = -> key, **kw do + kw[key] = 1 + ScratchPad << kw + end + insert.call(:foo, **nil) + insert.call(:bar, **nil) + ScratchPad.recorded.should == [{ foo: 1 }, { bar: 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}" }.should complain(/key :a is duplicated|duplicated key/) @h.should == {a: 2, b: 3, c: 3} + + -> { + h = {a: 2, b: 3, c: 1} + @h = eval "{a: 1, **h, c: 3}" + }.should_not complain + @h.should == {a: 2, b: 3, c: 3} + end + + it "expands an '**{}' and warns when finding an additional duplicate key afterwards" do + -> { + @h = eval "{d: 1, **{a: 2, b: 3, c: 1}, c: 3}" + }.should complain(/key :c is duplicated|duplicated key/) + @h.should == {a: 2, b: 3, c: 3, d: 1} end it "merges multiple nested '**obj' in Hash literals" do @@ -128,31 +214,22 @@ describe "Hash literal" do {a: 1, **obj, c: 3}.should == {a:1, b: 2, c: 3, d: 4} end - ruby_version_is ""..."2.7" do - it "raises a TypeError if any splatted elements keys are not symbols" do - h = {1 => 2, b: 3} - -> { {a: 1, **h} }.should raise_error(TypeError) - end - end - - ruby_version_is "2.7" do - it "allows splatted elements keys that are not symbols" do - h = {1 => 2, b: 3} - {a: 1, **h}.should == {a: 1, 1 => 2, b: 3} - end + it "allows splatted elements keys that are not symbols" do + h = {1 => 2, b: 3} + {a: 1, **h}.should == {a: 1, 1 => 2, b: 3} end it "raises a TypeError if #to_hash does not return a Hash" do obj = mock("hash splat") obj.should_receive(:to_hash).and_return(obj) - -> { {**obj} }.should raise_error(TypeError) + -> { {**obj} }.should.raise(TypeError) end it "raises a TypeError if the object does not respond to #to_hash" do obj = 42 - -> { {**obj} }.should raise_error(TypeError) - -> { {a: 1, **obj} }.should raise_error(TypeError) + -> { {**obj} }.should.raise(TypeError) + -> { {a: 1, **obj} }.should.raise(TypeError) end it "does not change encoding of literal string keys during creation" do @@ -166,4 +243,91 @@ describe "Hash literal" do utf8_hash.keys.first.should == usascii_hash.keys.first usascii_hash.keys.first.encoding.should == Encoding::US_ASCII end + + ruby_bug "#20280", ""..."3.4" do + it "raises a SyntaxError at parse time when Symbol key with invalid bytes" do + ScratchPad.record [] + -> { + eval 'ScratchPad << 1; {:"\xC3" => 1}' + }.should.raise(SyntaxError, /invalid symbol/) + ScratchPad.recorded.should == [] + end + + it "raises a SyntaxError at parse time when Symbol key with invalid bytes and 'key: value' syntax used" do + ScratchPad.record [] + -> { + eval 'ScratchPad << 1; {"\xC3": 1}' + }.should.raise(SyntaxError, /invalid symbol/) + ScratchPad.recorded.should == [] + end + end +end + +describe "The ** operator" do + it "makes a copy when calling a method taking a keyword rest argument" do + def m(**h) + h.delete(:one); h + end + + h = { one: 1, two: 2 } + m(**h).should == { two: 2 } + m(**h).should_not.equal?(h) + h.should == { one: 1, two: 2 } + end + + it "makes a copy when calling a method taking a positional Hash" do + def m(h) + h.delete(:one); h + end + + h = { one: 1, two: 2 } + m(**h).should == { two: 2 } + m(**h).should_not.equal?(h) + h.should == { one: 1, two: 2 } + 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 = {a:, b:, c:,} + {a: 1, b: 2, c: 3}.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 + + def foo(val) + {bar:, val:} + end + RUBY + + 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(SyntaxError, /identifier a! is not valid to get/) + end + + it "raises a SyntaxError when the hash key ends with `?`" do + -> { eval("{a?:}") }.should.raise(SyntaxError, /identifier a\? is not valid to get/) + end + end end diff --git a/spec/ruby/language/heredoc_spec.rb b/spec/ruby/language/heredoc_spec.rb index 95df8457e4..535f18cba8 100644 --- a/spec/ruby/language/heredoc_spec.rb +++ b/spec/ruby/language/heredoc_spec.rb @@ -59,20 +59,10 @@ HERE s.encoding.should == Encoding::US_ASCII end - ruby_version_is "2.7" do - it 'raises SyntaxError if quoted HEREDOC identifier is ending not on same line' do - -> { - eval %{<<"HERE\n"\nraises syntax error\nHERE} - }.should raise_error(SyntaxError) - end - end - - ruby_version_is ""..."2.7" do - it 'prints a warning if quoted HEREDOC identifier is ending not on same line' do - -> { - eval %{<<"HERE\n"\nit warns\nHERE} - }.should complain(/here document identifier ends with a newline/) - end + it 'raises SyntaxError if quoted HEREDOC identifier is ending not on same line' do + -> { + eval %{<<"HERE\n"\nraises syntax error\nHERE} + }.should.raise(SyntaxError) end it "allows HEREDOC with <<~'identifier', allowing to indent identifier and content" do @@ -100,6 +90,11 @@ HERE SquigglyHeredocSpecs.singlequoted.should == "singlequoted \#{\"interpolated\"}\n" end + it "allows HEREDOC with <<~'identifier', no interpolation, with backslash" do + require_relative 'fixtures/squiggly_heredoc' + SquigglyHeredocSpecs.backslash.should == "a\nbc\n" + end + it "selects the least-indented line and removes its indentation from all the lines" do require_relative 'fixtures/squiggly_heredoc' SquigglyHeredocSpecs.least_indented_on_the_first_line.should == "a\n b\n c\n" @@ -111,4 +106,14 @@ HERE SquigglyHeredocSpecs.least_indented_on_the_first_line_single.should == "a\n b\n c\n" SquigglyHeredocSpecs.least_indented_on_the_last_line_single.should == " a\n b\nc\n" end + + it "reports line numbers inside HEREDOC with method call" do + -> { + <<-HERE.chomp + a + b + #{c} + HERE + }.should.raise(NameError) { |e| e.backtrace[0].should.start_with?("#{__FILE__}:#{__LINE__ - 2}") } + end end diff --git a/spec/ruby/language/if_spec.rb b/spec/ruby/language/if_spec.rb index d1d95c1607..53fcb853d5 100644 --- a/spec/ruby/language/if_spec.rb +++ b/spec/ruby/language/if_spec.rb @@ -305,6 +305,59 @@ describe "The if expression" do 6.times(&b) ScratchPad.recorded.should == [4, 5, 4, 5] end + + it "warns when Integer literals are used instead of predicates" do + -> { + eval <<~RUBY + $. = 0 + 10.times { |i| ScratchPad << i if 4..5 } + RUBY + }.should complain(/warning: integer literal in flip-flop/, verbose: true) + ScratchPad.recorded.should == [] + end + end + + describe "when a branch syntactically does not return a value" do + it "raises SyntaxError if both do not return a value" do + -> { + eval <<~RUBY + def m + a = if rand + return + else + return + end + a + end + RUBY + }.should.raise(SyntaxError, /void value expression/) + end + + it "does not raise SyntaxError if one branch returns a value" do + eval(<<~RUBY).should == 1 + def m + a = if false # using false to make it clear that's not checked for + 42 + else + return 1 + end + a + end + m + RUBY + + eval(<<~RUBY).should == 1 + def m + a = if true # using true to make it clear that's not checked for + return 1 + else + 42 + end + a + end + m + RUBY + 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..6ba67cbb4f --- /dev/null +++ b/spec/ruby/language/it_parameter_spec.rb @@ -0,0 +1,108 @@ +require_relative '../spec_helper' + +ruby_version_is "3.4" do + eval <<-RUBY # use eval to avoid warnings on Ruby 3.3 + 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 "can be reassigned to act as a local variable" do + proc { tmp = it; it = tmp * 2; it }.call(21).should == 42 + 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 "is a regular local variable if there is a method `it` defined" do + o = Object.new + def o.it + 21 + end + + o.instance_eval("proc { it * 2 }").call(1).should == 2 + end + + it "is not shadowed by an reassignment in a block" do + a = nil + proc { a = it; it = 42 }.call(0) + a.should == 0 # if `it` were shadowed its value would be nil + end + + it "raises SyntaxError when block parameters are specified explicitly" do + -> { eval("-> () { it }") }.should.raise(SyntaxError, /ordinary parameter is defined/) + -> { eval("-> (x) { it }") }.should.raise(SyntaxError, /ordinary parameter is defined/) + + -> { eval("proc { || it }") }.should.raise(SyntaxError, /ordinary parameter is defined/) + -> { eval("proc { |x| it }") }.should.raise(SyntaxError, /ordinary parameter is defined/) + + -> { eval("lambda { || it }") }.should.raise(SyntaxError, /ordinary parameter is defined/) + -> { eval("lambda { |x| it }") }.should.raise(SyntaxError, /ordinary parameter is defined/) + + -> { eval("['a'].map { || it }") }.should.raise(SyntaxError, /ordinary parameter is defined/) + -> { eval("['a'].map { |x| it }") }.should.raise(SyntaxError, /ordinary parameter is defined/) + end + + it "cannot be mixed with numbered parameters" do + -> { + eval("proc { it + _1 }") + }.should.raise(SyntaxError, /numbered parameters are not allowed when 'it' is already used|'it' is already used in/) + + -> { + eval("proc { _1 + it }") + }.should.raise(SyntaxError, /numbered parameter is already used in|'it' is not allowed when a numbered parameter is already used/) + 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(ArgumentError, /wrong number of arguments/) + end + + context "given multiple arguments" do + it "provides it in a block and assigns the first argument for a block" do + proc { it }.call("a", "b").should == "a" + end + + it "raises ArgumentError for a proc" do + -> { -> { it }.call("a", "b") }.should.raise(ArgumentError, "wrong number of arguments (given 2, expected 1)") + -> { lambda { it }.call("a", "b") }.should.raise(ArgumentError, "wrong number of arguments (given 2, expected 1)") + end + end + end + RUBY +end diff --git a/spec/ruby/language/keyword_arguments_spec.rb b/spec/ruby/language/keyword_arguments_spec.rb new file mode 100644 index 0000000000..38edc24414 --- /dev/null +++ b/spec/ruby/language/keyword_arguments_spec.rb @@ -0,0 +1,398 @@ +require_relative '../spec_helper' + +describe "Keyword arguments" do + def target(*args, **kwargs) + [args, kwargs] + end + + it "are separated from positional arguments" do + def m(*args, **kwargs) + [args, kwargs] + end + + empty = {} + m(**empty).should == [[], {}] + m(empty).should == [[{}], {}] + + m(a: 1).should == [[], {a: 1}] + m({a: 1}).should == [[{a: 1}], {}] + end + + it "when the receiving method has not keyword parameters it treats kwargs as positional" do + def m(*a) + a + end + + m(a: 1).should == [{a: 1}] + m({a: 1}).should == [{a: 1}] + end + + context "empty kwargs are treated as if they were not passed" do + it "when calling a method" do + def m(*a) + a + end + + empty = {} + m(**empty).should == [] + m(empty).should == [{}] + end + + it "when yielding to a block" do + def y(*args, **kwargs) + yield(*args, **kwargs) + end + + empty = {} + y(**empty) { |*a| a }.should == [] + y(empty) { |*a| a }.should == [{}] + end + end + + it "extra keywords are not allowed without **kwrest" do + def m(*a, kw:) + a + end + + m(kw: 1).should == [] + -> { m(kw: 1, kw2: 2) }.should.raise(ArgumentError, 'unknown keyword: :kw2') + -> { m(kw: 1, true => false) }.should.raise(ArgumentError, 'unknown keyword: true') + -> { m(kw: 1, a: 1, b: 2, c: 3) }.should.raise(ArgumentError, 'unknown keywords: :a, :b, :c') + end + + it "raises ArgumentError exception when required keyword argument is not passed" do + def m(a:, b:, c:) + [a, b, c] + end + + -> { m(a: 1, b: 2) }.should.raise(ArgumentError, /missing keyword: :c/) + -> { m() }.should.raise(ArgumentError, /missing keywords: :a, :b, :c/) + end + + it "raises ArgumentError for missing keyword arguments even if there are extra ones" do + def m(a:) + a + end + + -> { m(b: 1) }.should.raise(ArgumentError, /missing keyword: :a/) + end + + it "handle * and ** at the same call site" do + def m(*a) + a + end + + m(*[], **{}).should == [] + m(*[], 42, **{}).should == [42] + end + + context "marked as ruby2_keywords_hash" do + it "is not copied when passed as a positional argument" do + h = Hash.ruby2_keywords_hash(a:1) + + def bar(a) + a + end + + h2 = bar(h) + h2.should.equal?(h) + Hash.ruby2_keywords_hash?(h).should == true + end + end + + context "**" do + it "copies a non-empty Hash for a method taking (*args)" do + def m(*args) + args[0] + end + + h = {a: 1} + m(**h).should_not.equal?(h) + h.should == {a: 1} + end + + it "copies the given Hash for a method taking (**kwargs)" do + def m(**kw) + kw + end + + empty = {} + m(**empty).should == empty + m(**empty).should_not.equal?(empty) + + h = {a: 1} + m(**h).should == h + m(**h).should_not.equal?(h) + end + end + + context "delegation" do + it "works with (*args, **kwargs)" do + def m(*args, **kwargs) + target(*args, **kwargs) + end + + empty = {} + m(**empty).should == [[], {}] + m(empty).should == [[{}], {}] + + m(a: 1).should == [[], {a: 1}] + m({a: 1}).should == [[{a: 1}], {}] + end + + it "works with proc { |*args, **kwargs| }" do + m = proc do |*args, **kwargs| + target(*args, **kwargs) + end + + empty = {} + m.(**empty).should == [[], {}] + m.(empty).should == [[{}], {}] + + m.(a: 1).should == [[], {a: 1}] + m.({a: 1}).should == [[{a: 1}], {}] + + # no autosplatting for |*args, **kwargs| + m.([1, 2]).should == [[[1, 2]], {}] + end + + it "works with -> (*args, **kwargs) {}" do + m = -> *args, **kwargs do + target(*args, **kwargs) + end + + empty = {} + m.(**empty).should == [[], {}] + m.(empty).should == [[{}], {}] + + m.(a: 1).should == [[], {a: 1}] + m.({a: 1}).should == [[{a: 1}], {}] + end + + it "works with (...)" do + instance_eval <<~DEF + def m(...) + target(...) + end + DEF + + empty = {} + m(**empty).should == [[], {}] + m(empty).should == [[{}], {}] + + m(a: 1).should == [[], {a: 1}] + m({a: 1}).should == [[{a: 1}], {}] + end + + it "works with call(*ruby2_keyword_args)" do + class << self + ruby2_keywords def m(*args) + target(*args) + end + 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} + + 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 + + m(kw).should == [[{a: 1}], {}] + m(kw)[0][0].should.equal?(kw) + Hash.ruby2_keywords_hash?(kw).should == false + end + + it "works with super(*ruby2_keyword_args)" do + parent = Class.new do + def m(*args, **kwargs) + [args, kwargs] + end + end + + child = Class.new(parent) do + ruby2_keywords def m(*args) + super(*args) + end + end + + obj = child.new + + empty = {} + obj.m(**empty).should == [[], {}] + Hash.ruby2_keywords_hash?(empty).should == false + obj.m(empty).should == [[{}], {}] + Hash.ruby2_keywords_hash?(empty).should == false + + obj.m(a: 1).should == [[], {a: 1}] + obj.m({a: 1}).should == [[{a: 1}], {}] + + kw = {a: 1} + + obj.m(**kw).should == [[], {a: 1}] + obj.m(**kw)[1].should == kw + obj.m(**kw)[1].should_not.equal?(kw) + Hash.ruby2_keywords_hash?(kw).should == false + Hash.ruby2_keywords_hash?(obj.m(**kw)[1]).should == false + + obj.m(kw).should == [[{a: 1}], {}] + obj.m(kw)[0][0].should.equal?(kw) + Hash.ruby2_keywords_hash?(kw).should == false + end + + it "works with zsuper" do + parent = Class.new do + def m(*args, **kwargs) + [args, kwargs] + end + end + + child = Class.new(parent) do + ruby2_keywords def m(*args) + super + end + end + + obj = child.new + + empty = {} + obj.m(**empty).should == [[], {}] + Hash.ruby2_keywords_hash?(empty).should == false + obj.m(empty).should == [[{}], {}] + Hash.ruby2_keywords_hash?(empty).should == false + + obj.m(a: 1).should == [[], {a: 1}] + obj.m({a: 1}).should == [[{a: 1}], {}] + + kw = {a: 1} + + obj.m(**kw).should == [[], {a: 1}] + obj.m(**kw)[1].should == kw + obj.m(**kw)[1].should_not.equal?(kw) + Hash.ruby2_keywords_hash?(kw).should == false + Hash.ruby2_keywords_hash?(obj.m(**kw)[1]).should == false + + obj.m(kw).should == [[{a: 1}], {}] + obj.m(kw)[0][0].should.equal?(kw) + Hash.ruby2_keywords_hash?(kw).should == false + end + + it "works with yield(*ruby2_keyword_args)" do + class << self + def y(args) + yield(*args) + end + + ruby2_keywords def m(*outer_args) + y(outer_args, &-> *args, **kwargs { target(*args, **kwargs) }) + end + 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} + + 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 + + m(kw).should == [[{a: 1}], {}] + m(kw)[0][0].should.equal?(kw) + Hash.ruby2_keywords_hash?(kw).should == false + end + + it "does not work with (*args)" do + class << self + def m(*args) + target(*args) + end + end + + empty = {} + m(**empty).should == [[], {}] + m(empty).should == [[{}], {}] + + m(a: 1).should == [[{a: 1}], {}] + m({a: 1}).should == [[{a: 1}], {}] + end + + 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 + + m(a:, b:).should == [1, 2] + end + 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 + + ruby2_keywords def m(*args) + n(*args) + end + end + + empty = {} + m(**empty).should == [[], {}] + m(empty).should == [[{}], {}] + + m(a: 1).should == [[{a: 1}], {}] + m({a: 1}).should == [[{a: 1}], {}] + end + end + + context "in define_method(name, &proc)" do + # This tests that a free-standing proc used in define_method and converted to ruby2_keywords adopts that logic. + # See jruby/jruby#8119 for a case where aggressive JIT optimization broke later ruby2_keywords changes. + it "works with ruby2_keywords" do + m = Class.new do + def bar(a, foo: nil) + [a, foo] + end + + # define_method and ruby2_keywords using send to avoid peephole optimizations + def self.setup + pr = make_proc + send :define_method, :foo, &pr + send :ruby2_keywords, :foo + end + + # create proc in isolated method to force jit compilation on some implementations + def self.make_proc + proc { |a, *args| bar(a, *args) } + end + end + + m.setup + + m.new.foo(1, foo:2).should == [1, 2] + end + end +end diff --git a/spec/ruby/language/lambda_spec.rb b/spec/ruby/language/lambda_spec.rb index 630817c909..2a2953bd97 100644 --- a/spec/ruby/language/lambda_spec.rb +++ b/spec/ruby/language/lambda_spec.rb @@ -11,25 +11,23 @@ describe "A lambda literal -> () { }" do end end - klass.new.create_lambda.should be_an_instance_of(Proc) + klass.new.create_lambda.should.instance_of?(Proc) end it "does not execute the block" do - -> { fail }.should be_an_instance_of(Proc) + -> { fail }.should.instance_of?(Proc) end it "returns a lambda" do - -> { }.lambda?.should be_true + -> { }.lambda?.should == true end - ruby_version_is "2.6" do - it "may include a rescue clause" do - eval('-> do raise ArgumentError; rescue ArgumentError; 7; end').should be_an_instance_of(Proc) - end + it "may include a rescue clause" do + eval('-> do raise ArgumentError; rescue ArgumentError; 7; end').should.instance_of?(Proc) + end - it "may include a ensure clause" do - eval('-> do 1; ensure; 2; end').should be_an_instance_of(Proc) - end + it "may include a ensure clause" do + eval('-> do 1; ensure; 2; end').should.instance_of?(Proc) end it "has its own scope for local variables" do @@ -50,10 +48,10 @@ describe "A lambda literal -> () { }" do @d = -> do end ruby - @a.().should be_nil - @b.().should be_nil - @c.().should be_nil - @d.().should be_nil + @a.().should == nil + @b.().should == nil + @c.().should == nil + @d.().should == nil end end @@ -93,9 +91,9 @@ describe "A lambda literal -> () { }" do @a = -> (*) { } ruby - @a.().should be_nil - @a.(1).should be_nil - @a.(1, 2, 3).should be_nil + @a.().should == nil + @a.(1).should == nil + @a.(1, 2, 3).should == nil end evaluate <<-ruby do @@ -111,7 +109,7 @@ describe "A lambda literal -> () { }" do @a = -> (a:) { a } ruby - -> { @a.() }.should raise_error(ArgumentError) + -> { @a.() }.should.raise(ArgumentError) @a.(a: 1).should == 1 end @@ -127,9 +125,9 @@ describe "A lambda literal -> () { }" do @a = -> (**) { } ruby - @a.().should be_nil - @a.(a: 1, b: 2).should be_nil - -> { @a.(1) }.should raise_error(ArgumentError) + @a.().should == nil + @a.(a: 1, b: 2).should == nil + -> { @a.(1) }.should.raise(ArgumentError) end evaluate <<-ruby do @@ -144,8 +142,8 @@ describe "A lambda literal -> () { }" do @a = -> (&b) { b } ruby - @a.().should be_nil - @a.() { }.should be_an_instance_of(Proc) + @a.().should == nil + @a.() { }.should.instance_of?(Proc) end evaluate <<-ruby do @@ -153,8 +151,8 @@ describe "A lambda literal -> () { }" do ruby @a.(1, 2).should == [1, 2] - -> { @a.() }.should raise_error(ArgumentError) - -> { @a.(1) }.should raise_error(ArgumentError) + -> { @a.() }.should.raise(ArgumentError) + -> { @a.(1) }.should.raise(ArgumentError) end evaluate <<-ruby do @@ -179,43 +177,25 @@ describe "A lambda literal -> () { }" do result.should == [1, 2, 3, [4, 5], 6, [7, 8], 9, 10, 11, 12] end - ruby_version_is ''...'3.0' do - evaluate <<-ruby do - @a = -> (*, **k) { k } - ruby - - @a.().should == {} - @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} - - suppress_keyword_warning do - h = mock("keyword splat") - h.should_receive(:to_hash).and_return({a: 1}) - @a.(h).should == {a: 1} - end - end - end - - ruby_version_is '3.0' do - evaluate <<-ruby do - @a = -> (*, **k) { k } - ruby + evaluate <<-ruby do + @a = -> (*, **k) { k } + ruby - @a.().should == {} - @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} + @a.().should == {} + @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} - h = mock("keyword splat") - h.should_not_receive(:to_hash) - @a.(h).should == {} - end + h = mock("keyword splat") + h.should_not_receive(:to_hash) + @a.(h).should == {} end evaluate <<-ruby do @a = -> (*, &b) { b } ruby - @a.().should be_nil - @a.(1, 2, 3, 4).should be_nil - @a.(&(l = ->{})).should equal(l) + @a.().should == nil + @a.(1, 2, 3, 4).should == nil + @a.(&(l = ->{})).should.equal?(l) end evaluate <<-ruby do @@ -283,37 +263,20 @@ describe "A lambda literal -> () { }" do end describe "with circular optional argument reference" do - ruby_version_is ''...'2.7' do - it "warns and uses a nil value when there is an existing local variable with same name" do + ruby_version_is ""..."3.4" do + it "raises a SyntaxError if using the argument in its default value" do a = 1 -> { - @proc = eval "-> (a=a) { a }" - }.should complain(/circular argument reference/) - @proc.call.should == nil - end - - it "warns and uses a nil value when there is an existing method with same name" do - def a; 1; end - -> { - @proc = eval "-> (a=a) { a }" - }.should complain(/circular argument reference/) - @proc.call.should == nil + eval "-> (a=a) { a }" + }.should.raise(SyntaxError) end end - ruby_version_is '2.7' do - it "raises a SyntaxError if using an existing local with the same name as the argument" do - a = 1 - -> { - @proc = eval "-> (a=a) { a }" - }.should raise_error(SyntaxError) - end - - it "raises a SyntaxError if there is an existing method with the same name as the argument" do - def a; 1; end + ruby_version_is "3.4" do + it "is nil if using the argument in its default value" do -> { - @proc = eval "-> (a=a) { a }" - }.should raise_error(SyntaxError) + eval "-> (a=a) { a }.call" + }.call.should == nil end end @@ -323,6 +286,24 @@ describe "A lambda literal -> () { }" do end end end + + evaluate <<-ruby do + @a = -> (**nil) { :ok } + ruby + + @a.call().should == :ok + -> { @a.call(a: 1) }.should.raise(ArgumentError, 'no keywords accepted') + -> { @a.call(**{a: 1}) }.should.raise(ArgumentError, 'no keywords accepted') + -> { @a.call("a" => 1) }.should.raise(ArgumentError, 'no keywords accepted') + end + + evaluate <<-ruby do + @a = -> (a, **nil) { a } + ruby + + @a.call({a: 1}).should == {a: 1} + -> { @a.call(a: 1) }.should.raise(ArgumentError, 'no keywords accepted') + end end describe "A lambda expression 'lambda { ... }'" do @@ -336,25 +317,25 @@ describe "A lambda expression 'lambda { ... }'" do lambda { } end - obj.define.should equal(obj) + obj.define.should.equal?(obj) end it "does not execute the block" do - lambda { fail }.should be_an_instance_of(Proc) + lambda { fail }.should.instance_of?(Proc) end it "returns a lambda" do - lambda { }.lambda?.should be_true + lambda { }.lambda?.should == true end it "requires a block" do suppress_warning do - lambda { lambda }.should raise_error(ArgumentError) + lambda { lambda }.should.raise(ArgumentError) end end it "may include a rescue clause" do - eval('lambda do raise ArgumentError; rescue ArgumentError; 7; end').should be_an_instance_of(Proc) + eval('lambda do raise ArgumentError; rescue ArgumentError; 7; end').should.instance_of?(Proc) end context "with an implicit block" do @@ -362,26 +343,12 @@ describe "A lambda expression 'lambda { ... }'" do def meth; lambda; end end - ruby_version_is ""..."2.7" do - it "can be created" do - implicit_lambda = nil + it "raises ArgumentError" do + implicit_lambda = nil + suppress_warning do -> { - implicit_lambda = meth { 1 } - }.should complain(/tried to create Proc object without a block/) - - implicit_lambda.lambda?.should be_true - implicit_lambda.call.should == 1 - end - end - - ruby_version_is "2.7" do - it "raises ArgumentError" do - implicit_lambda = nil - suppress_warning do - -> { - meth { 1 } - }.should raise_error(ArgumentError, /tried to create Proc object without a block/) - end + meth { 1 } + }.should.raise(ArgumentError, /tried to create Proc object without a block/) end end end @@ -392,8 +359,8 @@ describe "A lambda expression 'lambda { ... }'" do @b = lambda { || } ruby - @a.().should be_nil - @b.().should be_nil + @a.().should == nil + @b.().should == nil end end @@ -410,8 +377,8 @@ describe "A lambda expression 'lambda { ... }'" do @a = lambda { |a| a } ruby - lambda { m(&@a) }.should raise_error(ArgumentError) - lambda { m(1, 2, &@a) }.should raise_error(ArgumentError) + lambda { m(&@a) }.should.raise(ArgumentError) + lambda { m(1, 2, &@a) }.should.raise(ArgumentError) end evaluate <<-ruby do @@ -421,8 +388,8 @@ describe "A lambda expression 'lambda { ... }'" do @a.(1).should == 1 @a.([1, 2]).should == [1, 2] - lambda { @a.() }.should raise_error(ArgumentError) - lambda { @a.(1, 2) }.should raise_error(ArgumentError) + lambda { @a.() }.should.raise(ArgumentError) + lambda { @a.(1, 2) }.should.raise(ArgumentError) end evaluate <<-ruby do @@ -435,7 +402,7 @@ describe "A lambda expression 'lambda { ... }'" do m(1, &@a).should == 1 m([1, 2], &@a).should == [1, 2] - lambda { m2(&@a) }.should raise_error(ArgumentError) + lambda { m2(&@a) }.should.raise(ArgumentError) end evaluate <<-ruby do @@ -466,9 +433,9 @@ describe "A lambda expression 'lambda { ... }'" do @a = lambda { |*| } ruby - @a.().should be_nil - @a.(1).should be_nil - @a.(1, 2, 3).should be_nil + @a.().should == nil + @a.(1).should == nil + @a.(1, 2, 3).should == nil end evaluate <<-ruby do @@ -484,7 +451,7 @@ describe "A lambda expression 'lambda { ... }'" do @a = lambda { |a:| a } ruby - lambda { @a.() }.should raise_error(ArgumentError) + lambda { @a.() }.should.raise(ArgumentError) @a.(a: 1).should == 1 end @@ -500,9 +467,9 @@ describe "A lambda expression 'lambda { ... }'" do @a = lambda { |**| } ruby - @a.().should be_nil - @a.(a: 1, b: 2).should be_nil - lambda { @a.(1) }.should raise_error(ArgumentError) + @a.().should == nil + @a.(a: 1, b: 2).should == nil + lambda { @a.(1) }.should.raise(ArgumentError) end evaluate <<-ruby do @@ -517,8 +484,8 @@ describe "A lambda expression 'lambda { ... }'" do @a = lambda { |&b| b } ruby - @a.().should be_nil - @a.() { }.should be_an_instance_of(Proc) + @a.().should == nil + @a.() { }.should.instance_of?(Proc) end evaluate <<-ruby do @@ -550,43 +517,25 @@ describe "A lambda expression 'lambda { ... }'" do result.should == [1, 2, 3, [4, 5], 6, [7, 8], 9, 10, 11, 12] end - ruby_version_is ''...'3.0' do - evaluate <<-ruby do - @a = lambda { |*, **k| k } - ruby - - @a.().should == {} - @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} - - suppress_keyword_warning do - h = mock("keyword splat") - h.should_receive(:to_hash).and_return({a: 1}) - @a.(h).should == {a: 1} - end - end - end - - ruby_version_is '3.0' do - evaluate <<-ruby do - @a = lambda { |*, **k| k } - ruby + evaluate <<-ruby do + @a = lambda { |*, **k| k } + ruby - @a.().should == {} - @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} + @a.().should == {} + @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} - h = mock("keyword splat") - h.should_not_receive(:to_hash) - @a.(h).should == {} - end + h = mock("keyword splat") + h.should_not_receive(:to_hash) + @a.(h).should == {} end evaluate <<-ruby do @a = lambda { |*, &b| b } ruby - @a.().should be_nil - @a.(1, 2, 3, 4).should be_nil - @a.(&(l = ->{})).should equal(l) + @a.().should == nil + @a.(1, 2, 3, 4).should == nil + @a.(&(l = ->{})).should.equal?(l) end evaluate <<-ruby do @@ -652,5 +601,23 @@ describe "A lambda expression 'lambda { ... }'" do result = @a.(1, 2, e: 3, g: 4, h: 5, i: 6, &(l = ->{})) result.should == [1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l] end + + evaluate <<-ruby do + @a = lambda { |**nil| :ok } + ruby + + @a.call().should == :ok + -> { @a.call(a: 1) }.should.raise(ArgumentError, 'no keywords accepted') + -> { @a.call(**{a: 1}) }.should.raise(ArgumentError, 'no keywords accepted') + -> { @a.call("a" => 1) }.should.raise(ArgumentError, 'no keywords accepted') + end + + evaluate <<-ruby do + @a = lambda { |a, **nil| a } + ruby + + @a.call({a: 1}).should == {a: 1} + -> { @a.call(a: 1) }.should.raise(ArgumentError, 'no keywords accepted') + end end end diff --git a/spec/ruby/language/line_spec.rb b/spec/ruby/language/line_spec.rb index fcadaa71d7..2864798079 100644 --- a/spec/ruby/language/line_spec.rb +++ b/spec/ruby/language/line_spec.rb @@ -4,7 +4,7 @@ require_relative 'shared/__LINE__' describe "The __LINE__ pseudo-variable" do it "raises a SyntaxError if assigned to" do - -> { eval("__LINE__ = 1") }.should raise_error(SyntaxError) + -> { eval("__LINE__ = 1") }.should.raise(SyntaxError) end before :each do diff --git a/spec/ruby/language/loop_spec.rb b/spec/ruby/language/loop_spec.rb index fd17b53910..9b12765a5f 100644 --- a/spec/ruby/language/loop_spec.rb +++ b/spec/ruby/language/loop_spec.rb @@ -15,7 +15,7 @@ describe "The loop expression" do inner_loop = 123 break end - -> { inner_loop }.should raise_error(NameError) + -> { inner_loop }.should.raise(NameError) end it "returns the value passed to break if interrupted by break" do 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/match_spec.rb b/spec/ruby/language/match_spec.rb index ebf677cabc..096ebee022 100644 --- a/spec/ruby/language/match_spec.rb +++ b/spec/ruby/language/match_spec.rb @@ -46,6 +46,14 @@ describe "The =~ operator with named captures" do matched.should == "foo" unmatched.should == nil end + + it "sets existing local variables if declared in a higher scope" do + a = 42 + 1.times do + /(?<a>foo)/ =~ @string + end + a.should == "foo" + end end describe "on syntax of 'string_literal' =~ /regexp/" do diff --git a/spec/ruby/language/metaclass_spec.rb b/spec/ruby/language/metaclass_spec.rb index fc83067977..3bee823a75 100644 --- a/spec/ruby/language/metaclass_spec.rb +++ b/spec/ruby/language/metaclass_spec.rb @@ -16,17 +16,17 @@ describe "self in a metaclass body (class << obj)" do end it "raises a TypeError for numbers" do - -> { class << 1; self; end }.should raise_error(TypeError) + -> { class << 1; self; end }.should.raise(TypeError) end it "raises a TypeError for symbols" do - -> { class << :symbol; self; end }.should raise_error(TypeError) + -> { class << :symbol; self; end }.should.raise(TypeError) end it "is a singleton Class instance" do cls = class << mock('x'); self; end cls.is_a?(Class).should == true - cls.should_not equal(Object) + cls.should_not.equal?(Object) end end @@ -57,20 +57,20 @@ describe "A constant on a metaclass" do end it "is not defined on the object's class" do - @object.class.const_defined?(:CONST).should be_false + @object.class.const_defined?(:CONST).should == false end it "is not defined in the metaclass opener's scope" do class << @object CONST end - -> { CONST }.should raise_error(NameError) + -> { CONST }.should.raise(NameError) end it "cannot be accessed via object::CONST" do -> do @object::CONST - end.should raise_error(TypeError) + end.should.raise(TypeError) end it "raises a NameError for anonymous_module::CONST" do @@ -81,16 +81,16 @@ describe "A constant on a metaclass" do -> do @object::CONST - end.should raise_error(NameError) + end.should.raise(NameError) end it "appears in the metaclass constant list" do constants = class << @object; constants; end - constants.should include(:CONST) + constants.should.include?(:CONST) end it "does not appear in the object's class constant list" do - @object.class.constants.should_not include(:CONST) + @object.class.constants.should_not.include?(:CONST) end it "is not preserved when the object is duped" do @@ -98,14 +98,14 @@ describe "A constant on a metaclass" do -> do class << @object; CONST; end - end.should raise_error(NameError) + end.should.raise(NameError) end it "is preserved when the object is cloned" do @object = @object.clone class << @object - CONST.should_not be_nil + CONST.should_not == nil end end end diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb index 462a182b3d..324bd6cea5 100644 --- a/spec/ruby/language/method_spec.rb +++ b/spec/ruby/language/method_spec.rb @@ -19,7 +19,7 @@ describe "A method send" do x = mock("splat argument") x.should_not_receive(:to_ary) - m(*x).should equal(x) + m(*x).should.equal?(x) end it "calls #to_a" do @@ -40,7 +40,7 @@ describe "A method send" do x = mock("splat argument") x.should_receive(:to_a).and_return(1) - -> { m(*x) }.should raise_error(TypeError) + -> { m(*x) }.should.raise(TypeError) end end @@ -74,7 +74,7 @@ describe "A method send" do x = mock("splat argument") x.should_receive(:to_a).and_return(1) - -> { m(*x, 2, 3) }.should raise_error(TypeError) + -> { m(*x, 2, 3) }.should.raise(TypeError) end end @@ -108,13 +108,13 @@ describe "A method send" do x = mock("splat argument") x.should_receive(:to_a).and_return(1) - -> { m(1, *x, 2, 3) }.should raise_error(TypeError) + -> { m(1, *x, 2, 3) }.should.raise(TypeError) end it "copies the splatted array" do args = [3, 4] m(1, 2, *args, 4, 5).should == [1, 2, [3, 4], 4, 5] - m(1, 2, *args, 4, 5)[2].should_not equal(args) + m(1, 2, *args, 4, 5)[2].should_not.equal?(args) end it "allows an array being splatted to be modified by another argument" do @@ -153,7 +153,7 @@ describe "A method send" do x = mock("splat argument") x.should_receive(:to_a).and_return(1) - -> { m(1, 2, *x) }.should raise_error(TypeError) + -> { m(1, 2, *x) }.should.raise(TypeError) end end @@ -217,7 +217,7 @@ describe "An element assignment method send" do x = mock("splat argument") x.should_receive(:to_a).and_return(1) - -> { @o[*x] = 1 }.should raise_error(TypeError) + -> { @o[*x] = 1 }.should.raise(TypeError) end end @@ -255,7 +255,7 @@ describe "An element assignment method send" do x = mock("splat argument") x.should_receive(:to_a).and_return(1) - -> { @o[*x, 2, 3] = 4 }.should raise_error(TypeError) + -> { @o[*x, 2, 3] = 4 }.should.raise(TypeError) end end @@ -293,7 +293,7 @@ describe "An element assignment method send" do x = mock("splat argument") x.should_receive(:to_a).and_return(1) - -> { @o[1, 2, *x, 3] = 4 }.should raise_error(TypeError) + -> { @o[1, 2, *x, 3] = 4 }.should.raise(TypeError) end end @@ -331,7 +331,7 @@ describe "An element assignment method send" do x = mock("splat argument") x.should_receive(:to_a).and_return(1) - -> { @o[1, 2, 3, *x] = 4 }.should raise_error(TypeError) + -> { @o[1, 2, 3, *x] = 4 }.should.raise(TypeError) end end end @@ -368,7 +368,7 @@ describe "An attribute assignment method send" do x = mock("splat argument") x.should_receive(:to_a).and_return(1) - -> { @o.send :m=, *x, 1 }.should raise_error(TypeError) + -> { @o.send :m=, *x, 1 }.should.raise(TypeError) end end @@ -403,7 +403,7 @@ describe "An attribute assignment method send" do x = mock("splat argument") x.should_receive(:to_a).and_return(1) - -> { @o.send :m=, *x, 2, 3, 4 }.should raise_error(TypeError) + -> { @o.send :m=, *x, 2, 3, 4 }.should.raise(TypeError) end end @@ -438,7 +438,7 @@ describe "An attribute assignment method send" do x = mock("splat argument") x.should_receive(:to_a).and_return(1) - -> { @o.send :m=, 1, 2, *x, 3, 4 }.should raise_error(TypeError) + -> { @o.send :m=, 1, 2, *x, 3, 4 }.should.raise(TypeError) end end @@ -473,7 +473,7 @@ describe "An attribute assignment method send" do x = mock("splat argument") x.should_receive(:to_a).and_return(1) - -> { @o.send :m=, 1, 2, 3, *x, 4 }.should raise_error(TypeError) + -> { @o.send :m=, 1, 2, 3, *x, 4 }.should.raise(TypeError) end end end @@ -487,7 +487,7 @@ describe "A method" do end ruby - m.should be_nil + m.should == nil end evaluate <<-ruby do @@ -495,7 +495,7 @@ describe "A method" do end ruby - m.should be_nil + m.should == nil end end @@ -504,7 +504,7 @@ describe "A method" do def m(a) a end ruby - m((args = 1, 2, 3)).should equal(args) + m((args = 1, 2, 3)).should.equal?(args) end evaluate <<-ruby do @@ -535,18 +535,18 @@ describe "A method" do def m() end ruby - m().should be_nil - m(*[]).should be_nil - m(**{}).should be_nil + m().should == nil + m(*[]).should == nil + m(**{}).should == nil end evaluate <<-ruby do def m(*) end ruby - m().should be_nil - m(1).should be_nil - m(1, 2, 3).should be_nil + m().should == nil + m(1).should == nil + m(1, 2, 3).should == nil end evaluate <<-ruby do @@ -564,14 +564,21 @@ describe "A method" do def m(a:) a end ruby - -> { m() }.should raise_error(ArgumentError) + -> { m() }.should.raise(ArgumentError) m(a: 1).should == 1 suppress_keyword_warning do - -> { m("a" => 1, a: 1) }.should raise_error(ArgumentError) + -> { m("a" => 1, a: 1) }.should.raise(ArgumentError) end end evaluate <<-ruby do + def m(a:, **kw) [a, kw] end + ruby + + -> { m(b: 1) }.should.raise(ArgumentError) + end + + evaluate <<-ruby do def m(a: 1) a end ruby @@ -583,9 +590,9 @@ describe "A method" do def m(**) end ruby - m().should be_nil - m(a: 1, b: 2).should be_nil - -> { m(1) }.should raise_error(ArgumentError) + m().should == nil + m(a: 1, b: 2).should == nil + -> { m(1) }.should.raise(ArgumentError) end evaluate <<-ruby do @@ -599,23 +606,21 @@ describe "A method" do suppress_warning { eval "m(**{a: 1, b: 2}, **{a: 4, c: 7})" }.should == { a: 4, b: 2, c: 7 } - -> { m(2) }.should raise_error(ArgumentError) + -> { m(2) }.should.raise(ArgumentError) end - ruby_version_is "2.7" do - evaluate <<-ruby do - def m(**k); k end; - ruby + evaluate <<-ruby do + def m(**k); k end; + ruby - m("a" => 1).should == { "a" => 1 } - end + m("a" => 1).should == { "a" => 1 } end evaluate <<-ruby do def m(&b) b end ruby - m { }.should be_an_instance_of(Proc) + m { }.should.instance_of?(Proc) end evaluate <<-ruby do @@ -645,9 +650,9 @@ describe "A method" do def m((*), (*)) end ruby - m(2, 3).should be_nil - m([2, 3, 4], [5, 6]).should be_nil - -> { m a: 1 }.should raise_error(ArgumentError) + m(2, 3).should == nil + m([2, 3, 4], [5, 6]).should == nil + -> { m a: 1 }.should.raise(ArgumentError) end evaluate <<-ruby do @@ -740,72 +745,35 @@ describe "A method" do m(1, b: 2).should == [1, 2] suppress_keyword_warning do - -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError) + -> { m("a" => 1, b: 2) }.should.raise(ArgumentError) end end - ruby_version_is ""..."3.0" do - evaluate <<-ruby do - def m(a, b: 1) [a, b] end - ruby - - m(2).should == [2, 1] - m(1, b: 2).should == [1, 2] - suppress_keyword_warning do - m("a" => 1, b: 2).should == [{"a" => 1, b: 2}, 1] - end - end - - evaluate <<-ruby do - def m(a, **) a end - ruby - - m(1).should == 1 - m(1, a: 2, b: 3).should == 1 - suppress_keyword_warning do - m("a" => 1, b: 2).should == {"a" => 1, b: 2} - end - end - - evaluate <<-ruby do - def m(a, **k) [a, k] end - ruby + evaluate <<-ruby do + def m(a, b: 1) [a, b] end + ruby - m(1).should == [1, {}] - m(1, a: 2, b: 3).should == [1, {a: 2, b: 3}] - suppress_keyword_warning do - m("a" => 1, b: 2).should == [{"a" => 1, b: 2}, {}] - end - end + m(2).should == [2, 1] + m(1, b: 2).should == [1, 2] + -> { m("a" => 1, b: 2) }.should.raise(ArgumentError) end - ruby_version_is "3.0" do - evaluate <<-ruby do - def m(a, b: 1) [a, b] end - ruby - - m(2).should == [2, 1] - m(1, b: 2).should == [1, 2] - -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError) - end - - evaluate <<-ruby do - def m(a, **) a end - ruby + evaluate <<-ruby do + def m(a, **) a end + ruby - m(1).should == 1 - m(1, a: 2, b: 3).should == 1 - -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError) - end + m(1).should == 1 + m(1, a: 2, b: 3).should == 1 + -> { m("a" => 1, b: 2) }.should.raise(ArgumentError) + end - evaluate <<-ruby do - def m(a, **k) [a, k] end - ruby + evaluate <<-ruby do + def m(a, **k) [a, k] end + ruby - m(1).should == [1, {}] - m(1, a: 2, b: 3).should == [1, {a: 2, b: 3}] - -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError) - end + m(1).should == [1, {}] + m(1, a: 2, b: 3).should == [1, {a: 2, b: 3}] + -> { m("a" => 1, b: 2) }.should.raise(ArgumentError) end evaluate <<-ruby do @@ -880,8 +848,8 @@ describe "A method" do def m(a=1, (*b), (*c)) [a, b, c] end ruby - -> { m() }.should raise_error(ArgumentError) - -> { m(2) }.should raise_error(ArgumentError) + -> { m() }.should.raise(ArgumentError) + -> { m(2) }.should.raise(ArgumentError) m(2, 3).should == [1, [2], [3]] m(2, [3, 4], [5, 6]).should == [2, [3, 4], [5, 6]] end @@ -916,72 +884,32 @@ describe "A method" do result.should == [[1, 2, 3], 4, [5, 6], 7, [], 8] end - ruby_version_is ""..."3.0" do - evaluate <<-ruby do - def m(a=1, b:) [a, b] end - ruby - - m(b: 2).should == [1, 2] - m(2, b: 1).should == [2, 1] - suppress_keyword_warning do - m("a" => 1, b: 2).should == [{"a" => 1}, 2] - end - end - - evaluate <<-ruby do - def m(a=1, b: 2) [a, b] end - ruby - - m().should == [1, 2] - m(2).should == [2, 2] - m(b: 3).should == [1, 3] - suppress_keyword_warning do - m("a" => 1, b: 2).should == [{"a" => 1}, 2] - end - end - end - - ruby_version_is "3.0" do - evaluate <<-ruby do - def m(a=1, b:) [a, b] end - ruby - - m(b: 2).should == [1, 2] - m(2, b: 1).should == [2, 1] - -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError) - end - - evaluate <<-ruby do - def m(a=1, b: 2) [a, b] end - ruby + evaluate <<-ruby do + def m(a=1, b:) [a, b] end + ruby - m().should == [1, 2] - m(2).should == [2, 2] - m(b: 3).should == [1, 3] - -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError) - end + m(b: 2).should == [1, 2] + m(2, b: 1).should == [2, 1] + -> { m("a" => 1, b: 2) }.should.raise(ArgumentError) end - ruby_version_is ""..."2.7" do - evaluate <<-ruby do - def m(a=1, **) a end - ruby + evaluate <<-ruby do + def m(a=1, b: 2) [a, b] end + ruby - m().should == 1 - m(2, a: 1, b: 0).should == 2 - m("a" => 1, a: 2).should == {"a" => 1} - end + m().should == [1, 2] + m(2).should == [2, 2] + m(b: 3).should == [1, 3] + -> { m("a" => 1, b: 2) }.should.raise(ArgumentError) end - ruby_version_is "2.7" do - evaluate <<-ruby do - def m(a=1, **) a end - ruby + evaluate <<-ruby do + def m(a=1, **) a end + ruby - m().should == 1 - m(2, a: 1, b: 0).should == 2 - m("a" => 1, a: 2).should == 1 - end + m().should == 1 + m(2, a: 1, b: 0).should == 2 + m("a" => 1, a: 2).should == 1 end evaluate <<-ruby do @@ -1021,456 +949,13 @@ describe "A method" do m(1, 2, 3).should == [[1, 2], 3] end - ruby_version_is ""..."2.7" do - evaluate <<-ruby do - def m(*, a:) a end - ruby - - m(a: 1).should == 1 - m(1, 2, a: 3).should == 3 - suppress_keyword_warning do - m("a" => 1, a: 2).should == 2 - end - end - - evaluate <<-ruby do - def m(*a, b:) [a, b] end - ruby - - m(b: 1).should == [[], 1] - m(1, 2, b: 3).should == [[1, 2], 3] - suppress_keyword_warning do - m("a" => 1, b: 2).should == [[{"a" => 1}], 2] - end - end - - evaluate <<-ruby do - def m(*, a: 1) a end - ruby - - m().should == 1 - m(1, 2).should == 1 - m(a: 2).should == 2 - m(1, a: 2).should == 2 - suppress_keyword_warning do - m("a" => 1, a: 2).should == 2 - end - end - - evaluate <<-ruby do - def m(*a, b: 1) [a, b] end - ruby - - m().should == [[], 1] - m(1, 2, 3, b: 4).should == [[1, 2, 3], 4] - suppress_keyword_warning do - m("a" => 1, b: 2).should == [[{"a" => 1}], 2] - end - - a = mock("splat") - a.should_not_receive(:to_ary) - m(*a).should == [[a], 1] - end - - evaluate <<-ruby do - def m(*, **) end - ruby - - m().should be_nil - m(a: 1, b: 2).should be_nil - m(1, 2, 3, a: 4, b: 5).should be_nil - - h = mock("keyword splat") - h.should_receive(:to_hash).and_return({a: 1}) - suppress_keyword_warning do - m(h).should be_nil - end - - h = mock("keyword splat") - error = RuntimeError.new("error while converting to a hash") - h.should_receive(:to_hash).and_raise(error) - -> { m(h) }.should raise_error(error) - end - - evaluate <<-ruby do - def m(*a, **) a end - ruby - - m().should == [] - m(1, 2, 3, a: 4, b: 5).should == [1, 2, 3] - m("a" => 1, a: 1).should == [{"a" => 1}] - m(1, **{a: 2}).should == [1] - - h = mock("keyword splat") - h.should_receive(:to_hash) - -> { m(**h) }.should raise_error(TypeError) - end - - evaluate <<-ruby do - def m(*, **k) k end - ruby - - m().should == {} - m(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} - m("a" => 1, a: 1).should == {a: 1} - - h = mock("keyword splat") - h.should_receive(:to_hash).and_return({a: 1}) - m(h).should == {a: 1} - end - - evaluate <<-ruby do - def m(a = nil, **k) [a, k] end - ruby - - m().should == [nil, {}] - m("a" => 1).should == [{"a" => 1}, {}] - m(a: 1).should == [nil, {a: 1}] - m("a" => 1, a: 1).should == [{"a" => 1}, {a: 1}] - m({ "a" => 1 }, a: 1).should == [{"a" => 1}, {a: 1}] - m({a: 1}, {}).should == [{a: 1}, {}] - - h = {"a" => 1, b: 2} - m(h).should == [{"a" => 1}, {b: 2}] - h.should == {"a" => 1, b: 2} - - h = {"a" => 1} - m(h).first.should == h - - h = {} - r = m(h) - r.first.should be_nil - r.last.should == {} - - hh = {} - h = mock("keyword splat empty hash") - h.should_receive(:to_hash).and_return(hh) - r = m(h) - r.first.should be_nil - r.last.should == {} - - h = mock("keyword splat") - h.should_receive(:to_hash).and_return({"a" => 1, a: 2}) - m(h).should == [{"a" => 1}, {a: 2}] - end - - evaluate <<-ruby do - def m(*a, **k) [a, k] end - ruby - - m().should == [[], {}] - m(1).should == [[1], {}] - m(a: 1, b: 2).should == [[], {a: 1, b: 2}] - m(1, 2, 3, a: 2).should == [[1, 2, 3], {a: 2}] - - m("a" => 1).should == [[{"a" => 1}], {}] - m(a: 1).should == [[], {a: 1}] - m("a" => 1, a: 1).should == [[{"a" => 1}], {a: 1}] - m({ "a" => 1 }, a: 1).should == [[{"a" => 1}], {a: 1}] - m({a: 1}, {}).should == [[{a: 1}], {}] - m({a: 1}, {"a" => 1}).should == [[{a: 1}, {"a" => 1}], {}] - - bo = BasicObject.new - def bo.to_a; [1, 2, 3]; end - def bo.to_hash; {:b => 2, :c => 3}; end - - m(*bo, **bo).should == [[1, 2, 3], {:b => 2, :c => 3}] - end - end - - ruby_version_is "2.7"...'3.0' do - evaluate <<-ruby do - def m(*, a:) a end - ruby - - m(a: 1).should == 1 - m(1, 2, a: 3).should == 3 - suppress_keyword_warning do - m("a" => 1, a: 2).should == 2 - end - end - - evaluate <<-ruby do - def m(*a, b:) [a, b] end - ruby - - m(b: 1).should == [[], 1] - m(1, 2, b: 3).should == [[1, 2], 3] - suppress_keyword_warning do - m("a" => 1, b: 2).should == [[{"a" => 1}], 2] - end - end - - evaluate <<-ruby do - def m(*, a: 1) a end - ruby - - m().should == 1 - m(1, 2).should == 1 - m(a: 2).should == 2 - m(1, a: 2).should == 2 - suppress_keyword_warning do - m("a" => 1, a: 2).should == 2 - end - end - - evaluate <<-ruby do - def m(*a, b: 1) [a, b] end - ruby - - m().should == [[], 1] - m(1, 2, 3, b: 4).should == [[1, 2, 3], 4] - suppress_keyword_warning do - m("a" => 1, b: 2).should == [[{"a" => 1}], 2] - end - - a = mock("splat") - a.should_not_receive(:to_ary) - m(*a).should == [[a], 1] - end - - evaluate <<-ruby do - def m(*a, **) a end - ruby - - m().should == [] - m(1, 2, 3, a: 4, b: 5).should == [1, 2, 3] - m("a" => 1, a: 1).should == [] - m(1, **{a: 2}).should == [1] - - h = mock("keyword splat") - h.should_receive(:to_hash) - -> { m(**h) }.should raise_error(TypeError) - end - - evaluate <<-ruby do - def m(*, **k) k end - ruby - - m().should == {} - m(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} - m("a" => 1, a: 1).should == {"a" => 1, a: 1} - - h = mock("keyword splat") - h.should_receive(:to_hash).and_return({a: 1}) - suppress_warning do - m(h).should == {a: 1} - end - end - - evaluate <<-ruby do - def m(a = nil, **k) [a, k] end - ruby - - m().should == [nil, {}] - m("a" => 1).should == [nil, {"a" => 1}] - m(a: 1).should == [nil, {a: 1}] - m("a" => 1, a: 1).should == [nil, {"a" => 1, a: 1}] - m({ "a" => 1 }, a: 1).should == [{"a" => 1}, {a: 1}] - suppress_warning do - m({a: 1}, {}).should == [{a: 1}, {}] - - h = {"a" => 1, b: 2} - m(h).should == [{"a" => 1}, {b: 2}] - h.should == {"a" => 1, b: 2} - - h = {"a" => 1} - m(h).first.should == h - - h = {} - r = m(h) - r.first.should be_nil - r.last.should == {} - - hh = {} - h = mock("keyword splat empty hash") - h.should_receive(:to_hash).and_return(hh) - r = m(h) - r.first.should be_nil - r.last.should == {} - - h = mock("keyword splat") - h.should_receive(:to_hash).and_return({"a" => 1, a: 2}) - m(h).should == [{"a" => 1}, {a: 2}] - end - end - - evaluate <<-ruby do - def m(*a, **k) [a, k] end - ruby - - m().should == [[], {}] - m(1).should == [[1], {}] - m(a: 1, b: 2).should == [[], {a: 1, b: 2}] - m(1, 2, 3, a: 2).should == [[1, 2, 3], {a: 2}] - - m("a" => 1).should == [[], {"a" => 1}] - m(a: 1).should == [[], {a: 1}] - m("a" => 1, a: 1).should == [[], {"a" => 1, a: 1}] - m({ "a" => 1 }, a: 1).should == [[{"a" => 1}], {a: 1}] - suppress_warning do - m({a: 1}, {}).should == [[{a: 1}], {}] - end - m({a: 1}, {"a" => 1}).should == [[{a: 1}, {"a" => 1}], {}] - - bo = BasicObject.new - def bo.to_a; [1, 2, 3]; end - def bo.to_hash; {:b => 2, :c => 3}; end - - m(*bo, **bo).should == [[1, 2, 3], {:b => 2, :c => 3}] - end - - evaluate <<-ruby do - def m(*, a:) a end - ruby - - m(a: 1).should == 1 - m(1, 2, a: 3).should == 3 - suppress_keyword_warning do - m("a" => 1, a: 2).should == 2 - end - end - - evaluate <<-ruby do - def m(*a, b:) [a, b] end - ruby - - m(b: 1).should == [[], 1] - m(1, 2, b: 3).should == [[1, 2], 3] - suppress_keyword_warning do - m("a" => 1, b: 2).should == [[{"a" => 1}], 2] - end - end - - evaluate <<-ruby do - def m(*, a: 1) a end - ruby - - m().should == 1 - m(1, 2).should == 1 - m(a: 2).should == 2 - m(1, a: 2).should == 2 - suppress_keyword_warning do - m("a" => 1, a: 2).should == 2 - end - end - - evaluate <<-ruby do - def m(*a, b: 1) [a, b] end - ruby - - m().should == [[], 1] - m(1, 2, 3, b: 4).should == [[1, 2, 3], 4] - suppress_keyword_warning do - m("a" => 1, b: 2).should == [[{"a" => 1}], 2] - end - - a = mock("splat") - a.should_not_receive(:to_ary) - m(*a).should == [[a], 1] - end - - evaluate <<-ruby do - def m(*a, **) a end - ruby - - m().should == [] - m(1, 2, 3, a: 4, b: 5).should == [1, 2, 3] - m("a" => 1, a: 1).should == [] - m(1, **{a: 2}).should == [1] - - h = mock("keyword splat") - h.should_receive(:to_hash) - -> { m(**h) }.should raise_error(TypeError) - end - - evaluate <<-ruby do - def m(*, **k) k end - ruby - - m().should == {} - m(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} - m("a" => 1, a: 1).should == {"a" => 1, a: 1} - - h = mock("keyword splat") - h.should_receive(:to_hash).and_return({a: 1}) - suppress_keyword_warning do - m(h).should == {a: 1} - end - end - - evaluate <<-ruby do - def m(a = nil, **k) [a, k] end - ruby - - m().should == [nil, {}] - m("a" => 1).should == [nil, {"a" => 1}] - m(a: 1).should == [nil, {a: 1}] - m("a" => 1, a: 1).should == [nil, {"a" => 1, a: 1}] - m({ "a" => 1 }, a: 1).should == [{"a" => 1}, {a: 1}] - suppress_keyword_warning do - m({a: 1}, {}).should == [{a: 1}, {}] - end - - h = {"a" => 1, b: 2} - suppress_keyword_warning do - m(h).should == [{"a" => 1}, {b: 2}] - end - h.should == {"a" => 1, b: 2} - - h = {"a" => 1} - m(h).first.should == h - - h = {} - suppress_keyword_warning do - m(h).should == [nil, {}] - end - - hh = {} - h = mock("keyword splat empty hash") - h.should_receive(:to_hash).and_return({a: 1}) - suppress_keyword_warning do - m(h).should == [nil, {a: 1}] - end - - h = mock("keyword splat") - h.should_receive(:to_hash).and_return({"a" => 1}) - m(h).should == [h, {}] - end - - evaluate <<-ruby do - def m(*a, **k) [a, k] end - ruby - - m().should == [[], {}] - m(1).should == [[1], {}] - m(a: 1, b: 2).should == [[], {a: 1, b: 2}] - m(1, 2, 3, a: 2).should == [[1, 2, 3], {a: 2}] - - m("a" => 1).should == [[], {"a" => 1}] - m(a: 1).should == [[], {a: 1}] - m("a" => 1, a: 1).should == [[], {"a" => 1, a: 1}] - m({ "a" => 1 }, a: 1).should == [[{"a" => 1}], {a: 1}] - suppress_keyword_warning do - m({a: 1}, {}).should == [[{a: 1}], {}] - end - m({a: 1}, {"a" => 1}).should == [[{a: 1}, {"a" => 1}], {}] - - bo = BasicObject.new - def bo.to_a; [1, 2, 3]; end - def bo.to_hash; {:b => 2, :c => 3}; end - - m(*bo, **bo).should == [[1, 2, 3], {:b => 2, :c => 3}] - end - end - evaluate <<-ruby do def m(*, &b) b end ruby - m().should be_nil - m(1, 2, 3, 4).should be_nil - m(&(l = ->{})).should equal(l) + m().should == nil + m(1, 2, 3, 4).should == nil + m(&(l = ->{})).should.equal?(l) end evaluate <<-ruby do @@ -1488,7 +973,7 @@ describe "A method" do m(a: 1, b: 2).should == [1, 2] suppress_keyword_warning do - -> { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError) + -> { m("a" => 1, a: 1, b: 2) }.should.raise(ArgumentError) end end @@ -1499,48 +984,26 @@ describe "A method" do m(a: 1).should == [1, 1] m(a: 1, b: 2).should == [1, 2] suppress_keyword_warning do - -> { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError) + -> { m("a" => 1, a: 1, b: 2) }.should.raise(ArgumentError) end end - ruby_version_is ''...'2.7' do - evaluate <<-ruby do - def m(a:, **) a end - ruby - - m(a: 1).should == 1 - m(a: 1, b: 2).should == 1 - -> { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError) - end - - evaluate <<-ruby do - def m(a:, **k) [a, k] end - ruby + evaluate <<-ruby do + def m(a:, **) a end + ruby - m(a: 1).should == [1, {}] - m(a: 1, b: 2, c: 3).should == [1, {b: 2, c: 3}] - -> { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError) - end + m(a: 1).should == 1 + m(a: 1, b: 2).should == 1 + m("a" => 1, a: 1, b: 2).should == 1 end - ruby_version_is '2.7' do - evaluate <<-ruby do - def m(a:, **) a end - ruby - - m(a: 1).should == 1 - m(a: 1, b: 2).should == 1 - m("a" => 1, a: 1, b: 2).should == 1 - end - - evaluate <<-ruby do - def m(a:, **k) [a, k] end - ruby + evaluate <<-ruby do + def m(a:, **k) [a, k] end + ruby - m(a: 1).should == [1, {}] - m(a: 1, b: 2, c: 3).should == [1, {b: 2, c: 3}] - m("a" => 1, a: 1, b: 2).should == [1, {"a" => 1, b: 2}] - end + m(a: 1).should == [1, {}] + m(a: 1, b: 2, c: 3).should == [1, {b: 2, c: 3}] + m("a" => 1, a: 1, b: 2).should == [1, {"a" => 1, b: 2}] end evaluate <<-ruby do @@ -1637,125 +1100,124 @@ describe "A method" do result.should == [1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l] end - ruby_version_is "2.7" do - evaluate <<-ruby do - def m(a, **nil); a end; - ruby - - m({a: 1}).should == {a: 1} - m({"a" => 1}).should == {"a" => 1} + evaluate <<-ruby do + def m(**nil); :ok; end; + ruby - -> { m(a: 1) }.should raise_error(ArgumentError) - -> { m(**{a: 1}) }.should raise_error(ArgumentError) - -> { m("a" => 1) }.should raise_error(ArgumentError) - end + m().should == :ok + -> { m(a: 1) }.should.raise(ArgumentError, 'no keywords accepted') + -> { m(**{a: 1}) }.should.raise(ArgumentError, 'no keywords accepted') + -> { m("a" => 1) }.should.raise(ArgumentError, 'no keywords accepted') end - ruby_version_is ''...'3.0' do - evaluate <<-ruby do - def m(a, b = nil, c = nil, d, e: nil, **f) - [a, b, c, d, e, f] - end - ruby + evaluate <<-ruby do + def m(a, **nil); a end; + ruby - result = m(1, 2) - result.should == [1, nil, nil, 2, nil, {}] + m({a: 1}).should == {a: 1} + m({"a" => 1}).should == {"a" => 1} + + -> { m(a: 1) }.should.raise(ArgumentError, 'no keywords accepted') + -> { m(**{a: 1}) }.should.raise(ArgumentError, 'no keywords accepted') + -> { m("a" => 1) }.should.raise(ArgumentError, 'no keywords accepted') + end - suppress_warning do - result = m(1, 2, {foo: :bar}) - result.should == [1, nil, nil, 2, nil, {foo: :bar}] + evaluate <<-ruby do + def m(a, b = nil, c = nil, d, e: nil, **f) + [a, b, c, d, e, f] end + ruby - result = m(1, {foo: :bar}) - result.should == [1, nil, nil, {foo: :bar}, nil, {}] - end + result = m(1, 2) + result.should == [1, nil, nil, 2, nil, {}] + + result = m(1, 2, {foo: :bar}) + result.should == [1, 2, nil, {foo: :bar}, nil, {}] + + result = m(1, {foo: :bar}) + result.should == [1, nil, nil, {foo: :bar}, nil, {}] end - ruby_version_is '3.0' do + ruby_version_is "4.1" do evaluate <<-ruby do - def m(a, b = nil, c = nil, d, e: nil, **f) - [a, b, c, d, e, f] - end + def m(a, &nil); a end; ruby - result = m(1, 2) - result.should == [1, nil, nil, 2, nil, {}] - - result = m(1, 2, {foo: :bar}) - result.should == [1, 2, nil, {foo: :bar}, nil, {}] + m(1).should == 1 - result = m(1, {foo: :bar}) - result.should == [1, nil, nil, {foo: :bar}, nil, {}] + -> { m(1) {} }.should.raise(ArgumentError, 'no block accepted') + -> { m(1, &proc {}) }.should.raise(ArgumentError, 'no block accepted') end end end - ruby_version_is '2.7' do - context 'when passing an empty keyword splat to a method that does not accept keywords' do - evaluate <<-ruby do - def m(*a); a; end - ruby + context 'when passing an empty keyword splat to a method that does not accept keywords' do + evaluate <<-ruby do + def m(*a); a; end + ruby - h = {} - m(**h).should == [] - end + h = {} + m(**h).should == [] end end - ruby_version_is '2.7'...'3.0' do - context 'when passing an empty keyword splat to a method that does not accept keywords' do - evaluate <<-ruby do - def m(a); a; end - ruby - h = {} + context 'when passing an empty keyword splat to a method that does not accept keywords' do + evaluate <<-ruby do + def m(a); a; end + ruby + h = {} - -> do - m(**h).should == {} - end.should complain(/warning: Passing the keyword argument as the last hash parameter is deprecated/) - end + -> do + m(**h).should == {} + end.should.raise(ArgumentError) end end - ruby_version_is ''...'3.0' do - context "assigns keyword arguments from a passed Hash without modifying it" do - evaluate <<-ruby do - def m(a: nil); a; end - ruby + context "raises ArgumentError if passing hash as keyword arguments" do + evaluate <<-ruby do + def m(a: nil); a; end + ruby - options = {a: 1}.freeze - -> do - suppress_warning do - m(options).should == 1 - end - end.should_not raise_error - options.should == {a: 1} - end + options = {a: 1}.freeze + -> do + m(options) + end.should.raise(ArgumentError) end end - ruby_version_is '3.0' do - context 'when passing an empty keyword splat to a method that does not accept keywords' do - evaluate <<-ruby do - def m(a); a; end - ruby - h = {} + it "assigns the last Hash to the last optional argument if the Hash contains non-Symbol keys and is not passed as keywords" do + def m(a = nil, b = {}, v: false) + [a, b, v] + end - -> do - m(**h).should == {} - end.should raise_error(ArgumentError) - end + h = { "key" => "value" } + m(:a, h).should == [:a, h, false] + m(:a, h, v: true).should == [:a, h, true] + m(v: true).should == [nil, {}, true] + 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(TypeError, "no implicit conversion of nil into Hash") + -> { m(a: 1, **nil) }.should.raise(TypeError, "no implicit conversion of nil into Hash") end + end - context "raises ArgumentError if passing hash as keyword arguments" do - evaluate <<-ruby do - def m(a: nil); a; end - ruby + ruby_version_is "3.4" do + it "expands nil using ** into {}" do + def m(**kw) kw; end - options = {a: 1}.freeze - -> do - m(options) - end.should raise_error(ArgumentError) - 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 @@ -1778,22 +1240,48 @@ describe "A method call with a space between method name and parentheses" do end end - context "when a single argument provided" do - it "assigns it" do + context "when a single argument is provided" do + it "assigns a simple expression" do + args = m (1) + args.should == [1] + end + + it "assigns an expression consisting of multiple statements" do + args = m ((0; 1)) + args.should == [1] + end + + it "assigns one single statement, without the need of parentheses" do args = m (1 == 1 ? true : false) args.should == [true] end + + it "supports multiple statements" do + eval("m (1; 2)").should == [2] + end end - context "when 2+ arguments provided" do + context "when multiple arguments are provided" do + it "assigns simple expressions" do + args = m (1), (2) + args.should == [1, 2] + end + + it "assigns expressions consisting of multiple statements" do + args = m ((0; 1)), ((2; 3)) + args.should == [1, 3] + end + end + + context "when the argument looks like an argument list" do it "raises a syntax error" do -> { eval("m (1, 2)") - }.should raise_error(SyntaxError) + }.should.raise(SyntaxError) -> { eval("m (1, 2, 3)") - }.should raise_error(SyntaxError) + }.should.raise(SyntaxError) end end @@ -1850,15 +1338,332 @@ describe "An array-dereference method ([])" do end end -ruby_version_is '3.0' do - describe "An endless method definition" do +describe "An endless method definition" do + context "without arguments" do evaluate <<-ruby do - def m(a) = a - ruby + def m() = 42 + ruby + + m.should == 42 + end + + context "without parenthesis" do + evaluate <<-ruby do + def m = 42 + ruby + + m.should == 42 + end + end + end + + context "with arguments" do + evaluate <<-ruby do + def m(a, b) = a + b + ruby + + m(1, 4).should == 5 + end + end + + context "with multiline body" do + evaluate <<-ruby do + def m(n) = + if n > 2 + m(n - 2) + m(n - 1) + else + 1 + end + ruby - a = b = m 1 - a.should == 1 - b.should == 1 + m(6).should == 8 + end + end + + # tested more thoroughly in language/delegation_spec.rb + context "with args forwarding" do + evaluate <<-ruby do + def mm(word, num:) + word * num + end + + def m(...) = mm(...) + mm(...) + ruby + + m("meow", num: 2).should == "meow" * 4 + end + end +end + +describe "Keyword arguments are now separated from positional arguments" do + context "when the method has only positional parameters" do + it "treats incoming keyword arguments as positional for compatibility" do + def foo(a, b, c, hsh) + hsh[:key] + end + + foo(1, 2, 3, key: 42).should == 42 + end + end + + context "when the method takes a ** parameter" do + it "captures the passed literal keyword arguments" do + def foo(a, b, c, **hsh) + hsh[:key] + end + + foo(1, 2, 3, key: 42).should == 42 + end + + it "captures the passed ** keyword arguments" do + def foo(a, b, c, **hsh) + hsh[:key] + end + + h = { key: 42 } + foo(1, 2, 3, **h).should == 42 + end + + it "does not convert a positional Hash to keyword arguments" do + def foo(a, b, c, **hsh) + hsh[:key] + end + + -> { + foo(1, 2, 3, { key: 42 }) + }.should.raise(ArgumentError, 'wrong number of arguments (given 4, expected 3)') + end + end + + context "when the method takes a key: parameter" do + context "when it's called with a positional Hash and no **" do + it "raises ArgumentError" do + def foo(a, b, c, key: 1) + key + end + + -> { + foo(1, 2, 3, { key: 42 }) + }.should.raise(ArgumentError, 'wrong number of arguments (given 4, expected 3)') + end + end + + context "when it's called with **" do + it "captures the passed keyword arguments" do + def foo(a, b, c, key: 1) + key + end + + h = { key: 42 } + foo(1, 2, 3, **h).should == 42 + end + end + end +end + +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 + + foo(1).should == [[], {bar: "baz", val: 1}] + end + end +end + +describe "Inside 'endless' method definitions" do + it "allows method calls without parenthesis" do + def greet(person) = "Hi, ".dup.concat person + + 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 + + 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 + + -> { m_with_block_parameter { } }.should_not complain(verbose: true) + end + + 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 + + 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 + + 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 + + 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 1a5fcaf46f..2f22e383d5 100644 --- a/spec/ruby/language/module_spec.rb +++ b/spec/ruby/language/module_spec.rb @@ -4,85 +4,105 @@ require_relative 'fixtures/module' describe "The module keyword" do it "creates a new module without semicolon" do module ModuleSpecsKeywordWithoutSemicolon end - ModuleSpecsKeywordWithoutSemicolon.should be_an_instance_of(Module) + ModuleSpecsKeywordWithoutSemicolon.should.instance_of?(Module) end it "creates a new module with a non-qualified constant name" do module ModuleSpecsToplevel; end - ModuleSpecsToplevel.should be_an_instance_of(Module) + ModuleSpecsToplevel.should.instance_of?(Module) end it "creates a new module with a qualified constant name" do module ModuleSpecs::Nested; end - ModuleSpecs::Nested.should be_an_instance_of(Module) + ModuleSpecs::Nested.should.instance_of?(Module) end it "creates a new module with a variable qualified constant name" do m = Module.new module m::N; end - m::N.should be_an_instance_of(Module) + m::N.should.instance_of?(Module) end it "reopens an existing module" do module ModuleSpecs; Reopened = true; end - ModuleSpecs::Reopened.should be_true - end - - it "reopens a module included in Object" do - module IncludedModuleSpecs; Reopened = true; end - ModuleSpecs::IncludedInObject::IncludedModuleSpecs::Reopened.should be_true + ModuleSpecs::Reopened.should == true + 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 -> do module ModuleSpecs::Modules::Klass; end - end.should raise_error(TypeError) + end.should.raise(TypeError) end it "raises a TypeError if the constant is a String" do - -> { module ModuleSpecs::Modules::A; end }.should raise_error(TypeError) + -> { module ModuleSpecs::Modules::A; end }.should.raise(TypeError) end it "raises a TypeError if the constant is an Integer" do - -> { module ModuleSpecs::Modules::B; end }.should raise_error(TypeError) + -> { module ModuleSpecs::Modules::B; end }.should.raise(TypeError) end it "raises a TypeError if the constant is nil" do - -> { module ModuleSpecs::Modules::C; end }.should raise_error(TypeError) + -> { module ModuleSpecs::Modules::C; end }.should.raise(TypeError) end it "raises a TypeError if the constant is true" do - -> { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError) + -> { module ModuleSpecs::Modules::D; end }.should.raise(TypeError) end it "raises a TypeError if the constant is false" do - -> { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError) + -> { module ModuleSpecs::Modules::D; end }.should.raise(TypeError) end end describe "Assigning an anonymous module to a constant" do it "sets the name of the module" do mod = Module.new - mod.name.should be_nil + mod.name.should == nil ::ModuleSpecs_CS1 = mod mod.name.should == "ModuleSpecs_CS1" + ensure + Object.send(:remove_const, :ModuleSpecs_CS1) end - ruby_version_is ""..."3.0" do - it "does not set the name of a module scoped by an anonymous module" do - a, b = Module.new, Module.new - a::B = b - b.name.should be_nil - end - end - - ruby_version_is "3.0" do - it "sets the name of a module scoped by an anonymous module" do - a, b = Module.new, Module.new - a::B = b - b.name.should.end_with? '::B' - end + it "sets the name of a module scoped by an anonymous module" do + a, b = Module.new, Module.new + a::B = b + b.name.should.end_with? '::B' end it "sets the name of contained modules when assigning a toplevel anonymous module" do @@ -97,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/next_spec.rb b/spec/ruby/language/next_spec.rb index 6fbfc4a54d..eac151eeb3 100644 --- a/spec/ruby/language/next_spec.rb +++ b/spec/ruby/language/next_spec.rb @@ -21,7 +21,7 @@ describe "The next statement from within the block" do end it "causes block to return nil if invoked with an empty expression" do - -> { next (); 456 }.call.should be_nil + -> { next (); 456 }.call.should == nil end it "returns the argument passed" do @@ -111,7 +111,7 @@ describe "The next statement" do it "is invalid and raises a SyntaxError" do -> { eval("def m; next; end") - }.should raise_error(SyntaxError) + }.should.raise(SyntaxError) end end end diff --git a/spec/ruby/language/not_spec.rb b/spec/ruby/language/not_spec.rb index 052af9b256..23411a8e1d 100644 --- a/spec/ruby/language/not_spec.rb +++ b/spec/ruby/language/not_spec.rb @@ -2,50 +2,50 @@ require_relative '../spec_helper' describe "The not keyword" do it "negates a `true' value" do - (not true).should be_false - (not 'true').should be_false + (not true).should == false + (not 'true').should == false end it "negates a `false' value" do - (not false).should be_true - (not nil).should be_true + (not false).should == true + (not nil).should == true end it "accepts an argument" do - not(true).should be_false + not(true).should == false end it "returns false if the argument is true" do - (not(true)).should be_false + (not(true)).should == false end it "returns true if the argument is false" do - (not(false)).should be_true + (not(false)).should == true end it "returns true if the argument is nil" do - (not(nil)).should be_true + (not(nil)).should == true end end describe "The `!' keyword" do it "negates a `true' value" do - (!true).should be_false - (!'true').should be_false + (!true).should == false + (!'true').should == false end it "negates a `false' value" do - (!false).should be_true - (!nil).should be_true + (!false).should == true + (!nil).should == true end it "doubled turns a truthful object into `true'" do - (!!true).should be_true - (!!'true').should be_true + (!!true).should == true + (!!'true').should == true end it "doubled turns a not truthful object into `false'" do - (!!false).should be_false - (!!nil).should be_false + (!!false).should == false + (!!nil).should == false end end diff --git a/spec/ruby/language/numbered_parameters_spec.rb b/spec/ruby/language/numbered_parameters_spec.rb index cd1ddf4555..23e89a14be 100644 --- a/spec/ruby/language/numbered_parameters_spec.rb +++ b/spec/ruby/language/numbered_parameters_spec.rb @@ -1,106 +1,113 @@ require_relative '../spec_helper' -ruby_version_is "2.7" do - describe "Numbered parameters" do - it "provides default parameters _1, _2, ... in a block" do - -> { _1 }.call("a").should == "a" - proc { _1 }.call("a").should == "a" - lambda { _1 }.call("a").should == "a" - ["a"].map { _1 }.should == ["a"] - end +describe "Numbered parameters" do + it "provides default parameters _1, _2, ... in a block" do + -> { _1 }.call("a").should == "a" + proc { _1 }.call("a").should == "a" + lambda { _1 }.call("a").should == "a" + ["a"].map { _1 }.should == ["a"] + end - it "assigns nil to not passed parameters" do - proc { [_1, _2] }.call("a").should == ["a", nil] - proc { [_1, _2] }.call("a", "b").should == ["a", "b"] - end + it "assigns nil to not passed parameters" do + proc { [_1, _2] }.call("a").should == ["a", nil] + proc { [_1, _2] }.call("a", "b").should == ["a", "b"] + end - it "supports variables _1-_9 only for the first 9 passed parameters" do - block = proc { [_1, _2, _3, _4, _5, _6, _7, _8, _9] } - result = block.call(1, 2, 3, 4, 5, 6, 7, 8, 9) - result.should == [1, 2, 3, 4, 5, 6, 7, 8, 9] - end + it "supports variables _1-_9 only for the first 9 passed parameters" do + block = proc { [_1, _2, _3, _4, _5, _6, _7, _8, _9] } + result = block.call(1, 2, 3, 4, 5, 6, 7, 8, 9) + result.should == [1, 2, 3, 4, 5, 6, 7, 8, 9] + end - it "does not support more than 9 parameters" do - -> { - proc { [_10] }.call(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) - }.should raise_error(NameError, /undefined local variable or method `_10'/) - end + it "does not support more than 9 parameters" do + -> { + proc { [_10] }.call(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + }.should.raise(NameError, /undefined local variable or method [`']_10'/) + end - it "can not be used in both outer and nested blocks at the same time" do - -> { - eval("-> { _1; -> { _2 } }") - }.should raise_error(SyntaxError, /numbered parameter is already used in/m) - end + it "can not be used in both outer and nested blocks at the same time" do + -> { + eval("-> { _1; -> { _2 } }") + }.should.raise(SyntaxError, /numbered parameter is already used in/m) + end - ruby_version_is '2.7'...'3.0' do - it "can be overwritten with local variable" do - suppress_warning do - eval <<~CODE - _1 = 0 - proc { _1 }.call("a").should == 0 - CODE - end - end - - it "warns when numbered parameter is overwritten with local variable" do - -> { - eval("_1 = 0") - }.should complain(/warning: `_1' is reserved for numbered parameter; consider another name/) - end - end + it "cannot be overwritten with local variable" do + -> { + eval <<~CODE + _1 = 0 + proc { _1 }.call("a").should == 0 + CODE + }.should.raise(SyntaxError, /_1 is reserved for numbered parameter/) + end - ruby_version_is '3.0' do - it "cannot be overwritten with local variable" do - -> { - eval <<~CODE - _1 = 0 - proc { _1 }.call("a").should == 0 - CODE - }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/) - end - - it "errors when numbered parameter is overwritten with local variable" do - -> { - eval("_1 = 0") - }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/) - end + it "errors when numbered parameter is overwritten with local variable" do + -> { + eval("_1 = 0") + }.should.raise(SyntaxError, /_1 is reserved for numbered parameter/) + end + + it "raises SyntaxError when block parameters are specified explicitly" do + -> { eval("-> () { _1 }") }.should.raise(SyntaxError, /ordinary parameter is defined/) + -> { eval("-> (x) { _1 }") }.should.raise(SyntaxError, /ordinary parameter is defined/) + + -> { eval("proc { || _1 }") }.should.raise(SyntaxError, /ordinary parameter is defined/) + -> { eval("proc { |x| _1 }") }.should.raise(SyntaxError, /ordinary parameter is defined/) + + -> { eval("lambda { || _1 }") }.should.raise(SyntaxError, /ordinary parameter is defined/) + -> { eval("lambda { |x| _1 }") }.should.raise(SyntaxError, /ordinary parameter is defined/) + + -> { eval("['a'].map { || _1 }") }.should.raise(SyntaxError, /ordinary parameter is defined/) + -> { eval("['a'].map { |x| _1 }") }.should.raise(SyntaxError, /ordinary parameter is defined/) + end + + describe "assigning to a numbered parameter" do + it "raises SyntaxError" do + -> { eval("proc { _1 = 0 }") }.should.raise(SyntaxError, /_1 is reserved for numbered parameter/) end + end - it "raises SyntaxError when block parameters are specified explicitly" do - -> { eval("-> () { _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) - -> { eval("-> (x) { _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + it "affects block arity" do + -> { _1 }.arity.should == 1 + -> { _2 }.arity.should == 2 + -> { _3 }.arity.should == 3 + -> { _4 }.arity.should == 4 + -> { _5 }.arity.should == 5 + -> { _6 }.arity.should == 6 + -> { _7 }.arity.should == 7 + -> { _8 }.arity.should == 8 + -> { _9 }.arity.should == 9 + + -> { _9 }.arity.should == 9 + proc { _9 }.arity.should == 9 + lambda { _9 }.arity.should == 9 + end - -> { eval("proc { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) - -> { eval("proc { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + it "affects block parameters" do + -> { _1 }.parameters.should == [[:req, :_1]] + -> { _2 }.parameters.should == [[:req, :_1], [:req, :_2]] - -> { eval("lambda { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) - -> { eval("lambda { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + proc { _1 }.parameters.should == [[:opt, :_1]] + proc { _2 }.parameters.should == [[:opt, :_1], [:opt, :_2]] + end - -> { eval("['a'].map { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) - -> { eval("['a'].map { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + 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 - it "affects block arity" do - -> { _1 }.arity.should == 1 - -> { _2 }.arity.should == 2 - -> { _3 }.arity.should == 3 - -> { _4 }.arity.should == 4 - -> { _5 }.arity.should == 5 - -> { _6 }.arity.should == 6 - -> { _7 }.arity.should == 7 - -> { _8 }.arity.should == 8 - -> { _9 }.arity.should == 9 - - -> { _9 }.arity.should == 9 - proc { _9 }.arity.should == 9 - lambda { _9 }.arity.should == 9 + 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 - obj = Object.new - def obj.foo; _1 end + it "does not work in methods" do + obj = Object.new + def obj.foo; _1 end - -> { obj.foo("a") }.should raise_error(ArgumentError, /wrong number of arguments/) - end + -> { obj.foo("a") }.should.raise(ArgumentError, /wrong number of arguments/) end end diff --git a/spec/ruby/language/numbers_spec.rb b/spec/ruby/language/numbers_spec.rb index 418bc60fa6..ed0e49c048 100644 --- a/spec/ruby/language/numbers_spec.rb +++ b/spec/ruby/language/numbers_spec.rb @@ -11,7 +11,7 @@ describe "A number literal" do end it "cannot have a leading underscore" do - -> { eval("_4_2") }.should raise_error(NameError) + -> { eval("_4_2") }.should.raise(NameError) end it "can have a decimal point" do @@ -20,8 +20,8 @@ describe "A number literal" do it "must have a digit before the decimal point" do 0.75.should == 0.75 - -> { eval(".75") }.should raise_error(SyntaxError) - -> { eval("-.75") }.should raise_error(SyntaxError) + -> { eval(".75") }.should.raise(SyntaxError) + -> { eval("-.75") }.should.raise(SyntaxError) end it "can have an exponent" do @@ -45,11 +45,15 @@ describe "A number literal" do eval('-3r').should == Rational(-3, 1) end + it "can be an float literal with trailing 'r' to represent a Rational in a canonical form" do + eval('1.0r').should == Rational(1, 1) + end + it "can be a float literal with trailing 'r' to represent a Rational" do eval('0.0174532925199432957r').should == Rational(174532925199432957, 10000000000000000000) end - it "can be an bignum literal with trailing 'r' to represent a Rational" do + it "can be a bignum literal with trailing 'r' to represent a Rational" do eval('1111111111111111111111111111111111111111111111r').should == Rational(1111111111111111111111111111111111111111111111, 1) eval('-1111111111111111111111111111111111111111111111r').should == Rational(-1111111111111111111111111111111111111111111111, 1) end diff --git a/spec/ruby/language/optional_assignments_spec.rb b/spec/ruby/language/optional_assignments_spec.rb index 3d9e0dbb65..ebb5d36351 100644 --- a/spec/ruby/language/optional_assignments_spec.rb +++ b/spec/ruby/language/optional_assignments_spec.rb @@ -1,4 +1,5 @@ require_relative '../spec_helper' +require_relative '../fixtures/constants' describe 'Optional variable assignments' do describe 'using ||=' do @@ -56,7 +57,7 @@ describe 'Optional variable assignments' do end end - describe 'using a accessor' do + describe 'using an accessor' do before do klass = Class.new { attr_accessor :b } @a = klass.new @@ -102,6 +103,16 @@ describe 'Optional variable assignments' do @a.b.should == 10 end + it 'does evaluate receiver only once when assigns' do + ScratchPad.record [] + @a.b = nil + + (ScratchPad << :evaluated; @a).b ||= 10 + + ScratchPad.recorded.should == [:evaluated] + @a.b.should == 10 + end + it 'returns the new value if set to false' do def @a.b=(x) :v @@ -121,29 +132,148 @@ describe 'Optional variable assignments' do (@a.b ||= 20).should == 10 end - it 'works when writer is private' do + it 'ignores method visibility when receiver is self' do + klass_with_private_methods = Class.new do + def initialize(v) @a = v end + def public_method(v); self.a ||= v end + private + def a; @a end + def a=(v) @a = v; 42 end + end + + a = klass_with_private_methods.new(false) + a.public_method(10).should == 10 + end + end + + describe 'using a #[]' do + before do + @a = {} klass = Class.new do - def t - self.b = false - (self.b ||= 10).should == 10 - (self.b ||= 20).should == 10 + def [](k) + @hash ||= {} + @hash[k] end - def b - @b + def []=(k, v) + @hash ||= {} + @hash[k] = v + 7 end + end + @b = klass.new + end - def b=(x) - @b = x - :v + it 'returns the assigned value, not the result of the []= method with ||=' do + (@b[:k] ||= 12).should == 12 + end + + it "evaluates the index precisely once" do + ary = [:x, :y] + @a[:x] = 15 + @a[ary.pop] ||= 25 + ary.should == [:x] + @a.should == { x: 15, y: 25 } + end + + it "evaluates the index arguments in the correct order" do + ary = Class.new(Array) do + def [](x, y) + super(x + 3 * y) + end + + def []=(x, y, value) + super(x + 3 * y, value) end + end.new + ary[0, 0] = 1 + ary[1, 0] = 1 + ary[2, 0] = nil + ary[3, 0] = 1 + ary[4, 0] = 1 + ary[5, 0] = 1 + ary[6, 0] = nil + + foo = [0, 2] + + ary[foo.pop, foo.pop] ||= 2 # expected `ary[2, 0] ||= 2` + + ary[2, 0].should == 2 + ary[6, 0].should == nil # returns the same element as `ary[0, 2]` + end + + it 'evaluates receiver only once when assigns' do + ScratchPad.record [] + @a[:k] = nil - private :b= + (ScratchPad << :evaluated; @a)[:k] ||= 2 + + ScratchPad.recorded.should == [:evaluated] + @a[:k].should == 2 + end + + it 'ignores method visibility when receiver is self' do + klass_with_private_methods = Class.new do + def initialize(h) @a = h end + def public_method(k, v); self[k] ||= v end + private + def [](k) @a[k] end + def []=(k, v) @a[k] = v; 42 end end - klass.new.t + a = klass_with_private_methods.new(k: false) + a.public_method(:k, 10).should == 10 end + context 'splatted argument' do + it 'correctly handles it' do + (@b[*[:m]] ||= 10).should == 10 + @b[:m].should == 10 + + (@b[*(1; [:n])] ||= 10).should == 10 + @b[:n].should == 10 + + (@b[*begin 1; [:k] end] ||= 10).should == 10 + @b[:k].should == 10 + end + + it 'calls #to_a only once' do + k = Object.new + def k.to_a + ScratchPad << :to_a + [:k] + end + + ScratchPad.record [] + (@b[*k] ||= 20).should == 20 + @b[:k].should == 20 + ScratchPad.recorded.should == [:to_a] + end + + it 'correctly handles a nested splatted argument' do + (@b[*[*[:k]]] ||= 20).should == 20 + @b[:k].should == 20 + end + + it 'correctly handles multiple nested splatted arguments' do + klass_with_multiple_parameters = Class.new do + def [](k1, k2, k3) + @hash ||= {} + @hash[:"#{k1}#{k2}#{k3}"] + end + + def []=(k1, k2, k3, v) + @hash ||= {} + @hash[:"#{k1}#{k2}#{k3}"] = v + 7 + end + end + a = klass_with_multiple_parameters.new + + (a[*[:a], *[:b], *[:c]] ||= 20).should == 20 + a[:a, :b, :c].should == 20 + end + end end end @@ -190,7 +320,7 @@ describe 'Optional variable assignments' do end end - describe 'using a single variable' do + describe 'using an accessor' do before do klass = Class.new { attr_accessor :b } @a = klass.new @@ -235,6 +365,29 @@ describe 'Optional variable assignments' do @a.b.should == 20 end + + it 'does evaluate receiver only once when assigns' do + ScratchPad.record [] + @a.b = 10 + + (ScratchPad << :evaluated; @a).b &&= 20 + + ScratchPad.recorded.should == [:evaluated] + @a.b.should == 20 + end + + it 'ignores method visibility when receiver is self' do + klass_with_private_methods = Class.new do + def initialize(v) @a = v end + def public_method(v); self.a &&= v end + private + def a; @a end + def a=(v) @a = v; 42 end + end + + a = klass_with_private_methods.new(true) + a.public_method(10).should == 10 + end end describe 'using a #[]' do @@ -296,13 +449,128 @@ describe 'Optional variable assignments' do end it 'returns the assigned value, not the result of the []= method with ||=' do - (@b[:k] ||= 12).should == 12 + @b[:k] = 10 + (@b[:k] &&= 12).should == 12 + end + + it "evaluates the index precisely once" do + ary = [:x, :y] + @a[:x] = 15 + @a[:y] = 20 + @a[ary.pop] &&= 25 + ary.should == [:x] + @a.should == { x: 15, y: 25 } + end + + it "evaluates the index arguments in the correct order" do + ary = Class.new(Array) do + def [](x, y) + super(x + 3 * y) + end + + def []=(x, y, value) + super(x + 3 * y, value) + end + end.new + ary[0, 0] = 1 + ary[1, 0] = 1 + ary[2, 0] = 1 + ary[3, 0] = 1 + ary[4, 0] = 1 + ary[5, 0] = 1 + ary[6, 0] = 1 + + foo = [0, 2] + + ary[foo.pop, foo.pop] &&= 2 # expected `ary[2, 0] &&= 2` + + ary[2, 0].should == 2 + ary[6, 0].should == 1 # returns the same element as `ary[0, 2]` + end + + it 'evaluates receiver only once when assigns' do + ScratchPad.record [] + @a[:k] = 1 + + (ScratchPad << :evaluated; @a)[:k] &&= 2 + + ScratchPad.recorded.should == [:evaluated] + @a[:k].should == 2 end it 'returns the assigned value, not the result of the []= method with +=' do @b[:k] = 17 (@b[:k] += 12).should == 29 end + + it 'ignores method visibility when receiver is self' do + klass_with_private_methods = Class.new do + def initialize(h) @a = h end + def public_method(k, v); self[k] &&= v end + private + def [](k) @a[k] end + def []=(k, v) @a[k] = v; 42 end + end + + a = klass_with_private_methods.new(k: true) + a.public_method(:k, 10).should == 10 + end + + context 'splatted argument' do + it 'correctly handles it' do + @b[:m] = 0 + (@b[*[:m]] &&= 10).should == 10 + @b[:m].should == 10 + + @b[:n] = 0 + (@b[*(1; [:n])] &&= 10).should == 10 + @b[:n].should == 10 + + @b[:k] = 0 + (@b[*begin 1; [:k] end] &&= 10).should == 10 + @b[:k].should == 10 + end + + it 'calls #to_a only once' do + k = Object.new + def k.to_a + ScratchPad << :to_a + [:k] + end + + ScratchPad.record [] + @b[:k] = 10 + (@b[*k] &&= 20).should == 20 + @b[:k].should == 20 + ScratchPad.recorded.should == [:to_a] + end + + it 'correctly handles a nested splatted argument' do + @b[:k] = 10 + (@b[*[*[:k]]] &&= 20).should == 20 + @b[:k].should == 20 + end + + it 'correctly handles multiple nested splatted arguments' do + klass_with_multiple_parameters = Class.new do + def [](k1, k2, k3) + @hash ||= {} + @hash[:"#{k1}#{k2}#{k3}"] + end + + def []=(k1, k2, k3, v) + @hash ||= {} + @hash[:"#{k1}#{k2}#{k3}"] = v + 7 + end + end + a = klass_with_multiple_parameters.new + + a[:a, :b, :c] = 10 + (a[*[:a], *[:b], *[:c]] &&= 20).should == 20 + a[:a, :b, :c].should == 20 + end + end end end @@ -335,7 +603,7 @@ describe 'Optional variable assignments' do end it 'with &&= assignments will fail with non-existent constants' do - -> { Object::A &&= 10 }.should raise_error(NameError) + -> { Object::A &&= 10 }.should.raise(NameError) end it 'with operator assignments' do @@ -347,7 +615,128 @@ describe 'Optional variable assignments' do end it 'with operator assignments will fail with non-existent constants' do - -> { Object::A += 10 }.should raise_error(NameError) + -> { Object::A += 10 }.should.raise(NameError) + end + end +end + +describe 'Optional constant assignment' do + describe 'with ||=' do + it "assigns a scoped constant if previously undefined" do + ConstantSpecs.should_not.const_defined?(:OpAssignUndefined) + module ConstantSpecs + OpAssignUndefined ||= 42 + end + ConstantSpecs::OpAssignUndefined.should == 42 + ConstantSpecs::OpAssignUndefinedOutside ||= 42 + ConstantSpecs::OpAssignUndefinedOutside.should == 42 + ConstantSpecs.send(:remove_const, :OpAssignUndefined) + ConstantSpecs.send(:remove_const, :OpAssignUndefinedOutside) + end + + it "assigns a global constant if previously undefined" do + OpAssignGlobalUndefined ||= 42 + ::OpAssignGlobalUndefinedExplicitScope ||= 42 + OpAssignGlobalUndefined.should == 42 + ::OpAssignGlobalUndefinedExplicitScope.should == 42 + Object.send :remove_const, :OpAssignGlobalUndefined + Object.send :remove_const, :OpAssignGlobalUndefinedExplicitScope + end + + it 'correctly defines non-existing constants' do + ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT1 ||= :assigned + ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT1.should == :assigned + end + + it 'correctly overwrites nil constants' do + suppress_warning do # already initialized constant + ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT1 = nil + ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT1 ||= :assigned + ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT1.should == :assigned + end + end + + it 'causes side-effects of the module part to be applied only once (for undefined constant)' do + x = 0 + (x += 1; ConstantSpecs::ClassA)::OR_ASSIGNED_CONSTANT2 ||= :assigned + x.should == 1 + ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT2.should == :assigned + end + + it 'causes side-effects of the module part to be applied only once (for nil constant)' do + suppress_warning do # already initialized constant + ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT2 = nil + x = 0 + (x += 1; ConstantSpecs::ClassA)::NIL_OR_ASSIGNED_CONSTANT2 ||= :assigned + x.should == 1 + ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT2.should == :assigned + end + end + + it 'does not evaluate the right-hand side if the module part raises an exception (for undefined constant)' do + x = 0 + y = 0 + + -> { + (x += 1; raise Exception; ConstantSpecs::ClassA)::OR_ASSIGNED_CONSTANT3 ||= (y += 1; :assigned) + }.should.raise(Exception) + + x.should == 1 + y.should == 0 + defined?(ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT3).should == nil + end + + it 'does not evaluate the right-hand side if the module part raises an exception (for nil constant)' do + ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT3 = nil + x = 0 + y = 0 + + -> { + (x += 1; raise Exception; ConstantSpecs::ClassA)::NIL_OR_ASSIGNED_CONSTANT3 ||= (y += 1; :assigned) + }.should.raise(Exception) + + 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 + + describe "with &&=" do + it "re-assigns a scoped constant if already true" do + module ConstantSpecs + OpAssignTrue = true + end + suppress_warning do + ConstantSpecs::OpAssignTrue &&= 1 + end + ConstantSpecs::OpAssignTrue.should == 1 + ConstantSpecs.send :remove_const, :OpAssignTrue + end + + it "leaves scoped constant if not true" do + module ConstantSpecs + OpAssignFalse = false + end + ConstantSpecs::OpAssignFalse &&= 1 + ConstantSpecs::OpAssignFalse.should == false + ConstantSpecs.send :remove_const, :OpAssignFalse + end + + it 'causes side-effects of the module part to be applied only once (when assigns)' do + module ConstantSpecs + OpAssignTrue = true + end + + suppress_warning do # already initialized constant + x = 0 + (x += 1; ConstantSpecs)::OpAssignTrue &&= :assigned + x.should == 1 + ConstantSpecs::OpAssignTrue.should == :assigned + end + + ConstantSpecs.send :remove_const, :OpAssignTrue end end end diff --git a/spec/ruby/language/or_spec.rb b/spec/ruby/language/or_spec.rb index fb75e788f1..8ae577a142 100644 --- a/spec/ruby/language/or_spec.rb +++ b/spec/ruby/language/or_spec.rb @@ -26,24 +26,24 @@ describe "The || operator" do end it "treats empty expressions as nil" do - (() || true).should be_true - (() || false).should be_false - (true || ()).should be_true - (false || ()).should be_nil - (() || ()).should be_nil + (() || true).should == true + (() || false).should == false + (true || ()).should == true + (false || ()).should == nil + (() || ()).should == nil end it "has a higher precedence than 'break' in 'break true || false'" do # see also 'break true or false' below - -> { break false || true }.call.should be_true + -> { break false || true }.call.should == true end it "has a higher precedence than 'next' in 'next true || false'" do - -> { next false || true }.call.should be_true + -> { next false || true }.call.should == true end it "has a higher precedence than 'return' in 'return true || false'" do - -> { return false || true }.call.should be_true + -> { return false || true }.call.should == true end end @@ -68,23 +68,23 @@ describe "The or operator" do end it "treats empty expressions as nil" do - (() or true).should be_true - (() or false).should be_false - (true or ()).should be_true - (false or ()).should be_nil - (() or ()).should be_nil + (() or true).should == true + (() or false).should == false + (true or ()).should == true + (false or ()).should == nil + (() or ()).should == nil end it "has a lower precedence than 'break' in 'break true or false'" do # see also 'break true || false' above - -> { eval "break true or false" }.should raise_error(SyntaxError, /void value expression/) + -> { eval "break true or false" }.should.raise(SyntaxError, /void value expression/) end it "has a lower precedence than 'next' in 'next true or false'" do - -> { eval "next true or false" }.should raise_error(SyntaxError, /void value expression/) + -> { eval "next true or false" }.should.raise(SyntaxError, /void value expression/) end it "has a lower precedence than 'return' in 'return true or false'" do - -> { eval "return true or false" }.should raise_error(SyntaxError, /void value expression/) + -> { eval "return true or false" }.should.raise(SyntaxError, /void value expression/) end end diff --git a/spec/ruby/language/pattern_matching_spec.rb b/spec/ruby/language/pattern_matching_spec.rb index dae2828bf6..a24500c9fd 100644 --- a/spec/ruby/language/pattern_matching_spec.rb +++ b/spec/ruby/language/pattern_matching_spec.rb @@ -1,1137 +1,1310 @@ require_relative '../spec_helper' -ruby_version_is "2.7" do - describe "Pattern matching" do - # TODO: Remove excessive eval calls when support of previous version - # Ruby 2.6 will be dropped +describe "Pattern matching" do + before :each do + ScratchPad.record [] + end - before :each do - ScratchPad.record [] + describe "Rightward assignment (`=>`) that can be standalone assoc operator that" do + it "deconstructs value" do + suppress_warning do + [0, 1] => [a, b] + [a, b].should == [0, 1] + end end - ruby_version_is "3.0" do - it "can be standalone assoc operator that deconstructs value" do - suppress_warning do - eval(<<-RUBY).should == [0, 1] - [0, 1] => [a, b] - [a, b] - RUBY - end + it "deconstructs value and properly scopes variables" do + suppress_warning do + a = nil + 1.times { + [0, 1] => [a, b] + } + [a, defined?(b)].should == [0, nil] end end - it "extends case expression with case/in construction" do - eval(<<~RUBY).should == :bar - case [0, 1] - in [0] - :foo - in [0, 1] - :bar - end - RUBY + it "can work with keywords" do + { a: 0, b: 1 } => { a:, b: } + [a, b].should == [0, 1] end + end - it "allows using then operator" do - eval(<<~RUBY).should == :bar - case [0, 1] - in [0] then :foo - in [0, 1] then :bar - end - RUBY + describe "One-line pattern matching" do + it "can be used to check if a pattern matches for Array-like entities" do + ([0, 1] in [a, b]).should == true + ([0, 1] in [a, b, c]).should == false + end + + it "can be used to check if a pattern matches for Hash-like entities" do + ({ a: 0, b: 1 } in { a:, b: }).should == true + ({ a: 0, b: 1 } in { a:, b:, c: }).should == false + end + end + + describe "find pattern" do + it "captures preceding elements to the pattern" do + case [0, 1, 2, 3] + in [*pre, 2, 3] + pre + else + false + end.should == [0, 1] end - describe "warning" do + it "captures following elements to the pattern" do + case [0, 1, 2, 3] + in [0, 1, *post] + post + else + false + end.should == [2, 3] + end + + it "captures both preceding and following elements to the pattern" do + case [0, 1, 2, 3, 4] + in [*pre, 2, *post] + [pre, post] + else + false + end.should == [[0, 1], [3, 4]] + end + + it "can capture the entirety of the pattern" do + case [0, 1, 2, 3, 4] + in [*everything] + everything + else + false + end.should == [0, 1, 2, 3, 4] + end + + it "will match an empty Array-like structure" do + case [] + in [*everything] + everything + else + false + end.should == [] + end + + it "can be nested" do + case [0, [2, 4, 6], [3, 9, 27], [4, 16, 64]] + in [*pre, [*, 9, a], *post] + [pre, post, a] + else + false + end.should == [[0, [2, 4, 6]], [[4, 16, 64]], 27] + end + + it "can be nested with an array pattern" do + case [0, [2, 4, 6], [3, 9, 27], [4, 16, 64]] + in [_, _, [*, 9, *], *post] + post + else + false + end.should == [[4, 16, 64]] + end + + it "can be nested within a hash pattern" do + case {a: [3, 9, 27]} + in {a: [*, 9, *post]} + post + else + false + end.should == [27] + end + + it "can nest hash and array patterns" do + case [0, {a: 42, b: [0, 1]}, {a: 42, b: [1, 2]}] + in [*, {a:, b: [1, c]}, *] + [a, c] + else + false + end.should == [42, 2] + end + end + + it "extends case expression with case/in construction" do + case [0, 1] + in [0] + :foo + in [0, 1] + :bar + end.should == :bar + end + + it "allows using then operator" do + case [0, 1] + in [0] then :foo + in [0, 1] then :bar + end.should == :bar + end + + describe "warning" do + before :each do + @experimental, Warning[:experimental] = Warning[:experimental], true + end + + after :each do + Warning[:experimental] = @experimental + end + + context 'when regular form' do before :each do - ruby_version_is ""..."3.0" do - @src = 'case [0, 1]; in [a, b]; end' + @src = 'case [0, 1]; in [a, b]; end' + end + + it "does not warn about pattern matching is experimental feature" do + -> { eval @src }.should_not complain + end + end + + context 'when one-line form' do + before :each do + @src = '[0, 1] => [a, b]' + end + + it "does not warn about pattern matching is experimental feature" do + -> { eval @src }.should_not complain + end + end + end + + it "binds variables" do + case [0, 1] + in [0, a] + a + end.should == 1 + end + + it "cannot mix in and when operators" do + -> { + eval <<~RUBY + case [] + when 1 == 1 + in [] end + RUBY + }.should.raise(SyntaxError, /syntax error, unexpected `in'|\(eval\):3: syntax error, unexpected keyword_in|unexpected 'in'/) - ruby_version_is "3.0" do - @src = '[0, 1] => [a, b]' + -> { + eval <<~RUBY + case [] + in [] + when 1 == 1 end + RUBY + }.should.raise(SyntaxError, /syntax error, unexpected `when'|\(eval\):3: syntax error, unexpected keyword_when|unexpected 'when'/) + end + + it "checks patterns until the first matching" do + case [0, 1] + in [0] + :foo + in [0, 1] + :bar + in [0, 1] + :baz + end.should == :bar + end + + it "executes else clause if no pattern matches" do + case [0, 1] + in [0] + true + else + false + end.should == false + end - @experimental, Warning[:experimental] = Warning[:experimental], true + it "raises NoMatchingPatternError if no pattern matches and no else clause" do + -> { + case [0, 1] + in [0] end + }.should.raise(NoMatchingPatternError, /\[0, 1\]/) - after :each do - Warning[:experimental] = @experimental + error_pattern = ruby_version_is("3.4") ? /\{a: 0, b: 1\}/ : /\{:a=>0, :b=>1\}/ + -> { + case {a: 0, b: 1} + in a: 1, b: 1 end + }.should.raise(NoMatchingPatternError, error_pattern) + end - 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) + it "raises NoMatchingPatternError if no pattern matches and evaluates the expression only once" do + evals = 0 + -> { + case (evals += 1; [0, 1]) + in [0] end - end + }.should.raise(NoMatchingPatternError, /\[0, 1\]/) + evals.should == 1 + end - it "binds variables" do - eval(<<~RUBY).should == 1 - case [0, 1] - in [0, a] - a + it "does not allow calculation or method calls in a pattern" do + -> { + eval <<~RUBY + case 0 + in 1 + 1 + true end RUBY + }.should.raise(SyntaxError, /unexpected|expected a delimiter after the patterns of an `in` clause/) + end + + it "evaluates the case expression once for multiple patterns, caching the result" do + case (ScratchPad << :foo; 1) + in 0 + false + in 1 + true + end.should == true + + ScratchPad.recorded.should == [:foo] + end + + describe "guards" do + it "supports if guard" do + case 0 + in 0 if false + true + else + false + end.should == false + + case 0 + in 0 if true + true + else + false + end.should == true end - it "cannot mix in and when operators" do - -> { - eval <<~RUBY - case [] - when 1 == 1 - in [] - end - RUBY - }.should raise_error(SyntaxError, /syntax error, unexpected `in'/) + it "supports unless guard" do + case 0 + in 0 unless true + true + else + false + end.should == false + + case 0 + in 0 unless false + true + else + false + end.should == true + end - -> { - eval <<~RUBY - case [] - in [] - when 1 == 1 - end - RUBY - }.should raise_error(SyntaxError, /syntax error, unexpected `when'/) + it "makes bound variables visible in guard" do + case [0, 1] + in [a, 1] if a >= 0 + true + end.should == true end - it "checks patterns until the first matching" do - eval(<<~RUBY).should == :bar - case [0, 1] - in [0] - :foo - in [0, 1] - :bar - in [0, 1] - :baz - end - RUBY + it "does not evaluate guard if pattern does not match" do + case 0 + in 1 if (ScratchPad << :foo) || true + else + end + + ScratchPad.recorded.should == [] end - it "executes else clause if no pattern matches" do - eval(<<~RUBY).should == false - case [0, 1] - in [0] - true - else - false - end - RUBY + it "takes guards into account when there are several matching patterns" do + case 0 + in 0 if false + :foo + in 0 if true + :bar + end.should == :bar end - it "raises NoMatchingPatternError if no pattern matches and no else clause" do - -> { - eval <<~RUBY - case [0, 1] - in [0] - end - RUBY - }.should raise_error(NoMatchingPatternError, /\[0, 1\]/) + it "executes else clause if no guarded pattern matches" do + case 0 + in 0 if false + true + else + false + end.should == false end - it "does not allow calculation or method calls in a pattern" do + it "raises NoMatchingPatternError if no guarded pattern matches and no else clause" do -> { - eval <<~RUBY - case 0 - in 1 + 1 - true - end - RUBY - }.should raise_error(SyntaxError, /unexpected/) + case [0, 1] + in [0, 1] if false + end + }.should.raise(NoMatchingPatternError, /\[0, 1\]/) end + end - it "evaluates the case expression once for multiple patterns, caching the result" do - eval(<<~RUBY).should == true - case (ScratchPad << :foo; 1) - in 0 - false - in 1 - true - end - RUBY + describe "value pattern" do + it "matches an object such that pattern === object" do + case 0 + in 0 + true + end.should == true + + case 0 + in ( + -1..1) + true + end.should == true + + case 0 + in Integer + true + end.should == true + + case "0" + in /0/ + true + end.should == true + + case "0" + in -> s { s == "0" } + true + end.should == true + end + + it "allows string literal with interpolation" do + x = "x" - ScratchPad.recorded.should == [:foo] + case "x" + in "#{x + ""}" + true + end.should == true end + end - describe "guards" do - it "supports if guard" do - eval(<<~RUBY).should == false - case 0 - in 0 if false - true - else - false - end - RUBY + describe "variable pattern" do + it "matches a value and binds variable name to this value" do + case 0 + in a + a + end.should == 0 + end - eval(<<~RUBY).should == true - case 0 - in 0 if true - true - else - false - end - RUBY + it "makes bounded variable visible outside a case statement scope" do + case 0 + in a end - it "supports unless guard" do - eval(<<~RUBY).should == false - case 0 - in 0 unless true - true - else - false - end - RUBY + a.should == 0 + end - eval(<<~RUBY).should == true - case 0 - in 0 unless false - true - else - false - end - RUBY + it "create local variables even if a pattern doesn't match" do + case 0 + in a + in b + in c end - it "makes bound variables visible in guard" do - eval(<<~RUBY).should == true - case [0, 1] - in [a, 1] if a >= 0 - true - end - RUBY - end + [a, b, c].should == [0, nil, nil] + end - it "does not evaluate guard if pattern does not match" do + it "allow using _ name to drop values" do + case [0, 1] + in [a, _] + a + end.should == 0 + end + + it "supports using _ in a pattern several times" do + case [0, 1, 2] + in [0, _, _] + true + end.should == true + end + + it "supports using any name with _ at the beginning in a pattern several times" do + case [0, 1, 2] + in [0, _x, _x] + true + end.should == true + + case {a: 0, b: 1, c: 2} + in {a: 0, b: _x, c: _x} + true + end.should == true + end + + it "does not support using variable name (except _) several times" do + -> { eval <<~RUBY - case 0 - in 1 if (ScratchPad << :foo) || true - else + case [0] + in [a, a] end RUBY + }.should.raise(SyntaxError, /duplicated variable name/) + end - ScratchPad.recorded.should == [] - end + it "supports existing variables in a pattern specified with ^ operator" do + a = 0 - it "takes guards into account when there are several matching patterns" do - eval(<<~RUBY).should == :bar - case 0 - in 0 if false - :foo - in 0 if true - :bar - end - RUBY - end + case 0 + in ^a + true + end.should == true + end + + it "allows applying ^ operator to bound variables" do + case [1, 1] + in [n, ^n] + n + end.should == 1 + + case [1, 2] + in [n, ^n] + true + else + false + end.should == false + end - it "executes else clause if no guarded pattern matches" do - eval(<<~RUBY).should == false - case 0 - in 0 if false + it "requires bound variable to be specified in a pattern before ^ operator when it relies on a bound variable" do + -> { + eval <<~RUBY + case [1, 2] + in [^n, n] true else false end RUBY - end + }.should.raise(SyntaxError, /n: no such local variable/) + end + end - it "raises NoMatchingPatternError if no guarded pattern matches and no else clause" do - -> { - eval <<~RUBY - case [0, 1] - in [0, 1] if false - end - RUBY - }.should raise_error(NoMatchingPatternError, /\[0, 1\]/) - end + describe "alternative pattern" do + it "matches if any of patterns matches" do + case 0 + in 0 | 1 | 2 + true + end.should == true end - describe "value pattern" do - it "matches an object such that pattern === object" do - eval(<<~RUBY).should == true - case 0 - in 0 - true + it "does not support variable binding" do + -> { + eval <<~RUBY + case [0, 1] + in [0, 0] | [0, a] end RUBY + }.should.raise(SyntaxError) + end - eval(<<~RUBY).should == true - case 0 - in (-1..1) - true - end - RUBY + it "support underscore prefixed variables in alternation" do + case [0, 1] + in [1, _] + false + in [0, 0] | [0, _a] + true + end.should == true + end - eval(<<~RUBY).should == true - case 0 - in Integer - true - end - RUBY + it "can be used as a nested pattern" do + case [[1], ["2"]] + in [[0] | nil, _] + false + in [[1], [1]] + false + in [[1], [2 | "2"]] + true + end.should == true + + case [1, 2] + in [0, _] | {a: 0} + false + in {a: 1, b: 2} | [1, 2] + true + end.should == true + end + end - eval(<<~RUBY).should == true - case "0" - in /0/ - true - end - RUBY + describe "AS pattern" do + it "binds a variable to a value if pattern matches" do + case 0 + in Integer => n + n + end.should == 0 + end - eval(<<~RUBY).should == true - case "0" - in ->(s) { s == "0" } - true - end - RUBY - end + it "can be used as a nested pattern" do + case [1, [2, 3]] + in [1, Array => ary] + ary + end.should == [2, 3] + end + end - it "allows string literal with interpolation" do - x = "x" + describe "Array pattern" do + it "supports form Constant(pat, pat, ...)" do + case [0, 1, 2] + in Array(0, 1, 2) + true + end.should == true + end - eval(<<~RUBY).should == true - case "x" - in "#{x + ""}" - true - end - RUBY - end + it "supports form Constant[pat, pat, ...]" do + case [0, 1, 2] + in Array[0, 1, 2] + true + end.should == true end - describe "variable pattern" do - it "matches a value and binds variable name to this value" do - eval(<<~RUBY).should == 0 - case 0 - in a - a - end - RUBY - end + it "supports form [pat, pat, ...]" do + case [0, 1, 2] + in [0, 1, 2] + true + end.should == true + end - it "makes bounded variable visible outside a case statement scope" do - eval(<<~RUBY).should == 0 - case 0 - in a - end + it "supports form pat, pat, ..." do + case [0, 1, 2] + in 0, 1, 2 + true + end.should == true + + case [0, 1, 2] + in 0, a, 2 + a + end.should == 1 + + case [0, 1, 2] + in 0, *rest + rest + end.should == [1, 2] + end - a - RUBY + it "matches an object with #deconstruct method which returns an array and each element in array matches element in pattern" do + obj = Object.new + + def obj.deconstruct + [0, 1] end - it "create local variables even if a pattern doesn't match" do - eval(<<~RUBY).should == [0, nil, nil] - case 0 - in a - in b - in c - end + case obj + in [Integer, Integer] + true + end.should == true + end - [a, b, c] - RUBY - end + it "calls #deconstruct once for multiple patterns, caching the result" do + obj = Object.new - it "allow using _ name to drop values" do - eval(<<~RUBY).should == 0 - case [0, 1] - in [a, _] - a - end - RUBY + def obj.deconstruct + ScratchPad << :deconstruct + [0, 1] end - it "supports using _ in a pattern several times" do - eval(<<~RUBY).should == true - case [0, 1, 2] - in [0, _, _] - true - end - RUBY - end + case obj + in [1, 2] + false + in [0, 1] + true + end.should == true - it "supports using any name with _ at the beginning in a pattern several times" do - eval(<<~RUBY).should == true - case [0, 1, 2] - in [0, _x, _x] - true - end - RUBY + ScratchPad.recorded.should == [:deconstruct] + end - eval(<<~RUBY).should == true - case {a: 0, b: 1, c: 2} - in {a: 0, b: _x, c: _x} - true - end - RUBY - end + it "calls #deconstruct even on objects that are already an array" do + obj = [1, 2] - it "does not support using variable name (except _) several times" do - -> { - eval <<~RUBY - case [0] - in [a, a] - end - RUBY - }.should raise_error(SyntaxError, /duplicated variable name/) + def obj.deconstruct + ScratchPad << :deconstruct + [3, 4] end - it "supports existing variables in a pattern specified with ^ operator" do - a = 0 + case obj + in [3, 4] + true + else + false + end.should == true - eval(<<~RUBY).should == true - case 0 - in ^a - true - end - RUBY - end + ScratchPad.recorded.should == [:deconstruct] + end - it "allows applying ^ operator to bound variables" do - eval(<<~RUBY).should == 1 - case [1, 1] - in [n, ^n] - n - end - RUBY + it "does not match object if Constant === object returns false" do + case [0, 1, 2] + in String[0, 1, 2] + true + else + false + end.should == false + end - eval(<<~RUBY).should == false - case [1, 2] - in [n, ^n] - true - else - false - end - RUBY - end + it "checks Constant === object before calling #deconstruct" do + c1 = Class.new + obj = c1.new + obj.should_not_receive(:deconstruct) + + case obj + in String[1] + true + else + false + end.should == false + end - it "requires bound variable to be specified in a pattern before ^ operator when it relies on a bound variable" do - -> { - eval <<~RUBY - case [1, 2] - in [^n, n] - true - else - false - end - RUBY - }.should raise_error(SyntaxError, /n: no such local variable/) - end + it "does not match object without #deconstruct method" do + obj = Object.new + obj.should_receive(:respond_to?).with(:deconstruct) + + case obj + in Object[] + true + else + false + end.should == false end - describe "alternative pattern" do - it "matches if any of patterns matches" do - eval(<<~RUBY).should == true - case 0 - in 0 | 1 | 2 - true - end - RUBY - end + it "raises TypeError if #deconstruct method does not return array" do + obj = Object.new - it "does not support variable binding" do - -> { - eval <<~RUBY - case [0, 1] - in [0, 0] | [0, a] - end - RUBY - }.should raise_error(SyntaxError, /illegal variable in alternative pattern/) + def obj.deconstruct + "" end - it "support underscore prefixed variables in alternation" do - eval(<<~RUBY).should == true - case [0, 1] - in [1, _] - false - in [0, 0] | [0, _a] - true - end - RUBY - end + -> { + case obj + in Object[] + else + end + }.should.raise(TypeError, /deconstruct must return Array/) end - describe "AS pattern" do - it "binds a variable to a value if pattern matches" do - eval(<<~RUBY).should == 0 - case 0 - in Integer => n - n - end - RUBY - end + it "accepts a subclass of Array from #deconstruct" do + obj = Object.new - it "can be used as a nested pattern" do - eval(<<~RUBY).should == [2, 3] - case [1, [2, 3]] - in [1, Array => ary] - ary - end - RUBY + def obj.deconstruct + Class.new(Array).new([0, 1]) end + + case obj + in [1, 2] + false + in [0, 1] + true + end.should == true end - describe "Array pattern" do - it "supports form Constant(pat, pat, ...)" do - eval(<<~RUBY).should == true - case [0, 1, 2] - in Array(0, 1, 2) - true - end - RUBY - end + it "does not match object if elements of array returned by #deconstruct method does not match elements in pattern" do + obj = Object.new - it "supports form Constant[pat, pat, ...]" do - eval(<<~RUBY).should == true - case [0, 1, 2] - in Array[0, 1, 2] - true - end - RUBY + def obj.deconstruct + [1] end - it "supports form [pat, pat, ...]" do - eval(<<~RUBY).should == true - case [0, 1, 2] - in [0, 1, 2] - true - end - RUBY - end - - it "supports form pat, pat, ..." do - eval(<<~RUBY).should == true - case [0, 1, 2] - in 0, 1, 2 - true - end - RUBY - - eval(<<~RUBY).should == 1 - case [0, 1, 2] - in 0, a, 2 - a - end - RUBY + case obj + in Object[0] + true + else + false + end.should == false + end - eval(<<~RUBY).should == [1, 2] - case [0, 1, 2] - in 0, *rest - rest - end - RUBY - end + it "binds variables" do + case [0, 1, 2] + in [a, b, c] + [a, b, c] + end.should == [0, 1, 2] + end - it "matches an object with #deconstruct method which returns an array and each element in array matches element in pattern" do - obj = Object.new - def obj.deconstruct; [0, 1] end + it "supports splat operator *rest" do + case [0, 1, 2] + in [0, *rest] + rest + end.should == [1, 2] + end - eval(<<~RUBY).should == true - case obj - in [Integer, Integer] - true - end - RUBY - end + it "does not match partially by default" do + case [0, 1, 2, 3] + in [1, 2] + true + else + false + end.should == false + end - ruby_version_is "3.0" do - it "calls #deconstruct once for multiple patterns, caching the result" do - obj = Object.new + it "does match partially from the array beginning if list + , syntax used" do + case [0, 1, 2, 3] + in [0, 1, ] + true + end.should == true - def obj.deconstruct - ScratchPad << :deconstruct - [0, 1] - end + case [0, 1, 2, 3] + in 0, 1,; + true + end.should == true + end - eval(<<~RUBY).should == true - case obj - in [1, 2] - false - in [0, 1] - true - end - RUBY + it "matches [] with []" do + case [] + in [] + true + end.should == true + end - ScratchPad.recorded.should == [:deconstruct] - end - end + it "matches anything with *" do + case [0, 1] + in *; + true + end.should == true + end - it "calls #deconstruct even on objects that are already an array" do - obj = [1, 2] - def obj.deconstruct - ScratchPad << :deconstruct - [3, 4] - end + it "can be used as a nested pattern" do + case [[1], ["2"]] + in [[0] | nil, _] + false + in [[1], [1]] + false + in [[1], [2 | "2"]] + true + end.should == true + + case [1, 2] + in [0, _] | {a: 0} + false + in {a: 1, b: 2} | [1, 2] + true + end.should == true + end + end - eval(<<~RUBY).should == true - case obj - in [3, 4] - true - else - false - end - RUBY + describe "Hash pattern" do + it "supports form Constant(id: pat, id: pat, ...)" do + case {a: 0, b: 1} + in Hash(a: 0, b: 1) + true + end.should == true + end - ScratchPad.recorded.should == [:deconstruct] - end + it "supports form Constant[id: pat, id: pat, ...]" do + case {a: 0, b: 1} + in Hash[a: 0, b: 1] + true + end.should == true + end - it "does not match object if Constant === object returns false" do - eval(<<~RUBY).should == false - case [0, 1, 2] - in String[0, 1, 2] - true - else - false - end - RUBY - end + it "supports form {id: pat, id: pat, ...}" do + case {a: 0, b: 1} + in {a: 0, b: 1} + true + end.should == true + end - it "does not match object without #deconstruct method" do - obj = Object.new - obj.should_receive(:respond_to?).with(:deconstruct) + it "supports form id: pat, id: pat, ..." do + case {a: 0, b: 1} + in a: 0, b: 1 + true + end.should == true + + case {a: 0, b: 1} + in a: a, b: b + [a, b] + end.should == [0, 1] + + case {a: 0, b: 1, c: 2} + in a: 0, **rest + rest + end.should == {b: 1, c: 2} + end - eval(<<~RUBY).should == false - case obj - in Object[] - true - else - false - end - RUBY - end + it "supports a: which means a: a" do + case {a: 0, b: 1} + in Hash(a:, b:) + [a, b] + end.should == [0, 1] - it "raises TypeError if #deconstruct method does not return array" do - obj = Object.new - def obj.deconstruct; "" end - - -> { - eval <<~RUBY - case obj - in Object[] - else - end - RUBY - }.should raise_error(TypeError, /deconstruct must return Array/) - end + a = b = nil - it "accepts a subclass of Array from #deconstruct" do - obj = Object.new - def obj.deconstruct - subarray = Class.new(Array).new(2) - def subarray.[](n) - n - end - subarray - end + case {a: 0, b: 1} + in Hash[a:, b:] + [a, b] + end.should == [0, 1] - eval(<<~RUBY).should == true - case obj - in [1, 2] - false - in [0, 1] - true - end - RUBY - end + a = b = nil - it "does not match object if elements of array returned by #deconstruct method does not match elements in pattern" do - obj = Object.new - def obj.deconstruct; [1] end + case {a: 0, b: 1} + in {a:, b:} + [a, b] + end.should == [0, 1] - eval(<<~RUBY).should == false - case obj - in Object[0] - true - else - false - end - RUBY - end + a = nil - it "binds variables" do - eval(<<~RUBY).should == [0, 1, 2] - case [0, 1, 2] - in [a, b, c] - [a, b, c] - end - RUBY - end + case {a: 0, b: 1, c: 2} + in {a:, **rest} + [a, rest] + end.should == [0, {b: 1, c: 2}] - it "supports splat operator *rest" do - eval(<<~RUBY).should == [1, 2] - case [0, 1, 2] - in [0, *rest] - rest - end - RUBY - end + a = b = nil - it "does not match partially by default" do - eval(<<~RUBY).should == false - case [0, 1, 2, 3] - in [1, 2] - true - else - false - end - RUBY - end + case {a: 0, b: 1} + in a:, b: + [a, b] + end.should == [0, 1] + end - it "does match partially from the array beginning if list + , syntax used" do - eval(<<~RUBY).should == true - case [0, 1, 2, 3] - in [0, 1,] - true - end - RUBY + it "can mix key (a:) and key-value (a: b) declarations" do + case {a: 0, b: 1} + in Hash(a:, b: x) + [a, x] + end.should == [0, 1] + end - eval(<<~RUBY).should == true - case [0, 1, 2, 3] - in 0, 1,; - true - end - RUBY - end + it "supports 'string': key literal" do + case {a: 0} + in {"a": 0} + true + end.should == true + end - it "matches [] with []" do - eval(<<~RUBY).should == true - case [] - in [] - true + it "does not support non-symbol keys" do + -> { + eval <<~RUBY + case {a: 1} + in {"a" => 1} end RUBY - end + }.should.raise(SyntaxError, /unexpected|expected a label as the key in the hash pattern/) + end - it "matches anything with *" do - eval(<<~RUBY).should == true - case [0, 1] - in *; - true + it "does not support string interpolation in keys" do + -> { + eval <<~'RUBY' + case {a: 1} + in {"#{x}": 1} end RUBY - end + }.should.raise(SyntaxError, /symbol literal with interpolation is not allowed|expected a label as the key in the hash pattern/) end - describe "Hash pattern" do - it "supports form Constant(id: pat, id: pat, ...)" do - eval(<<~RUBY).should == true - case {a: 0, b: 1} - in Hash(a: 0, b: 1) - true + it "raise SyntaxError when keys duplicate in pattern" do + -> { + eval <<~RUBY + case {a: 1} + in {a: 1, b: 2, a: 3} end RUBY - end + }.should.raise(SyntaxError, /duplicated key name/) + end - it "supports form Constant[id: pat, id: pat, ...]" do - eval(<<~RUBY).should == true - case {a: 0, b: 1} - in Hash[a: 0, b: 1] - true - end - RUBY - end + it "matches an object with #deconstruct_keys method which returns a Hash with equal keys and each value in Hash matches value in pattern" do + obj = Object.new - it "supports form {id: pat, id: pat, ...}" do - eval(<<~RUBY).should == true - case {a: 0, b: 1} - in {a: 0, b: 1} - true - end - RUBY + def obj.deconstruct_keys(*) + {a: 1} end - it "supports form id: pat, id: pat, ..." do - eval(<<~RUBY).should == true - case {a: 0, b: 1} - in a: 0, b: 1 - true - end - RUBY + case obj + in {a: 1} + true + end.should == true + end - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in a: a, b: b - [a, b] - end - RUBY + it "calls #deconstruct_keys per pattern" do + obj = Object.new - eval(<<~RUBY).should == { b: 1, c: 2 } - case {a: 0, b: 1, c: 2} - in a: 0, **rest - rest - end - RUBY + def obj.deconstruct_keys(*) + ScratchPad << :deconstruct_keys + {a: 1} end - it "supports a: which means a: a" do - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in Hash(a:, b:) - [a, b] - end - RUBY + case obj + in {b: 1} + false + in {a: 1} + true + end.should == true - a = b = nil - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in Hash[a:, b:] - [a, b] - end - RUBY + ScratchPad.recorded.should == [:deconstruct_keys, :deconstruct_keys] + end - a = b = nil - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in {a:, b:} - [a, b] - end - RUBY + it "does not match object if Constant === object returns false" do + case {a: 1} + in String[a: 1] + true + else + false + end.should == false + end - a = nil - eval(<<~RUBY).should == [0, {b: 1, c: 2}] - case {a: 0, b: 1, c: 2} - in {a:, **rest} - [a, rest] - end - RUBY + it "checks Constant === object before calling #deconstruct_keys" do + c1 = Class.new + obj = c1.new + obj.should_not_receive(:deconstruct_keys) + + case obj + in String(a: 1) + true + else + false + end.should == false + end - a = b = nil - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in a:, b: - [a, b] - end - RUBY - end + it "does not match object without #deconstruct_keys method" do + obj = Object.new + obj.should_receive(:respond_to?).with(:deconstruct_keys) - it "can mix key (a:) and key-value (a: b) declarations" do - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in Hash(a:, b: x) - [a, x] - end - RUBY - end + case obj + in Object[a: 1] + true + else + false + end.should == false + end - it "supports 'string': key literal" do - eval(<<~RUBY).should == true - case {a: 0} - in {"a": 0} - true - end - RUBY - end + it "does not match object if #deconstruct_keys method does not return Hash" do + obj = Object.new - it "does not support non-symbol keys" do - -> { - eval <<~RUBY - case {a: 1} - in {"a" => 1} - end - RUBY - }.should raise_error(SyntaxError, /unexpected/) + def obj.deconstruct_keys(*) + "" end - it "does not support string interpolation in keys" do - x = "a" + -> { + case obj + in Object[a: 1] + end + }.should.raise(TypeError, /deconstruct_keys must return Hash/) + end - -> { - eval <<~'RUBY' - case {a: 1} - in {"#{x}": 1} - end - RUBY - }.should raise_error(SyntaxError, /symbol literal with interpolation is not allowed/) - end + it "does not match object if #deconstruct_keys method returns Hash with non-symbol keys" do + obj = Object.new - it "raise SyntaxError when keys duplicate in pattern" do - -> { - eval <<~RUBY - case {a: 1} - in {a: 1, b: 2, a: 3} - end - RUBY - }.should raise_error(SyntaxError, /duplicated key name/) + def obj.deconstruct_keys(*) + {"a" => 1} end - it "matches an object with #deconstruct_keys method which returns a Hash with equal keys and each value in Hash matches value in pattern" do - obj = Object.new - def obj.deconstruct_keys(*); {a: 1} end + case obj + in Object[a: 1] + true + else + false + end.should == false + end - eval(<<~RUBY).should == true - case obj - in {a: 1} - true - end - RUBY + it "does not match object if elements of Hash returned by #deconstruct_keys method does not match values in pattern" do + obj = Object.new + + def obj.deconstruct_keys(*) + {a: 1} end - it "calls #deconstruct_keys per pattern" do - obj = Object.new + case obj + in Object[a: 2] + true + else + false + end.should == false + end - def obj.deconstruct_keys(*) - ScratchPad << :deconstruct_keys - {a: 1} - end + it "passes keys specified in pattern as arguments to #deconstruct_keys method" do + obj = Object.new - eval(<<~RUBY).should == true - case obj - in {b: 1} - false - in {a: 1} - true - end - RUBY - - ScratchPad.recorded.should == [:deconstruct_keys, :deconstruct_keys] + def obj.deconstruct_keys(*args) + ScratchPad << args + {a: 1, b: 2, c: 3} end - it "does not match object if Constant === object returns false" do - eval(<<~RUBY).should == false - case {a: 1} - in String[a: 1] - true - else - false - end - RUBY + case obj + in Object[a: 1, b: 2, c: 3] end - it "does not match object without #deconstruct_keys method" do - obj = Object.new - obj.should_receive(:respond_to?).with(:deconstruct_keys) + ScratchPad.recorded.sort.should == [[[:a, :b, :c]]] + end - eval(<<~RUBY).should == false - case obj - in Object[a: 1] - true - else - false - end - RUBY + it "passes keys specified in pattern to #deconstruct_keys method if pattern contains double splat operator **" do + obj = Object.new + + def obj.deconstruct_keys(*args) + ScratchPad << args + {a: 1, b: 2, c: 3} end - it "does not match object if #deconstruct_keys method does not return Hash" do - obj = Object.new - def obj.deconstruct_keys(*); "" end - - -> { - eval <<~RUBY - case obj - in Object[a: 1] - end - RUBY - }.should raise_error(TypeError, /deconstruct_keys must return Hash/) + case obj + in Object[a: 1, b: 2, **] end - it "does not match object if #deconstruct_keys method returns Hash with non-symbol keys" do - obj = Object.new - def obj.deconstruct_keys(*); {"a" => 1} end + ScratchPad.recorded.sort.should == [[[:a, :b]]] + end - eval(<<~RUBY).should == false - case obj - in Object[a: 1] - true - else - false - end - RUBY - end + it "passes nil to #deconstruct_keys method if pattern contains double splat operator **rest" do + obj = Object.new - it "does not match object if elements of Hash returned by #deconstruct_keys method does not match values in pattern" do - obj = Object.new - def obj.deconstruct_keys(*); {a: 1} end + def obj.deconstruct_keys(*args) + ScratchPad << args + {a: 1, b: 2} + end - eval(<<~RUBY).should == false - case obj - in Object[a: 2] - true - else - false - end - RUBY + case obj + in Object[a: 1, **rest] end - it "passes keys specified in pattern as arguments to #deconstruct_keys method" do - obj = Object.new + ScratchPad.recorded.should == [[nil]] + end - def obj.deconstruct_keys(*args) - ScratchPad << args - {a: 1, b: 2, c: 3} - end + it "binds variables" do + case {a: 0, b: 1, c: 2} + in {a: x, b: y, c: z} + [x, y, z] + end.should == [0, 1, 2] + end - eval <<~RUBY - case obj - in Object[a: 1, b: 2, c: 3] - end - RUBY + it "supports double splat operator **rest" do + case {a: 0, b: 1, c: 2} + in {a: 0, **rest} + rest + end.should == {b: 1, c: 2} + end - ScratchPad.recorded.sort.should == [[[:a, :b, :c]]] - end + it "treats **nil like there should not be any other keys in a matched Hash" do + case {a: 1, b: 2} + in {a: 1, b: 2, **nil} + true + end.should == true + + case {a: 1, b: 2} + in {a: 1, **nil} + true + else + false + end.should == false + end - it "passes keys specified in pattern to #deconstruct_keys method if pattern contains double splat operator **" do - obj = Object.new + it "can match partially" do + case {a: 1, b: 2} + in {a: 1} + true + end.should == true + end - def obj.deconstruct_keys(*args) - ScratchPad << args - {a: 1, b: 2, c: 3} - end + it "matches {} with {}" do + case {} + in {} + true + end.should == true + end - eval <<~RUBY - case obj - in Object[a: 1, b: 2, **] - end - RUBY + it "in {} only matches empty hashes" do + case {a: 1} + in {} + true + else + false + end.should == false + end - ScratchPad.recorded.sort.should == [[[:a, :b]]] - end + it "in {**nil} only matches empty hashes" do + case {} + in {**nil} + true + else + false + end.should == true + + case {a: 1} + in {**nil} + true + else + false + end.should == false + end - it "passes nil to #deconstruct_keys method if pattern contains double splat operator **rest" do - obj = Object.new + it "matches anything with **" do + case {a: 1} + in **; + true + end.should == true + end - def obj.deconstruct_keys(*args) - ScratchPad << args - {a: 1, b: 2} - end + it "can be used as a nested pattern" do + case {a: {a: 1, b: 1}, b: {a: 1, b: 2}} + in {a: {a: 0}} + false + in {a: {a: 1}, b: {b: 1}} + false + in {a: {a: 1}, b: {b: 2} } + true + end.should == true + + case [{a: 1, b: [1]}, {a: 1, c: ["2"]}] + in [{a:, c:}, ] + false + in [{a: 1, b:}, {a: 1, c: [Integer]}] + false + in [_, {a: 1, c: [String]}] + true + end.should == true + end + end - eval <<~RUBY - case obj - in Object[a: 1, **rest] + describe "refinements" do + it "are used for #deconstruct" do + refinery = Module.new do + refine Array do + def deconstruct + [0] end - RUBY - - ScratchPad.recorded.should == [[nil]] + end end - it "binds variables" do - eval(<<~RUBY).should == [0, 1, 2] - case {a: 0, b: 1, c: 2} - in {a: x, b: y, c: z} - [x, y, z] + result = nil + Module.new do + using refinery + + result = + case [] + in [0] + true end - RUBY end - it "supports double splat operator **rest" do - eval(<<~RUBY).should == {b: 1, c: 2} - case {a: 0, b: 1, c: 2} - in {a: 0, **rest} - rest + result.should == true + end + + it "are used for #deconstruct_keys" do + refinery = Module.new do + refine Hash do + def deconstruct_keys(_) + {a: 0} end - RUBY + end end - it "treats **nil like there should not be any other keys in a matched Hash" do - eval(<<~RUBY).should == true - case {a: 1, b: 2} - in {a: 1, b: 2, **nil} - true - end - RUBY + result = nil + Module.new do + using refinery - eval(<<~RUBY).should == false - case {a: 1, b: 2} - in {a: 1, **nil} + result = + case {} + in a: 0 true - else - false end - RUBY end - it "can match partially" do - eval(<<~RUBY).should == true - case {a: 1, b: 2} - in {a: 1} - true + result.should == true + end + + it "are used for #=== in constant pattern" do + refinery = Module.new do + refine Array.singleton_class do + def ===(obj) + obj.is_a?(Hash) end - RUBY + end end - it "matches {} with {}" do - eval(<<~RUBY).should == true + result = nil + Module.new do + using refinery + + result = case {} - in {} + in Array true end - RUBY end - it "matches anything with **" do - eval(<<~RUBY).should == true - case {a: 1} - in **; + result.should == true + end + end + + 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 - describe "refinements" do - it "are used for #deconstruct" do - refinery = Module.new do - refine Array do - def deconstruct - [0] - end - end - end + it "supports pinning global variables" do + $a = /a/ + case 'abc' + in ^$a + true + end.should == true + end - result = nil - Module.new do - using refinery + it "supports pinning expressions" do + case 'abc' + in ^(/a/) + true + end.should == true - result = eval(<<~RUBY) - case [] - in [0] - true - end - RUBY - end + case 0 + in ^(0 + 0) + true + end.should == true + end - result.should == true - end + it "supports pinning expressions in array pattern" do + case [3] + in [^(1 + 2)] + true + end.should == true + end - it "are used for #deconstruct_keys" do - refinery = Module.new do - refine Hash do - def deconstruct_keys(_) - {a: 0} - end - end - 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 - result = nil - Module.new do - using refinery + describe "value in pattern" do + it "returns true if the pattern matches" do + (1 in 1).should == true - result = eval(<<~RUBY) - case {} - in a: 0 - true - end - RUBY - end + (1 in Integer).should == true - result.should == true - end + e = nil + ([1, 2] in [1, e]).should == true + e.should == 2 - it "are used for #=== in constant pattern" do - refinery = Module.new do - refine Array.singleton_class do - def ===(obj) - obj.is_a?(Hash) - end - end - end + k = nil + ({k: 1} in {k:}).should == true + k.should == 1 + end - result = nil - Module.new do - using refinery + it "returns false if the pattern does not match" do + (1 in 2).should == false - result = eval(<<~RUBY) - case {} - in Array - true - end - RUBY - end + (1 in Float).should == false - result.should == true - end + ([1, 2] in [2, e]).should == false + + ({k: 1} in {k: 2}).should == false end end end diff --git a/spec/ruby/language/precedence_spec.rb b/spec/ruby/language/precedence_spec.rb index 5a3c2861ce..edb990525e 100644 --- a/spec/ruby/language/precedence_spec.rb +++ b/spec/ruby/language/precedence_spec.rb @@ -14,46 +14,44 @@ require_relative 'fixtures/precedence' # the level below (as well as showing associativity within # the precedence level). -=begin -Excerpted from 'Programming Ruby: The Pragmatic Programmer's Guide' -Second Edition by Dave Thomas, Chad Fowler, and Andy Hunt, page 324 - -Table 22.4. Ruby operators (high to low precedence) -Method Operator Description ------------------------------------------------------------------------ - :: . - x* [ ] [ ]= Element reference, element set - x ** Exponentiation - x ! ~ + - Not, complement, unary plus and minus - (method names for the last two are +@ and -@) - x * / % Multiply, divide, and modulo - x + - Plus and minus - x >> << Right and left shift - x & “And” (bitwise for integers) - x ^ | Exclusive “or” and regular “or” (bitwise for integers) - x <= < > >= Comparison operators - x <=> == === != =~ !~ Equality and pattern match operators (!= - and !~ may not be defined as methods) - && Logical “and” - || Logical “or” - .. ... Range (inclusive and exclusive) - ? : Ternary if-then-else - = %= /= -= += |= &= Assignment - >>= <<= *= &&= ||= **= - defined? Check if symbol defined - not Logical negation - or and Logical composition - if unless while until Expression modifiers - begin/end Block expression ------------------------------------------------------------------------ - -* Operators marked with 'x' in the Method column are implemented as methods -and can be overridden (except != and !~ as noted). (But see the specs -below for implementations that define != and !~ as methods.) - -** These are not included in the excerpted table but are shown here for -completeness. -=end +# Excerpted from 'Programming Ruby: The Pragmatic Programmer's Guide' +# Second Edition by Dave Thomas, Chad Fowler, and Andy Hunt, page 324 +# +# Table 22.4. Ruby operators (high to low precedence) +# Method Operator Description +# ----------------------------------------------------------------------- +# :: . +# x* [ ] [ ]= Element reference, element set +# x ** Exponentiation +# x ! ~ + - Not, complement, unary plus and minus +# (method names for the last two are +@ and -@) +# x * / % Multiply, divide, and modulo +# x + - Plus and minus +# x >> << Right and left shift +# x & “And” (bitwise for integers) +# x ^ | Exclusive “or” and regular “or” (bitwise for integers) +# x <= < > >= Comparison operators +# x <=> == === != =~ !~ Equality and pattern match operators (!= +# and !~ may not be defined as methods) +# && Logical “and” +# || Logical “or” +# .. ... Range (inclusive and exclusive) +# ? : Ternary if-then-else +# = %= /= -= += |= &= Assignment +# >>= <<= *= &&= ||= **= +# defined? Check if symbol defined +# not Logical negation +# or and Logical composition +# if unless while until Expression modifiers +# begin/end Block expression +# ----------------------------------------------------------------------- +# +# * Operators marked with 'x' in the Method column are implemented as methods +# and can be overridden (except != and !~ as noted). (But see the specs +# below for implementations that define != and !~ as methods.) +# +# ** These are not included in the excerpted table but are shown here for +# completeness. # ----------------------------------------------------------------------- # It seems that this table is not correct anymore @@ -253,12 +251,12 @@ describe "Operators" do end it "<=> == === != =~ !~ are non-associative" do - -> { eval("1 <=> 2 <=> 3") }.should raise_error(SyntaxError) - -> { eval("1 == 2 == 3") }.should raise_error(SyntaxError) - -> { eval("1 === 2 === 3") }.should raise_error(SyntaxError) - -> { eval("1 != 2 != 3") }.should raise_error(SyntaxError) - -> { eval("1 =~ 2 =~ 3") }.should raise_error(SyntaxError) - -> { eval("1 !~ 2 !~ 3") }.should raise_error(SyntaxError) + -> { eval("1 <=> 2 <=> 3") }.should.raise(SyntaxError) + -> { eval("1 == 2 == 3") }.should.raise(SyntaxError) + -> { eval("1 === 2 === 3") }.should.raise(SyntaxError) + -> { eval("1 != 2 != 3") }.should.raise(SyntaxError) + -> { eval("1 =~ 2 =~ 3") }.should.raise(SyntaxError) + -> { eval("1 !~ 2 !~ 3") }.should.raise(SyntaxError) end it "<=> == === != =~ !~ have higher precedence than &&" do @@ -292,18 +290,18 @@ describe "Operators" do end it ".. ... are non-associative" do - -> { eval("1..2..3") }.should raise_error(SyntaxError) - -> { eval("1...2...3") }.should raise_error(SyntaxError) - end - - it ".. ... have higher precedence than ? :" do - # Use variables to avoid warnings - from = 1 - to = 2 - # These are flip-flop, not Range instances - (from..to ? 3 : 4).should == 3 - (from...to ? 3 : 4).should == 3 - end + -> { eval("1..2..3") }.should.raise(SyntaxError) + -> { eval("1...2...3") }.should.raise(SyntaxError) + end + + it ".. ... have higher precedence than ? :" do + # Use variables to avoid warnings + from = 1 + to = 2 + # These are flip-flop, not Range instances + (from..to ? 3 : 4).should == 3 + (from...to ? 3 : 4).should == 3 + end it "? : is right-associative" do (true ? 2 : 3 ? 4 : 5).should == 2 diff --git a/spec/ruby/language/predefined_spec.rb b/spec/ruby/language/predefined_spec.rb index b1f5d80ad4..d57b924bcf 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' @@ -7,48 +8,44 @@ require 'stringio' # Entries marked [r/o] are read-only and an error will be raised of the program attempts to # modify them. Entries marked [thread] are thread local. -=begin -Exception Information ---------------------------------------------------------------------------------------------------- - -$! Exception The exception object passed to raise. [thread] -$@ Array The stack backtrace generated by the last exception. [thread] -=end - -=begin -Pattern Matching Variables ---------------------------------------------------------------------------------------------------- - -These variables are set to nil after an unsuccessful pattern match. - -$& String The string matched (following a successful pattern match). This variable is - local to the current scope. [r/o, thread] -$+ String The contents of the highest-numbered group matched following a successful - pattern match. Thus, in "cat" =~/(c|a)(t|z)/, $+ will be set to “t”. This - variable is local to the current scope. [r/o, thread] -$` String The string preceding the match in a successful pattern match. This variable - is local to the current scope. [r/o, thread] -$' String The string following the match in a successful pattern match. This variable - is local to the current scope. [r/o, thread] -$1 to $<N> String The contents of successive groups matched in a successful pattern match. In - "cat" =~/(c|a)(t|z)/, $1 will be set to “a” and $2 to “t”. This variable - is local to the current scope. [r/o, thread] -$~ MatchData An object that encapsulates the results of a successful pattern match. The - variables $&, $`, $', and $1 to $<N> are all derived from $~. Assigning to $~ - changes the values of these derived variables. This variable is local to the - current scope. [thread] -=end +# Exception Information +# --------------------------------------------------------------------------------------------------- +# +# $! Exception The exception object passed to raise. [thread] +# $@ Array The stack backtrace generated by the last exception. [thread] + +# Pattern Matching Variables +# --------------------------------------------------------------------------------------------------- +# +# These variables are set to nil after an unsuccessful pattern match. +# +# $& String The string matched (following a successful pattern match). This variable is +# local to the current scope. [r/o, thread] +# $+ String The contents of the highest-numbered group matched following a successful +# pattern match. Thus, in "cat" =~/(c|a)(t|z)/, $+ will be set to “t”. This +# variable is local to the current scope. [r/o, thread] +# $` String The string preceding the match in a successful pattern match. This variable +# is local to the current scope. [r/o, thread] +# $' String The string following the match in a successful pattern match. This variable +# is local to the current scope. [r/o, thread] +# $1 to $<N> String The contents of successive groups matched in a successful pattern match. In +# "cat" =~/(c|a)(t|z)/, $1 will be set to “a” and $2 to “t”. This variable +# is local to the current scope. [r/o, thread] +# $~ MatchData An object that encapsulates the results of a successful pattern match. The +# variables $&, $`, $', and $1 to $<N> are all derived from $~. Assigning to $~ +# changes the values of these derived variables. This variable is local to the +# current scope. [thread] describe "Predefined global $~" do it "is set to contain the MatchData object of the last match if successful" do md = /foo/.match 'foo' - $~.should be_kind_of(MatchData) - $~.should equal md + $~.should.is_a?(MatchData) + $~.should.equal? md /bar/ =~ 'bar' - $~.should be_kind_of(MatchData) - $~.should_not equal md + $~.should.is_a?(MatchData) + $~.should_not.equal? md end it "is set to nil if the last match was unsuccessful" do @@ -75,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 @@ -90,10 +87,10 @@ describe "Predefined global $~" do $~ = nil $~.should == nil $~ = /foo/.match("foo") - $~.should be_an_instance_of(MatchData) + $~.should.instance_of?(MatchData) - -> { $~ = Object.new }.should raise_error(TypeError) - -> { $~ = 1 }.should raise_error(TypeError) + -> { $~ = Object.new }.should.raise(TypeError, 'wrong argument type Object (expected MatchData)') + -> { $~ = 1 }.should.raise(TypeError, 'wrong argument type Integer (expected MatchData)') end it "changes the value of derived capture globals when assigned" do @@ -137,8 +134,21 @@ describe "Predefined global $&" do end it "sets the encoding to the encoding of the source String" do - "abc".force_encoding(Encoding::EUC_JP) =~ /b/ - $&.encoding.should equal(Encoding::EUC_JP) + "abc".dup.force_encoding(Encoding::EUC_JP) =~ /b/ + $&.encoding.should.equal?(Encoding::EUC_JP) + end + + it "is read-only" do + -> { + eval %q{$& = ""} + }.should.raise(SyntaxError, /Can't set variable \$&/) + end + + it "is read-only when aliased" do + alias $predefined_spec_ampersand $& + -> { + $predefined_spec_ampersand = "" + }.should.raise(NameError, '$predefined_spec_ampersand is a read-only variable') end end @@ -150,13 +160,26 @@ describe "Predefined global $`" do end it "sets the encoding to the encoding of the source String" do - "abc".force_encoding(Encoding::EUC_JP) =~ /b/ - $`.encoding.should equal(Encoding::EUC_JP) + "abc".dup.force_encoding(Encoding::EUC_JP) =~ /b/ + $`.encoding.should.equal?(Encoding::EUC_JP) end it "sets an empty result to the encoding of the source String" do - "abc".force_encoding(Encoding::ISO_8859_1) =~ /a/ - $`.encoding.should equal(Encoding::ISO_8859_1) + "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(SyntaxError, /Can't set variable \$`/) + end + + it "is read-only when aliased" do + alias $predefined_spec_backquote $` + -> { + $predefined_spec_backquote = "" + }.should.raise(NameError, '$predefined_spec_backquote is a read-only variable') end end @@ -168,13 +191,26 @@ describe "Predefined global $'" do end it "sets the encoding to the encoding of the source String" do - "abc".force_encoding(Encoding::EUC_JP) =~ /b/ - $'.encoding.should equal(Encoding::EUC_JP) + "abc".dup.force_encoding(Encoding::EUC_JP) =~ /b/ + $'.encoding.should.equal?(Encoding::EUC_JP) end it "sets an empty result to the encoding of the source String" do - "abc".force_encoding(Encoding::ISO_8859_1) =~ /c/ - $'.encoding.should equal(Encoding::ISO_8859_1) + "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(SyntaxError, /Can't set variable \$'/) + end + + it "is read-only when aliased" do + alias $predefined_spec_single_quote $' + -> { + $predefined_spec_single_quote = "" + }.should.raise(NameError, '$predefined_spec_single_quote is a read-only variable') end end @@ -191,8 +227,21 @@ describe "Predefined global $+" do end it "sets the encoding to the encoding of the source String" do - "abc".force_encoding(Encoding::EUC_JP) =~ /(b)/ - $+.encoding.should equal(Encoding::EUC_JP) + "abc".dup.force_encoding(Encoding::EUC_JP) =~ /(b)/ + $+.encoding.should.equal?(Encoding::EUC_JP) + end + + it "is read-only" do + -> { + eval %q{$+ = ""} + }.should.raise(SyntaxError, /Can't set variable \$\+/) + end + + it "is read-only when aliased" do + alias $predefined_spec_plus $+ + -> { + $predefined_spec_plus = "" + }.should.raise(NameError, '$predefined_spec_plus is a read-only variable') end end @@ -218,8 +267,8 @@ describe "Predefined globals $1..N" do end it "sets the encoding to the encoding of the source String" do - "abc".force_encoding(Encoding::EUC_JP) =~ /(b)/ - $1.encoding.should equal(Encoding::EUC_JP) + "abc".dup.force_encoding(Encoding::EUC_JP) =~ /(b)/ + $1.encoding.should.equal?(Encoding::EUC_JP) end end @@ -233,20 +282,36 @@ 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(TypeError, '$stdout must have write method, NilClass given') end it "raises TypeError error if assigned to object that doesn't respond to #write" do obj = mock('object') - -> { $stdout = obj }.should raise_error(TypeError) + -> { $stdout = obj }.should.raise(TypeError) obj.stub!(:write) $stdout = obj - $stdout.should equal(obj) + $stdout.should.equal?(obj) 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 read-only" do + -> { + $! = [] + }.should.raise(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 @@ -506,41 +571,108 @@ describe "Predefined global $!" do end end -=begin -Input/Output Variables ---------------------------------------------------------------------------------------------------- - -$/ String The input record separator (newline by default). This is the value that rou- - tines such as Kernel#gets use to determine record boundaries. If set to - nil, gets will read the entire file. -$-0 String Synonym for $/. -$\ String The string appended to the output of every call to methods such as - Kernel#print and IO#write. The default value is nil. -$, String The separator string output between the parameters to methods such as - Kernel#print and Array#join. Defaults to nil, which adds no text. -$. Integer The number of the last line read from the current input file. -$; String The default separator pattern used by String#split. May be set from the - command line using the -F flag. -$< Object An object that provides access to the concatenation of the contents of all - the files given as command-line arguments or $stdin (in the case where - there are no arguments). $< supports methods similar to a File object: - binmode, close, closed?, each, each_byte, each_line, eof, eof?, - file, filename, fileno, getc, gets, lineno, lineno=, path, pos, pos=, - read, readchar, readline, readlines, rewind, seek, skip, tell, to_a, - to_i, to_io, to_s, along with the methods in Enumerable. The method - file returns a File object for the file currently being read. This may change - as $< reads through the files on the command line. [r/o] -$> IO The destination of output for Kernel#print and Kernel#printf. The - default value is $stdout. -$_ String The last line read by Kernel#gets or Kernel#readline. Many string- - related functions in the Kernel module operate on $_ by default. The vari- - able is local to the current scope. [thread] -$-F String Synonym for $;. -$stderr IO The current standard error output. -$stdin IO The current standard input. -$stdout IO The current standard output. Assignment to $stdout is deprecated: use - $stdout.reopen instead. -=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.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.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(ArgumentError, '$! not set') + end +end + +# Input/Output Variables +# --------------------------------------------------------------------------------------------------- +# +# $/ String The input record separator (newline by default). This is the value that rou- +# tines such as Kernel#gets use to determine record boundaries. If set to +# nil, gets will read the entire file. +# $-0 String Synonym for $/. +# $\ String The string appended to the output of every call to methods such as +# Kernel#print and IO#write. The default value is nil. +# $, String The separator string output between the parameters to methods such as +# Kernel#print and Array#join. Defaults to nil, which adds no text. +# $. Integer The number of the last line read from the current input file. +# $; String The default separator pattern used by String#split. May be set from the +# command line using the -F flag. +# $< Object An object that provides access to the concatenation of the contents of all +# the files given as command-line arguments or $stdin (in the case where +# there are no arguments). $< supports methods similar to a File object: +# binmode, close, closed?, each, each_byte, each_line, eof, eof?, +# file, filename, fileno, getc, gets, lineno, lineno=, path, pos, pos=, +# read, readchar, readline, readlines, rewind, seek, skip, tell, to_a, +# to_i, to_io, to_s, along with the methods in Enumerable. The method +# file returns a File object for the file currently being read. This may change +# as $< reads through the files on the command line. [r/o] +# $> IO The destination of output for Kernel#print and Kernel#printf. The +# default value is $stdout. +# $_ String The last line read by Kernel#gets or Kernel#readline. Many string- +# related functions in the Kernel module operate on $_ by default. The vari- +# able is local to the current scope. [thread] +# $-F String Synonym for $;. +# $stderr IO The current standard error output. +# $stdin IO The current standard input. +# $stdout IO The current standard output. Assignment to $stdout is deprecated: use +# $stdout.reopen instead. describe "Predefined global $/" do before :each do @@ -555,40 +687,71 @@ 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.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 + $/.should == nil end it "returns the value assigned" do ($/ = "xyz").should == "xyz" end - it "changes $-0" do $/ = "xyz" - $-0.should equal($/) + $-0.should.equal?($/) end it "does not call #to_str to convert the object to a String" do obj = mock("$/ value") obj.should_not_receive(:to_str) - -> { $/ = obj }.should raise_error(TypeError) + -> { $/ = obj }.should.raise(TypeError, 'value of $/ must be String') end it "raises a TypeError if assigned an Integer" do - -> { $/ = 1 }.should raise_error(TypeError) + -> { $/ = 1 }.should.raise(TypeError, 'value of $/ must be String') end it "raises a TypeError if assigned a boolean" do - -> { $/ = true }.should raise_error(TypeError) + -> { $/ = true }.should.raise(TypeError, 'value of $/ must be String') + end + + it "warns if assigned non-nil" do + -> { $/ = "_" }.should complain(/warning: (?:non-nil )?[`']\$\/' is deprecated/) end end @@ -605,15 +768,43 @@ 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.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 $-0 = nil - $-0.should be_nil + $-0.should == nil end it "returns the value assigned" do @@ -622,22 +813,69 @@ describe "Predefined global $-0" do it "changes $/" do $-0 = "xyz" - $/.should equal($-0) + $/.should.equal?($-0) end it "does not call #to_str to convert the object to a String" do obj = mock("$-0 value") obj.should_not_receive(:to_str) - -> { $-0 = obj }.should raise_error(TypeError) + -> { $-0 = obj }.should.raise(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(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(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 + +describe "Predefined global $\\" do + before :each do + @verbose, $VERBOSE = $VERBOSE, nil + @dollar_backslash = $\ + end + + after :each do + $\ = @dollar_backslash + $VERBOSE = @verbose + end + + it "can be assigned a String" do + str = "abc" + $\ = str + $\.should.equal?(str) + end + + it "can be assigned nil" do + $\ = nil + $\.should == nil + end + + it "returns the value assigned" do + ($\ = "xyz").should == "xyz" + end + + it "does not call #to_str to convert the object to a String" do + obj = mock("$\\ value") + obj.should_not_receive(:to_str) + + -> { $\ = obj }.should.raise(TypeError, 'value of $\ must be String') + end + + it "raises a TypeError if assigned not String" do + -> { $\ = 1 }.should.raise(TypeError, 'value of $\ must be String') + -> { $\ = true }.should.raise(TypeError, 'value of $\ must be String') + end + + it "warns if assigned non-nil" do + -> { $\ = "_" }.should complain(/warning: (?:non-nil )?[`']\$\\' is deprecated/) end end @@ -647,17 +885,15 @@ describe "Predefined global $," do end it "defaults to nil" do - $,.should be_nil + $,.should == nil end it "raises TypeError if assigned a non-String" do - -> { $, = Object.new }.should raise_error(TypeError) + -> { $, = Object.new }.should.raise(TypeError, 'value of $, must be String') end - ruby_version_is "2.7" do - it "warns if assigned non-nil" do - -> { $, = "_" }.should complain(/warning: `\$,' is deprecated/) - end + it "warns if assigned non-nil" do + -> { $, = "_" }.should complain(/warning: (?:non-nil )?[`']\$,' is deprecated/) end end @@ -684,7 +920,7 @@ describe "Predefined global $." do obj = mock("bad-value") obj.should_receive(:to_int).and_return('abc') - -> { $. = obj }.should raise_error(TypeError) + -> { $. = obj }.should.raise(TypeError) end end @@ -693,10 +929,8 @@ describe "Predefined global $;" do $; = nil end - ruby_version_is "2.7" do - it "warns if assigned non-nil" do - -> { $; = "_" }.should complain(/warning: `\$;' is deprecated/) - end + it "warns if assigned non-nil" do + -> { $; = "_" }.should complain(/warning: (?:non-nil )?[`']\$;' is deprecated/) end end @@ -730,7 +964,7 @@ describe "Predefined global $_" do match.should == "bar\n" $_.should == match - eval 'match = stdin.gets' + match = stdin.gets match.should == "baz\n" $_.should == match @@ -751,7 +985,7 @@ describe "Predefined global $_" do end Thread.pass until running - $_.should be_nil + $_.should == nil thr.join end @@ -769,54 +1003,52 @@ describe "Predefined global $_" do end end -=begin -Execution Environment Variables ---------------------------------------------------------------------------------------------------- - -$0 String The name of the top-level Ruby program being executed. Typically this will - be the program’s filename. On some operating systems, assigning to this - variable will change the name of the process reported (for example) by the - ps(1) command. -$* Array An array of strings containing the command-line options from the invoca- - tion of the program. Options used by the Ruby interpreter will have been - removed. [r/o] -$" Array An array containing the filenames of modules loaded by require. [r/o] -$$ Integer The process number of the program being executed. [r/o] -$? Process::Status The exit status of the last child process to terminate. [r/o, thread] -$: Array An array of strings, where each string specifies a directory to be searched for - Ruby scripts and binary extensions used by the load and require methods. - The initial value is the value of the arguments passed via the -I command- - line option, followed by an installation-defined standard library location, fol- - lowed by the current directory (“.”). This variable may be set from within a - program to alter the default search path; typically, programs use $: << dir - to append dir to the path. [r/o] -$-a Object True if the -a option is specified on the command line. [r/o] -$-d Object Synonym for $DEBUG. -$DEBUG Object Set to true if the -d command-line option is specified. -__FILE__ String The name of the current source file. [r/o] -$F Array The array that receives the split input line if the -a command-line option is - used. -$FILENAME String The name of the current input file. Equivalent to $<.filename. [r/o] -$-i String If in-place edit mode is enabled (perhaps using the -i command-line - option), $-i holds the extension used when creating the backup file. If you - set a value into $-i, enables in-place edit mode. -$-I Array Synonym for $:. [r/o] -$-K String Sets the multibyte coding system for strings and regular expressions. Equiv- - alent to the -K command-line option. -$-l Object Set to true if the -l option (which enables line-end processing) is present - on the command line. [r/o] -__LINE__ String The current line number in the source file. [r/o] -$LOAD_PATH Array A synonym for $:. [r/o] -$-p Object Set to true if the -p option (which puts an implicit while gets . . . end - loop around your program) is present on the command line. [r/o] -$VERBOSE Object Set to true if the -v, --version, -W, or -w option is specified on the com- - mand line. Set to false if no option, or -W1 is given. Set to nil if -W0 - was specified. Setting this option to true causes the interpreter and some - library routines to report additional information. Setting to nil suppresses - all warnings (including the output of Kernel.warn). -$-v Object Synonym for $VERBOSE. -$-w Object Synonym for $VERBOSE. -=end +# Execution Environment Variables +# --------------------------------------------------------------------------------------------------- +# +# $0 String The name of the top-level Ruby program being executed. Typically this will +# be the program’s filename. On some operating systems, assigning to this +# variable will change the name of the process reported (for example) by the +# ps(1) command. +# $* Array An array of strings containing the command-line options from the invoca- +# tion of the program. Options used by the Ruby interpreter will have been +# removed. [r/o] +# $" Array An array containing the filenames of modules loaded by require. [r/o] +# $$ Integer The process number of the program being executed. [r/o] +# $? Process::Status The exit status of the last child process to terminate. [r/o, thread] +# $: Array An array of strings, where each string specifies a directory to be searched for +# Ruby scripts and binary extensions used by the load and require methods. +# The initial value is the value of the arguments passed via the -I command- +# line option, followed by an installation-defined standard library location, fol- +# lowed by the current directory (“.”). This variable may be set from within a +# program to alter the default search path; typically, programs use $: << dir +# to append dir to the path. [r/o] +# $-a Object True if the -a option is specified on the command line. [r/o] +# $-d Object Synonym for $DEBUG. +# $DEBUG Object Set to true if the -d command-line option is specified. +# __FILE__ String The name of the current source file. [r/o] +# $F Array The array that receives the split input line if the -a command-line option is +# used. +# $FILENAME String The name of the current input file. Equivalent to $<.filename. [r/o] +# $-i String If in-place edit mode is enabled (perhaps using the -i command-line +# option), $-i holds the extension used when creating the backup file. If you +# set a value into $-i, enables in-place edit mode. +# $-I Array Synonym for $:. [r/o] +# $-K String Sets the multibyte coding system for strings and regular expressions. Equiv- +# alent to the -K command-line option. +# $-l Object Set to true if the -l option (which enables line-end processing) is present +# on the command line. [r/o] +# __LINE__ String The current line number in the source file. [r/o] +# $LOAD_PATH Array A synonym for $:. [r/o] +# $-p Object Set to true if the -p option (which puts an implicit while gets . . . end +# loop around your program) is present on the command line. [r/o] +# $VERBOSE Object Set to true if the -v, --version, -W, or -w option is specified on the com- +# mand line. Set to false if no option, or -W1 is given. Set to nil if -W0 +# was specified. Setting this option to true causes the interpreter and some +# library routines to report additional information. Setting to nil suppresses +# all warnings (including the output of Kernel.warn). +# $-v Object Synonym for $VERBOSE. +# $-w Object Synonym for $VERBOSE. describe "Execution variable $:" do it "is initialized to an array of strings" do $:.is_a?(Array).should == true @@ -824,7 +1056,7 @@ describe "Execution variable $:" do end it "does not include the current directory" do - $:.should_not include(".") + $:.should_not.include?(".") end it "is the same object as $LOAD_PATH and $-I" do @@ -834,37 +1066,55 @@ describe "Execution variable $:" do it "can be changed via <<" do $: << "foo" - $:.should include("foo") + $:.should.include?("foo") + ensure + $:.delete("foo") end it "is read-only" do -> { $: = [] - }.should raise_error(NameError) + }.should.raise(NameError, '$: is a read-only variable') -> { $LOAD_PATH = [] - }.should raise_error(NameError) + }.should.raise(NameError, '$LOAD_PATH is a read-only variable') -> { $-I = [] - }.should raise_error(NameError) + }.should.raise(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) + + 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 == true + $:[0...idx].all? { |p| !p.instance_variable_defined?(:@gem_prelude_index) }.should == true end end describe "Global variable $\"" do it "is an alias for $LOADED_FEATURES" do - $".should equal $LOADED_FEATURES + $".should.equal? $LOADED_FEATURES end it "is read-only" do -> { $" = [] - }.should raise_error(NameError) + }.should.raise(NameError, '$" is a read-only variable') -> { $LOADED_FEATURES = [] - }.should raise_error(NameError) + }.should.raise(NameError, '$LOADED_FEATURES is a read-only variable') end end @@ -872,7 +1122,7 @@ describe "Global variable $<" do it "is read-only" do -> { $< = nil - }.should raise_error(NameError) + }.should.raise(NameError, '$< is a read-only variable') end end @@ -880,7 +1130,7 @@ describe "Global variable $FILENAME" do it "is read-only" do -> { $FILENAME = "-" - }.should raise_error(NameError) + }.should.raise(NameError, '$FILENAME is a read-only variable') end end @@ -888,30 +1138,30 @@ describe "Global variable $?" do it "is read-only" do -> { $? = nil - }.should raise_error(NameError) + }.should.raise(NameError, '$? is a read-only variable') end it "is thread-local" do system(ruby_cmd('exit 0')) - Thread.new { $?.should be_nil }.join + Thread.new { $?.should == nil }.join end end describe "Global variable $-a" do it "is read-only" do - -> { $-a = true }.should raise_error(NameError) + -> { $-a = true }.should.raise(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(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(NameError, '$-p is a read-only variable') end end @@ -926,9 +1176,9 @@ describe "Global variable $-d" do it "is an alias of $DEBUG" do $DEBUG = true - $-d.should be_true + $-d.should == true $-d = false - $DEBUG.should be_false + $DEBUG.should == false end end @@ -941,21 +1191,25 @@ describe "Global variable $VERBOSE" do $VERBOSE = @verbose end + it "is false by default" do + $VERBOSE.should == false + end + it "converts truthy values to true" do [true, 1, 0, [], ""].each do |true_value| $VERBOSE = true_value - $VERBOSE.should be_true + $VERBOSE.should == true end end it "allows false" do $VERBOSE = false - $VERBOSE.should be_false + $VERBOSE.should == false end it "allows nil without coercing to false" do $VERBOSE = nil - $VERBOSE.should be_nil + $VERBOSE.should == nil end end @@ -970,9 +1224,9 @@ describe :verbose_global_alias, shared: true do it "is an alias of $VERBOSE" do $VERBOSE = true - eval(@method).should be_true + eval(@method).should == true eval("#{@method} = false") - $VERBOSE.should be_false + $VERBOSE.should == false end end @@ -995,7 +1249,7 @@ describe "Global variable $0" do it "is the path given as the main script and the same as __FILE__" do script = "fixtures/dollar_zero.rb" - Dir.chdir(File.dirname(__FILE__)) do + Dir.chdir(__dir__) do ruby_exe(script).should == "#{script}\n#{script}\nOK" end end @@ -1009,7 +1263,7 @@ describe "Global variable $0" do it "actually sets the program name" do title = "rubyspec-dollar0-test" $0 = title - `ps -ocommand= -p#{$$}`.should include(title) + `ps -ocommand= -p#{$$}`.should.include?(title) end end @@ -1018,26 +1272,24 @@ describe "Global variable $0" do end it "raises a TypeError when not given an object that can be coerced to a String" do - -> { $0 = nil }.should raise_error(TypeError) + -> { $0 = nil }.should.raise(TypeError) end end -=begin -Standard Objects ---------------------------------------------------------------------------------------------------- - -ARGF Object A synonym for $<. -ARGV Array A synonym for $*. -ENV Object A hash-like object containing the program’s environment variables. An - instance of class Object, ENV implements the full set of Hash methods. Used - to query and set the value of an environment variable, as in ENV["PATH"] - and ENV["term"]="ansi". -false FalseClass Singleton instance of class FalseClass. [r/o] -nil NilClass The singleton instance of class NilClass. The value of uninitialized - instance and global variables. [r/o] -self Object The receiver (object) of the current method. [r/o] -true TrueClass Singleton instance of class TrueClass. [r/o] -=end +# Standard Objects +# --------------------------------------------------------------------------------------------------- +# +# ARGF Object A synonym for $<. +# ARGV Array A synonym for $*. +# ENV Object A hash-like object containing the program’s environment variables. An +# instance of class Object, ENV implements the full set of Hash methods. Used +# to query and set the value of an environment variable, as in ENV["PATH"] +# and ENV["term"]="ansi". +# false FalseClass Singleton instance of class FalseClass. [r/o] +# nil NilClass The singleton instance of class NilClass. The value of uninitialized +# instance and global variables. [r/o] +# self Object The receiver (object) of the current method. [r/o] +# true TrueClass Singleton instance of class TrueClass. [r/o] describe "The predefined standard objects" do it "includes ARGF" do @@ -1056,69 +1308,88 @@ end describe "The predefined standard object nil" do it "is an instance of NilClass" do - nil.should be_kind_of(NilClass) + nil.should.is_a?(NilClass) end it "raises a SyntaxError if assigned to" do - -> { eval("nil = true") }.should raise_error(SyntaxError) + -> { eval("nil = true") }.should.raise(SyntaxError, /Can't assign to nil/) end end describe "The predefined standard object true" do it "is an instance of TrueClass" do - true.should be_kind_of(TrueClass) + true.should.is_a?(TrueClass) end it "raises a SyntaxError if assigned to" do - -> { eval("true = false") }.should raise_error(SyntaxError) + -> { eval("true = false") }.should.raise(SyntaxError, /Can't assign to true/) end end describe "The predefined standard object false" do it "is an instance of FalseClass" do - false.should be_kind_of(FalseClass) + false.should.is_a?(FalseClass) end it "raises a SyntaxError if assigned to" do - -> { eval("false = nil") }.should raise_error(SyntaxError) + -> { eval("false = nil") }.should.raise(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(SyntaxError, /Can't change the value of self/) end end -=begin -Global Constants ---------------------------------------------------------------------------------------------------- - -The following constants are defined by the Ruby interpreter. - -DATA IO If the main program file contains the directive __END__, then - the constant DATA will be initialized so that reading from it will - return lines following __END__ from the source file. -RUBY_PLATFORM String The identifier of the platform running this program. This string - is in the same form as the platform identifier used by the GNU - configure utility (which is not a coincidence). -RUBY_RELEASE_DATE String The date of this release. -RUBY_VERSION String The version number of the interpreter. -STDERR IO The actual standard error stream for the program. The initial - value of $stderr. -STDIN IO The actual standard input stream for the program. The initial - value of $stdin. -STDOUT IO The actual standard output stream for the program. The initial - value of $stdout. -SCRIPT_LINES__ Hash If a constant SCRIPT_LINES__ is defined and references a Hash, - Ruby will store an entry containing the contents of each file it - parses, with the file’s name as the key and an array of strings as - the value. -TOPLEVEL_BINDING Binding A Binding object representing the binding at Ruby’s top level— - the level where programs are initially executed. -=end +# Global Constants +# --------------------------------------------------------------------------------------------------- +# +# The following constants are defined by the Ruby interpreter. +# +# DATA IO If the main program file contains the directive __END__, then +# the constant DATA will be initialized so that reading from it will +# return lines following __END__ from the source file. +# FALSE FalseClass Synonym for false (deprecated, removed in Ruby 3). +# NIL NilClass Synonym for nil (deprecated, removed in Ruby 3). +# RUBY_PLATFORM String The identifier of the platform running this program. This string +# is in the same form as the platform identifier used by the GNU +# configure utility (which is not a coincidence). +# RUBY_RELEASE_DATE String The date of this release. +# RUBY_VERSION String The version number of the interpreter. +# STDERR IO The actual standard error stream for the program. The initial +# value of $stderr. +# STDIN IO The actual standard input stream for the program. The initial +# value of $stdin. +# STDOUT IO The actual standard output stream for the program. The initial +# value of $stdout. +# SCRIPT_LINES__ Hash If a constant SCRIPT_LINES__ is defined and references a Hash, +# Ruby will store an entry containing the contents of each file it +# parses, with the file’s name as the key and an array of strings as +# the value. +# TOPLEVEL_BINDING Binding A Binding object representing the binding at Ruby’s top level— +# the level where programs are initially executed. +# TRUE TrueClass Synonym for true (deprecated, removed in Ruby 3). describe "The predefined global constants" do + describe "TRUE" do + it "is no longer defined" do + Object.const_defined?(:TRUE).should == false + end + end + + describe "FALSE" do + it "is no longer defined" do + Object.const_defined?(:FALSE).should == false + end + end + + describe "NIL" do + it "is no longer defined" do + Object.const_defined?(:NIL).should == false + end + end + it "includes STDIN" do Object.const_defined?(:STDIN).should == true end @@ -1146,7 +1417,6 @@ describe "The predefined global constants" do it "includes TOPLEVEL_BINDING" do Object.const_defined?(:TOPLEVEL_BINDING).should == true end - end describe "The predefined global constant" do @@ -1161,13 +1431,24 @@ describe "The predefined global constant" do end describe "STDIN" do - it "has the same external encoding as Encoding.default_external" do - STDIN.external_encoding.should equal(Encoding.default_external) - end + platform_is_not :windows do + it "has the same external encoding as Encoding.default_external" do + STDIN.external_encoding.should.equal?(Encoding.default_external) + end + + it "has the same external encoding as Encoding.default_external when that encoding is changed" do + Encoding.default_external = Encoding::ISO_8859_16 + STDIN.external_encoding.should.equal?(Encoding::ISO_8859_16) + end - it "has the same external encoding as Encoding.default_external when that encoding is changed" do - Encoding.default_external = Encoding::ISO_8859_16 - STDIN.external_encoding.should equal(Encoding::ISO_8859_16) + it "has nil for the internal encoding" do + STDIN.internal_encoding.should == nil + end + + it "has nil for the internal encoding despite Encoding.default_internal being changed" do + Encoding.default_internal = Encoding::IBM437 + STDIN.internal_encoding.should == nil + end end it "has the encodings set by #set_encoding" do @@ -1182,25 +1463,16 @@ describe "The predefined global constant" do "p [STDIN.external_encoding.name, STDIN.internal_encoding.name]" ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]} end - - it "has nil for the internal encoding" do - STDIN.internal_encoding.should be_nil - end - - it "has nil for the internal encoding despite Encoding.default_internal being changed" do - Encoding.default_internal = Encoding::IBM437 - STDIN.internal_encoding.should be_nil - end end describe "STDOUT" do it "has nil for the external encoding" do - STDOUT.external_encoding.should be_nil + STDOUT.external_encoding.should == nil end it "has nil for the external encoding despite Encoding.default_external being changed" do Encoding.default_external = Encoding::ISO_8859_1 - STDOUT.external_encoding.should be_nil + STDOUT.external_encoding.should == nil end it "has the encodings set by #set_encoding" do @@ -1210,23 +1482,23 @@ describe "The predefined global constant" do end it "has nil for the internal encoding" do - STDOUT.internal_encoding.should be_nil + STDOUT.internal_encoding.should == nil end it "has nil for the internal encoding despite Encoding.default_internal being changed" do Encoding.default_internal = Encoding::IBM437 - STDOUT.internal_encoding.should be_nil + STDOUT.internal_encoding.should == nil end end describe "STDERR" do it "has nil for the external encoding" do - STDERR.external_encoding.should be_nil + STDERR.external_encoding.should == nil end it "has nil for the external encoding despite Encoding.default_external being changed" do Encoding.default_external = Encoding::ISO_8859_1 - STDERR.external_encoding.should be_nil + STDERR.external_encoding.should == nil end it "has the encodings set by #set_encoding" do @@ -1236,12 +1508,12 @@ describe "The predefined global constant" do end it "has nil for the internal encoding" do - STDERR.internal_encoding.should be_nil + STDERR.internal_encoding.should == nil end it "has nil for the internal encoding despite Encoding.default_internal being changed" do Encoding.default_internal = Encoding::IBM437 - STDERR.internal_encoding.should be_nil + STDERR.internal_encoding.should == nil end end @@ -1255,24 +1527,49 @@ describe "The predefined global constant" do end end -ruby_version_is "2.7" do - 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.should == :rb - path.should.end_with?('/set.rb') - 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('pp') + extension.should == :rb + path.should.end_with?('/pp.rb') + end - it "returns what will be loaded without actual loading, .so file" do - require 'rbconfig' + it "returns what will be loaded without actual loading, .so file" do + require 'rbconfig' + skip "no dynamically loadable standard extension" if RbConfig::CONFIG["EXTSTATIC"] == "static" - extension, path = $LOAD_PATH.resolve_feature_path('etc') - extension.should == :so - path.should.end_with?("/etc.#{RbConfig::CONFIG['DLEXT']}") - end + extension, path = $LOAD_PATH.resolve_feature_path('etc') + extension.should == :so + path.should.end_with?("/etc.#{RbConfig::CONFIG['DLEXT']}") + end - it "raises LoadError if feature cannot be found" do - -> { $LOAD_PATH.resolve_feature_path('noop') }.should raise_error(LoadError) - end + it "return nil if feature cannot be found" do + $LOAD_PATH.resolve_feature_path('noop').should == nil + end +end + +# Some other pre-defined global variables + +describe "Predefined global $=" do + before :each do + @verbose, $VERBOSE = $VERBOSE, nil + @dollar_assign = $= + end + + after :each do + $= = @dollar_assign + $VERBOSE = @verbose + end + + it "warns when accessed" do + -> { a = $= }.should complain(/is no longer effective/) + end + + it "warns when assigned" do + -> { $= = "_" }.should complain(/is no longer effective/) + end + + it "returns the value assigned" do + ($= = "xyz").should == "xyz" end end diff --git a/spec/ruby/language/private_spec.rb b/spec/ruby/language/private_spec.rb index ddf185e6d2..94b56fee5b 100644 --- a/spec/ruby/language/private_spec.rb +++ b/spec/ruby/language/private_spec.rb @@ -4,12 +4,12 @@ require_relative 'fixtures/private' describe "The private keyword" do it "marks following methods as being private" do a = Private::A.new - a.methods.should_not include(:bar) - -> { a.bar }.should raise_error(NoMethodError) + a.methods.should_not.include?(:bar) + -> { a.bar }.should.raise(NoMethodError) b = Private::B.new - b.methods.should_not include(:bar) - -> { b.bar }.should raise_error(NoMethodError) + b.methods.should_not.include?(:bar) + -> { b.bar }.should.raise(NoMethodError) end # def expr.meth() methods are always public @@ -19,22 +19,22 @@ describe "The private keyword" do it "is overridden when a new class is opened" do c = Private::B::C.new - c.methods.should include(:baz) + c.methods.should.include?(:baz) c.baz Private::B.public_class_method1.should == 1 - -> { Private::B.private_class_method1 }.should raise_error(NoMethodError) + -> { Private::B.private_class_method1 }.should.raise(NoMethodError) end it "is no longer in effect when the class is closed" do b = Private::B.new - b.methods.should include(:foo) + b.methods.should.include?(:foo) b.foo end it "changes visibility of previously called method" do klass = Class.new do def foo - "foo" + "foo" end end f = klass.new @@ -42,7 +42,7 @@ describe "The private keyword" do klass.class_eval do private :foo end - -> { f.foo }.should raise_error(NoMethodError) + -> { f.foo }.should.raise(NoMethodError) end it "changes visibility of previously called methods with same send/call site" do @@ -56,12 +56,12 @@ describe "The private keyword" do end end end - }.should raise_error(NoMethodError) + }.should.raise(NoMethodError) end it "changes the visibility of the existing method in the subclass" do ::Private::A.new.foo.should == 'foo' - -> { ::Private::H.new.foo }.should raise_error(NoMethodError) + -> { ::Private::H.new.foo }.should.raise(NoMethodError) ::Private::H.new.send(:foo).should == 'foo' end end diff --git a/spec/ruby/language/proc_spec.rb b/spec/ruby/language/proc_spec.rb index c44e711d2b..53a21d6b85 100644 --- a/spec/ruby/language/proc_spec.rb +++ b/spec/ruby/language/proc_spec.rb @@ -22,7 +22,7 @@ describe "A Proc" do end it "raises an ArgumentError if a value is passed" do - lambda { @l.call(0) }.should raise_error(ArgumentError) + lambda { @l.call(0) }.should.raise(ArgumentError) end end @@ -36,7 +36,7 @@ describe "A Proc" do end it "raises an ArgumentError if a value is passed" do - lambda { @l.call(0) }.should raise_error(ArgumentError) + lambda { @l.call(0) }.should.raise(ArgumentError) end end @@ -57,11 +57,11 @@ describe "A Proc" do obj = mock("block yield to_ary") obj.should_not_receive(:to_ary) - @l.call(obj).should equal(obj) + @l.call(obj).should.equal?(obj) end it "raises an ArgumentError if no value is passed" do - lambda { @l.call }.should raise_error(ArgumentError) + lambda { @l.call }.should.raise(ArgumentError) end end @@ -71,11 +71,11 @@ describe "A Proc" do end it "raises an ArgumentError if passed no values" do - lambda { @l.call }.should raise_error(ArgumentError) + lambda { @l.call }.should.raise(ArgumentError) end it "raises an ArgumentError if passed one value" do - lambda { @l.call(0) }.should raise_error(ArgumentError) + lambda { @l.call(0) }.should.raise(ArgumentError) end it "assigns the values passed to the arguments" do @@ -86,7 +86,7 @@ describe "A Proc" do obj = mock("proc call to_ary") obj.should_not_receive(:to_ary) - lambda { @l.call(obj) }.should raise_error(ArgumentError) + lambda { @l.call(obj) }.should.raise(ArgumentError) end end @@ -96,7 +96,7 @@ describe "A Proc" do end it "raises an ArgumentError if passed no values" do - lambda { @l.call }.should raise_error(ArgumentError) + lambda { @l.call }.should.raise(ArgumentError) end it "does not destructure a single Array value yielded" do @@ -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 @@ -161,17 +161,29 @@ describe "A Proc" do end end + describe "taking |*a, b| arguments" do + it "assigns [] to the argument when passed no values" do + proc { |*a, b| [a, b] }.call.should == [[], nil] + end + end + + describe "taking |a, *b, c| arguments" do + it "assigns [] to the argument when passed no values" do + proc { |a, *b, c| [a, b, c] }.call.should == [nil, [], nil] + end + end + describe "taking |a, | arguments" do before :each do @l = lambda { |a, | a } end it "raises an ArgumentError when passed no values" do - lambda { @l.call }.should raise_error(ArgumentError) + lambda { @l.call }.should.raise(ArgumentError) end it "raises an ArgumentError when passed more than one value" do - lambda { @l.call(1, 2) }.should raise_error(ArgumentError) + lambda { @l.call(1, 2) }.should.raise(ArgumentError) end it "assigns the argument the value passed" do @@ -196,7 +208,7 @@ describe "A Proc" do end it "raises an ArgumentError when passed no values" do - lambda { @l.call }.should raise_error(ArgumentError) + lambda { @l.call }.should.raise(ArgumentError) end it "destructures a single Array value yielded" do @@ -214,7 +226,42 @@ describe "A Proc" do obj = mock("block yield to_ary invalid") obj.should_receive(:to_ary).and_return(1) - lambda { @l.call(obj) }.should raise_error(TypeError) + lambda { @l.call(obj) }.should.raise(TypeError) + end + end + + describe "taking |*a, **kw| arguments" do + before :each do + @p = proc { |*a, **kw| [a, kw] } end + + it 'does not autosplat keyword arguments' do + @p.call([1, {a: 1}]).should == [[[1, {a: 1}]], {}] + end + end + + describe "taking |required keyword arguments, **kw| arguments" do + it "raises ArgumentError for missing required argument" do + p = proc { |a:, **kw| [a, kw] } + -> { p.call() }.should.raise(ArgumentError) + end + end + + evaluate <<-ruby do + @p = proc { |**nil| :ok } + ruby + + @p.call().should == :ok + -> { @p.call(a: 1) }.should.raise(ArgumentError, 'no keywords accepted') + -> { @p.call(**{a: 1}) }.should.raise(ArgumentError, 'no keywords accepted') + -> { @p.call("a" => 1) }.should.raise(ArgumentError, 'no keywords accepted') + end + + evaluate <<-ruby do + @p = proc { |a, **nil| a } + ruby + + @p.call({a: 1}).should == {a: 1} + -> { @p.call(a: 1) }.should.raise(ArgumentError, 'no keywords accepted') end end diff --git a/spec/ruby/language/range_spec.rb b/spec/ruby/language/range_spec.rb index c0f90f84d6..ccc9f55537 100644 --- a/spec/ruby/language/range_spec.rb +++ b/spec/ruby/language/range_spec.rb @@ -10,17 +10,21 @@ describe "Literal Ranges" do (1...10).should == Range.new(1, 10, true) end - ruby_version_is "2.6" do - it "creates endless ranges" do - eval("(1..)").should == Range.new(1, nil) - eval("(1...)").should == Range.new(1, nil, true) + it "creates a simple range as an object literal" do + ary = [] + 2.times do + ary.push(1..3) end + ary[0].should.equal?(ary[1]) end - ruby_version_is "2.7" do - it "creates beginless ranges" do - eval("(..1)").should == Range.new(nil, 1) - eval("(...1)").should == Range.new(nil, 1, true) - end + it "creates endless ranges" do + (1..).should == Range.new(1, nil) + (1...).should == Range.new(1, nil, true) + end + + it "creates beginless ranges" do + (..1).should == Range.new(nil, 1) + (...1).should == Range.new(nil, 1, true) end end diff --git a/spec/ruby/language/redo_spec.rb b/spec/ruby/language/redo_spec.rb index 57532553b3..9b14c5add1 100644 --- a/spec/ruby/language/redo_spec.rb +++ b/spec/ruby/language/redo_spec.rb @@ -60,7 +60,7 @@ describe "The redo statement" do it "is invalid and raises a SyntaxError" do -> { eval("def m; redo; end") - }.should raise_error(SyntaxError) + }.should.raise(SyntaxError) end end end diff --git a/spec/ruby/language/regexp/anchors_spec.rb b/spec/ruby/language/regexp/anchors_spec.rb index 0129e255da..8e597b65e8 100644 --- a/spec/ruby/language/regexp/anchors_spec.rb +++ b/spec/ruby/language/regexp/anchors_spec.rb @@ -7,8 +7,8 @@ describe "Regexps with anchors" do /^foo/.match("foo").to_a.should == ["foo"] /^bar/.match("foo\nbar").to_a.should == ["bar"] # Basic non-matching - /^foo/.match(" foo").should be_nil - /foo^/.match("foo\n\n\n").should be_nil + /^foo/.match(" foo").should == nil + /foo^/.match("foo\n\n\n").should == nil # A bit advanced /^^^foo/.match("foo").to_a.should == ["foo"] @@ -16,8 +16,8 @@ describe "Regexps with anchors" do (/($^)($^)/ =~ "foo\n\n").should == "foo\n".size and $~.to_a.should == ["", "", ""] # Different start of line chars - /^bar/.match("foo\rbar").should be_nil - /^bar/.match("foo\0bar").should be_nil + /^bar/.match("foo\rbar").should == nil + /^bar/.match("foo\0bar").should == nil # Trivial /^/.match("foo").to_a.should == [""] @@ -29,7 +29,7 @@ describe "Regexps with anchors" do end it "does not match ^ after trailing \\n" do - /^(?!\A)/.match("foo\n").should be_nil # There is no (empty) line after a trailing \n + /^(?!\A)/.match("foo\n").should == nil # There is no (empty) line after a trailing \n end it "supports $ (line end anchor)" do @@ -37,16 +37,16 @@ describe "Regexps with anchors" do /foo$/.match("foo").to_a.should == ["foo"] /foo$/.match("foo\nbar").to_a.should == ["foo"] # Basic non-matching - /foo$/.match("foo ").should be_nil - /$foo/.match("\n\n\nfoo").should be_nil + /foo$/.match("foo ").should == nil + /$foo/.match("\n\n\nfoo").should == nil # A bit advanced /foo$$$/.match("foo").to_a.should == ["foo"] (/[^o]$/ =~ "foo\n\n").should == ("foo\n".size - 1) and $~.to_a.should == ["\n"] # Different end of line chars - /foo$/.match("foo\r\nbar").should be_nil - /foo$/.match("foo\0bar").should be_nil + /foo$/.match("foo\r\nbar").should == nil + /foo$/.match("foo\0bar").should == nil # Trivial (/$/ =~ "foo").should == "foo".size and $~.to_a.should == [""] @@ -61,15 +61,15 @@ describe "Regexps with anchors" do # Basic matching /\Afoo/.match("foo").to_a.should == ["foo"] # Basic non-matching - /\Abar/.match("foo\nbar").should be_nil - /\Afoo/.match(" foo").should be_nil + /\Abar/.match("foo\nbar").should == nil + /\Afoo/.match(" foo").should == nil # A bit advanced /\A\A\Afoo/.match("foo").to_a.should == ["foo"] /(\A\Z)(\A\Z)/.match("").to_a.should == ["", "", ""] # Different start of line chars - /\Abar/.match("foo\0bar").should be_nil + /\Abar/.match("foo\0bar").should == nil # Grouping /(\Afoo)/.match("foo").to_a.should == ["foo", "foo"] @@ -81,8 +81,8 @@ describe "Regexps with anchors" do /foo\Z/.match("foo").to_a.should == ["foo"] /foo\Z/.match("foo\n").to_a.should == ["foo"] # Basic non-matching - /foo\Z/.match("foo\nbar").should be_nil - /foo\Z/.match("foo ").should be_nil + /foo\Z/.match("foo\nbar").should == nil + /foo\Z/.match("foo ").should == nil # A bit advanced /foo\Z\Z\Z/.match("foo\n").to_a.should == ["foo"] @@ -90,8 +90,8 @@ describe "Regexps with anchors" do (/(\z\Z)(\z\Z)/ =~ "foo\n").should == "foo\n".size and $~.to_a.should == ["", "", ""] # Different end of line chars - /foo\Z/.match("foo\0bar").should be_nil - /foo\Z/.match("foo\r\n").should be_nil + /foo\Z/.match("foo\0bar").should == nil + /foo\Z/.match("foo\r\n").should == nil # Grouping /(foo\Z)/.match("foo").to_a.should == ["foo", "foo"] @@ -102,17 +102,17 @@ describe "Regexps with anchors" do # Basic matching /foo\z/.match("foo").to_a.should == ["foo"] # Basic non-matching - /foo\z/.match("foo\nbar").should be_nil - /foo\z/.match("foo\n").should be_nil - /foo\z/.match("foo ").should be_nil + /foo\z/.match("foo\nbar").should == nil + /foo\z/.match("foo\n").should == nil + /foo\z/.match("foo ").should == nil # A bit advanced /foo\z\z\z/.match("foo").to_a.should == ["foo"] (/($\z)($\z)/ =~ "foo").should == "foo".size and $~.to_a.should == ["", "", ""] # Different end of line chars - /foo\z/.match("foo\0bar").should be_nil - /foo\z/.match("foo\r\nbar").should be_nil + /foo\z/.match("foo\0bar").should == nil + /foo\z/.match("foo\r\nbar").should == nil # Grouping /(foo\z)/.match("foo").to_a.should == ["foo", "foo"] @@ -124,16 +124,16 @@ 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 - /foo\b/.match("foobar").should be_nil - /foo\b/.match("foo123").should be_nil - /foo\b/.match("foo_").should be_nil + /foo\b/.match("foobar").should == nil + /foo\b/.match("foo123").should == nil + /foo\b/.match("foo_").should == nil end it "supports \\B (non-word-boundary)" do @@ -142,15 +142,15 @@ describe "Regexps with anchors" do /foo\B/.match("foo123").to_a.should == ["foo"] /foo\B/.match("foo_").to_a.should == ["foo"] # Basic non-matching - /foo\B/.match("foo").should be_nil - /foo\B/.match("foo\n").should be_nil + /foo\B/.match("foo").should == nil + /foo\B/.match("foo\n").should == nil LanguageSpecs.white_spaces.scan(/./).each do |c| - /foo\B/.match("foo" + c).should be_nil + /foo\B/.match("foo" + c).should == 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 == nil end - /foo\B/.match("foo\0").should be_nil + /foo\B/.match("foo\0").should == nil end it "supports (?= ) (positive lookahead)" do diff --git a/spec/ruby/language/regexp/back-references_spec.rb b/spec/ruby/language/regexp/back-references_spec.rb index e8df8725c5..3b4c5656a2 100644 --- a/spec/ruby/language/regexp/back-references_spec.rb +++ b/spec/ruby/language/regexp/back-references_spec.rb @@ -22,6 +22,15 @@ describe "Regexps with back-references" do $10.should == "0" end + it "returns nil for numbered variable with too large index" do + -> { + eval(<<~CODE).should == nil + "a" =~ /(.)/ + eval('$4294967296') + CODE + }.should complain(/warning: ('|`)\$4294967296' is too big for a number variable, always nil/) + end + it "will not clobber capture variables across threads" do cap1, cap2, cap3 = nil "foo" =~ /(o+)/ @@ -40,7 +49,7 @@ describe "Regexps with back-references" do it "supports \<n> (backreference to previous group match)" do /(foo.)\1/.match("foo1foo1").to_a.should == ["foo1foo1", "foo1"] - /(foo.)\1/.match("foo1foo2").should be_nil + /(foo.)\1/.match("foo1foo2").should == nil end it "resets nested \<n> backreference before match of outer subexpression" do @@ -63,12 +72,17 @@ describe "Regexps with back-references" do (/\10()()()()()()()()()()/ =~ "\x08").should == 0 end + it "fails when trying to match a backreference to an unmatched capture group" do + /\1()/.match("").should == nil + /(?:(a)|b)\1/.match("b").should == nil + end + it "ignores backreferences > 1000" do /\99999/.match("99999")[0].should == "99999" end it "0 is not a valid backreference" do - -> { Regexp.new("\\k<0>") }.should raise_error(RegexpError) + -> { Regexp.new("\\k<0>") }.should.raise(RegexpError) end it "allows numeric conditional backreferences" do @@ -78,7 +92,7 @@ describe "Regexps with back-references" do end it "allows either <> or '' in named conditional backreferences" do - -> { Regexp.new("(?<a>a)(?(a)a|b)") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a>a)(?(a)a|b)") }.should.raise(RegexpError) /(?<a>a)(?(<a>)a|b)/.match("aa").to_a.should == [ "aa", "a" ] /(?<a>a)(?('a')a|b)/.match("aa").to_a.should == [ "aa", "a" ] end @@ -104,32 +118,32 @@ describe "Regexps with back-references" do end it "named capture groups invalidate numeric backreferences" do - -> { Regexp.new("(?<a>a)\\1") }.should raise_error(RegexpError) - -> { Regexp.new("(?<a>a)\\k<1>") }.should raise_error(RegexpError) - -> { Regexp.new("(a)(?<a>a)\\1") }.should raise_error(RegexpError) - -> { Regexp.new("(a)(?<a>a)\\k<1>") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a>a)\\1") }.should.raise(RegexpError) + -> { Regexp.new("(?<a>a)\\k<1>") }.should.raise(RegexpError) + -> { Regexp.new("(a)(?<a>a)\\1") }.should.raise(RegexpError) + -> { Regexp.new("(a)(?<a>a)\\k<1>") }.should.raise(RegexpError) end it "treats + or - as the beginning of a level specifier in \\k<> backreferences and (?(...)...|...) conditional backreferences" do - -> { Regexp.new("(?<a+>a)\\k<a+>") }.should raise_error(RegexpError) - -> { Regexp.new("(?<a+b>a)\\k<a+b>") }.should raise_error(RegexpError) - -> { Regexp.new("(?<a+1>a)\\k<a+1>") }.should raise_error(RegexpError) - -> { Regexp.new("(?<a->a)\\k<a->") }.should raise_error(RegexpError) - -> { Regexp.new("(?<a-b>a)\\k<a-b>") }.should raise_error(RegexpError) - -> { Regexp.new("(?<a-1>a)\\k<a-1>") }.should raise_error(RegexpError) - - -> { Regexp.new("(?<a+>a)(?(<a+>)a|b)") }.should raise_error(RegexpError) - -> { Regexp.new("(?<a+b>a)(?(<a+b>)a|b)") }.should raise_error(RegexpError) - -> { Regexp.new("(?<a+1>a)(?(<a+1>)a|b)") }.should raise_error(RegexpError) - -> { Regexp.new("(?<a->a)(?(<a->)a|b)") }.should raise_error(RegexpError) - -> { Regexp.new("(?<a-b>a)(?(<a-b>)a|b)") }.should raise_error(RegexpError) - -> { Regexp.new("(?<a-1>a)(?(<a-1>)a|b)") }.should raise_error(RegexpError) - - -> { Regexp.new("(?<a+>a)(?('a+')a|b)") }.should raise_error(RegexpError) - -> { Regexp.new("(?<a+b>a)(?('a+b')a|b)") }.should raise_error(RegexpError) - -> { Regexp.new("(?<a+1>a)(?('a+1')a|b)") }.should raise_error(RegexpError) - -> { Regexp.new("(?<a->a)(?('a-')a|b)") }.should raise_error(RegexpError) - -> { Regexp.new("(?<a-b>a)(?('a-b')a|b)") }.should raise_error(RegexpError) - -> { Regexp.new("(?<a-1>a)(?('a-1')a|b)") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a+>a)\\k<a+>") }.should.raise(RegexpError) + -> { Regexp.new("(?<a+b>a)\\k<a+b>") }.should.raise(RegexpError) + -> { Regexp.new("(?<a+1>a)\\k<a+1>") }.should.raise(RegexpError) + -> { Regexp.new("(?<a->a)\\k<a->") }.should.raise(RegexpError) + -> { Regexp.new("(?<a-b>a)\\k<a-b>") }.should.raise(RegexpError) + -> { Regexp.new("(?<a-1>a)\\k<a-1>") }.should.raise(RegexpError) + + -> { Regexp.new("(?<a+>a)(?(<a+>)a|b)") }.should.raise(RegexpError) + -> { Regexp.new("(?<a+b>a)(?(<a+b>)a|b)") }.should.raise(RegexpError) + -> { Regexp.new("(?<a+1>a)(?(<a+1>)a|b)") }.should.raise(RegexpError) + -> { Regexp.new("(?<a->a)(?(<a->)a|b)") }.should.raise(RegexpError) + -> { Regexp.new("(?<a-b>a)(?(<a-b>)a|b)") }.should.raise(RegexpError) + -> { Regexp.new("(?<a-1>a)(?(<a-1>)a|b)") }.should.raise(RegexpError) + + -> { Regexp.new("(?<a+>a)(?('a+')a|b)") }.should.raise(RegexpError) + -> { Regexp.new("(?<a+b>a)(?('a+b')a|b)") }.should.raise(RegexpError) + -> { Regexp.new("(?<a+1>a)(?('a+1')a|b)") }.should.raise(RegexpError) + -> { Regexp.new("(?<a->a)(?('a-')a|b)") }.should.raise(RegexpError) + -> { Regexp.new("(?<a-b>a)(?('a-b')a|b)") }.should.raise(RegexpError) + -> { Regexp.new("(?<a-1>a)(?('a-1')a|b)") }.should.raise(RegexpError) end end diff --git a/spec/ruby/language/regexp/character_classes_spec.rb b/spec/ruby/language/regexp/character_classes_spec.rb index 0cf1e9b6f4..c6ed92b78e 100644 --- a/spec/ruby/language/regexp/character_classes_spec.rb +++ b/spec/ruby/language/regexp/character_classes_spec.rb @@ -9,9 +9,9 @@ describe "Regexp with character classes" do /\w/.match("_").to_a.should == ["_"] # Non-matches - /\w/.match(LanguageSpecs.white_spaces).should be_nil - /\w/.match(LanguageSpecs.non_alphanum_non_space).should be_nil - /\w/.match("\0").should be_nil + /\w/.match(LanguageSpecs.white_spaces).should == nil + /\w/.match(LanguageSpecs.non_alphanum_non_space).should == nil + /\w/.match("\0").should == nil end it "supports \\W (non-word character)" do @@ -20,19 +20,19 @@ describe "Regexp with character classes" do /\W/.match("\0").to_a.should == ["\0"] # Non-matches - /\W/.match("a").should be_nil - /\W/.match("1").should be_nil - /\W/.match("_").should be_nil + /\W/.match("a").should == nil + /\W/.match("1").should == nil + /\W/.match("_").should == nil end it "supports \\s (space character)" do /\s+/.match(LanguageSpecs.white_spaces).to_a.should == [LanguageSpecs.white_spaces] # Non-matches - /\s/.match("a").should be_nil - /\s/.match("1").should be_nil - /\s/.match(LanguageSpecs.non_alphanum_non_space).should be_nil - /\s/.match("\0").should be_nil + /\s/.match("a").should == nil + /\s/.match("1").should == nil + /\s/.match(LanguageSpecs.non_alphanum_non_space).should == nil + /\s/.match("\0").should == nil end it "supports \\S (non-space character)" do @@ -42,17 +42,17 @@ describe "Regexp with character classes" do /\S/.match("\0").to_a.should == ["\0"] # Non-matches - /\S/.match(LanguageSpecs.white_spaces).should be_nil + /\S/.match(LanguageSpecs.white_spaces).should == nil end it "supports \\d (numeric digit)" do /\d/.match("1").to_a.should == ["1"] # Non-matches - /\d/.match("a").should be_nil - /\d/.match(LanguageSpecs.white_spaces).should be_nil - /\d/.match(LanguageSpecs.non_alphanum_non_space).should be_nil - /\d/.match("\0").should be_nil + /\d/.match("a").should == nil + /\d/.match(LanguageSpecs.white_spaces).should == nil + /\d/.match(LanguageSpecs.non_alphanum_non_space).should == nil + /\d/.match("\0").should == nil end it "supports \\D (non-digit)" do @@ -62,7 +62,7 @@ describe "Regexp with character classes" do /\D/.match("\0").to_a.should == ["\0"] # Non-matches - /\D/.match("1").should be_nil + /\D/.match("1").should == nil end it "supports [] (character class)" do @@ -89,7 +89,7 @@ describe "Regexp with character classes" do /[^[:lower:]A-C]+/.match("abcABCDEF123def").to_a.should == ["DEF123"] # negated character class /[:alnum:]+/.match("a:l:n:u:m").to_a.should == ["a:l:n:u:m"] # should behave like regular character class composed of the individual letters /[\[:alnum:]+/.match("[:a:l:n:u:m").to_a.should == ["[:a:l:n:u:m"] # should behave like regular character class composed of the individual letters - -> { eval('/[[:alpha:]-[:digit:]]/') }.should raise_error(SyntaxError) # can't use character class as a start value of range + -> { eval('/[[:alpha:]-[:digit:]]/') }.should.raise(SyntaxError) # can't use character class as a start value of range end it "matches ASCII characters with [[:ascii:]]" do @@ -99,8 +99,8 @@ describe "Regexp with character classes" do not_supported_on :opal do it "doesn't match non-ASCII characters with [[:ascii:]]" do - /[[:ascii:]]/.match("\u{80}").should be_nil - /[[:ascii:]]/.match("\u{9898}").should be_nil + /[[:ascii:]]/.match("\u{80}").should == nil + /[[:ascii:]]/.match("\u{9898}").should == nil end end @@ -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 == 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 == nil end it "doesn't match Unicode control characters with [[:alpha:]]" do @@ -149,39 +149,39 @@ describe "Regexp with character classes" do end it "doesn't match Unicode control characters with [[:blank:]]" do - "\u{16}".match(/[[:blank:]]/).should be_nil + "\u{16}".match(/[[:blank:]]/).should == nil end it "doesn't match Unicode punctuation characters with [[:blank:]]" do - "\u{3F}".match(/[[:blank:]]/).should be_nil + "\u{3F}".match(/[[:blank:]]/).should == nil end it "doesn't match Unicode letter characters with [[:blank:]]" do - "à".match(/[[:blank:]]/).should be_nil + "à".match(/[[:blank:]]/).should == nil end it "doesn't match Unicode digits with [[:blank:]]" do - "\u{0660}".match(/[[:blank:]]/).should be_nil + "\u{0660}".match(/[[:blank:]]/).should == nil end it "doesn't match Unicode marks with [[:blank:]]" do - "\u{36F}".match(/[[:blank:]]/).should be_nil + "\u{36F}".match(/[[:blank:]]/).should == nil end it "doesn't Unicode letter characters with [[:cntrl:]]" do - "à".match(/[[:cntrl:]]/).should be_nil + "à".match(/[[:cntrl:]]/).should == nil end it "doesn't match Unicode digits with [[:cntrl:]]" do - "\u{0660}".match(/[[:cntrl:]]/).should be_nil + "\u{0660}".match(/[[:cntrl:]]/).should == nil end it "doesn't match Unicode marks with [[:cntrl:]]" do - "\u{36F}".match(/[[:cntrl:]]/).should be_nil + "\u{36F}".match(/[[:cntrl:]]/).should == nil end it "doesn't match Unicode punctuation characters with [[:cntrl:]]" do - "\u{3F}".match(/[[:cntrl:]]/).should be_nil + "\u{3F}".match(/[[:cntrl:]]/).should == nil end it "matches Unicode control characters with [[:cntrl:]]" do @@ -189,15 +189,15 @@ describe "Regexp with character classes" do end it "doesn't match Unicode format characters with [[:cntrl:]]" do - "\u{2060}".match(/[[:cntrl:]]/).should be_nil + "\u{2060}".match(/[[:cntrl:]]/).should == nil end it "doesn't match Unicode private-use characters with [[:cntrl:]]" do - "\u{E001}".match(/[[:cntrl:]]/).should be_nil + "\u{E001}".match(/[[:cntrl:]]/).should == nil end it "doesn't match Unicode letter characters with [[:digit:]]" do - "à".match(/[[:digit:]]/).should be_nil + "à".match(/[[:digit:]]/).should == nil end it "matches Unicode digits with [[:digit:]]" do @@ -206,27 +206,27 @@ describe "Regexp with character classes" do end it "doesn't match Unicode marks with [[:digit:]]" do - "\u{36F}".match(/[[:digit:]]/).should be_nil + "\u{36F}".match(/[[:digit:]]/).should == nil end it "doesn't match Unicode punctuation characters with [[:digit:]]" do - "\u{3F}".match(/[[:digit:]]/).should be_nil + "\u{3F}".match(/[[:digit:]]/).should == nil end it "doesn't match Unicode control characters with [[:digit:]]" do - "\u{16}".match(/[[:digit:]]/).should be_nil + "\u{16}".match(/[[:digit:]]/).should == nil end it "doesn't match Unicode format characters with [[:digit:]]" do - "\u{2060}".match(/[[:digit:]]/).should be_nil + "\u{2060}".match(/[[:digit:]]/).should == nil end it "doesn't match Unicode private-use characters with [[:digit:]]" do - "\u{E001}".match(/[[:digit:]]/).should be_nil + "\u{E001}".match(/[[:digit:]]/).should == nil 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 @@ -243,7 +243,7 @@ describe "Regexp with character classes" do end it "doesn't match Unicode control characters with [[:graph:]]" do - "\u{16}".match(/[[:graph:]]/).should be_nil + "\u{16}".match(/[[:graph:]]/).should == nil end it "match Unicode format characters with [[:graph:]]" do @@ -261,40 +261,40 @@ describe "Regexp with character classes" do end it "doesn't match Unicode uppercase letter characters with [[:lower:]]" do - "\u{100}".match(/[[:lower:]]/).should be_nil - "\u{130}".match(/[[:lower:]]/).should be_nil - "\u{405}".match(/[[:lower:]]/).should be_nil + "\u{100}".match(/[[:lower:]]/).should == nil + "\u{130}".match(/[[:lower:]]/).should == nil + "\u{405}".match(/[[:lower:]]/).should == nil end it "doesn't match Unicode title-case characters with [[:lower:]]" do - "\u{1F88}".match(/[[:lower:]]/).should be_nil - "\u{1FAD}".match(/[[:lower:]]/).should be_nil - "\u{01C5}".match(/[[:lower:]]/).should be_nil + "\u{1F88}".match(/[[:lower:]]/).should == nil + "\u{1FAD}".match(/[[:lower:]]/).should == nil + "\u{01C5}".match(/[[:lower:]]/).should == nil end it "doesn't match Unicode digits with [[:lower:]]" do - "\u{0660}".match(/[[:lower:]]/).should be_nil - "\u{FF12}".match(/[[:lower:]]/).should be_nil + "\u{0660}".match(/[[:lower:]]/).should == nil + "\u{FF12}".match(/[[:lower:]]/).should == nil end it "doesn't match Unicode marks with [[:lower:]]" do - "\u{36F}".match(/[[:lower:]]/).should be_nil + "\u{36F}".match(/[[:lower:]]/).should == nil end it "doesn't match Unicode punctuation characters with [[:lower:]]" do - "\u{3F}".match(/[[:lower:]]/).should be_nil + "\u{3F}".match(/[[:lower:]]/).should == nil end it "doesn't match Unicode control characters with [[:lower:]]" do - "\u{16}".match(/[[:lower:]]/).should be_nil + "\u{16}".match(/[[:lower:]]/).should == nil end it "doesn't match Unicode format characters with [[:lower:]]" do - "\u{2060}".match(/[[:lower:]]/).should be_nil + "\u{2060}".match(/[[:lower:]]/).should == nil end it "doesn't match Unicode private-use characters with [[:lower:]]" do - "\u{E001}".match(/[[:lower:]]/).should be_nil + "\u{E001}".match(/[[:lower:]]/).should == nil end it "matches Unicode lowercase letter characters with [[:print:]]" do @@ -329,7 +329,7 @@ describe "Regexp with character classes" do end it "doesn't match Unicode control characters with [[:print:]]" do - "\u{16}".match(/[[:print:]]/).should be_nil + "\u{16}".match(/[[:print:]]/).should == nil end it "match Unicode format characters with [[:print:]]" do @@ -342,30 +342,30 @@ describe "Regexp with character classes" do it "doesn't match Unicode lowercase letter characters with [[:punct:]]" do - "\u{FF41}".match(/[[:punct:]]/).should be_nil - "\u{1D484}".match(/[[:punct:]]/).should be_nil - "\u{E8}".match(/[[:punct:]]/).should be_nil + "\u{FF41}".match(/[[:punct:]]/).should == nil + "\u{1D484}".match(/[[:punct:]]/).should == nil + "\u{E8}".match(/[[:punct:]]/).should == nil end it "doesn't match Unicode uppercase letter characters with [[:punct:]]" do - "\u{100}".match(/[[:punct:]]/).should be_nil - "\u{130}".match(/[[:punct:]]/).should be_nil - "\u{405}".match(/[[:punct:]]/).should be_nil + "\u{100}".match(/[[:punct:]]/).should == nil + "\u{130}".match(/[[:punct:]]/).should == nil + "\u{405}".match(/[[:punct:]]/).should == nil end it "doesn't match Unicode title-case characters with [[:punct:]]" do - "\u{1F88}".match(/[[:punct:]]/).should be_nil - "\u{1FAD}".match(/[[:punct:]]/).should be_nil - "\u{01C5}".match(/[[:punct:]]/).should be_nil + "\u{1F88}".match(/[[:punct:]]/).should == nil + "\u{1FAD}".match(/[[:punct:]]/).should == nil + "\u{01C5}".match(/[[:punct:]]/).should == nil end it "doesn't match Unicode digits with [[:punct:]]" do - "\u{0660}".match(/[[:punct:]]/).should be_nil - "\u{FF12}".match(/[[:punct:]]/).should be_nil + "\u{0660}".match(/[[:punct:]]/).should == nil + "\u{FF12}".match(/[[:punct:]]/).should == nil end it "doesn't match Unicode marks with [[:punct:]]" do - "\u{36F}".match(/[[:punct:]]/).should be_nil + "\u{36F}".match(/[[:punct:]]/).should == nil end it "matches Unicode Pc characters with [[:punct:]]" do @@ -398,38 +398,38 @@ describe "Regexp with character classes" do end it "doesn't match Unicode format characters with [[:punct:]]" do - "\u{2060}".match(/[[:punct:]]/).should be_nil + "\u{2060}".match(/[[:punct:]]/).should == nil end it "doesn't match Unicode private-use characters with [[:punct:]]" do - "\u{E001}".match(/[[:punct:]]/).should be_nil + "\u{E001}".match(/[[:punct:]]/).should == nil end it "doesn't match Unicode lowercase letter characters with [[:space:]]" do - "\u{FF41}".match(/[[:space:]]/).should be_nil - "\u{1D484}".match(/[[:space:]]/).should be_nil - "\u{E8}".match(/[[:space:]]/).should be_nil + "\u{FF41}".match(/[[:space:]]/).should == nil + "\u{1D484}".match(/[[:space:]]/).should == nil + "\u{E8}".match(/[[:space:]]/).should == nil end it "doesn't match Unicode uppercase letter characters with [[:space:]]" do - "\u{100}".match(/[[:space:]]/).should be_nil - "\u{130}".match(/[[:space:]]/).should be_nil - "\u{405}".match(/[[:space:]]/).should be_nil + "\u{100}".match(/[[:space:]]/).should == nil + "\u{130}".match(/[[:space:]]/).should == nil + "\u{405}".match(/[[:space:]]/).should == nil end it "doesn't match Unicode title-case characters with [[:space:]]" do - "\u{1F88}".match(/[[:space:]]/).should be_nil - "\u{1FAD}".match(/[[:space:]]/).should be_nil - "\u{01C5}".match(/[[:space:]]/).should be_nil + "\u{1F88}".match(/[[:space:]]/).should == nil + "\u{1FAD}".match(/[[:space:]]/).should == nil + "\u{01C5}".match(/[[:space:]]/).should == nil end it "doesn't match Unicode digits with [[:space:]]" do - "\u{0660}".match(/[[:space:]]/).should be_nil - "\u{FF12}".match(/[[:space:]]/).should be_nil + "\u{0660}".match(/[[:space:]]/).should == nil + "\u{FF12}".match(/[[:space:]]/).should == nil end it "doesn't match Unicode marks with [[:space:]]" do - "\u{36F}".match(/[[:space:]]/).should be_nil + "\u{36F}".match(/[[:space:]]/).should == nil end it "matches Unicode Zs characters with [[:space:]]" do @@ -445,17 +445,17 @@ describe "Regexp with character classes" do end it "doesn't match Unicode format characters with [[:space:]]" do - "\u{2060}".match(/[[:space:]]/).should be_nil + "\u{2060}".match(/[[:space:]]/).should == nil end it "doesn't match Unicode private-use characters with [[:space:]]" do - "\u{E001}".match(/[[:space:]]/).should be_nil + "\u{E001}".match(/[[:space:]]/).should == nil end it "doesn't match Unicode lowercase characters with [[:upper:]]" do - "\u{FF41}".match(/[[:upper:]]/).should be_nil - "\u{1D484}".match(/[[:upper:]]/).should be_nil - "\u{E8}".match(/[[:upper:]]/).should be_nil + "\u{FF41}".match(/[[:upper:]]/).should == nil + "\u{1D484}".match(/[[:upper:]]/).should == nil + "\u{E8}".match(/[[:upper:]]/).should == nil end it "matches Unicode uppercase characters with [[:upper:]]" do @@ -465,40 +465,40 @@ describe "Regexp with character classes" do end it "doesn't match Unicode title-case characters with [[:upper:]]" do - "\u{1F88}".match(/[[:upper:]]/).should be_nil - "\u{1FAD}".match(/[[:upper:]]/).should be_nil - "\u{01C5}".match(/[[:upper:]]/).should be_nil + "\u{1F88}".match(/[[:upper:]]/).should == nil + "\u{1FAD}".match(/[[:upper:]]/).should == nil + "\u{01C5}".match(/[[:upper:]]/).should == nil end it "doesn't match Unicode digits with [[:upper:]]" do - "\u{0660}".match(/[[:upper:]]/).should be_nil - "\u{FF12}".match(/[[:upper:]]/).should be_nil + "\u{0660}".match(/[[:upper:]]/).should == nil + "\u{FF12}".match(/[[:upper:]]/).should == nil end it "doesn't match Unicode marks with [[:upper:]]" do - "\u{36F}".match(/[[:upper:]]/).should be_nil + "\u{36F}".match(/[[:upper:]]/).should == nil end it "doesn't match Unicode punctuation characters with [[:upper:]]" do - "\u{3F}".match(/[[:upper:]]/).should be_nil + "\u{3F}".match(/[[:upper:]]/).should == nil end it "doesn't match Unicode control characters with [[:upper:]]" do - "\u{16}".match(/[[:upper:]]/).should be_nil + "\u{16}".match(/[[:upper:]]/).should == nil end it "doesn't match Unicode format characters with [[:upper:]]" do - "\u{2060}".match(/[[:upper:]]/).should be_nil + "\u{2060}".match(/[[:upper:]]/).should == nil end it "doesn't match Unicode private-use characters with [[:upper:]]" do - "\u{E001}".match(/[[:upper:]]/).should be_nil + "\u{E001}".match(/[[:upper:]]/).should == nil end it "doesn't match Unicode letter characters [^a-fA-F] with [[:xdigit:]]" do - "à".match(/[[:xdigit:]]/).should be_nil - "g".match(/[[:xdigit:]]/).should be_nil - "X".match(/[[:xdigit:]]/).should be_nil + "à".match(/[[:xdigit:]]/).should == nil + "g".match(/[[:xdigit:]]/).should == nil + "X".match(/[[:xdigit:]]/).should == nil end it "matches Unicode letter characters [a-fA-F] with [[:xdigit:]]" do @@ -507,28 +507,28 @@ describe "Regexp with character classes" do end it "doesn't match Unicode digits [^0-9] with [[:xdigit:]]" do - "\u{0660}".match(/[[:xdigit:]]/).should be_nil - "\u{FF12}".match(/[[:xdigit:]]/).should be_nil + "\u{0660}".match(/[[:xdigit:]]/).should == nil + "\u{FF12}".match(/[[:xdigit:]]/).should == nil end it "doesn't match Unicode marks with [[:xdigit:]]" do - "\u{36F}".match(/[[:xdigit:]]/).should be_nil + "\u{36F}".match(/[[:xdigit:]]/).should == nil end it "doesn't match Unicode punctuation characters with [[:xdigit:]]" do - "\u{3F}".match(/[[:xdigit:]]/).should be_nil + "\u{3F}".match(/[[:xdigit:]]/).should == nil end it "doesn't match Unicode control characters with [[:xdigit:]]" do - "\u{16}".match(/[[:xdigit:]]/).should be_nil + "\u{16}".match(/[[:xdigit:]]/).should == nil end it "doesn't match Unicode format characters with [[:xdigit:]]" do - "\u{2060}".match(/[[:xdigit:]]/).should be_nil + "\u{2060}".match(/[[:xdigit:]]/).should == nil end it "doesn't match Unicode private-use characters with [[:xdigit:]]" do - "\u{E001}".match(/[[:xdigit:]]/).should be_nil + "\u{E001}".match(/[[:xdigit:]]/).should == nil end it "matches Unicode lowercase characters with [[:word:]]" do @@ -562,23 +562,30 @@ 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 + "\u{17F0}".match(/[[:word:]]/).should == nil end it "doesn't match Unicode punctuation characters with [[:word:]]" do - "\u{3F}".match(/[[:word:]]/).should be_nil + "\u{3F}".match(/[[:word:]]/).should == nil end it "doesn't match Unicode control characters with [[:word:]]" do - "\u{16}".match(/[[:word:]]/).should be_nil + "\u{16}".match(/[[:word:]]/).should == nil end it "doesn't match Unicode format characters with [[:word:]]" do - "\u{2060}".match(/[[:word:]]/).should be_nil + "\u{2060}".match(/[[:word:]]/).should == nil end it "doesn't match Unicode private-use characters with [[:word:]]" do - "\u{E001}".match(/[[:word:]]/).should be_nil + "\u{E001}".match(/[[:word:]]/).should == nil end it "matches unicode named character properties" do @@ -609,10 +616,13 @@ describe "Regexp with character classes" do "루비(Ruby)".match(/\p{Hangul}+/u).to_a.should == ["루비"] end - ruby_bug "#17340", ''...'3.0' do - it "raises a RegexpError for an unterminated unicode property" do - -> { Regexp.new('\p{') }.should raise_error(RegexpError) - end + it "supports negated property condition" do + "a".match(eval("/\P{L}/")).should == nil + "1".match(eval("/\P{N}/")).should == nil + end + + it "raises a RegexpError for an unterminated unicode property" do + -> { Regexp.new('\p{') }.should.raise(RegexpError) end it "supports \\X (unicode 9.0 with UTR #51 workarounds)" do diff --git a/spec/ruby/language/regexp/empty_checks_spec.rb b/spec/ruby/language/regexp/empty_checks_spec.rb new file mode 100644 index 0000000000..391e65b003 --- /dev/null +++ b/spec/ruby/language/regexp/empty_checks_spec.rb @@ -0,0 +1,135 @@ +require_relative '../../spec_helper' +require_relative '../fixtures/classes' + +describe "empty checks in Regexps" do + + it "allow extra empty iterations" do + /()?/.match("").to_a.should == ["", ""] + /(a*)?/.match("").to_a.should == ["", ""] + /(a*)*/.match("").to_a.should == ["", ""] + # The bounds are high to avoid DFA-based matchers in implementations + # and to check backtracking behavior. + /(?:a|()){500,1000}/.match("a" * 500).to_a.should == ["a" * 500, ""] + + # Variations with non-greedy loops. + /()??/.match("").to_a.should == ["", nil] + /(a*?)?/.match("").to_a.should == ["", ""] + /(a*)??/.match("").to_a.should == ["", nil] + /(a*?)??/.match("").to_a.should == ["", nil] + /(a*?)*/.match("").to_a.should == ["", ""] + /(a*)*?/.match("").to_a.should == ["", nil] + /(a*?)*?/.match("").to_a.should == ["", nil] + end + + it "allow empty iterations in the middle of a loop" do + # One empty iteration between a's and b's. + /(a|\2b|())*/.match("aaabbb").to_a.should == ["aaabbb", "", ""] + /(a|\2b|()){2,4}/.match("aaabbb").to_a.should == ["aaa", "", ""] + + # Two empty iterations between a's and b's. + /(a|\2b|\3()|())*/.match("aaabbb").to_a.should == ["aaabbb", "", "", ""] + /(a|\2b|\3()|()){2,4}/.match("aaabbb").to_a.should == ["aaa", "", nil, ""] + + # Check that the empty iteration correctly updates the loop counter. + /(a|\2b|()){20,24}/.match("a" * 20 + "b" * 5).to_a.should == ["a" * 20 + "b" * 3, "b", ""] + + # Variations with non-greedy loops. + /(a|\2b|())*?/.match("aaabbb").to_a.should == ["", nil, nil] + /(a|\2b|()){2,4}/.match("aaabbb").to_a.should == ["aaa", "", ""] + /(a|\2b|\3()|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil] + /(a|\2b|\3()|()){2,4}/.match("aaabbb").to_a.should == ["aaa", "", nil, ""] + /(a|\2b|()){20,24}/.match("a" * 20 + "b" * 5).to_a.should == ["a" * 20 + "b" * 3, "b", ""] + end + + it "make the Regexp proceed past the quantified expression on failure" do + # If the contents of the ()* quantified group are empty (i.e., they fail + # the empty check), the loop will abort. It will not try to backtrack + # and try other alternatives (e.g. matching the "a") like in other Regexp + # dialects such as ECMAScript. + /(?:|a)*/.match("aaa").to_a.should == [""] + /(?:()|a)*/.match("aaa").to_a.should == ["", ""] + /(|a)*/.match("aaa").to_a.should == ["", ""] + /(()|a)*/.match("aaa").to_a.should == ["", "", ""] + + # Same expressions, but with backreferences, to force the use of non-DFA-based + # engines. + /()\1(?:|a)*/.match("aaa").to_a.should == ["", ""] + /()\1(?:()|a)*/.match("aaa").to_a.should == ["", "", ""] + /()\1(|a)*/.match("aaa").to_a.should == ["", "", ""] + /()\1(()|a)*/.match("aaa").to_a.should == ["", "", "", ""] + + # Variations with other zero-width contents of the quantified + # group: backreferences, capture groups, lookarounds + /()(?:\1|a)*/.match("aaa").to_a.should == ["", ""] + /()(?:()\1|a)*/.match("aaa").to_a.should == ["", "", ""] + /()(?:(\1)|a)*/.match("aaa").to_a.should == ["", "", ""] + /()(?:\1()|a)*/.match("aaa").to_a.should == ["", "", ""] + /()(\1|a)*/.match("aaa").to_a.should == ["", "", ""] + /()(()\1|a)*/.match("aaa").to_a.should == ["", "", "", ""] + /()((\1)|a)*/.match("aaa").to_a.should == ["", "", "", ""] + /()(\1()|a)*/.match("aaa").to_a.should == ["", "", "", ""] + + /(?:(?=a)|a)*/.match("aaa").to_a.should == [""] + /(?:(?=a)()|a)*/.match("aaa").to_a.should == ["", ""] + /(?:()(?=a)|a)*/.match("aaa").to_a.should == ["", ""] + /(?:((?=a))|a)*/.match("aaa").to_a.should == ["", ""] + /()\1(?:(?=a)|a)*/.match("aaa").to_a.should == ["", ""] + /()\1(?:(?=a)()|a)*/.match("aaa").to_a.should == ["", "", ""] + /()\1(?:()(?=a)|a)*/.match("aaa").to_a.should == ["", "", ""] + /()\1(?:((?=a))|a)*/.match("aaa").to_a.should == ["", "", ""] + + # Variations with non-greedy loops. + /(?:|a)*?/.match("aaa").to_a.should == [""] + /(?:()|a)*?/.match("aaa").to_a.should == ["", nil] + /(|a)*?/.match("aaa").to_a.should == ["", nil] + /(()|a)*?/.match("aaa").to_a.should == ["", nil, nil] + + /()\1(?:|a)*?/.match("aaa").to_a.should == ["", ""] + /()\1(?:()|a)*?/.match("aaa").to_a.should == ["", "", nil] + /()\1(|a)*?/.match("aaa").to_a.should == ["", "", nil] + /()\1(()|a)*?/.match("aaa").to_a.should == ["", "", nil, nil] + + /()(?:\1|a)*?/.match("aaa").to_a.should == ["", ""] + /()(?:()\1|a)*?/.match("aaa").to_a.should == ["", "", nil] + /()(?:(\1)|a)*?/.match("aaa").to_a.should == ["", "", nil] + /()(?:\1()|a)*?/.match("aaa").to_a.should == ["", "", nil] + /()(\1|a)*?/.match("aaa").to_a.should == ["", "", nil] + /()(()\1|a)*?/.match("aaa").to_a.should == ["", "", nil, nil] + /()((\1)|a)*?/.match("aaa").to_a.should == ["", "", nil, nil] + /()(\1()|a)*?/.match("aaa").to_a.should == ["", "", nil, nil] + + /(?:(?=a)|a)*?/.match("aaa").to_a.should == [""] + /(?:(?=a)()|a)*?/.match("aaa").to_a.should == ["", nil] + /(?:()(?=a)|a)*?/.match("aaa").to_a.should == ["", nil] + /(?:((?=a))|a)*?/.match("aaa").to_a.should == ["", nil] + /()\1(?:(?=a)|a)*?/.match("aaa").to_a.should == ["", ""] + /()\1(?:(?=a)()|a)*?/.match("aaa").to_a.should == ["", "", nil] + /()\1(?:()(?=a)|a)*?/.match("aaa").to_a.should == ["", "", nil] + /()\1(?:((?=a))|a)*?/.match("aaa").to_a.should == ["", "", nil] + end + + it "shouldn't cause the Regexp parser to get stuck in a loop" do + /(|a|\2b|())*/.match("aaabbb").to_a.should == ["", "", nil] + /(a||\2b|())*/.match("aaabbb").to_a.should == ["aaa", "", nil] + /(a|\2b||())*/.match("aaabbb").to_a.should == ["aaa", "", nil] + /(a|\2b|()|)*/.match("aaabbb").to_a.should == ["aaabbb", "", ""] + /(()|a|\3b|())*/.match("aaabbb").to_a.should == ["", "", "", nil] + /(a|()|\3b|())*/.match("aaabbb").to_a.should == ["aaa", "", "", nil] + /(a|\2b|()|())*/.match("aaabbb").to_a.should == ["aaabbb", "", "", nil] + /(a|\3b|()|())*/.match("aaabbb").to_a.should == ["aaa", "", "", nil] + /(a|()|())*/.match("aaa").to_a.should == ["aaa", "", "", nil] + /^(()|a|())*$/.match("aaa").to_a.should == ["aaa", "", "", nil] + + # Variations with non-greedy loops. + /(|a|\2b|())*?/.match("aaabbb").to_a.should == ["", nil, nil] + /(a||\2b|())*?/.match("aaabbb").to_a.should == ["", nil, nil] + /(a|\2b||())*?/.match("aaabbb").to_a.should == ["", nil, nil] + /(a|\2b|()|)*?/.match("aaabbb").to_a.should == ["", nil, nil] + /(()|a|\3b|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil] + /(a|()|\3b|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil] + /(a|\2b|()|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil] + /(a|\3b|()|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil] + /(a|()|())*?/.match("aaa").to_a.should == ["", nil, nil, nil] + /^(()|a|())*?$/.match("aaa").to_a.should == ["aaa", "a", "", nil] + end +end diff --git a/spec/ruby/language/regexp/encoding_spec.rb b/spec/ruby/language/regexp/encoding_spec.rb index 8e2a294b95..81e845af0c 100644 --- a/spec/ruby/language/regexp/encoding_spec.rb +++ b/spec/ruby/language/regexp/encoding_spec.rb @@ -1,21 +1,21 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative '../fixtures/classes' describe "Regexps with encoding modifiers" do it "supports /e (EUC encoding)" do - match = /./e.match("\303\251".force_encoding(Encoding::EUC_JP)) - match.to_a.should == ["\303\251".force_encoding(Encoding::EUC_JP)] + match = /./e.match("\303\251".dup.force_encoding(Encoding::EUC_JP)) + match.to_a.should == ["\303\251".dup.force_encoding(Encoding::EUC_JP)] end it "supports /e (EUC encoding) with interpolation" do - match = /#{/./}/e.match("\303\251".force_encoding(Encoding::EUC_JP)) - match.to_a.should == ["\303\251".force_encoding(Encoding::EUC_JP)] + match = /#{/./}/e.match("\303\251".dup.force_encoding(Encoding::EUC_JP)) + match.to_a.should == ["\303\251".dup.force_encoding(Encoding::EUC_JP)] end it "supports /e (EUC encoding) with interpolation /o" do - match = /#{/./}/e.match("\303\251".force_encoding(Encoding::EUC_JP)) - match.to_a.should == ["\303\251".force_encoding(Encoding::EUC_JP)] + match = /#{/./}/e.match("\303\251".dup.force_encoding(Encoding::EUC_JP)) + match.to_a.should == ["\303\251".dup.force_encoding(Encoding::EUC_JP)] end it 'uses EUC-JP as /e encoding' do @@ -38,6 +38,14 @@ describe "Regexps with encoding modifiers" do /#{/./}/n.match("\303\251").to_a.should == ["\303"] end + it "warns when using /n with a match string with non-ASCII characters and an encoding other than ASCII-8BIT" do + -> { + 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 /./n.encoding.should == Encoding::US_ASCII end @@ -59,18 +67,18 @@ describe "Regexps with encoding modifiers" do end it "supports /s (Windows_31J encoding)" do - match = /./s.match("\303\251".force_encoding(Encoding::Windows_31J)) - match.to_a.should == ["\303".force_encoding(Encoding::Windows_31J)] + match = /./s.match("\303\251".dup.force_encoding(Encoding::Windows_31J)) + match.to_a.should == ["\303".dup.force_encoding(Encoding::Windows_31J)] end it "supports /s (Windows_31J encoding) with interpolation" do - match = /#{/./}/s.match("\303\251".force_encoding(Encoding::Windows_31J)) - match.to_a.should == ["\303".force_encoding(Encoding::Windows_31J)] + match = /#{/./}/s.match("\303\251".dup.force_encoding(Encoding::Windows_31J)) + match.to_a.should == ["\303".dup.force_encoding(Encoding::Windows_31J)] end it "supports /s (Windows_31J encoding) with interpolation and /o" do - match = /#{/./}/s.match("\303\251".force_encoding(Encoding::Windows_31J)) - match.to_a.should == ["\303".force_encoding(Encoding::Windows_31J)] + match = /#{/./}/s.match("\303\251".dup.force_encoding(Encoding::Windows_31J)) + match.to_a.should == ["\303".dup.force_encoding(Encoding::Windows_31J)] end it 'uses Windows-31J as /s encoding' do @@ -82,15 +90,15 @@ describe "Regexps with encoding modifiers" do end it "supports /u (UTF8 encoding)" do - /./u.match("\303\251".force_encoding('utf-8')).to_a.should == ["\u{e9}"] + /./u.match("\303\251".dup.force_encoding('utf-8')).to_a.should == ["\u{e9}"] end it "supports /u (UTF8 encoding) with interpolation" do - /#{/./}/u.match("\303\251".force_encoding('utf-8')).to_a.should == ["\u{e9}"] + /#{/./}/u.match("\303\251".dup.force_encoding('utf-8')).to_a.should == ["\u{e9}"] end it "supports /u (UTF8 encoding) with interpolation and /o" do - /#{/./}/u.match("\303\251".force_encoding('utf-8')).to_a.should == ["\u{e9}"] + /#{/./}/u.match("\303\251".dup.force_encoding('utf-8')).to_a.should == ["\u{e9}"] end it 'uses UTF-8 as /u encoding' do @@ -106,25 +114,38 @@ describe "Regexps with encoding modifiers" do end it "raises Encoding::CompatibilityError when trying match against different encodings" do - -> { /\A[[:space:]]*\z/.match(" ".encode("UTF-16LE")) }.should raise_error(Encoding::CompatibilityError) + -> { /\A[[:space:]]*\z/.match(" ".encode("UTF-16LE")) }.should.raise(Encoding::CompatibilityError) end it "raises Encoding::CompatibilityError when trying match? against different encodings" do - -> { /\A[[:space:]]*\z/.match?(" ".encode("UTF-16LE")) }.should raise_error(Encoding::CompatibilityError) + -> { /\A[[:space:]]*\z/.match?(" ".encode("UTF-16LE")) }.should.raise(Encoding::CompatibilityError) end it "raises Encoding::CompatibilityError when trying =~ against different encodings" do - -> { /\A[[:space:]]*\z/ =~ " ".encode("UTF-16LE") }.should raise_error(Encoding::CompatibilityError) + -> { /\A[[:space:]]*\z/ =~ " ".encode("UTF-16LE") }.should.raise(Encoding::CompatibilityError) + end + + it "raises Encoding::CompatibilityError when the regexp has a fixed, non-ASCII-compatible encoding" do + -> { Regexp.new("".dup.force_encoding("UTF-16LE"), Regexp::FIXEDENCODING) =~ " ".encode("UTF-8") }.should.raise(Encoding::CompatibilityError) + end + + it "raises Encoding::CompatibilityError when the regexp has a fixed encoding and the match string has non-ASCII characters" do + -> { Regexp.new("".dup.force_encoding("US-ASCII"), Regexp::FIXEDENCODING) =~ "\303\251".dup.force_encoding('UTF-8') }.should.raise(Encoding::CompatibilityError) + end + + it "raises ArgumentError when trying to match a broken String" do + s = "\x80".dup.force_encoding('UTF-8') + -> { s =~ /./ }.should.raise(ArgumentError, "invalid byte sequence in UTF-8") end it "computes the Regexp Encoding for each interpolated Regexp instance" do make_regexp = -> str { /#{str}/ } - r = make_regexp.call("été".force_encoding(Encoding::UTF_8)) + r = make_regexp.call("été".dup.force_encoding(Encoding::UTF_8)) r.should.fixed_encoding? r.encoding.should == Encoding::UTF_8 - r = make_regexp.call("abc".force_encoding(Encoding::UTF_8)) + r = make_regexp.call("abc".dup.force_encoding(Encoding::UTF_8)) r.should_not.fixed_encoding? r.encoding.should == Encoding::US_ASCII end diff --git a/spec/ruby/language/regexp/escapes_spec.rb b/spec/ruby/language/regexp/escapes_spec.rb index 2e5fe5ad2e..4a0e611540 100644 --- a/spec/ruby/language/regexp/escapes_spec.rb +++ b/spec/ruby/language/regexp/escapes_spec.rb @@ -1,9 +1,11 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative '../fixtures/classes' +# TODO: synchronize with spec/core/regexp/new_spec.rb - +# escaping is also tested there describe "Regexps with escape characters" do - it "they're supported" do + it "supports escape sequences" do /\t/.match("\t").to_a.should == ["\t"] # horizontal tab /\v/.match("\v").to_a.should == ["\v"] # vertical tab /\n/.match("\n").to_a.should == ["\n"] # newline @@ -15,9 +17,7 @@ describe "Regexps with escape characters" do # \nnn octal char (encoded byte value) end - it "support quoting meta-characters via escape sequence" do - /\\/.match("\\").to_a.should == ["\\"] - /\//.match("/").to_a.should == ["/"] + it "supports quoting meta-characters via escape sequence" do # parenthesis, etc /\(/.match("(").to_a.should == ["("] /\)/.match(")").to_a.should == [")"] @@ -25,6 +25,8 @@ describe "Regexps with escape characters" do /\]/.match("]").to_a.should == ["]"] /\{/.match("{").to_a.should == ["{"] /\}/.match("}").to_a.should == ["}"] + /\</.match("<").to_a.should == ["<"] + /\>/.match(">").to_a.should == [">"] # alternation separator /\|/.match("|").to_a.should == ["|"] # quantifiers @@ -37,23 +39,93 @@ describe "Regexps with escape characters" do /\$/.match("$").to_a.should == ["$"] end + it "supports quoting meta-characters via escape sequence when used as a terminator" do + # parenthesis, etc + # %r[[, %r((, etc literals - are forbidden + %r(\().match("(").to_a.should == ["("] + %r(\)).match(")").to_a.should == [")"] + %r)\().match("(").to_a.should == ["("] + %r)\)).match(")").to_a.should == [")"] + + %r[\[].match("[").to_a.should == ["["] + %r[\]].match("]").to_a.should == ["]"] + %r]\[].match("[").to_a.should == ["["] + %r]\]].match("]").to_a.should == ["]"] + + %r{\{}.match("{").to_a.should == ["{"] + %r{\}}.match("}").to_a.should == ["}"] + %r}\{}.match("{").to_a.should == ["{"] + %r}\}}.match("}").to_a.should == ["}"] + + %r<\<>.match("<").to_a.should == ["<"] + %r<\>>.match(">").to_a.should == [">"] + %r>\<>.match("<").to_a.should == ["<"] + %r>\>>.match(">").to_a.should == [">"] + + # alternation separator + %r|\||.match("|").to_a.should == ["|"] + # quantifiers + %r?\??.match("?").to_a.should == ["?"] + %r.\...match(".").to_a.should == ["."] + %r*\**.match("*").to_a.should == ["*"] + %r+\++.match("+").to_a.should == ["+"] + # line anchors + %r^\^^.match("^").to_a.should == ["^"] + %r$\$$.match("$").to_a.should == ["$"] + end + + it "supports quoting non-meta-characters via escape sequence when used as a terminator" do + non_meta_character_terminators = [ + '!', '"', '#', '%', '&', "'", ',', '-', ':', ';', '@', '_', '`', '/', '=', '~' + ] + + non_meta_character_terminators.each do |c| + pattern = eval("%r" + c + "\\" + c + c) + pattern.match(c).to_a.should == [c] + end + end + + it "does not change semantics of escaped non-meta-character when used as a terminator" do + all_terminators = [*("!".."/"), *(":".."@"), *("[".."`"), *("{".."~")] + meta_character_terminators = ["$", "^", "*", "+", ".", "?", "|", "}", ")", ">", "]"] + special_cases = ['(', '{', '[', '<', '\\'] + + # it should be equivalent to + # [ '!', '"', '#', '%', '&', "'", ',', '-', ':', ';', '@', '_', '`', '/', '=', '~' ] + non_meta_character_terminators = all_terminators - meta_character_terminators - special_cases + + non_meta_character_terminators.each do |c| + pattern = eval("%r" + c + "\\" + c + c) + pattern.should == /#{c}/ + end + end + + it "does not change semantics of escaped meta-character when used as a terminator" do + meta_character_terminators = ["$", "^", "*", "+", ".", "?", "|", "}", ")", ">", "]"] + + meta_character_terminators.each do |c| + pattern = eval("%r" + c + "\\" + c + c) + pattern.should == eval("/\\#{c}/") + end + end + it "allows any character to be escaped" do /\y/.match("y").to_a.should == ["y"] end - it "support \\x (hex characters)" do + it "supports \\x (hex characters)" do /\xA/.match("\nxyz").to_a.should == ["\n"] /\x0A/.match("\n").to_a.should == ["\n"] - /\xAA/.match("\nA").should be_nil + /\xAA/.match("\nA").should == nil /\x0AA/.match("\nA").to_a.should == ["\nA"] /\xAG/.match("\nG").to_a.should == ["\nG"] # Non-matches - -> { eval('/\xG/') }.should raise_error(SyntaxError) + -> { eval('/\xG/') }.should.raise(SyntaxError) # \x{7HHHHHHH} wide hexadecimal char (character code point value) end - it "support \\c (control characters)" do + it "supports \\c (control characters)" do #/\c \c@\c`/.match("\00\00\00").to_a.should == ["\00\00\00"] /\c#\cc\cC/.match("\03\03\03").to_a.should == ["\03\03\03"] /\c'\cG\cg/.match("\a\a\a").to_a.should == ["\a\a\a"] @@ -64,14 +136,14 @@ describe "Regexps with escape characters" do /\c,\cL\cl/.match("\f\f\f").to_a.should == ["\f\f\f"] /\c-\cM\cm/.match("\r\r\r").to_a.should == ["\r\r\r"] - /\cJ/.match("\r").should be_nil + /\cJ/.match("\r").should == nil # Parsing precedence /\cJ+/.match("\n\n").to_a.should == ["\n\n"] # Quantifiers apply to entire escape sequence /\\cJ/.match("\\cJ").to_a.should == ["\\cJ"] - -> { eval('/[abc\x]/') }.should raise_error(SyntaxError) # \x is treated as a escape sequence even inside a character class + -> { eval('/[abc\x]/') }.should.raise(SyntaxError) # \x is treated as a escape sequence even inside a character class # Syntax error - -> { eval('/\c/') }.should raise_error(SyntaxError) + -> { eval('/\c/') }.should.raise(SyntaxError) # \cx control char (character code point value) # \C-x control char (character code point value) diff --git a/spec/ruby/language/regexp/grouping_spec.rb b/spec/ruby/language/regexp/grouping_spec.rb index 2fecf2d2cb..80ad7460da 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(SyntaxError) end it "supports (?: ) (non-capturing group)" do @@ -22,7 +22,42 @@ describe "Regexps with grouping" do end it "group names cannot start with digits or minus" do - -> { Regexp.new("(?<1a>a)") }.should raise_error(RegexpError) - -> { Regexp.new("(?<-a>a)") }.should raise_error(RegexpError) + -> { Regexp.new("(?<1a>a)") }.should.raise(RegexpError) + -> { Regexp.new("(?<-a>a)") }.should.raise(RegexpError) + end + + it "ignore capture groups in line comments" do + /^ + (a) # there is a capture group on this line + b # there is no capture group on this line (not even here) + $/x.match("ab").to_a.should == [ "ab", "a" ] + end + + it "does not consider # inside a character class as a comment" do + # From https://github.com/rubocop/rubocop/blob/39fcf1c568/lib/rubocop/cop/utils/format_string.rb#L18 + regexp = / + % (?<type>%) # line comment + | % (?<flags>(?-mix:[ #0+-]|(?-mix:(\d+)\$))*) (?#group comment) + (?: + (?: (?-mix:(?<width>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:\.(?<precision>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:<(?<name>\w+)>)? + | (?-mix:(?<width>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:<(?<name>\w+)>) (?-mix:\.(?<precision>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? + | (?-mix:<(?<name>\w+)>) (?<more_flags>(?-mix:[ #0+-]|(?-mix:(\d+)\$))*) (?-mix:(?<width>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:\.(?<precision>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? + ) (?-mix:(?<type>[bBdiouxXeEfgGaAcps])) + | (?-mix:(?<width>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:\.(?<precision>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:\{(?<name>\w+)\}) + ) + /x + regexp.named_captures.should == { + "type" => [1, 13], + "flags" => [2], + "width" => [3, 6, 11, 14], + "precision" => [4, 8, 12, 15], + "name" => [5, 7, 9, 16], + "more_flags" => [10] + } + match = regexp.match("%6.3f") + match[:width].should == '6' + match[:precision].should == '3' + match[:type].should == 'f' + match.to_a.should == [ "%6.3f", nil, "", "6", "3"] + [nil] * 8 + ["f"] + [nil] * 3 end end diff --git a/spec/ruby/language/regexp/interpolation_spec.rb b/spec/ruby/language/regexp/interpolation_spec.rb index ed0b724763..f771d0a395 100644 --- a/spec/ruby/language/regexp/interpolation_spec.rb +++ b/spec/ruby/language/regexp/interpolation_spec.rb @@ -36,14 +36,14 @@ describe "Regexps with interpolation" do it "gives precedence to escape sequences over substitution" do str = "J" - /\c#{str}/.to_s.should == '(?-mix:\c#' + '{str})' + /\c#{str}/.to_s.should.include?('{str}') end it "throws RegexpError for malformed interpolation" do s = "" - -> { /(#{s}/ }.should raise_error(RegexpError) + -> { /(#{s}/ }.should.raise(RegexpError) s = "(" - -> { /#{s}/ }.should raise_error(RegexpError) + -> { /#{s}/ }.should.raise(RegexpError) end it "allows interpolation in extended mode" do diff --git a/spec/ruby/language/regexp/modifiers_spec.rb b/spec/ruby/language/regexp/modifiers_spec.rb index 2f5522bc8a..c96fbfa983 100644 --- a/spec/ruby/language/regexp/modifiers_spec.rb +++ b/spec/ruby/language/regexp/modifiers_spec.rb @@ -8,7 +8,7 @@ describe "Regexps with modifiers" do it "supports /m (multiline)" do /foo.bar/m.match("foo\nbar").to_a.should == ["foo\nbar"] - /foo.bar/.match("foo\nbar").should be_nil + /foo.bar/.match("foo\nbar").should == nil end it "supports /x (extended syntax)" do @@ -36,7 +36,7 @@ describe "Regexps with modifiers" do /foo/imox.match("foo").to_a.should == ["foo"] /foo/imoximox.match("foo").to_a.should == ["foo"] - -> { eval('/foo/a') }.should raise_error(SyntaxError) + -> { eval('/foo/a') }.should.raise(SyntaxError) end it "supports (?~) (absent operator)" do @@ -46,57 +46,57 @@ describe "Regexps with modifiers" do it "supports (?imx-imx) (inline modifiers)" do /(?i)foo/.match("FOO").to_a.should == ["FOO"] - /foo(?i)/.match("FOO").should be_nil + /foo(?i)/.match("FOO").should == nil # Interaction with /i - /(?-i)foo/i.match("FOO").should be_nil + /(?-i)foo/i.match("FOO").should == nil /foo(?-i)/i.match("FOO").to_a.should == ["FOO"] # Multiple uses /foo (?i)bar (?-i)baz/.match("foo BAR baz").to_a.should == ["foo BAR baz"] - /foo (?i)bar (?-i)baz/.match("foo BAR BAZ").should be_nil + /foo (?i)bar (?-i)baz/.match("foo BAR BAZ").should == nil /(?m)./.match("\n").to_a.should == ["\n"] - /.(?m)/.match("\n").should be_nil + /.(?m)/.match("\n").should == nil # Interaction with /m - /(?-m)./m.match("\n").should be_nil + /(?-m)./m.match("\n").should == nil /.(?-m)/m.match("\n").to_a.should == ["\n"] # Multiple uses /. (?m). (?-m)./.match(". \n .").to_a.should == [". \n ."] - /. (?m). (?-m)./.match(". \n \n").should be_nil + /. (?m). (?-m)./.match(". \n \n").should == nil /(?x) foo /.match("foo").to_a.should == ["foo"] - / foo (?x)/.match("foo").should be_nil + / foo (?x)/.match("foo").should == nil # Interaction with /x - /(?-x) foo /x.match("foo").should be_nil + /(?-x) foo /x.match("foo").should == nil / foo (?-x)/x.match("foo").to_a.should == ["foo"] # Multiple uses /( foo )(?x)( bar )(?-x)( baz )/.match(" foo bar baz ").to_a.should == [" foo bar baz ", " foo ", "bar", " baz "] - /( foo )(?x)( bar )(?-x)( baz )/.match(" foo barbaz").should be_nil + /( foo )(?x)( bar )(?-x)( baz )/.match(" foo barbaz").should == nil # Parsing - /(?i-i)foo/.match("FOO").should be_nil + /(?i-i)foo/.match("FOO").should == nil /(?ii)foo/.match("FOO").to_a.should == ["FOO"] /(?-)foo/.match("foo").to_a.should == ["foo"] - -> { eval('/(?o)/') }.should raise_error(SyntaxError) + -> { eval('/(?o)/') }.should.raise(SyntaxError) end it "supports (?imx-imx:expr) (scoped inline modifiers)" do /foo (?i:bar) baz/.match("foo BAR baz").to_a.should == ["foo BAR baz"] - /foo (?i:bar) baz/.match("foo BAR BAZ").should be_nil - /foo (?-i:bar) baz/i.match("foo BAR BAZ").should be_nil + /foo (?i:bar) baz/.match("foo BAR BAZ").should == nil + /foo (?-i:bar) baz/i.match("foo BAR BAZ").should == nil /. (?m:.) ./.match(". \n .").to_a.should == [". \n ."] - /. (?m:.) ./.match(". \n \n").should be_nil - /. (?-m:.) ./m.match("\n \n \n").should be_nil + /. (?m:.) ./.match(". \n \n").should == nil + /. (?-m:.) ./m.match("\n \n \n").should == nil /( foo )(?x: bar )( baz )/.match(" foo bar baz ").to_a.should == [" foo bar baz ", " foo ", " baz "] - /( foo )(?x: bar )( baz )/.match(" foo barbaz").should be_nil + /( foo )(?x: bar )( baz )/.match(" foo barbaz").should == nil /( foo )(?-x: bar )( baz )/x.match("foo bar baz").to_a.should == ["foo bar baz", "foo", "baz"] # Parsing - /(?i-i:foo)/.match("FOO").should be_nil + /(?i-i:foo)/.match("FOO").should == nil /(?ii:foo)/.match("FOO").to_a.should == ["FOO"] /(?-:)foo/.match("foo").to_a.should == ["foo"] - -> { eval('/(?o:)/') }.should raise_error(SyntaxError) + -> { eval('/(?o:)/') }.should.raise(SyntaxError) end it "supports . with /m" do diff --git a/spec/ruby/language/regexp/repetition_spec.rb b/spec/ruby/language/regexp/repetition_spec.rb index 295b3bf553..f24323de5c 100644 --- a/spec/ruby/language/regexp/repetition_spec.rb +++ b/spec/ruby/language/regexp/repetition_spec.rb @@ -15,7 +15,7 @@ describe "Regexps with repetition" do it "supports + (1 or more of previous subexpression)" do /a+/.match("aaa").to_a.should == ["aaa"] - /a+/.match("bbb").should be_nil + /a+/.match("bbb").should == nil /<.+>/.match("<a>foo</a>").to_a.should == ["<a>foo</a>"] # it is greedy end @@ -87,9 +87,7 @@ describe "Regexps with repetition" do /a+?*/.match("a")[0].should == "a" /(a+?)*/.match("a")[0].should == "a" - ruby_bug '#17341', ''...'3.0' do - /a+?*/.match("aa")[0].should == "aa" - end + /a+?*/.match("aa")[0].should == "aa" /(a+?)*/.match("aa")[0].should == "aa" # a+?+ should not be reduced, it should be equivalent to (a+?)+ @@ -100,9 +98,7 @@ describe "Regexps with repetition" do /a+?+/.match("a")[0].should == "a" /(a+?)+/.match("a")[0].should == "a" - ruby_bug '#17341', ''...'3.0' do - /a+?+/.match("aa")[0].should == "aa" - end + /a+?+/.match("aa")[0].should == "aa" /(a+?)+/.match("aa")[0].should == "aa" # both a**? and a+*? should be equivalent to (a+)?? @@ -128,4 +124,15 @@ describe "Regexps with repetition" do RUBY end end + + it "treats ? after {n} quantifier as another quantifier, not as non-greedy marker" do + /a{2}?/.match("").to_a.should == [""] + end + + it "matches zero-width capture groups in optional iterations of loops" do + /()?/.match("").to_a.should == ["", ""] + /(a*)?/.match("").to_a.should == ["", ""] + /(a*)*/.match("").to_a.should == ["", ""] + /(?:a|()){500,1000}/.match("a" * 500).to_a.should == ["a" * 500, ""] + end end diff --git a/spec/ruby/language/regexp_spec.rb b/spec/ruby/language/regexp_spec.rb index def9bba5f7..1452b01935 100644 --- a/spec/ruby/language/regexp_spec.rb +++ b/spec/ruby/language/regexp_spec.rb @@ -15,13 +15,11 @@ describe "Literal Regexps" do end it "yields a Regexp" do - /Hello/.should be_kind_of(Regexp) + /Hello/.should.is_a?(Regexp) end - ruby_version_is "3.0" do - it "is frozen" do - /Hello/.should.frozen? - end + it "is frozen" do + /Hello/.should.frozen? end it "caches the Regexp object" do @@ -29,11 +27,11 @@ describe "Literal Regexps" do 2.times do |i| rs << /foo/ end - rs[0].should equal(rs[1]) + rs[0].should.equal?(rs[1]) end it "throws SyntaxError for malformed literals" do - -> { eval('/(/') }.should raise_error(SyntaxError) + -> { eval('/(/') }.should.raise(SyntaxError) end ############################################################################# @@ -60,22 +58,22 @@ describe "Literal Regexps" do it "disallows first part of paired delimiters to be used as non-paired delimiters" do LanguageSpecs.paired_delimiters.each do |p0, p1| - -> { eval("%r#{p0} foo #{p0}") }.should raise_error(SyntaxError) + -> { eval("%r#{p0} foo #{p0}") }.should.raise(SyntaxError) end end - it "supports non-paired delimiters delimiters with %r" do + it "supports non-paired delimiters with %r" do LanguageSpecs.non_paired_delimiters.each do |c| eval("%r#{c} foo #{c}").should == / foo / end end it "disallows alphabets as non-paired delimiter with %r" do - -> { eval('%ra foo a') }.should raise_error(SyntaxError) + -> { eval('%ra foo a') }.should.raise(SyntaxError) end it "disallows spaces after %r and delimiter" do - -> { eval('%r !foo!') }.should raise_error(SyntaxError) + -> { eval('%r !foo!') }.should.raise(SyntaxError) end it "allows unescaped / to be used with %r" do @@ -91,19 +89,18 @@ describe "Literal Regexps" do # Basic matching /./.match("foo").to_a.should == ["f"] # Basic non-matching - /./.match("").should be_nil - /./.match("\n").should be_nil + /./.match("").should == nil + /./.match("\n").should == nil /./.match("\0").to_a.should == ["\0"] end - it "supports | (alternations)" do /a|b/.match("a").to_a.should == ["a"] end it "supports (?> ) (embedded subexpression)" do /(?>foo)(?>bar)/.match("foobar").to_a.should == ["foobar"] - /(?>foo*)obar/.match("foooooooobar").should be_nil # it is possessive + /(?>foo*)obar/.match("foooooooobar").should == nil # it is possessive end it "supports (?# )" do @@ -115,10 +112,11 @@ describe "Literal Regexps" do /foo.(?<=\d)/.match("fooA foo1").to_a.should == ["foo1"] end - # https://bugs.ruby-lang.org/issues/13671 - it "raises a RegexpError for lookbehind with specific characters" do - r = Regexp.new("(?<!dss)", Regexp::IGNORECASE) - -> { r =~ "✨" }.should raise_error(RegexpError) + 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 =~ "✨" + end end it "supports (?<! ) (negative lookbehind)" do @@ -137,9 +135,9 @@ describe "Literal Regexps" do it "supports possessive quantifiers" do /fooA++bar/.match("fooAAAbar").to_a.should == ["fooAAAbar"] - /fooA++Abar/.match("fooAAAbar").should be_nil - /fooA?+Abar/.match("fooAAAbar").should be_nil - /fooA*+Abar/.match("fooAAAbar").should be_nil + /fooA++Abar/.match("fooAAAbar").should == nil + /fooA?+Abar/.match("fooAAAbar").should == nil + /fooA*+Abar/.match("fooAAAbar").should == nil end it "supports conditional regular expressions with positional capture groups" do @@ -160,26 +158,6 @@ describe "Literal Regexps" do pattern.should_not =~ 'T' end - escapable_terminators = ['!', '"', '#', '%', '&', "'", ',', '-', ':', ';', '@', '_', '`'] - - it "supports escaping characters when used as a terminator" do - escapable_terminators.each do |c| - ref = "(?-mix:#{c})" - pattern = eval("%r" + c + "\\" + c + c) - pattern.to_s.should == ref - end - end - - it "treats an escaped non-escapable character normally when used as a terminator" do - all_terminators = [*("!".."/"), *(":".."@"), *("[".."`"), *("{".."~")] - special_cases = ['(', '{', '[', '<', '\\', '=', '~'] - (all_terminators - special_cases - escapable_terminators).each do |c| - ref = "(?-mix:\\#{c})" - pattern = eval("%r" + c + "\\" + c + c) - pattern.to_s.should == ref - end - end - it "support handling unicode 9.0 characters with POSIX bracket expressions" do char_lowercase = "\u{104D8}" # OSAGE SMALL LETTER A /[[:lower:]]/.match(char_lowercase).to_s.should == char_lowercase diff --git a/spec/ruby/language/rescue_spec.rb b/spec/ruby/language/rescue_spec.rb index 54cbae1440..cf16d8f6f8 100644 --- a/spec/ruby/language/rescue_spec.rb +++ b/spec/ruby/language/rescue_spec.rb @@ -52,6 +52,16 @@ describe "The rescue keyword" do RescueSpecs::SafeNavigationSetterCaptor.should_capture_exception end + it 'using a safely navigated setter method on a nil target' do + target = nil + begin + raise SpecificExampleException, "Raising this to be handled below" + rescue SpecificExampleException => target&.captured_error + :caught + end.should == :caught + target.should == nil + end + it 'using a setter method' do RescueSpecs::SetterCaptor.should_capture_exception end @@ -61,6 +71,82 @@ describe "The rescue keyword" do end end + describe 'capturing in a local variable (that defines it)' do + it 'captures successfully in a method' do + ScratchPad.record [] + + def a + raise "message" + rescue => e + ScratchPad << e.message + end + + a + ScratchPad.recorded.should == ["message"] + end + + it 'captures successfully in a block' do + ScratchPad.record [] + + p = proc do + raise "message" + rescue => e + ScratchPad << e.message + end + + p.call + ScratchPad.recorded.should == ["message"] + end + + it 'captures successfully in a class' do + ScratchPad.record [] + + class RescueSpecs::C + raise "message" + rescue => e + ScratchPad << e.message + end + + ScratchPad.recorded.should == ["message"] + end + + it 'captures successfully in a module' do + ScratchPad.record [] + + module RescueSpecs::M + raise "message" + rescue => e + ScratchPad << e.message + end + + ScratchPad.recorded.should == ["message"] + end + + it 'captures sucpcessfully in a singleton class' do + ScratchPad.record [] + + class << Object.new + raise "message" + rescue => e + ScratchPad << e.message + end + + ScratchPad.recorded.should == ["message"] + end + + it 'captures successfully at the top-level' do + ScratchPad.record [] + loaded_features = $".dup + begin + require_relative 'fixtures/rescue/top_level' + + ScratchPad.recorded.should == ["message"] + ensure + $".replace loaded_features + end + end + end + it "returns value from `rescue` if an exception was raised" do begin raise @@ -100,7 +186,7 @@ describe "The rescue keyword" do rescue *exception_list caught_it = true end - caught_it.should be_true + caught_it.should == true caught = [] [->{raise ArbitraryException}, ->{raise SpecificExampleException}].each do |block| begin @@ -111,10 +197,22 @@ describe "The rescue keyword" do end caught.size.should == 2 exception_list.each do |exception_class| - caught.map{|e| e.class}.should include(exception_class) + caught.map{|e| e.class}.should.include?(exception_class) end end + it "converts the splatted list of exceptions using #to_a" do + exceptions = mock("to_a") + exceptions.should_receive(:to_a).and_return(exception_list) + caught_it = false + begin + raise SpecificExampleException, "not important" + rescue *exceptions + caught_it = true + end + caught_it.should == true + end + it "can combine a splatted list of exceptions with a literal list of exceptions" do caught_it = false begin @@ -122,7 +220,7 @@ describe "The rescue keyword" do rescue ArbitraryException, *exception_list caught_it = true end - caught_it.should be_true + caught_it.should == true caught = [] [->{raise ArbitraryException}, ->{raise SpecificExampleException}].each do |block| begin @@ -133,7 +231,7 @@ describe "The rescue keyword" do end caught.size.should == 2 exception_list.each do |exception_class| - caught.map{|e| e.class}.should include(exception_class) + caught.map{|e| e.class}.should.include?(exception_class) end end @@ -143,7 +241,7 @@ describe "The rescue keyword" do raise OtherCustomException, "not rescued!" rescue *exception_list end - end.should raise_error(OtherCustomException) + end.should.raise(OtherCustomException) end it "can rescue different types of exceptions in different ways" do @@ -179,7 +277,7 @@ describe "The rescue keyword" do rescue ArgumentError end rescue StandardError => e - e.backtrace.first.should include ":in `raise_standard_error'" + e.backtrace.first.should =~ /:in [`'](?:RescueSpecs\.)?raise_standard_error'/ else fail("exception wasn't handled by the correct rescue block") end @@ -238,34 +336,16 @@ describe "The rescue keyword" do ScratchPad.recorded.should == [:one, :else_ran, :ensure_ran, :outside_begin] end - ruby_version_is ''...'2.6' do - it "will execute an else block even without rescue and ensure" do - -> { - eval <<-ruby - begin - ScratchPad << :begin - else - ScratchPad << :else - end - ruby - }.should complain(/else without rescue is useless/) - - ScratchPad.recorded.should == [:begin, :else] - end - end - - ruby_version_is '2.6' do - it "raises SyntaxError when else is used without rescue and ensure" do - -> { - eval <<-ruby - begin - ScratchPad << :begin - else - ScratchPad << :else - end - ruby - }.should raise_error(SyntaxError, /else without rescue is useless/) - end + it "raises SyntaxError when else is used without rescue and ensure" do + -> { + eval <<-ruby + begin + ScratchPad << :begin + else + ScratchPad << :else + end + ruby + }.should.raise(SyntaxError, /else without rescue is useless/) end it "will not execute an else block if an exception was raised" do @@ -333,7 +413,7 @@ describe "The rescue keyword" do ScratchPad << :two raise SpecificExampleException, "an error from else" end - end.should raise_error(SpecificExampleException) + end.should.raise(SpecificExampleException) ScratchPad.recorded.should == [:one, :two] end @@ -365,7 +445,7 @@ describe "The rescue keyword" do rescue ScratchPad << :caught end - }.should raise_error(exception.class) + }.should.raise(exception.class) end ScratchPad.recorded.should == [] end @@ -396,7 +476,7 @@ describe "The rescue keyword" do raise "error" rescue rescuer end - }.should raise_error(TypeError) { |e| + }.should.raise(TypeError) { |e| e.message.should =~ /class or module required for rescue clause/ } end @@ -408,7 +488,7 @@ describe "The rescue keyword" do raise "error" rescue *rescuer end - }.should raise_error(TypeError) { |e| + }.should.raise(TypeError) { |e| e.message.should =~ /class or module required for rescue clause/ } end @@ -428,9 +508,9 @@ describe "The rescue keyword" do raise "from block" rescue (raise "from rescue expression") end - }.should raise_error(RuntimeError, "from rescue expression") do |e| + }.should.raise(RuntimeError, "from rescue expression") { |e| e.cause.message.should == "from block" - end + } end it "should splat the handling Error classes" do @@ -462,7 +542,7 @@ describe "The rescue keyword" do :caught } ruby - }.should raise_error(SyntaxError) + }.should.raise(SyntaxError) end it "allows rescue in 'do end' block" do @@ -483,10 +563,25 @@ describe "The rescue keyword" do end it "requires the 'rescue' in method arguments to be wrapped in parens" do - -> { eval '1.+(1 rescue 1)' }.should raise_error(SyntaxError) + -> { eval '1.+(1 rescue 1)' }.should.raise(SyntaxError) eval('1.+((1 rescue 1))').should == 2 end + ruby_version_is "3.4" do + it "does not introduce extra backtrace entries" do + def foo + begin + raise "oops" + rescue + return caller(0, 2) + end + end + line = __LINE__ + foo[0].should =~ /#{__FILE__}:#{line-3}:in 'foo'/ + foo[1].should =~ /#{__FILE__}:#{line+2}:in 'block/ + end + end + describe "inline form" do it "can be inlined" do a = 1/0 rescue 1 @@ -498,7 +593,7 @@ describe "The rescue keyword" do eval <<-ruby a = 1 rescue RuntimeError 2 ruby - }.should raise_error(SyntaxError) + }.should.raise(SyntaxError) end it "rescues only StandardError and its subclasses" do @@ -507,17 +602,15 @@ describe "The rescue keyword" do -> { a = raise(Exception) rescue 1 - }.should raise_error(Exception) + }.should.raise(Exception) end - ruby_version_is "2.7" do - it "rescues with multiple assignment" do + it "rescues with multiple assignment" do - a, b = raise rescue [1, 2] + a, b = raise rescue [1, 2] - a.should == 1 - b.should == 2 - end + a.should == 1 + b.should == 2 end end end diff --git a/spec/ruby/language/reserved_keywords.rb b/spec/ruby/language/reserved_keywords.rb new file mode 100644 index 0000000000..bd1a55feec --- /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(SyntaxError) + #{name} = :local_variable + RUBY + end + + if name == "defined?" + it "can't be used as an instance variable name" do + -> { eval(<<~RUBY) }.should.raise(SyntaxError) + @#{name} = :instance_variable + RUBY + end + + it "can't be used as a class variable name" do + -> { eval(<<~RUBY) }.should.raise(SyntaxError) + class C + @@#{name} = :class_variable + end + RUBY + end + + it "can't be used as a global variable name" do + -> { eval(<<~RUBY) }.should.raise(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(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(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/retry_spec.rb b/spec/ruby/language/retry_spec.rb index ee5377946f..39e58b7b5d 100644 --- a/spec/ruby/language/retry_spec.rb +++ b/spec/ruby/language/retry_spec.rb @@ -31,8 +31,11 @@ describe "The retry statement" do results.should == [1, 2, 3, 1, 2, 4, 5, 6, 4, 5] end - it "raises a SyntaxError when used outside of a begin statement" do - -> { eval 'retry' }.should raise_error(SyntaxError) + it "raises a SyntaxError when used outside of a rescue statement" do + -> { eval 'retry' }.should.raise(SyntaxError) + -> { eval 'begin; retry; end' }.should.raise(SyntaxError) + -> { eval 'def m; retry; end' }.should.raise(SyntaxError) + -> { eval 'module RetrySpecs; retry; end' }.should.raise(SyntaxError) end end diff --git a/spec/ruby/language/return_spec.rb b/spec/ruby/language/return_spec.rb index d8506834c8..36a3cba4d7 100644 --- a/spec/ruby/language/return_spec.rb +++ b/spec/ruby/language/return_spec.rb @@ -19,7 +19,7 @@ describe "The return keyword" do it "returns nil by default" do def r; return; end - r().should be_nil + r().should == nil end describe "in a Thread" do @@ -31,7 +31,7 @@ describe "The return keyword" do e end } - t.value.should be_an_instance_of(LocalJumpError) + t.value.should.instance_of?(LocalJumpError) end end @@ -176,11 +176,11 @@ describe "The return keyword" do end it "causes lambda to return nil if invoked without any arguments" do - -> { return; 456 }.call.should be_nil + -> { return; 456 }.call.should == nil end it "causes lambda to return nil if invoked with an empty expression" do - -> { return (); 456 }.call.should be_nil + -> { return (); 456 }.call.should == nil end it "causes lambda to return the value passed to return" do @@ -229,7 +229,7 @@ describe "The return keyword" do def f 1.times { 1.times {return true}; false}; false end - f.should be_true + f.should == true end end @@ -417,23 +417,36 @@ describe "The return keyword" do end END_OF_CODE - -> { load @filename }.should raise_error(SyntaxError) + -> { load @filename }.should.raise(SyntaxError) end end describe "within a block within a class" do - ruby_version_is "2.7" do - it "is not allowed" do - File.write(@filename, <<-END_OF_CODE) - class ReturnSpecs::A - ScratchPad << "before return" - 1.times { return } - ScratchPad << "after return" - end - END_OF_CODE - - -> { load @filename }.should raise_error(LocalJumpError) - end + it "is not allowed" do + File.write(@filename, <<-END_OF_CODE) + class ReturnSpecs::A + ScratchPad << "before return" + 1.times { return } + ScratchPad << "after return" + end + END_OF_CODE + + -> { load @filename }.should.raise(LocalJumpError) + end + end + + describe "within BEGIN" do + it "is allowed" do + File.write(@filename, <<-END_OF_CODE) + BEGIN { + ScratchPad << "before call" + return + ScratchPad << "after call" + } + END_OF_CODE + + load @filename + ScratchPad.recorded.should == ["before call"] end end @@ -464,25 +477,13 @@ describe "The return keyword" do end describe "return with argument" do - ruby_version_is ""..."2.7" do - it "does not affect exit status" do - ruby_exe(<<-END_OF_CODE).should == "" - return 10 - END_OF_CODE - - $?.exitstatus.should == 0 - end - end - - ruby_version_is "2.7" do - it "warns but does not affect exit status" do - err = ruby_exe(<<-END_OF_CODE, args: "2>&1") - return 10 - END_OF_CODE - $?.exitstatus.should == 0 + it "warns but does not affect exit status" do + err = ruby_exe(<<-END_OF_CODE, args: "2>&1") + return 10 + END_OF_CODE + $?.exitstatus.should == 0 - err.should =~ /warning: argument of top-level return is ignored/ - end + err.should =~ /warning: argument of top-level return is ignored/ end end end diff --git a/spec/ruby/language/safe_navigator_spec.rb b/spec/ruby/language/safe_navigator_spec.rb index c3aecff2dd..e8b429631d 100644 --- a/spec/ruby/language/safe_navigator_spec.rb +++ b/spec/ruby/language/safe_navigator_spec.rb @@ -2,48 +2,48 @@ require_relative '../spec_helper' describe "Safe navigator" do it "requires a method name to be provided" do - -> { eval("obj&. {}") }.should raise_error(SyntaxError) + -> { eval("obj&. {}") }.should.raise(SyntaxError) end context "when context is nil" do it "always returns nil" do - eval("nil&.unknown").should == nil - eval("[][10]&.unknown").should == nil + nil&.unknown.should == nil + [][10]&.unknown.should == nil end it "can be chained" do - eval("nil&.one&.two&.three").should == nil + nil&.one&.two&.three.should == nil end it "doesn't evaluate arguments" do obj = Object.new obj.should_not_receive(:m) - eval("nil&.unknown(obj.m) { obj.m }") + nil&.unknown(obj.m) { obj.m } end end context "when context is false" do it "calls the method" do - eval("false&.to_s").should == "false" + false&.to_s.should == "false" - -> { eval("false&.unknown") }.should raise_error(NoMethodError) + -> { false&.unknown }.should.raise(NoMethodError) end end context "when context is truthy" do it "calls the method" do - eval("1&.to_s").should == "1" + 1&.to_s.should == "1" - -> { eval("1&.unknown") }.should raise_error(NoMethodError) + -> { 1&.unknown }.should.raise(NoMethodError) end end it "takes a list of arguments" do - eval("[1,2,3]&.first(2)").should == [1,2] + [1,2,3]&.first(2).should == [1,2] end it "takes a block" do - eval("[1,2]&.map { |i| i * 2 }").should == [2, 4] + [1,2]&.map { |i| i * 2 }.should == [2, 4] end it "allows assignment methods" do @@ -56,29 +56,77 @@ describe "Safe navigator" do end obj = klass.new - eval("obj&.foo = 3").should == 3 + (obj&.foo = 3).should == 3 obj.foo.should == 3 obj = nil - eval("obj&.foo = 3").should == nil + (obj&.foo = 3).should == nil end it "allows assignment operators" do klass = Class.new do - attr_accessor :m + attr_reader :m def initialize @m = 0 end + + def m=(v) + @m = v + 42 + end end obj = klass.new - eval("obj&.m += 3") + obj&.m += 3 obj.m.should == 3 obj = nil - eval("obj&.m += 3").should == nil + (obj&.m += 3).should == nil + end + + it "allows ||= operator" do + klass = Class.new do + attr_reader :m + + def initialize + @m = false + end + + def m=(v) + @m = v + 42 + end + end + + obj = klass.new + + (obj&.m ||= true).should == true + obj.m.should == true + + obj = nil + (obj&.m ||= true).should == nil + obj.should == nil + end + + it "allows &&= operator" do + klass = Class.new do + attr_accessor :m + + def initialize + @m = true + end + end + + obj = klass.new + + (obj&.m &&= false).should == false + obj.m.should == false + + obj = nil + (obj&.m &&= false).should == nil + obj.should == nil end it "does not call the operator method lazily with an assignment operator" do @@ -91,8 +139,8 @@ describe "Safe navigator" do obj = klass.new -> { - eval("obj&.foo += 3") - }.should raise_error(NoMethodError) { |e| + obj&.foo += 3 + }.should.raise(NoMethodError) { |e| e.name.should == :+ } end diff --git a/spec/ruby/language/safe_spec.rb b/spec/ruby/language/safe_spec.rb index f3a7efc953..03ae96148e 100644 --- a/spec/ruby/language/safe_spec.rb +++ b/spec/ruby/language/safe_spec.rb @@ -1,152 +1,11 @@ require_relative '../spec_helper' describe "The $SAFE variable" do - ruby_version_is ""..."2.7" do - ruby_version_is "2.6" do - after :each do - $SAFE = 0 - end - end - - it "is 0 by default" do - $SAFE.should == 0 - proc { - $SAFE.should == 0 - }.call - end - - it "can be set to 0" do - proc { - $SAFE = 0 - $SAFE.should == 0 - }.call - end - - it "can be set to 1" do - proc { - $SAFE = 1 - $SAFE.should == 1 - }.call - end - - [2, 3, 4].each do |n| - it "cannot be set to #{n}" do - -> { - proc { - $SAFE = n - }.call - }.should raise_error(ArgumentError, /\$SAFE=2 to 4 are obsolete/) - end - end - - ruby_version_is ""..."2.6" do - it "cannot be set to values below 0" do - -> { - proc { - $SAFE = -100 - }.call - }.should raise_error(SecurityError, /tried to downgrade safe level from 0 to -100/) - end - end - - ruby_version_is "2.6" do - it "raises ArgumentError when set to values below 0" do - -> { - proc { - $SAFE = -100 - }.call - }.should raise_error(ArgumentError, "$SAFE should be >= 0") - end - end - - it "cannot be set to values above 4" do - -> { - proc { - $SAFE = 100 - }.call - }.should raise_error(ArgumentError, /\$SAFE=2 to 4 are obsolete/) - end - - ruby_version_is ""..."2.6" do - it "cannot be manually lowered" do - proc { - $SAFE = 1 - -> { - $SAFE = 0 - }.should raise_error(SecurityError, /tried to downgrade safe level from 1 to 0/) - }.call - end - - it "is automatically lowered when leaving a proc" do - $SAFE.should == 0 - proc { - $SAFE = 1 - }.call - $SAFE.should == 0 - end - - it "is automatically lowered when leaving a lambda" do - $SAFE.should == 0 - -> { - $SAFE = 1 - }.call - $SAFE.should == 0 - end - end - - ruby_version_is "2.6" do - it "can be manually lowered" do - $SAFE = 1 - $SAFE = 0 - $SAFE.should == 0 - end - - it "is not Proc local" do - $SAFE.should == 0 - proc { - $SAFE = 1 - }.call - $SAFE.should == 1 - end - - it "is not lambda local" do - $SAFE.should == 0 - -> { - $SAFE = 1 - }.call - $SAFE.should == 1 - end - - it "is global like regular global variables" do - Thread.new { $SAFE }.value.should == 0 - $SAFE = 1 - Thread.new { $SAFE }.value.should == 1 - end - end - - it "can be read when default from Thread#safe_level" do - Thread.current.safe_level.should == 0 - end - - it "can be read when modified from Thread#safe_level" do - proc { - $SAFE = 1 - Thread.current.safe_level.should == 1 - }.call - end - end - - ruby_version_is "2.7"..."3.0" do - it "warn when access" do - -> { - $SAFE - }.should complain(/\$SAFE will become a normal global variable in Ruby 3.0/) - end - - it "warn when set" do - -> { - $SAFE = 1 - }.should complain(/\$SAFE will become a normal global variable in Ruby 3.0/) - end + it "$SAFE is a regular global variable" do + $SAFE.should == nil + $SAFE = 42 + $SAFE.should == 42 + ensure + $SAFE = nil end end diff --git a/spec/ruby/language/send_spec.rb b/spec/ruby/language/send_spec.rb index e57e2c65dc..f56a77d529 100644 --- a/spec/ruby/language/send_spec.rb +++ b/spec/ruby/language/send_spec.rb @@ -22,7 +22,7 @@ describe "Invoking a method" do it "raises ArgumentError if the method has a positive arity" do -> { specs.fooM1 - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end end @@ -38,12 +38,12 @@ describe "Invoking a method" do it "raises ArgumentError if the methods arity doesn't match" do -> { specs.fooM1(1,2) - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end end describe "with optional arguments" do - it "uses the optional argument if none is is passed" do + it "uses the optional argument if none is passed" do specs.fooM0O1.should == [1] end @@ -54,7 +54,7 @@ describe "Invoking a method" do it "raises ArgumentError if extra arguments are passed" do -> { specs.fooM0O1(2,3) - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end end @@ -66,13 +66,13 @@ describe "Invoking a method" do it "raises an ArgumentError if there are no values for the mandatory args" do -> { specs.fooM1O1 - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end it "raises an ArgumentError if too many values are passed" do -> { specs.fooM1O1(1,2,3) - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end end @@ -94,7 +94,7 @@ describe "Invoking a method" do it "with a block converts the block to a Proc" do prc = specs.makeproc { "hello" } - prc.should be_kind_of(Proc) + prc.should.is_a?(Proc) prc.call.should == "hello" end @@ -106,10 +106,28 @@ 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_consistent_error(TypeError, "can't convert LangSendSpecs::RawToProc into 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(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 }" - }.should raise_error(SyntaxError) + }.should.raise(SyntaxError) end it "with same names as existing variables is ok" do @@ -194,21 +212,42 @@ describe "Invoking a method" do o.args.should == [1,2] end - it "raises NameError if invoked as a vcall" do - -> { no_such_method }.should raise_error NameError + describe "if invoked as a vcall" do + it "raises NameError" do + -> { no_such_method }.should.raise NameError + end + + it "raises NameError with $! as a cause" do + begin + raise RuntimeError.new + rescue => cause + -> { no_such_method }.should.raise(NameError, cause:) + end + end end it "should omit the method_missing call from the backtrace for NameError" do - -> { no_such_method }.should raise_error { |e| e.backtrace.first.should_not include("method_missing") } + -> { no_such_method }.should.raise { |e| e.backtrace.first.should_not.include?("method_missing") } end - it "raises NoMethodError if invoked as an unambiguous method call" do - -> { no_such_method() }.should raise_error NoMethodError - -> { no_such_method(1,2,3) }.should raise_error NoMethodError + describe "if invoked as an unambiguous method call" do + it "raises NoMethodError" do + -> { no_such_method() }.should.raise NoMethodError + -> { no_such_method(1,2,3) }.should.raise NoMethodError + end + + it "raises NoMethodError with $! as a cause" do + begin + raise + rescue => cause + -> { no_such_method() }.should.raise(NoMethodError, cause:) + -> { no_such_method(1,2,3) }.should.raise(NoMethodError, cause:) + end + end end it "should omit the method_missing call from the backtrace for NoMethodError" do - -> { no_such_method() }.should raise_error { |e| e.backtrace.first.should_not include("method_missing") } + -> { no_such_method() }.should.raise { |e| e.backtrace.first.should_not.include?("method_missing") } end end @@ -258,20 +297,10 @@ describe "Invoking a private setter method" do end describe "Invoking a private getter method" do - ruby_version_is ""..."2.7" do - it "does not permit self as a receiver" do - receiver = LangSendSpecs::PrivateGetter.new - -> { receiver.call_self_foo }.should raise_error(NoMethodError) - -> { receiver.call_self_foo_or_equals(6) }.should raise_error(NoMethodError) - end - end - - ruby_version_is "2.7" do - it "permits self as a receiver" do - receiver = LangSendSpecs::PrivateGetter.new - receiver.call_self_foo_or_equals(6) - receiver.call_self_foo.should == 6 - end + it "permits self as a receiver" do + receiver = LangSendSpecs::PrivateGetter.new + receiver.call_self_foo_or_equals(6) + receiver.call_self_foo.should == 6 end end @@ -347,7 +376,7 @@ describe "Invoking a method" do it "with splat operator * and non-Array value uses value unchanged if it does not respond_to?(:to_ary)" do obj = Object.new - obj.should_not respond_to(:to_a) + obj.should_not.respond_to?(:to_a) specs.fooM0R(*obj).should == [obj] specs.fooM1R(1,*obj).should == [1, [obj]] @@ -421,36 +450,18 @@ describe "Invoking a method" do specs.rest_len(0,*a,4,*5,6,7,*c,-1).should == 11 end - ruby_version_is ""..."3.0" do - it "expands the Array elements from the splat after executing the arguments and block if no other arguments follow the splat" do - def self.m(*args, &block) - [args, block] - end - - args = [1, nil] - m(*args, &args.pop).should == [[1], nil] - - args = [1, nil] - order = [] - m(*(order << :args; args), &(order << :block; args.pop)).should == [[1], nil] - order.should == [:args, :block] + it "expands the Array elements from the splat before applying block argument operations" do + def self.m(*args, &block) + [args, block] end - end - - ruby_version_is "3.0" do - it "expands the Array elements from the splat before applying block argument operations" do - def self.m(*args, &block) - [args, block] - end - args = [1, nil] - m(*args, &args.pop).should == [[1, nil], nil] + args = [1, nil] + m(*args, &args.pop).should == [[1, nil], nil] - args = [1, nil] - order = [] - m(*(order << :args; args), &(order << :block; args.pop)).should == [[1, nil], nil] - order.should == [:args, :block] - end + args = [1, nil] + order = [] + m(*(order << :args; args), &(order << :block; args.pop)).should == [[1, nil], nil] + order.should == [:args, :block] end it "evaluates the splatted arguments before the block if there are other arguments after the splat" do diff --git a/spec/ruby/language/shared/__FILE__.rb b/spec/ruby/language/shared/__FILE__.rb index 3e4f5c958d..7e2e871932 100644 --- a/spec/ruby/language/shared/__FILE__.rb +++ b/spec/ruby/language/shared/__FILE__.rb @@ -9,14 +9,14 @@ describe :language___FILE__, shared: true do end it "equals the absolute path of a file loaded by an absolute path" do - @object.send(@method, @path).should be_true + @object.send(@method, @path).should == true ScratchPad.recorded.should == [@path] end it "equals the absolute path of a file loaded by a relative path" do $LOAD_PATH << "." Dir.chdir CODE_LOADING_DIR do - @object.send(@method, "file_fixture.rb").should be_true + @object.send(@method, "file_fixture.rb").should == true end ScratchPad.recorded.should == [@path] end diff --git a/spec/ruby/language/shared/__LINE__.rb b/spec/ruby/language/shared/__LINE__.rb index 076b74b3ba..6bfc8c1c17 100644 --- a/spec/ruby/language/shared/__LINE__.rb +++ b/spec/ruby/language/shared/__LINE__.rb @@ -9,7 +9,7 @@ describe :language___LINE__, shared: true do end it "equals the line number of the text in a loaded file" do - @object.send(@method, @path).should be_true + @object.send(@method, @path).should == true ScratchPad.recorded.should == [1, 5] end end diff --git a/spec/ruby/language/singleton_class_spec.rb b/spec/ruby/language/singleton_class_spec.rb index 7e9370d6f9..20256a323c 100644 --- a/spec/ruby/language/singleton_class_spec.rb +++ b/spec/ruby/language/singleton_class_spec.rb @@ -15,39 +15,39 @@ describe "A singleton class" do end it "raises a TypeError for Integer's" do - -> { 1.singleton_class }.should raise_error(TypeError) + -> { 1.singleton_class }.should.raise(TypeError) end it "raises a TypeError for symbols" do - -> { :symbol.singleton_class }.should raise_error(TypeError) + -> { :symbol.singleton_class }.should.raise(TypeError) end it "is a singleton Class instance" do o = mock('x') - o.singleton_class.should be_kind_of(Class) - o.singleton_class.should_not equal(Object) - o.should be_kind_of(o.singleton_class) + o.singleton_class.should.is_a?(Class) + o.singleton_class.should_not.equal?(Object) + o.should.is_a?(o.singleton_class) end it "is a Class for classes" do - ClassSpecs::A.singleton_class.should be_kind_of(Class) + ClassSpecs::A.singleton_class.should.is_a?(Class) end it "inherits from Class for classes" do - Class.should be_ancestor_of(Object.singleton_class) + Object.singleton_class.ancestors.should.include?(Class) end it "is a subclass of Class's singleton class" do ec = ClassSpecs::A.singleton_class - ec.should be_kind_of(Class.singleton_class) + ec.should.is_a?(Class.singleton_class) end it "is a subclass of the same level of Class's singleton class" do ecec = ClassSpecs::A.singleton_class.singleton_class class_ec = Class.singleton_class - ecec.should be_kind_of(class_ec.singleton_class) - ecec.should be_kind_of(class_ec) + ecec.should.is_a?(class_ec.singleton_class) + ecec.should.is_a?(class_ec) end it "is a subclass of a superclass's singleton class" do @@ -60,7 +60,7 @@ describe "A singleton class" do ClassSpecs::H.singleton_class.singleton_class end - it "for BasicObject has Class as it's superclass" do + it "for BasicObject has Class as its superclass" do BasicObject.singleton_class.superclass.should == Class end @@ -70,11 +70,11 @@ describe "A singleton class" do end it "has class String as the superclass of a String instance" do - "blah".singleton_class.superclass.should == String + "blah".dup.singleton_class.superclass.should == String end it "doesn't have singleton class" do - -> { bignum_value.singleton_class }.should raise_error(TypeError) + -> { bignum_value.singleton_class }.should.raise(TypeError) end end @@ -105,20 +105,20 @@ describe "A constant on a singleton class" do end it "is not defined on the object's class" do - @object.class.const_defined?(:CONST).should be_false + @object.class.const_defined?(:CONST).should == false end it "is not defined in the singleton class opener's scope" do class << @object CONST end - -> { CONST }.should raise_error(NameError) + -> { CONST }.should.raise(NameError) end it "cannot be accessed via object::CONST" do -> do @object::CONST - end.should raise_error(TypeError) + end.should.raise(TypeError) end it "raises a NameError for anonymous_module::CONST" do @@ -129,15 +129,15 @@ describe "A constant on a singleton class" do -> do @object::CONST - end.should raise_error(NameError) + end.should.raise(NameError) end it "appears in the singleton class constant list" do - @object.singleton_class.should have_constant(:CONST) + @object.singleton_class.should.const_defined?(:CONST, false) end it "does not appear in the object's class constant list" do - @object.class.should_not have_constant(:CONST) + @object.class.should_not.const_defined?(:CONST) end it "is not preserved when the object is duped" do @@ -145,31 +145,14 @@ describe "A constant on a singleton class" do -> do class << @object; CONST; end - end.should raise_error(NameError) + end.should.raise(NameError) end it "is preserved when the object is cloned" do @object = @object.clone class << @object - CONST.should_not be_nil - end - end -end - -describe "Defining yield in singleton class" do - ruby_version_is "2.7"..."3.0" do - it 'emits a deprecation warning' do - code = <<~RUBY - def m - class << Object.new - yield - end - end - m { :ok } - RUBY - - -> { eval(code) }.should complain(/warning: `yield' in class syntax will not be supported from Ruby 3.0/) + CONST.should_not == nil end end end @@ -185,7 +168,7 @@ describe "Defining instance methods on a singleton class" do end it "defines public methods" do - @k_sc.should have_public_instance_method(:singleton_method) + @k_sc.public_instance_methods(false).should.include?(:singleton_method) end end @@ -198,46 +181,46 @@ describe "Instance methods of a singleton class" do end it "include ones of the object's class" do - @k_sc.should have_instance_method(:example_instance_method) + @k_sc.should.method_defined?(:example_instance_method, true) end it "does not include class methods of the object's class" do - @k_sc.should_not have_instance_method(:example_class_method) + @k_sc.should_not.method_defined?(:example_class_method) end it "include instance methods of Object" do - @a_sc.should have_instance_method(:example_instance_method_of_object) + @a_sc.should.method_defined?(:example_instance_method_of_object, true) end it "does not include class methods of Object" do - @a_sc.should_not have_instance_method(:example_class_method_of_object) + @a_sc.should_not.method_defined?(:example_class_method_of_object) end describe "for a class" do it "include instance methods of Class" do - @a_c_sc.should have_instance_method(:example_instance_method_of_class) + @a_c_sc.should.method_defined?(:example_instance_method_of_class, true) end it "does not include class methods of Class" do - @a_c_sc.should_not have_instance_method(:example_class_method_of_class) + @a_c_sc.should_not.method_defined?(:example_class_method_of_class) end it "does not include instance methods of the singleton class of Class" do - @a_c_sc.should_not have_instance_method(:example_instance_method_of_singleton_class) + @a_c_sc.should_not.method_defined?(:example_instance_method_of_singleton_class) end it "does not include class methods of the singleton class of Class" do - @a_c_sc.should_not have_instance_method(:example_class_method_of_singleton_class) + @a_c_sc.should_not.method_defined?(:example_class_method_of_singleton_class) end end describe "for a singleton class" do it "includes instance methods of the singleton class of Class" do - @a_c_sc.singleton_class.should have_instance_method(:example_instance_method_of_singleton_class) + @a_c_sc.singleton_class.should.method_defined?(:example_instance_method_of_singleton_class, true) end it "does not include class methods of the singleton class of Class" do - @a_c_sc.singleton_class.should_not have_instance_method(:example_class_method_of_singleton_class) + @a_c_sc.singleton_class.should_not.method_defined?(:example_class_method_of_singleton_class) end end end @@ -251,46 +234,46 @@ describe "Class methods of a singleton class" do end it "include ones of the object's class" do - @k_sc.should have_method(:example_class_method) + @k_sc.should.respond_to?(:example_class_method) end it "does not include instance methods of the object's class" do - @k_sc.should_not have_method(:example_instance_method) + @k_sc.should_not.respond_to?(:example_instance_method) end it "include instance methods of Class" do - @a_sc.should have_method(:example_instance_method_of_class) + @a_sc.should.respond_to?(:example_instance_method_of_class) end it "does not include class methods of Class" do - @a_sc.should_not have_method(:example_class_method_of_class) + @a_sc.should_not.respond_to?(:example_class_method_of_class) end describe "for a class" do it "include instance methods of Class" do - @a_c_sc.should have_method(:example_instance_method_of_class) + @a_c_sc.should.respond_to?(:example_instance_method_of_class) end it "include class methods of Class" do - @a_c_sc.should have_method(:example_class_method_of_class) + @a_c_sc.should.respond_to?(:example_class_method_of_class) end it "include instance methods of the singleton class of Class" do - @a_c_sc.should have_method(:example_instance_method_of_singleton_class) + @a_c_sc.should.respond_to?(:example_instance_method_of_singleton_class) end it "does not include class methods of the singleton class of Class" do - @a_c_sc.should_not have_method(:example_class_method_of_singleton_class) + @a_c_sc.should_not.respond_to?(:example_class_method_of_singleton_class) end end describe "for a singleton class" do it "include instance methods of the singleton class of Class" do - @a_c_sc.singleton_class.should have_method(:example_instance_method_of_singleton_class) + @a_c_sc.singleton_class.should.respond_to?(:example_instance_method_of_singleton_class) end it "include class methods of the singleton class of Class" do - @a_c_sc.singleton_class.should have_method(:example_class_method_of_singleton_class) + @a_c_sc.singleton_class.should.respond_to?(:example_class_method_of_singleton_class) end end end @@ -299,12 +282,36 @@ describe "Instantiating a singleton class" do it "raises a TypeError when new is called" do -> { Object.new.singleton_class.new - }.should raise_error(TypeError) + }.should.raise(TypeError) end it "raises a TypeError when allocate is called" do -> { Object.new.singleton_class.allocate - }.should raise_error(TypeError) + }.should.raise(TypeError) + end +end + +describe "Frozen properties" do + it "is frozen if the object it is created from is frozen" do + o = Object.new + o.freeze + klass = o.singleton_class + klass.frozen?.should == true + end + + it "will be frozen if the object it is created from becomes frozen" do + o = Object.new + klass = o.singleton_class + klass.frozen?.should == false + o.freeze + klass.frozen?.should == true + end + + it "will be unfrozen if the frozen object is cloned with freeze set to false" do + o = Object.new + o.freeze + o2 = o.clone(freeze: false) + o2.singleton_class.frozen?.should == false end end diff --git a/spec/ruby/language/source_encoding_spec.rb b/spec/ruby/language/source_encoding_spec.rb index a0a29f63de..5a243fa2c3 100644 --- a/spec/ruby/language/source_encoding_spec.rb +++ b/spec/ruby/language/source_encoding_spec.rb @@ -15,7 +15,7 @@ describe "Source files" do end describe "encoded in UTF-16 LE without a BOM" do - it "are parsed because empty as they contain a NUL byte before the encoding comment" do + it "are parsed as empty because they contain a NUL byte before the encoding comment" do ruby_exe(fixture(__FILE__, "utf16-le-nobom.rb"), args: "2>&1").should == "" end end @@ -29,7 +29,7 @@ describe "Source files" do touch(path, "wb") { |f| f.write source } begin - ruby_exe(path, args: "2>&1").should =~ /invalid multibyte char/ + ruby_exe(path, args: "2>&1", exit_status: 1).b.should =~ /invalid multibyte char/ ensure rm_r path end @@ -51,7 +51,7 @@ describe "Source files" do touch(path, "wb") { |f| f.write source } begin - ruby_exe(path, args: "2>&1").should =~ /invalid multibyte char/ + ruby_exe(path, args: "2>&1", exit_status: 1).b.should =~ /invalid multibyte char/ ensure rm_r path end diff --git a/spec/ruby/language/string_spec.rb b/spec/ruby/language/string_spec.rb index 0178083f58..163063032f 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' @@ -56,28 +62,6 @@ describe "Ruby character strings" do "#\$".should == '#$' end - ruby_version_is ''...'2.7' do - it "taints the result of interpolation when an interpolated value is tainted" do - "#{"".taint}".tainted?.should be_true - - @ip.taint - "#@ip".tainted?.should be_true - - $ip.taint - "#$ip".tainted?.should be_true - end - - it "untrusts the result of interpolation when an interpolated value is untrusted" do - "#{"".untrust}".untrusted?.should be_true - - @ip.untrust - "#@ip".untrusted?.should be_true - - $ip.untrust - "#$ip".untrusted?.should be_true - end - end - it "allows using non-alnum characters as string delimiters" do %(hey #{@ip}).should == "hey xxx" %[hey #{@ip}].should == "hey xxx" @@ -149,6 +133,12 @@ describe "Ruby character strings" do "#{obj}".should == '42' end + it "raise NoMethodError when #to_s is not defined for the object" do + obj = BasicObject.new + + -> { "#{obj}" }.should.raise(NoMethodError) + end + it "uses an internal representation when #to_s doesn't return a String" do obj = mock('to_s') obj.stub!(:to_s).and_return(42) @@ -159,7 +149,7 @@ describe "Ruby character strings" do # is that if you interpolate an object that fails to return # a String, you will still get a String and not raise an # exception. - "#{obj}".should be_an_instance_of(String) + "#{obj}".should.instance_of?(String) end it "allows a dynamic string to parse a nested do...end block as an argument to a call without parens, interpolated" do @@ -253,8 +243,16 @@ describe "Ruby String literals" do ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files.rb")).chomp.should == "true" end - it "produce different objects for literals with the same content in different files if the other file doesn't have the comment" do - ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_no_comment.rb")).chomp.should == "true" + guard -> { !(eval("'test'").frozen? && "test".equal?("test")) } do + it "produces different objects for literals with the same content in different files if the other file doesn't have the comment and String literals aren't frozen by default" do + ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_no_comment.rb")).chomp.should == "true" + end + end + + guard -> { eval("'test'").frozen? && "test".equal?("test") } do + it "produces the same objects for literals with the same content in different files if the other file doesn't have the comment and String literals are frozen by default" do + ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_no_comment.rb")).chomp.should == "false" + end end it "produce different objects for literals with the same content in different files if they have different encodings" do @@ -273,12 +271,12 @@ describe "Ruby String interpolation" do it "returns a string with the source encoding by default" do "a#{"b"}c".encoding.should == Encoding::BINARY - eval('"a#{"b"}c"'.force_encoding("us-ascii")).encoding.should == Encoding::US_ASCII + eval('"a#{"b"}c"'.dup.force_encoding("us-ascii")).encoding.should == Encoding::US_ASCII eval("# coding: US-ASCII \n 'a#{"b"}c'").encoding.should == Encoding::US_ASCII end it "returns a string with the source encoding, even if the components have another encoding" do - a = "abc".force_encoding("euc-jp") + a = "abc".dup.force_encoding("euc-jp") "#{a}".encoding.should == Encoding::BINARY b = "abc".encode("utf-8") @@ -287,25 +285,23 @@ describe "Ruby String interpolation" do it "raises an Encoding::CompatibilityError if the Encodings are not compatible" do a = "\u3042" - b = "\xff".force_encoding "binary" + b = "\xff".dup.force_encoding "binary" - -> { "#{a} #{b}" }.should raise_error(Encoding::CompatibilityError) + -> { "#{a} #{b}" }.should.raise(Encoding::CompatibilityError) end it "creates a non-frozen String" do code = <<~'RUBY' - "a#{6*7}c" + "a#{6*7}c" RUBY eval(code).should_not.frozen? end - ruby_version_is "3.0" do - it "creates a non-frozen String when # frozen-string-literal: true is used" do - code = <<~'RUBY' + it "creates a non-frozen String when # frozen-string-literal: true is used" do + code = <<~'RUBY' # frozen-string-literal: true "a#{6*7}c" - RUBY - eval(code).should_not.frozen? - end + RUBY + eval(code).should_not.frozen? end end diff --git a/spec/ruby/language/super_spec.rb b/spec/ruby/language/super_spec.rb index 1ac5c5e1be..f595d49395 100644 --- a/spec/ruby/language/super_spec.rb +++ b/spec/ruby/language/super_spec.rb @@ -70,7 +70,7 @@ describe "The super keyword" do SuperSpecs::S4::B.new.foo([],"test").should == ["B#foo(a,test)", "A#foo"] end - it "raises an error error when super method does not exist" do + it "raises an error when super method does not exist" do sup = Class.new sub_normal = Class.new(sup) do def foo @@ -83,8 +83,8 @@ describe "The super keyword" do end end - -> {sub_normal.new.foo}.should raise_error(NoMethodError, /super/) - -> {sub_zsuper.new.foo}.should raise_error(NoMethodError, /super/) + -> {sub_normal.new.foo}.should.raise(NoMethodError, /super/) + -> {sub_zsuper.new.foo}.should.raise(NoMethodError, /super/) end it "uses given block even if arguments are passed explicitly" do @@ -130,7 +130,7 @@ describe "The super keyword" do end end - c2.new.m('a') { raise }.should be_false + c2.new.m('a') { raise }.should == false end it "uses block argument given to method when used in a block" do @@ -200,7 +200,26 @@ describe "The super keyword" do end end - -> { klass.new.a(:a_called) }.should raise_error(RuntimeError) + -> { klass.new.a(:a_called) }.should.raise(RuntimeError) + end + + it "is able to navigate to super, when a method is defined dynamically on the singleton class" do + foo_class = Class.new do + def bar + "bar" + end + end + + mixin_module = Module.new do + def bar + "super_" + super + end + end + + foo = foo_class.new + foo.singleton_class.define_method(:bar, mixin_module.instance_method(:bar)) + + foo.bar.should == "super_bar" end # Rubinius ticket github#157 @@ -316,12 +335,23 @@ describe "The super keyword" do it "without explicit arguments that are '_'" do SuperSpecs::ZSuperWithUnderscores::B.new.m(1, 2).should == [1, 2] + SuperSpecs::ZSuperWithUnderscores::B.new.m3(1, 2, 3).should == [1, 2, 3] + SuperSpecs::ZSuperWithUnderscores::B.new.m4(1, 2, 3, 4).should == [1, 2, 3, 4] + SuperSpecs::ZSuperWithUnderscores::B.new.m_default(1).should == [1] + SuperSpecs::ZSuperWithUnderscores::B.new.m_default.should == [0] + SuperSpecs::ZSuperWithUnderscores::B.new.m_pre_default_rest_post(1, 2, 3, 4, 5, 6, 7).should == [1, 2, 3, 4, 5, 6, 7] + SuperSpecs::ZSuperWithUnderscores::B.new.m_rest(1, 2).should == [1, 2] + SuperSpecs::ZSuperWithUnderscores::B.new.m_kwrest(a: 1).should == {a: 1} end it "without explicit arguments that are '_' including any modifications" do SuperSpecs::ZSuperWithUnderscores::B.new.m_modified(1, 2).should == [14, 2] end + it "should pass method arguments when called within a closure" do + SuperSpecs::ZSuperInBlock::B.new.m(arg: 1).should == 1 + end + describe 'when using keyword arguments' do before :each do @req = SuperSpecs::Keywords::RequiredArguments.new @@ -431,4 +461,18 @@ describe "The super keyword" do @all.foo('a', b: 'b').should == [['a'], {b: 'b'}] end end + + it "works in method definitions using **nil" do + parent = Class.new do + def m(*args, **kwargs) + [args, kwargs] + end + end + child = Class.new(parent) do + def m(*args, **nil) + super + end + end + child.new.m(1, 2).should == [[1, 2], {}] + end end diff --git a/spec/ruby/language/symbol_spec.rb b/spec/ruby/language/symbol_spec.rb index d6a41d3059..81fe06b50b 100644 --- a/spec/ruby/language/symbol_spec.rb +++ b/spec/ruby/language/symbol_spec.rb @@ -3,7 +3,7 @@ require_relative '../spec_helper' describe "A Symbol literal" do it "is a ':' followed by any number of valid characters" do a = :foo - a.should be_kind_of(Symbol) + a.should.is_a?(Symbol) a.inspect.should == ':foo' end @@ -21,7 +21,7 @@ describe "A Symbol literal" do :_Foo, :&, :_9 - ].each { |s| s.should be_kind_of(Symbol) } + ].each { |s| s.should.is_a?(Symbol) } end it "is a ':' followed by a single- or double-quoted string that may contain otherwise invalid characters" do @@ -31,7 +31,7 @@ describe "A Symbol literal" do [:"foo #{1 + 1}", ':"foo 2"'], [:"foo\nbar", ':"foo\nbar"'], ].each { |sym, str| - sym.should be_kind_of(Symbol) + sym.should.is_a?(Symbol) sym.inspect.should == str } end @@ -42,22 +42,22 @@ describe "A Symbol literal" do end it "may contain '::' in the string" do - :'Some::Class'.should be_kind_of(Symbol) + :'Some::Class'.should.is_a?(Symbol) end it "is converted to a literal, unquoted representation if the symbol contains only valid characters" do a, b, c = :'foo', :'+', :'Foo__9' - a.should be_kind_of(Symbol) + a.should.is_a?(Symbol) a.inspect.should == ':foo' - b.should be_kind_of(Symbol) + b.should.is_a?(Symbol) b.inspect.should == ':+' - c.should be_kind_of(Symbol) + c.should.is_a?(Symbol) c.inspect.should == ':Foo__9' end it "can be created by the %s-delimited expression" do a, b = :'foo bar', %s{foo bar} - b.should be_kind_of(Symbol) + b.should.is_a?(Symbol) b.inspect.should == ':"foo bar"' b.should == a end @@ -68,7 +68,7 @@ describe "A Symbol literal" do [:'a string', :'a string'], [:"#{var}", :"#{var}"] ].each { |a, b| - a.should equal(b) + a.should.equal?(b) } end @@ -78,7 +78,7 @@ describe "A Symbol literal" do it "can be an empty string" do c = :'' - c.should be_kind_of(Symbol) + c.should.is_a?(Symbol) c.inspect.should == ':""' end @@ -96,11 +96,13 @@ describe "A Symbol literal" do %I{a b #{"c"}}.should == [:a, :b, :c] end - it "with invalid bytes raises an EncodingError at parse time" do - ScratchPad.record [] - -> { - eval 'ScratchPad << 1; :"\xC3"' - }.should raise_error(EncodingError, /invalid/) - ScratchPad.recorded.should == [] + ruby_bug "#20280", ""..."3.4" do + it "raises an SyntaxError at parse time when Symbol with invalid bytes" do + ScratchPad.record [] + -> { + eval 'ScratchPad << 1; :"\xC3"' + }.should.raise(SyntaxError, /invalid symbol/) + ScratchPad.recorded.should == [] + end end end diff --git a/spec/ruby/language/throw_spec.rb b/spec/ruby/language/throw_spec.rb index d723843688..73f64de17d 100644 --- a/spec/ruby/language/throw_spec.rb +++ b/spec/ruby/language/throw_spec.rb @@ -35,7 +35,7 @@ describe "The throw keyword" do throw :exit end end - $!.should be_nil + $!.should == nil end it "allows any object as its argument" do @@ -45,7 +45,7 @@ describe "The throw keyword" do end it "does not convert strings to a symbol" do - -> { catch(:exit) { throw "exit" } }.should raise_error(ArgumentError) + -> { catch(:exit) { throw "exit" } }.should.raise(ArgumentError) end it "unwinds stack from within a method" do @@ -64,8 +64,8 @@ describe "The throw keyword" do end it "raises an ArgumentError if outside of scope of a matching catch" do - -> { throw :test, 5 }.should raise_error(ArgumentError) - -> { catch(:different) { throw :test, 5 } }.should raise_error(ArgumentError) + -> { throw :test, 5 }.should.raise(ArgumentError) + -> { catch(:different) { throw :test, 5 } }.should.raise(ArgumentError) end it "raises an UncaughtThrowError if used to exit a thread" do @@ -73,7 +73,7 @@ describe "The throw keyword" do t = Thread.new { -> { throw :what - }.should raise_error(UncaughtThrowError) + }.should.raise(UncaughtThrowError) } t.join end diff --git a/spec/ruby/language/undef_spec.rb b/spec/ruby/language/undef_spec.rb index 4e473b803f..98ecd99c21 100644 --- a/spec/ruby/language/undef_spec.rb +++ b/spec/ruby/language/undef_spec.rb @@ -14,35 +14,42 @@ describe "The undef keyword" do @undef_class.class_eval do undef meth end - -> { @obj.meth(5) }.should raise_error(NoMethodError) + -> { @obj.meth(5) }.should.raise(NoMethodError) end it "with a simple symbol" do @undef_class.class_eval do undef :meth end - -> { @obj.meth(5) }.should raise_error(NoMethodError) + -> { @obj.meth(5) }.should.raise(NoMethodError) end it "with a single quoted symbol" do @undef_class.class_eval do undef :'meth' end - -> { @obj.meth(5) }.should raise_error(NoMethodError) + -> { @obj.meth(5) }.should.raise(NoMethodError) end it "with a double quoted symbol" do @undef_class.class_eval do undef :"meth" end - -> { @obj.meth(5) }.should raise_error(NoMethodError) + -> { @obj.meth(5) }.should.raise(NoMethodError) end - it "with a interpolated symbol" do + it "with an interpolated symbol" do @undef_class.class_eval do undef :"#{'meth'}" end - -> { @obj.meth(5) }.should raise_error(NoMethodError) + -> { @obj.meth(5) }.should.raise(NoMethodError) + end + + it "with an interpolated symbol when interpolated expression is not a String literal" do + @undef_class.class_eval do + undef :"#{'meth'.to_sym}" + end + -> { @obj.meth(5) }.should.raise(NoMethodError) end end @@ -62,8 +69,8 @@ describe "The undef keyword" do it "raises a NameError when passed a missing name" do Class.new do -> { - undef not_exist - }.should raise_error(NameError) { |e| + undef not_exist + }.should.raise(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 ce072baa2c..e01e03f01e 100644 --- a/spec/ruby/language/variables_spec.rb +++ b/spec/ruby/language/variables_spec.rb @@ -1,6 +1,51 @@ require_relative '../spec_helper' require_relative 'fixtures/variables' +describe "Evaluation order during assignment" do + context "with single assignment" do + it "evaluates from left to right" do + obj = VariablesSpecs::EvalOrder.new + obj.instance_eval do + foo[0] = a + end + + obj.order.should == ["foo", "a", "foo[]="] + end + end + + context "with multiple assignment" 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 + + obj.order.should == ["foo", "bar", "a", "b", "foo[]=", "bar.baz="] + end + + 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 + end +end + describe "Multiple assignment" do context "with a single RHS value" do it "assigns a simple MLHS" do @@ -46,7 +91,7 @@ describe "Multiple assignment" do x = mock("multi-assign single RHS") x.should_receive(:to_ary).and_return(1) - -> { a, b, c = x }.should raise_error(TypeError) + -> { a, b, c = x }.should.raise(TypeError) end it "does not call #to_a to convert an Object RHS when assigning a simple MLHS" do @@ -77,7 +122,7 @@ describe "Multiple assignment" do ary = [1, 2] x = (a, b = ary) - x.should equal(ary) + x.should.equal?(ary) end it "returns the RHS when it is an Array subclass" do @@ -85,7 +130,7 @@ describe "Multiple assignment" do ary = cls.new [1, 2] x = (a, b = ary) - x.should equal(ary) + x.should.equal?(ary) end it "does not call #to_ary on an Array subclass instance" do @@ -127,7 +172,7 @@ describe "Multiple assignment" do x = mock("multi-assign splat") x.should_receive(:to_ary).and_return(1) - -> { *a = x }.should raise_error(TypeError) + -> { *a = x }.should.raise(TypeError) end it "does not call #to_ary on an Array subclass" do @@ -144,8 +189,8 @@ describe "Multiple assignment" do ary = cls.new [1, 2] x = (*a = ary) - x.should equal(ary) - a.should be_an_instance_of(Array) + x.should.equal?(ary) + a.should.instance_of?(Array) end it "calls #to_ary to convert an Object RHS with MLHS" do @@ -160,7 +205,7 @@ describe "Multiple assignment" do x = mock("multi-assign splat") x.should_receive(:to_ary).and_return(1) - -> { a, *b, c = x }.should raise_error(TypeError) + -> { a, *b, c = x }.should.raise(TypeError) end it "does not call #to_a to convert an Object RHS with a MLHS" do @@ -256,7 +301,7 @@ describe "Multiple assignment" do x = mock("multi-assign attributes") x.should_receive(:m).and_return(y) - -> { a, b = x.m }.should raise_error(TypeError) + -> { a, b = x.m }.should.raise(TypeError) end it "assigns values from a RHS method call with receiver and arguments" do @@ -287,8 +332,13 @@ describe "Multiple assignment" do it "assigns indexed elements" do a = [] - a[1], a[2] = 1 - a.should == [nil, 1, nil] + a[1], a[2] = 1, 2 + a.should == [nil, 1, 2] + + # with splatted argument + a = [] + a[*[1]], a[*[2]] = 1, 2 + a.should == [nil, 1, 2] end it "assigns constants" do @@ -296,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 @@ -310,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 @@ -329,7 +393,7 @@ describe "Multiple assignment" do ary = [1, 2] (a = *ary).should == [1, 2] - a.should_not equal(ary) + a.should_not.equal?(ary) end it "does not call #to_a on an Array subclass" do @@ -348,10 +412,10 @@ describe "Multiple assignment" do x = (a = *ary) x.should == [1, 2] - x.should be_an_instance_of(Array) + x.should.instance_of?(Array) a.should == [1, 2] - a.should be_an_instance_of(Array) + a.should.instance_of?(Array) end it "unfreezes the array returned from calling 'to_a' on the splatted value" do @@ -417,7 +481,7 @@ describe "Multiple assignment" do x = mock("multi-assign RHS splat") x.should_receive(:to_a).and_return(1) - -> { *a = *x }.should raise_error(TypeError) + -> { *a = *x }.should.raise(TypeError) end it "does not call #to_ary to convert an Object RHS with a single splat LHS" do @@ -463,7 +527,7 @@ describe "Multiple assignment" do x = mock("multi-assign splat") x.should_receive(:to_a).and_return(1) - -> { a = *x }.should raise_error(TypeError) + -> { a = *x }.should.raise(TypeError) end it "calls #to_a to convert an Object splat RHS when assigned to a simple MLHS" do @@ -478,7 +542,7 @@ describe "Multiple assignment" do x = mock("multi-assign splat") x.should_receive(:to_a).and_return(1) - -> { a, b, c = *x }.should raise_error(TypeError) + -> { a, b, c = *x }.should.raise(TypeError) end it "does not call #to_ary to convert an Object splat RHS when assigned to a simple MLHS" do @@ -501,7 +565,7 @@ describe "Multiple assignment" do x = mock("multi-assign splat") x.should_receive(:to_a).and_return(1) - -> { a, *b, c = *x }.should raise_error(TypeError) + -> { a, *b, c = *x }.should.raise(TypeError) end it "does not call #to_ary to convert an Object RHS with a MLHS" do @@ -534,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 @@ -579,7 +645,7 @@ describe "Multiple assignment" do x = mock("multi-assign splat MRHS") x.should_receive(:to_a).and_return(1) - -> { a, *b = 1, *x }.should raise_error(TypeError) + -> { a, *b = 1, *x }.should.raise(TypeError) end it "does not call #to_ary to convert a splatted Object as part of a MRHS with a splat MRHS" do @@ -602,7 +668,7 @@ describe "Multiple assignment" do x = mock("multi-assign splat MRHS") x.should_receive(:to_a).and_return(1) - -> { a, *b = *x, 1 }.should raise_error(TypeError) + -> { a, *b = *x, 1 }.should.raise(TypeError) end it "does not call #to_ary to convert a splatted Object with a splat MRHS" do @@ -651,7 +717,7 @@ describe "Multiple assignment" do x = mock("multi-assign mixed RHS") x.should_receive(:to_ary).and_return(x) - -> { a, (b, c), d = 1, x, 3, 4 }.should raise_error(TypeError) + -> { a, (b, c), d = 1, x, 3, 4 }.should.raise(TypeError) end it "calls #to_a to convert a splatted Object value in a MRHS" do @@ -675,7 +741,7 @@ describe "Multiple assignment" do x = mock("multi-assign mixed splatted RHS") x.should_receive(:to_ary).and_return(x) - -> { a, *b, (c, d) = 1, 2, 3, *x }.should raise_error(TypeError) + -> { a, *b, (c, d) = 1, 2, 3, *x }.should.raise(TypeError) end it "does not call #to_ary to convert an Object when the position receiving the value is a simple variable" do @@ -715,12 +781,27 @@ describe "Multiple assignment" do x.should == [1, 2, 3, 4, 5] end + it "can be used to swap array elements" do + a = [1, 2] + a[0], a[1] = a[1], a[0] + a.should == [2, 1] + end + + it "can be used to swap range of array elements" do + a = [1, 2, 3, 4] + a[0, 2], a[2, 2] = a[2, 2], a[0, 2] + a.should == [3, 4, 1, 2] + end + it "assigns RHS values to LHS constants" do module VariableSpecs MRHS_VALUES_1, MRHS_VALUES_2 = 1, 2 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 @@ -728,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 @@ -770,47 +853,21 @@ describe "A local variable assigned only within a conditional block" do end describe 'Local variable shadowing' do - ruby_version_is ""..."2.6" do - it "leads to warning in verbose mode" do - -> do - eval <<-CODE - a = [1, 2, 3] - a.each { |a| a = 3 } - CODE - end.should complain(/shadowing outer local variable/, verbose: true) - end - end - - ruby_version_is "2.6" do - it "does not warn in verbose mode" do - result = nil + it "does not warn in verbose mode" do + result = nil - -> do - eval <<-CODE - a = [1, 2, 3] - result = a.map { |a| a = 3 } - CODE - end.should_not complain(verbose: true) + -> do + eval <<-CODE + a = [1, 2, 3] + result = a.map { |a| a = 3 } + CODE + end.should_not complain(verbose: true) - result.should == [3, 3, 3] - end + result.should == [3, 3, 3] end end describe 'Allowed characters' do - ruby_version_is "2.6" do - # new feature in 2.6 -- https://bugs.ruby-lang.org/issues/13770 - it 'does not allow non-ASCII upcased characters at the beginning' do - -> do - eval <<-CODE - def test - ἍBB = 1 - end - CODE - end.should raise_error(SyntaxError, /dynamic constant assignment/) - end - end - it 'allows non-ASCII lowercased characters at the beginning' do result = nil @@ -824,4 +881,50 @@ describe 'Allowed characters' do result.should == 1 end + + it 'parses a non-ASCII upcased character as a constant identifier' do + -> do + eval <<-CODE + def test + ἍBB = 1 + end + CODE + end.should.raise(SyntaxError, /dynamic constant assignment/) + end +end + +describe "Instance variables" do + context "when instance variable is uninitialized" do + it "doesn't warn about accessing uninitialized instance variable" do + obj = Object.new + def obj.foobar; a = @a; end + + -> { obj.foobar }.should_not complain(verbose: true) + end + + it "doesn't warn at lazy initialization" do + obj = Object.new + def obj.foobar; @a ||= 42; end + + -> { obj.foobar }.should_not complain(verbose: true) + end + end + + describe "global variable" do + context "when global variable is uninitialized" do + it "warns about accessing uninitialized global variable in verbose mode" do + obj = Object.new + def obj.foobar; a = $specs_uninitialized_global_variable; end + + -> { obj.foobar }.should complain(/warning: global variable [`']\$specs_uninitialized_global_variable' not initialized/, verbose: true) + end + + it "doesn't warn at lazy initialization" do + obj = Object.new + def obj.foobar; $specs_uninitialized_global_variable_lazy ||= 42; end + + -> { obj.foobar }.should_not complain(verbose: true) + end + end + end end diff --git a/spec/ruby/language/while_spec.rb b/spec/ruby/language/while_spec.rb index e172453ca6..b9bf32047d 100644 --- a/spec/ruby/language/while_spec.rb +++ b/spec/ruby/language/while_spec.rb @@ -88,7 +88,7 @@ describe "The while expression" do break if c c = false ) - end.should be_nil + end.should == nil end it "stops running body if interrupted by break in a begin ... end element op-assign-or value" do @@ -99,7 +99,7 @@ describe "The while expression" do break if c c = false end - end.should be_nil + end.should == nil end it "stops running body if interrupted by break in a parenthesized element op-assign value" do @@ -111,7 +111,7 @@ describe "The while expression" do break if c c = false ) - end.should be_nil + end.should == nil a.should == [1, 2] end @@ -123,7 +123,7 @@ describe "The while expression" do break if c c = false end - end.should be_nil + end.should == nil a.should == [1, 2] end @@ -139,7 +139,7 @@ describe "The while expression" do break unless d d = false ) - end.should be_nil + end.should == nil end it "stops running body if interrupted by break with unless in a begin ... end attribute op-assign-or value" do @@ -153,7 +153,7 @@ describe "The while expression" do break unless d d = false end - end.should be_nil + end.should == nil end it "stops running body if interrupted by break in a parenthesized attribute op-assign-or value" do @@ -168,7 +168,7 @@ describe "The while expression" do break if c c = false ) - end.should be_nil + end.should == nil end it "stops running body if interrupted by break in a begin ... end attribute op-assign-or value" do @@ -182,7 +182,7 @@ describe "The while expression" do break if c c = false end - end.should be_nil + end.should == nil end it "returns value passed to break if interrupted by break" do diff --git a/spec/ruby/language/yield_spec.rb b/spec/ruby/language/yield_spec.rb index 5fad7cb176..3173f41b0c 100644 --- a/spec/ruby/language/yield_spec.rb +++ b/spec/ruby/language/yield_spec.rb @@ -13,7 +13,7 @@ describe "The yield call" do describe "taking no arguments" do it "raises a LocalJumpError when the method is not passed a block" do - -> { @y.z }.should raise_error(LocalJumpError) + -> { @y.z }.should.raise(LocalJumpError) end it "ignores assignment to the explicit block argument and calls the passed block" do @@ -28,7 +28,7 @@ describe "The yield call" do describe "taking a single argument" do describe "when no block is given" do it "raises a LocalJumpError" do - -> { @y.s(1) }.should raise_error(LocalJumpError) + -> { @y.s(1) }.should.raise(LocalJumpError) end end @@ -48,6 +48,12 @@ describe "The yield call" do it "passes a single, multi-value Array" do @y.s([1, 2, 3]) { |*a| a }.should == [[1, 2, 3]] end + + describe "with optional argument" do + it "does not destructure a single array argument" do + @y.s([1, 2, 3]) { |a = 99| a }.should == [1, 2, 3] + end + end end describe "yielding to a lambda" do @@ -70,20 +76,20 @@ describe "The yield call" do it "raises an ArgumentError if too few arguments are passed" do -> { @y.s(1, &-> a, b { [a,b] }) - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end it "should not destructure an Array into multiple arguments" do -> { @y.s([1, 2], &-> a, b { [a,b] }) - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end end end describe "taking multiple arguments" do it "raises a LocalJumpError when the method is not passed a block" do - -> { @y.m(1, 2, 3) }.should raise_error(LocalJumpError) + -> { @y.m(1, 2, 3) }.should.raise(LocalJumpError) end it "passes the arguments to the block" do @@ -97,19 +103,19 @@ describe "The yield call" do it "raises an ArgumentError if too many arguments are passed to a lambda" do -> { @y.m(1, 2, 3, &-> a { }) - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end it "raises an ArgumentError if too few arguments are passed to a lambda" do -> { @y.m(1, 2, 3, &-> a, b, c, d { }) - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end end describe "taking a single splatted argument" do it "raises a LocalJumpError when the method is not passed a block" do - -> { @y.r(0) }.should raise_error(LocalJumpError) + -> { @y.r(0) }.should.raise(LocalJumpError) end it "passes a single value" do @@ -141,7 +147,7 @@ describe "The yield call" do describe "taking multiple arguments with a splat" do it "raises a LocalJumpError when the method is not passed a block" do - -> { @y.rs(1, 2, [3, 4]) }.should raise_error(LocalJumpError) + -> { @y.rs(1, 2, [3, 4]) }.should.raise(LocalJumpError) end it "passes the arguments to the block" do @@ -166,7 +172,7 @@ describe "The yield call" do describe "taking matching arguments with splats and post args" do it "raises a LocalJumpError when the method is not passed a block" do - -> { @y.rs(1, 2, [3, 4]) }.should raise_error(LocalJumpError) + -> { @y.rs(1, 2, [3, 4]) }.should.raise(LocalJumpError) end it "passes the arguments to the block" do @@ -183,5 +189,38 @@ describe "The yield call" do it "uses captured block of a block used in define_method" do @y.deep(2).should == 4 end +end + +describe "Using yield in a singleton class literal" do + it 'raises a SyntaxError' do + code = <<~RUBY + class << Object.new + yield + end + RUBY + + -> { eval(code) }.should.raise(SyntaxError, /Invalid yield/) + end +end +describe "Using yield in non-lambda block" do + it 'raises a SyntaxError' do + code = <<~RUBY + 1.times { yield } + RUBY + + -> { eval(code) }.should.raise(SyntaxError, /Invalid yield/) + end +end + +describe "Using yield in a module literal" do + it 'raises a SyntaxError' do + code = <<~RUBY + module YieldSpecs::ModuleWithYield + yield + end + RUBY + + -> { eval(code) }.should.raise(SyntaxError, /Invalid yield/) + end end |
