summaryrefslogtreecommitdiff
path: root/spec/ruby/language
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/language')
-rw-r--r--spec/ruby/language/BEGIN_spec.rb9
-rw-r--r--spec/ruby/language/END_spec.rb33
-rw-r--r--spec/ruby/language/README2
-rw-r--r--spec/ruby/language/alias_spec.rb78
-rw-r--r--spec/ruby/language/and_spec.rb2
-rw-r--r--spec/ruby/language/array_spec.rb11
-rw-r--r--spec/ruby/language/assignments_spec.rb590
-rw-r--r--spec/ruby/language/block_spec.rb467
-rw-r--r--spec/ruby/language/break_spec.rb57
-rw-r--r--spec/ruby/language/case_spec.rb396
-rw-r--r--spec/ruby/language/class_spec.rb119
-rw-r--r--spec/ruby/language/class_variable_spec.rb38
-rw-r--r--spec/ruby/language/comment_spec.rb13
-rw-r--r--spec/ruby/language/constants_spec.rb361
-rw-r--r--spec/ruby/language/def_spec.rb191
-rw-r--r--spec/ruby/language/defined_spec.rb302
-rw-r--r--spec/ruby/language/delegation_spec.rb158
-rw-r--r--spec/ruby/language/encoding_spec.rb18
-rw-r--r--spec/ruby/language/ensure_spec.rb167
-rw-r--r--spec/ruby/language/execution_spec.rb80
-rw-r--r--spec/ruby/language/file_spec.rb30
-rw-r--r--spec/ruby/language/fixtures/array.rb21
-rw-r--r--spec/ruby/language/fixtures/block.rb4
-rw-r--r--spec/ruby/language/fixtures/break.rb10
-rw-r--r--spec/ruby/language/fixtures/break_lambda_toplevel.rb2
-rw-r--r--spec/ruby/language/fixtures/break_lambda_toplevel_block.rb2
-rw-r--r--spec/ruby/language/fixtures/break_lambda_toplevel_method.rb2
-rw-r--r--spec/ruby/language/fixtures/bytes_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/case_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/class_with_class_variable.rb9
-rw-r--r--spec/ruby/language/fixtures/constant_visibility.rb18
-rw-r--r--spec/ruby/language/fixtures/def.rb6
-rw-r--r--spec/ruby/language/fixtures/defined.rb14
-rw-r--r--spec/ruby/language/fixtures/delegation.rb11
-rw-r--r--spec/ruby/language/fixtures/emacs_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/ensure.rb44
-rw-r--r--spec/ruby/language/fixtures/for_scope.rb15
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rbbin181 -> 120 bytes
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb2
-rw-r--r--spec/ruby/language/fixtures/hash_strings_binary.rb (renamed from spec/ruby/language/fixtures/hash_strings_ascii8bit.rb)4
-rw-r--r--spec/ruby/language/fixtures/magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/metaclass.rb1
-rw-r--r--spec/ruby/language/fixtures/module.rb9
-rw-r--r--spec/ruby/language/fixtures/no_magic_comment.rb1
-rw-r--r--spec/ruby/language/fixtures/print_magic_comment_result_at_exit.rb3
-rw-r--r--spec/ruby/language/fixtures/private.rb26
-rw-r--r--spec/ruby/language/fixtures/rescue.rb4
-rw-r--r--spec/ruby/language/fixtures/rescue/top_level.rb7
-rw-r--r--spec/ruby/language/fixtures/rescue_captures.rb107
-rw-r--r--spec/ruby/language/fixtures/return.rb8
-rw-r--r--spec/ruby/language/fixtures/second_line_magic_comment.rb3
-rw-r--r--spec/ruby/language/fixtures/second_token_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/send.rb19
-rwxr-xr-xspec/ruby/language/fixtures/shebang_magic_comment.rb3
-rw-r--r--spec/ruby/language/fixtures/squiggly_heredoc.rb32
-rw-r--r--spec/ruby/language/fixtures/super.rb148
-rw-r--r--spec/ruby/language/fixtures/utf16-be-nobom.rbbin0 -> 68 bytes
-rw-r--r--spec/ruby/language/fixtures/utf16-le-nobom.rbbin0 -> 69 bytes
-rw-r--r--spec/ruby/language/fixtures/utf8-bom.rb2
-rw-r--r--spec/ruby/language/fixtures/utf8-nobom.rb2
-rw-r--r--spec/ruby/language/fixtures/variables.rb72
-rw-r--r--spec/ruby/language/fixtures/vim_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/yield.rb4
-rw-r--r--spec/ruby/language/for_spec.rb120
-rw-r--r--spec/ruby/language/hash_spec.rb206
-rw-r--r--spec/ruby/language/heredoc_spec.rb94
-rw-r--r--spec/ruby/language/if_spec.rb83
-rw-r--r--spec/ruby/language/it_parameter_spec.rb66
-rw-r--r--spec/ruby/language/keyword_arguments_spec.rb386
-rw-r--r--spec/ruby/language/lambda_spec.rb82
-rw-r--r--spec/ruby/language/line_spec.rb8
-rw-r--r--spec/ruby/language/loop_spec.rb4
-rw-r--r--spec/ruby/language/magic_comment_spec.rb111
-rw-r--r--spec/ruby/language/match_spec.rb11
-rw-r--r--spec/ruby/language/metaclass_spec.rb18
-rw-r--r--spec/ruby/language/method_spec.rb719
-rw-r--r--spec/ruby/language/module_spec.rb60
-rw-r--r--spec/ruby/language/next_spec.rb16
-rw-r--r--spec/ruby/language/not_spec.rb2
-rw-r--r--spec/ruby/language/numbered_parameters_spec.rb113
-rw-r--r--spec/ruby/language/numbers_spec.rb18
-rw-r--r--spec/ruby/language/optional_assignments_spec.rb530
-rw-r--r--spec/ruby/language/or_spec.rb14
-rw-r--r--spec/ruby/language/order_spec.rb2
-rw-r--r--spec/ruby/language/pattern_matching_spec.rb1310
-rw-r--r--spec/ruby/language/precedence_spec.rb117
-rw-r--r--spec/ruby/language/predefined/data_spec.rb21
-rw-r--r--spec/ruby/language/predefined/fixtures/data2.rb3
-rw-r--r--spec/ruby/language/predefined/fixtures/data3.rb3
-rw-r--r--spec/ruby/language/predefined/fixtures/data_offset.rb12
-rw-r--r--spec/ruby/language/predefined/fixtures/empty_data.rb3
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic_required.rb2
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_id.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_id_required.rb1
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_required_before.rb2
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_values.rb9
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_variables.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_variables_required.rb2
-rw-r--r--spec/ruby/language/predefined/toplevel_binding_spec.rb34
-rw-r--r--spec/ruby/language/predefined_spec.rb1042
-rw-r--r--spec/ruby/language/private_spec.rb20
-rw-r--r--spec/ruby/language/proc_spec.rb35
-rw-r--r--spec/ruby/language/range_spec.rb30
-rw-r--r--spec/ruby/language/redo_spec.rb6
-rw-r--r--spec/ruby/language/regexp/anchors_spec.rb12
-rw-r--r--spec/ruby/language/regexp/back-references_spec.rb107
-rw-r--r--spec/ruby/language/regexp/character_classes_spec.rb58
-rw-r--r--spec/ruby/language/regexp/empty_checks_spec.rb135
-rw-r--r--spec/ruby/language/regexp/encoding_spec.rb93
-rw-r--r--spec/ruby/language/regexp/escapes_spec.rb114
-rw-r--r--spec/ruby/language/regexp/grouping_spec.rb46
-rw-r--r--spec/ruby/language/regexp/interpolation_spec.rb10
-rw-r--r--spec/ruby/language/regexp/modifiers_spec.rb22
-rw-r--r--spec/ruby/language/regexp/repetition_spec.rb111
-rw-r--r--spec/ruby/language/regexp/subexpression_call_spec.rb50
-rw-r--r--spec/ruby/language/regexp_spec.rb35
-rw-r--r--spec/ruby/language/rescue_spec.rb264
-rw-r--r--spec/ruby/language/reserved_keywords.rb149
-rw-r--r--spec/ruby/language/retry_spec.rb9
-rw-r--r--spec/ruby/language/return_spec.rb375
-rw-r--r--spec/ruby/language/safe_navigator_spec.rb186
-rw-r--r--spec/ruby/language/safe_spec.rb11
-rw-r--r--spec/ruby/language/send_spec.rb83
-rw-r--r--spec/ruby/language/singleton_class_spec.rb50
-rw-r--r--spec/ruby/language/source_encoding_spec.rb61
-rw-r--r--spec/ruby/language/string_spec.rb135
-rw-r--r--spec/ruby/language/super_spec.rb267
-rw-r--r--spec/ruby/language/symbol_spec.rb18
-rw-r--r--spec/ruby/language/throw_spec.rb10
-rw-r--r--spec/ruby/language/undef_spec.rb25
-rw-r--r--spec/ruby/language/unless_spec.rb2
-rw-r--r--spec/ruby/language/until_spec.rb4
-rw-r--r--spec/ruby/language/variables_spec.rb210
-rw-r--r--spec/ruby/language/while_spec.rb4
-rw-r--r--spec/ruby/language/yield_spec.rb89
136 files changed, 9693 insertions, 2128 deletions
diff --git a/spec/ruby/language/BEGIN_spec.rb b/spec/ruby/language/BEGIN_spec.rb
index c0784971b0..5aef5a1d7c 100644
--- a/spec/ruby/language/BEGIN_spec.rb
+++ b/spec/ruby/language/BEGIN_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The BEGIN keyword" do
before :each do
@@ -15,7 +15,12 @@ describe "The BEGIN keyword" do
end
it "must appear in a top-level context" do
- lambda { eval "1.times { BEGIN { 1 } }" }.should raise_error(SyntaxError)
+ -> { eval "1.times { BEGIN { 1 } }" }.should raise_error(SyntaxError)
+ end
+
+ it "uses top-level for self" do
+ eval("BEGIN { ScratchPad << self.to_s }", TOPLEVEL_BINDING)
+ ScratchPad.recorded.should == ['main']
end
it "runs first in a given code unit" do
diff --git a/spec/ruby/language/END_spec.rb b/spec/ruby/language/END_spec.rb
new file mode 100644
index 0000000000..c84f0cc9ac
--- /dev/null
+++ b/spec/ruby/language/END_spec.rb
@@ -0,0 +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 "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
+
+ 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/README b/spec/ruby/language/README
index 74eaf58709..ae08e17fb1 100644
--- a/spec/ruby/language/README
+++ b/spec/ruby/language/README
@@ -4,7 +4,7 @@ words. These words significantly describe major elements of the language,
including flow control constructs like 'for' and 'while', conditional
execution like 'if' and 'unless', exceptional execution control like 'rescue',
etc. There are also literals for the basic "types" like String, Regexp, Array
-and Fixnum.
+and Integer.
Behavioral specifications describe the behavior of concrete entities. Rather
than using concepts of computation to organize these spec files, we use
diff --git a/spec/ruby/language/alias_spec.rb b/spec/ruby/language/alias_spec.rb
index e9f0050e17..61fddb0184 100644
--- a/spec/ruby/language/alias_spec.rb
+++ b/spec/ruby/language/alias_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
class AliasObject
attr :foo
@@ -38,20 +38,29 @@ describe "The alias keyword" do
@obj.a.should == 5
end
- it "works with a doubule quoted symbol on the left-hand side" do
+ it "works with a double quoted symbol on the left-hand side" do
@meta.class_eval do
alias :"a" value
end
@obj.a.should == 5
end
- it "works with an interoplated symbol on the left-hand side" do
+ it "works with an interpolated symbol on the left-hand side" do
@meta.class_eval do
alias :"#{'a'}" value
end
@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
@@ -66,20 +75,29 @@ describe "The alias keyword" do
@obj.a.should == 5
end
- it "works with a doubule quoted symbol on the right-hand side" do
+ it "works with a double quoted symbol on the right-hand side" do
@meta.class_eval do
alias a :"value"
end
@obj.a.should == 5
end
- it "works with an interoplated symbol on the right-hand side" do
+ it "works with an interpolated symbol on the right-hand side" do
@meta.class_eval do
alias a :"#{'value'}"
end
@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
- lambda { AliasObject.new.__value }.should raise_error(NoMethodError)
+ -> { AliasObject.new.__value }.should raise_error(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
- lambda { Object.__klass_method }.should raise_error(NoMethodError)
+ -> { Object.__klass_method }.should raise_error(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
- lambda { Object.__klass_method2 }.should raise_error(NoMethodError)
+ -> { Object.__klass_method2 }.should raise_error(NoMethodError)
end
it "operates on methods defined via attr, attr_reader, and attr_accessor" do
@@ -204,7 +222,7 @@ describe "The alias keyword" do
end
it "operates on methods with splat arguments defined in a superclass using text block for class eval" do
- class Sub < AliasObject;end
+ subclass = Class.new(AliasObject)
AliasObject.class_eval <<-code
def test(*args)
4
@@ -215,17 +233,17 @@ describe "The alias keyword" do
alias test_without_check test
alias test test_with_check
code
- Sub.new.test("testing").should == 4
+ subclass.new.test("testing").should == 4
end
- it "is not allowed against Fixnum or String instances" do
- lambda do
+ it "is not allowed against Integer or String instances" do
+ -> do
1.instance_eval do
alias :foo :to_s
end
end.should raise_error(TypeError)
- lambda do
+ -> do
:blah.instance_eval do
alias :foo :to_s
end
@@ -234,13 +252,43 @@ describe "The alias keyword" do
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
- lambda { @meta.class_eval { alias undef_method not_exist } }.should raise_error(NameError) { |e|
+ -> { @meta.class_eval { alias undef_method not_exist } }.should raise_error(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
+ it "can create a new global variable, synonym of the original" do
+ code = '$a = 1; alias $b $a; p [$a, $b]; $b = 2; p [$a, $b]'
+ ruby_exe(code).should == "[1, 1]\n[2, 2]\n"
+ end
+
+ it "can override an existing global variable and make them synonyms" do
+ code = '$a = 1; $b = 2; alias $b $a; p [$a, $b]; $b = 3; p [$a, $b]'
+ ruby_exe(code).should == "[1, 1]\n[3, 3]\n"
+ end
+
+ it "supports aliasing twice the same global variables" do
+ code = '$a = 1; alias $b $a; alias $b $a; p [$a, $b]'
+ ruby_exe(code).should == "[1, 1]\n"
+ end
end
diff --git a/spec/ruby/language/and_spec.rb b/spec/ruby/language/and_spec.rb
index e084fd3cef..55a2a3103a 100644
--- a/spec/ruby/language/and_spec.rb
+++ b/spec/ruby/language/and_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The '&&' statement" do
diff --git a/spec/ruby/language/array_spec.rb b/spec/ruby/language/array_spec.rb
index c3ed8c14c5..2583cffbf7 100644
--- a/spec/ruby/language/array_spec.rb
+++ b/spec/ruby/language/array_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/array', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/array'
describe "Array literals" do
it "[] should return a new array populated with the given elements" do
@@ -36,6 +36,13 @@ describe "Array literals" do
[1, *nil, 3].should == [1, 3]
[*nil, *nil, *nil].should == []
end
+
+ it "evaluates each argument exactly once" do
+ se = ArraySpec::SideEffect.new
+ se.array_result(true)
+ se.array_result(false)
+ se.call_count.should == 4
+ end
end
describe "Bareword array literal" do
diff --git a/spec/ruby/language/assignments_spec.rb b/spec/ruby/language/assignments_spec.rb
new file mode 100644
index 0000000000..c4adf73c1c
--- /dev/null
+++ b/spec/ruby/language/assignments_spec.rb
@@ -0,0 +1,590 @@
+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_error(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_error(SyntaxError, /unexpected block arg given in index assignment|block arg given in index assignment/)
+ end
+ end
+ end
+
+ context "given keyword arguments" do
+ before do
+ @klass = Class.new do
+ attr_reader :x
+
+ def []=(*args, **kw)
+ @x = [args, kw]
+ end
+ end
+ end
+
+ ruby_version_is ""..."3.4" do
+ it "supports keyword arguments in index assignments" do
+ a = @klass.new
+ eval "a[1, 2, 3, b: 4] = 5"
+ a.x.should == [[1, 2, 3, {b: 4}, 5], {}]
+ end
+ end
+
+ ruby_version_is "3.4" do
+ it "raises SyntaxError when given keyword arguments in index assignments" do
+ a = @klass.new
+ -> { eval "a[1, 2, 3, b: 4] = 5" }.should raise_error(SyntaxError,
+ /keywords are not allowed in index assignment expressions|keyword arg given in index assignment/) # prism|parse.y
+ end
+ end
+ end
+ end
+
+ describe 'using +=' do
+ 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_error(SyntaxError, /unexpected block arg given in index assignment|block arg given in index assignment/)
+ end
+ end
+ end
+
+ context "given keyword arguments" do
+ before do
+ @klass = Class.new do
+ attr_reader :x
+
+ def [](*args)
+ 100
+ end
+
+ def []=(*args, **kw)
+ @x = [args, kw]
+ end
+ end
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "supports keyword arguments in index assignments" do
+ a = @klass.new
+ eval "a[1, 2, 3, b: 4] += 5"
+ a.x.should == [[1, 2, 3, {b: 4}, 105], {}]
+ end
+ end
+
+ ruby_version_is "3.3"..."3.4" do
+ it "supports keyword arguments in index assignments" do
+ a = @klass.new
+ eval "a[1, 2, 3, b: 4] += 5"
+ a.x.should == [[1, 2, 3, 105], {b: 4}]
+ end
+ end
+
+ ruby_version_is "3.4" do
+ it "raises SyntaxError when given keyword arguments in index assignments" do
+ a = @klass.new
+ -> { eval "a[1, 2, 3, b: 4] += 5" }.should raise_error(SyntaxError,
+ /keywords are not allowed in index assignment expressions|keyword arg given in index assignment/) # prism|parse.y
+ end
+ end
+ end
+
+ context 'splatted argument' do
+ it 'correctly handles it' do
+ @b[:m] = 10
+ (@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 733e90211c..cc003b8946 100644
--- a/spec/ruby/language/block_spec.rb
+++ b/spec/ruby/language/block_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/block', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/block'
describe "A block yielded a single" do
before :all do
@@ -36,89 +36,102 @@ describe "A block yielded a single" do
m([1, 2]) { |a=5, b=4, c=3| [a, b, c] }.should == [1, 2, 3]
end
- it "assgins elements to post arguments" do
+ it "assigns elements to post arguments" 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
- 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, 2, {x: 9}]
+ 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
- it "assigns symbol keys from a Hash to keyword arguments" do
- result = m(["a" => 1, a: 10]) { |a=nil, **b| [a, b] }
- result.should == [{"a" => 1}, a: 10]
+ 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
- it "assigns symbol keys from a Hash returned by #to_hash to keyword arguments" 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 a keyword rest argument is present" do
+ m([1, 2]) { |a, **k| [a, k] }.should == [[1, 2], {}]
+ end
- result = m([obj]) { |a=nil, **b| [a, b] }
- result.should == [{"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
+
+ it "raises error when required keyword arguments are present" do
+ -> {
+ m([1, 2]) { |a, b:, c:| [a, b, c] }
+ }.should raise_error(ArgumentError, "missing keywords: :b, :c")
+ end
+
+ it "assigns elements to mixed argument types" do
+ 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
- ruby_version_is "2.2.1" do # SEGV on MRI 2.2.0
- it "calls #to_hash on the argument but does not use the result when no keywords are present" do
+ 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_receive(:to_hash).and_return({"a" => 1, "b" => 2})
+ obj.should_not_receive(:to_hash)
result = m([obj]) { |a=nil, **b| [a, b] }
- result.should == [{"a" => 1, "b" => 2}, {}]
+ result.should == [[obj], {}]
end
end
- it "assigns non-symbol keys to non-keyword arguments" do
- result = m(["a" => 10, b: 2]) { |a=nil, **b| [a, b] }
- result.should == [{"a" => 10}, {b: 2}]
- 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)
- 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}, {}]
+ result = m([obj]) { |a=nil, **b| [a, b] }
+ result.should == [[obj], {}]
end
- it "calls #to_hash on the last element if keyword arguments are present" do
- obj = mock("destructure block keyword arguments")
- obj.should_receive(:to_hash).and_return({x: 9})
+ 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
+ result = m(["a" => 10, b: 2]) { |a=nil, **b| [a, b] }
+ result.should == [[{"a" => 10, b: 2}], {}]
+ end
+ end
+ end
- result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] }
- result.should == [1, [2], 3, {x: 9}]
+ 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
- it "assigns the last element to a non-keyword argument if #to_hash returns nil" 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_receive(:to_hash).and_return(nil)
+ 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
- it "calls #to_hash on the last element when there are more arguments than parameters" do
+ 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_receive(:to_hash).and_return({x: 9})
+ 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, {x: 9}]
- 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)
-
- lambda { 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)
-
- lambda { m([1, 2, 3, obj]) { |a, *b, c, **k| } }.should raise_error(error)
+ result.should == [1, 2, 3, {}]
end
it "does not call #to_ary on the Array" do
@@ -165,11 +178,54 @@ 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 "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)
- lambda { m(obj) { |a, b| } }.should raise_error(TypeError)
+ -> { m(obj) { |a, b| } }.should raise_error(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_error(RuntimeError, "Exception raised in #to_ary")
end
end
end
@@ -217,6 +273,10 @@ describe "A block" do
it "does not raise an exception when values are yielded" do
@y.s(0) { 1 }.should == 1
end
+
+ it "may include a rescue clause" do
+ @y.z do raise ArgumentError; rescue ArgumentError; 7; end.should == 7
+ end
end
describe "taking || arguments" do
@@ -227,6 +287,10 @@ describe "A block" do
it "does not raise an exception when values are yielded" do
@y.s(0) { || 1 }.should == 1
end
+
+ it "may include a rescue clause" do
+ @y.z do || raise ArgumentError; rescue ArgumentError; 7; end.should == 7
+ end
end
describe "taking |a| arguments" do
@@ -252,10 +316,14 @@ describe "A block" do
it "does not destructure a single Array value" do
@y.s([1, 2]) { |a| a }.should == [1, 2]
end
+
+ it "may include a rescue clause" do
+ @y.s(1) do |x| raise ArgumentError; rescue ArgumentError; 7; end.should == 7
+ end
end
describe "taking |a, b| arguments" do
- it "assgins nil to the arguments when no values are yielded" do
+ it "assigns nil to the arguments when no values are yielded" do
@y.z { |a, b| [a, b] }.should == [nil, nil]
end
@@ -314,16 +382,15 @@ describe "A block" do
obj = mock("block yield to_ary invalid")
obj.should_receive(:to_ary).and_return(1)
- lambda { @y.s(obj) { |a, b| } }.should raise_error(TypeError)
+ -> { @y.s(obj) { |a, b| } }.should raise_error(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)
- lambda { @y.s(obj) { |a, b| } }.should raise_error(ZeroDivisionError)
+ -> { @y.s(obj) { |a, b| } }.should raise_error(ZeroDivisionError)
end
-
end
describe "taking |a, *b| arguments" do
@@ -378,7 +445,7 @@ describe "A block" do
obj = mock("block yield to_ary invalid")
obj.should_receive(:to_ary).and_return(1)
- lambda { @y.s(obj) { |a, *b| } }.should raise_error(TypeError)
+ -> { @y.s(obj) { |a, *b| } }.should raise_error(TypeError)
end
end
@@ -459,7 +526,7 @@ describe "A block" do
@y.z { |a, | a }.should be_nil
end
- it "assgins the argument a single value yielded" do
+ it "assigns the argument a single value yielded" do
@y.s(1) { |a, | a }.should == 1
end
@@ -503,7 +570,7 @@ describe "A block" do
obj = mock("block yield to_ary invalid")
obj.should_receive(:to_ary).and_return(1)
- lambda { @y.s(obj) { |a, | } }.should raise_error(TypeError)
+ -> { @y.s(obj) { |a, | } }.should raise_error(TypeError)
end
end
@@ -545,7 +612,7 @@ describe "A block" do
obj = mock("block yield to_ary invalid")
obj.should_receive(:to_ary).and_return(1)
- lambda { @y.s(obj) { |(a, b)| } }.should raise_error(TypeError)
+ -> { @y.s(obj) { |(a, b)| } }.should raise_error(TypeError)
end
end
@@ -586,7 +653,7 @@ describe "A block" do
obj = mock("block yield to_ary invalid")
obj.should_receive(:to_ary).and_return(1)
- lambda { @y.s(obj) { |(a, b), c| } }.should raise_error(TypeError)
+ -> { @y.s(obj) { |(a, b), c| } }.should raise_error(TypeError)
end
end
@@ -626,6 +693,12 @@ describe "A block" do
end
end
+ describe "taking |*a, b:|" do
+ it "merges the hash into the splatted array" do
+ @y.k { |*a, b:| [a, b] }.should == [[], true]
+ end
+ end
+
describe "arguments with _" do
it "extracts arguments with _" do
@y.m([[1, 2, 3], 4]) { |(_, a, _), _| a }.should == 2
@@ -639,15 +712,51 @@ describe "A block" do
describe "taking identically-named arguments" do
it "raises a SyntaxError for standard arguments" do
- lambda { eval "lambda { |x,x| }" }.should raise_error(SyntaxError)
- lambda { eval "->(x,x) {}" }.should raise_error(SyntaxError)
- lambda { eval "Proc.new { |x,x| }" }.should raise_error(SyntaxError)
+ -> { eval "lambda { |x,x| }" }.should raise_error(SyntaxError)
+ -> { eval "->(x,x) {}" }.should raise_error(SyntaxError)
+ -> { eval "Proc.new { |x,x| }" }.should raise_error(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 be_an_instance_of(Proc) # rubocop:disable Style/Lambda
+ -> _,_ {}.should be_an_instance_of(Proc)
+ Proc.new { |_,_| }.should be_an_instance_of(Proc)
+ end
+ end
+
+ 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
@@ -662,27 +771,27 @@ describe "Block-local variables" do
end
it "can not have the same name as one of the standard parameters" do
- lambda { eval "[1].each {|foo; foo| }" }.should raise_error(SyntaxError)
- lambda { eval "[1].each {|foo, bar; glark, bar| }" }.should raise_error(SyntaxError)
+ -> { eval "[1].each {|foo; foo| }" }.should raise_error(SyntaxError)
+ -> { eval "[1].each {|foo, bar; glark, bar| }" }.should raise_error(SyntaxError)
end
it "can not be prefixed with an asterisk" do
- lambda { eval "[1].each {|foo; *bar| }" }.should raise_error(SyntaxError)
- lambda do
+ -> { eval "[1].each {|foo; *bar| }" }.should raise_error(SyntaxError)
+ -> do
eval "[1].each {|foo, bar; glark, *fnord| }"
end.should raise_error(SyntaxError)
end
it "can not be prefixed with an ampersand" do
- lambda { eval "[1].each {|foo; &bar| }" }.should raise_error(SyntaxError)
- lambda do
+ -> { eval "[1].each {|foo; &bar| }" }.should raise_error(SyntaxError)
+ -> do
eval "[1].each {|foo, bar; glark, &fnord| }"
end.should raise_error(SyntaxError)
end
it "can not be assigned default values" do
- lambda { eval "[1].each {|foo; bar=1| }" }.should raise_error(SyntaxError)
- lambda do
+ -> { eval "[1].each {|foo; bar=1| }" }.should raise_error(SyntaxError)
+ -> do
eval "[1].each {|foo, bar; glark, fnord=:fnord| }"
end.should raise_error(SyntaxError)
end
@@ -693,8 +802,8 @@ describe "Block-local variables" do
end
it "only allow a single semi-colon in the parameter list" do
- lambda { eval "[1].each {|foo; bar; glark| }" }.should raise_error(SyntaxError)
- lambda { eval "[1].each {|; bar; glark| }" }.should raise_error(SyntaxError)
+ -> { eval "[1].each {|foo; bar; glark| }" }.should raise_error(SyntaxError)
+ -> { eval "[1].each {|; bar; glark| }" }.should raise_error(SyntaxError)
end
it "override shadowed variables from the outer scope" do
@@ -756,14 +865,20 @@ describe "Post-args" do
end.call(1, 2, 3).should == [[], 1, 2, 3]
end
- it "are required" do
- lambda {
- lambda do |*a, b|
+ it "are required for a lambda" do
+ -> {
+ -> *a, b do
[a, b]
end.call
}.should raise_error(ArgumentError)
end
+ it "are assigned to nil when not enough arguments are given to a proc" do
+ proc do |a, *b, c|
+ [a, b, c]
+ end.call.should == [nil, [], nil]
+ end
+
describe "with required args" do
it "gathers remaining args in the splat" do
@@ -826,26 +941,27 @@ describe "Post-args" do
end
describe "with a circular argument reference" do
- it "shadows an existing local with the same name as the argument" do
- a = 1
- -> {
- @proc = eval "proc { |a=a| a }"
- }.should complain(/circular argument reference/)
- @proc.call.should == nil
+ ruby_version_is ""..."3.4" do
+ it "raises a SyntaxError if using the argument in its default value" do
+ a = 1
+ -> {
+ eval "proc { |a=a| a }"
+ }.should raise_error(SyntaxError)
+ end
end
- it "shadows an existing method with the same name as the argument" do
- def a; 1; end
- -> {
- @proc = eval "proc { |a=a| a }"
- }.should complain(/circular argument reference/)
- @proc.call.should == nil
+ ruby_version_is "3.4" do
+ it "is nil if using the argument in its default value" do
+ -> {
+ 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
@@ -863,3 +979,160 @@ 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_error(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 with no ordinary parameters" do
+ ruby_version_is "3.3"..."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 "3.4" do
+ it "does not emit a deprecation warning" do
+ -> {
+ eval "proc { it }"
+ }.should_not complain
+ end
+
+ it "acts as the first argument if no local variables exist" do
+ eval("proc { it * 2 }").call(5).should == 10
+ end
+
+ it "can be reassigned to act as a local variable" do
+ eval("proc { tmp = it; it = tmp * 2; it }").call(21).should == 42
+ end
+
+ it "can be used in nested calls" do
+ eval("proc { it.map { it * 2 } }").call([1, 2, 3]).should == [2, 4, 6]
+ end
+
+ it "cannot be mixed with numbered parameters" do
+ -> {
+ eval "proc { it + _1 }"
+ }.should raise_error(SyntaxError, /numbered parameters are not allowed when 'it' is already used|'it' is already used in/)
+
+ -> {
+ eval "proc { _1 + it }"
+ }.should raise_error(SyntaxError, /numbered parameter is already used in|'it' is not allowed when a numbered parameter is already used/)
+ end
+ end
+end
+
+describe "if `it` is defined as a variable" do
+ it "treats `it` as a captured variable if defined outside of a block" do
+ it = 5
+ proc { it }.call(0).should == 5
+ end
+
+ it "treats `it` as a local variable if defined inside of a block" do
+ proc { it = 5; it }.call(0).should == 5
+ end
+end
diff --git a/spec/ruby/language/break_spec.rb b/spec/ruby/language/break_spec.rb
index 09e8ff3d93..7e5b6fb328 100644
--- a/spec/ruby/language/break_spec.rb
+++ b/spec/ruby/language/break_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/break', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/break'
describe "The break statement in a block" do
before :each do
@@ -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
- lambda { @program.break_in_method }.should raise_error(LocalJumpError)
+ -> { @program.break_in_method }.should raise_error(LocalJumpError)
ScratchPad.recorded.should == [:a, :xa, :d, :b]
end
it "raises a LocalJumpError when invoking the block from a method" do
- lambda { @program.break_in_nested_method }.should raise_error(LocalJumpError)
+ -> { @program.break_in_nested_method }.should raise_error(LocalJumpError)
ScratchPad.recorded.should == [:a, :xa, :cc, :aa, :b]
end
it "raises a LocalJumpError when yielding to the block" do
- lambda { @program.break_in_yielding_method }.should raise_error(LocalJumpError)
+ -> { @program.break_in_yielding_method }.should raise_error(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
- lambda { @program.break_in_method_captured }.should raise_error(LocalJumpError)
+ -> { @program.break_in_method_captured }.should raise_error(LocalJumpError)
ScratchPad.recorded.should == [:a, :za, :xa, :zd, :zb]
end
it "raises a LocalJumpError when yielding to the block" do
- lambda { @program.break_in_yield_captured }.should raise_error(LocalJumpError)
+ -> { @program.break_in_yield_captured }.should raise_error(LocalJumpError)
ScratchPad.recorded.should == [:a, :za, :xa, :zd, :aa, :zb]
end
end
@@ -100,7 +100,7 @@ describe "The break statement in a lambda" do
end
it "returns from the lambda" do
- l = lambda {
+ l = -> {
ScratchPad << :before
break :foo
ScratchPad << :after
@@ -111,7 +111,7 @@ describe "The break statement in a lambda" do
it "returns from the call site if the lambda is passed as a block" do
def mid(&b)
- lambda {
+ -> {
ScratchPad << :before
b.call
ScratchPad << :unreachable1
@@ -208,7 +208,7 @@ describe "Break inside a while loop" do
it "passes the value returned by a method with omitted parenthesis and passed block" do
obj = BreakSpecs::Block.new
- lambda { break obj.method :value do |x| x end }.call.should == :value
+ -> { break obj.method :value do |x| x end }.call.should == :value
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_error(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_error(SyntaxError)
+ end
+end
# TODO: Rewrite all the specs from here to the end of the file in the style
# above.
@@ -362,4 +381,22 @@ describe "Executing break from within a block" do
bt2.three
ScratchPad.recorded.should == [:two_ensure, :three_post, :three_ensure]
end
+
+ it "works when passing through a super call" do
+ cls1 = Class.new { def foo; yield; end }
+ cls2 = Class.new(cls1) { def foo; super { break 1 }; end }
+
+ -> do
+ cls2.new.foo.should == 1
+ end.should_not raise_error
+ end
+
+ 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
end
diff --git a/spec/ruby/language/case_spec.rb b/spec/ruby/language/case_spec.rb
index 25f5d0efc4..464d06e46a 100644
--- a/spec/ruby/language/case_spec.rb
+++ b/spec/ruby/language/case_spec.rb
@@ -1,17 +1,17 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The 'case'-construct" do
it "evaluates the body of the when clause matching the case target expression" do
case 1
- when 2; false
- when 1; true
+ when 2; false
+ when 1; true
end.should == true
end
it "evaluates the body of the when clause whose array expression includes the case target expression" do
case 2
- when 3, 4; false
- when 1, 2; true
+ when 3, 4; false
+ when 1, 2; true
end.should == true
end
@@ -21,7 +21,7 @@ describe "The 'case'-construct" do
def bar; @calls << :bar; end
case true
- when foo, bar;
+ when foo, bar;
end
@calls.should == [:foo, :bar]
@@ -29,31 +29,31 @@ describe "The 'case'-construct" do
it "evaluates the body of the when clause whose range expression includes the case target expression" do
case 5
- when 21..30; false
- when 1..20; true
+ when 21..30; false
+ when 1..20; true
end.should == true
end
it "returns nil when no 'then'-bodies are given" do
case "a"
- when "a"
- when "b"
+ when "a"
+ when "b"
end.should == nil
end
it "evaluates the 'else'-body when no other expression matches" do
case "c"
- when "a"; 'foo'
- when "b"; 'bar'
- else 'zzz'
+ when "a"; 'foo'
+ when "b"; 'bar'
+ else 'zzz'
end.should == 'zzz'
end
it "returns nil when no expression matches and 'else'-body is empty" do
case "c"
- when "a"; "a"
- when "b"; "b"
- else
+ when "a"; "a"
+ when "b"; "b"
+ else
end.should == nil
end
@@ -70,105 +70,152 @@ describe "The 'case'-construct" do
it "returns the statement following 'then'" do
case "a"
- when "a" then 'foo'
- when "b" then 'bar'
+ when "a" then 'foo'
+ when "b" then 'bar'
end.should == 'foo'
end
it "tests classes with case equality" do
case "a"
- when String
- 'foo'
- when Symbol
- 'bar'
+ when String
+ 'foo'
+ when Symbol
+ 'bar'
end.should == 'foo'
end
it "tests with matching regexps" do
case "hello"
- when /abc/; false
- when /^hell/; true
+ when /abc/; false
+ when /^hell/; true
end.should == true
end
+ it "tests with matching regexps and sets $~ and captures" do
+ case "foo42"
+ when /oo(\d+)/
+ $~.should be_kind_of(MatchData)
+ $1.should == "42"
+ else
+ flunk
+ end
+ $~.should be_kind_of(MatchData)
+ $1.should == "42"
+ end
+
+ it "tests with a string interpolated in a regexp" do
+ digits = '\d+'
+ case "foo44"
+ when /oo(#{digits})/
+ $~.should be_kind_of(MatchData)
+ $1.should == "44"
+ else
+ flunk
+ end
+ $~.should be_kind_of(MatchData)
+ $1.should == "44"
+ end
+
+ it "tests with a regexp interpolated within another regexp" do
+ digits_regexp = /\d+/
+ case "foo43"
+ when /oo(#{digits_regexp})/
+ $~.should be_kind_of(MatchData)
+ $1.should == "43"
+ else
+ flunk
+ end
+ $~.should be_kind_of(MatchData)
+ $1.should == "43"
+ end
+
it "does not test with equality when given classes" do
case :symbol.class
- when Symbol
- "bar"
- when String
- "bar"
- else
- "foo"
+ when Symbol
+ "bar"
+ when String
+ "bar"
+ else
+ "foo"
end.should == "foo"
end
it "takes lists of values" do
case 'z'
- when 'a', 'b', 'c', 'd'
- "foo"
- when 'x', 'y', 'z'
- "bar"
+ when 'a', 'b', 'c', 'd'
+ "foo"
+ when 'x', 'y', 'z'
+ "bar"
end.should == "bar"
case 'b'
- when 'a', 'b', 'c', 'd'
- "foo"
- when 'x', 'y', 'z'
- "bar"
+ when 'a', 'b', 'c', 'd'
+ "foo"
+ when 'x', 'y', 'z'
+ "bar"
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']
- "foo"
- when *['x', 'y', 'z']
- "bar"
+ when *['a', 'b', 'c', 'd']
+ "foo"
+ when *['x', 'y', 'z']
+ "bar"
end.should == "bar"
end
it "takes an expanded array in addition to a list of values" do
case 'f'
- when 'f', *['a', 'b', 'c', 'd']
- "foo"
- when *['x', 'y', 'z']
- "bar"
+ when 'f', *['a', 'b', 'c', 'd']
+ "foo"
+ when *['x', 'y', 'z']
+ "bar"
end.should == "foo"
case 'b'
- when 'f', *['a', 'b', 'c', 'd']
- "foo"
- when *['x', 'y', 'z']
- "bar"
+ when 'f', *['a', 'b', 'c', 'd']
+ "foo"
+ when *['x', 'y', 'z']
+ "bar"
end.should == "foo"
end
it "takes an expanded array before additional listed values" do
case 'f'
- when *['a', 'b', 'c', 'd'], 'f'
- "foo"
- when *['x', 'y', 'z']
- "bar"
+ when *['a', 'b', 'c', 'd'], 'f'
+ "foo"
+ when *['x', 'y', 'z']
+ "bar"
end.should == 'foo'
end
it "expands arrays from variables before additional listed values" do
a = ['a', 'b', 'c']
case 'a'
- when *a, 'd', 'e'
- "foo"
- when 'x'
- "bar"
+ when *a, 'd', 'e'
+ "foo"
+ when 'x'
+ "bar"
end.should == "foo"
end
it "expands arrays from variables before a single additional listed value" do
a = ['a', 'b', 'c']
case 'a'
- when *a, 'd'
- "foo"
- when 'x'
- "bar"
+ when *a, 'd'
+ "foo"
+ when 'x'
+ "bar"
end.should == "foo"
end
@@ -177,10 +224,10 @@ describe "The 'case'-construct" do
b = ['d', 'e', 'f']
case 'f'
- when *a, *b, 'g', 'h'
- "foo"
- when 'x'
- "bar"
+ when *a, *b, 'g', 'h'
+ "foo"
+ when 'x'
+ "bar"
end.should == "foo"
end
@@ -190,47 +237,47 @@ describe "The 'case'-construct" do
b = ['f']
case 'f'
- when 'f', *a|b
- "foo"
- when *['x', 'y', 'z']
- "bar"
+ when 'f', *a|b
+ "foo"
+ when *['x', 'y', 'z']
+ "bar"
end.should == "foo"
end
it "never matches when clauses with no values" do
case nil
- when *[]
- "foo"
+ when *[]
+ "foo"
end.should == nil
end
it "lets you define a method after the case statement" do
case (def foo; 'foo'; end; 'f')
- when 'a'
- 'foo'
- when 'f'
- 'bar'
+ when 'a'
+ 'foo'
+ when 'f'
+ 'bar'
end.should == 'bar'
end
it "raises a SyntaxError when 'else' is used when no 'when' is given" do
- lambda {
+ -> {
eval <<-CODE
case 4
- else
- true
+ else
+ true
end
CODE
}.should raise_error(SyntaxError)
end
it "raises a SyntaxError when 'else' is used before a 'when' was given" do
- lambda {
+ -> {
eval <<-CODE
case 4
- else
- true
- when 4; false
+ else
+ true
+ when 4; false
end
CODE
}.should raise_error(SyntaxError)
@@ -282,61 +329,18 @@ 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
- when true; "bad"
- when Integer; "good"
+ when true; "bad"
+ when Integer; "good"
end.should == "good"
end
it "evaluates false as only 'false' when false is the first clause" do
case nil
- when false; "bad"
- when nil; "good"
+ when false; "bad"
+ when nil; "good"
end.should == "good"
end
@@ -352,17 +356,17 @@ describe "The 'case'-construct with no target expression" do
a2 = ['b', 'a', 'r']
case 'f'
- when *a1, *['x', 'y', 'z']
- "foo"
- when *a2, *['x', 'y', 'z']
- "bar"
+ when *a1, *['x', 'y', 'z']
+ "foo"
+ when *a2, *['x', 'y', 'z']
+ "bar"
end.should == "foo"
case 'b'
- when *a1, *['x', 'y', 'z']
- "foo"
- when *a2, *['x', 'y', 'z']
- "bar"
+ when *a1, *['x', 'y', 'z']
+ "foo"
+ when *a2, *['x', 'y', 'z']
+ "bar"
end.should == "bar"
end
@@ -386,4 +390,120 @@ describe "The 'case'-construct with no target expression" do
:called
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
+ case
+ when 1; 'foo'
+ 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 ba4af3d880..6fb785fd56 100644
--- a/spec/ruby/language/class_spec.rb
+++ b/spec/ruby/language/class_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/class', __FILE__)
+require_relative '../spec_helper'
+require_relative '../fixtures/class'
ClassSpecsNumber = 12
@@ -13,11 +13,22 @@ describe "The class keyword" do
ClassSpecsKeywordWithSemicolon.should be_an_instance_of(Class)
end
- ruby_version_is "2.3" do
- 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)
- 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)
+ end
+
+ it "can redefine a class when called from a block" do
+ ClassSpecs::DEFINE_CLASS.call
+ A.should be_an_instance_of(Class)
+
+ Object.send(:remove_const, :A)
+ defined?(A).should be_nil
+
+ ClassSpecs::DEFINE_CLASS.call
+ A.should be_an_instance_of(Class)
+ ensure
+ Object.send(:remove_const, :A) if defined?(::A)
end
end
@@ -32,27 +43,34 @@ describe "A class definition" do
end
it "raises TypeError if constant given as class name exists and is not a Module" do
- lambda {
+ -> {
class ClassSpecsNumber
end
- }.should raise_error(TypeError)
+ }.should raise_error(TypeError, /\AClassSpecsNumber is not a class/)
+ end
+
+ it "raises TypeError if constant given as class name exists and is a Module but not a Class" do
+ -> {
+ class ClassSpecs
+ end
+ }.should raise_error(TypeError, /\AClassSpecs is not a class/)
end
# test case known to be detecting bugs (JRuby, MRI)
it "raises TypeError if the constant qualifying the class is nil" do
- lambda {
+ -> {
class nil::Foo
end
}.should raise_error(TypeError)
end
it "raises TypeError if any constant qualifying the class is not a Module" do
- lambda {
+ -> {
class ClassSpecs::Number::MyClass
end
}.should raise_error(TypeError)
- lambda {
+ -> {
class ClassSpecsNumber::MyClass
end
}.should raise_error(TypeError)
@@ -66,7 +84,7 @@ describe "A class definition" do
module ClassSpecs
class SuperclassResetToSubclass < L
end
- lambda {
+ -> {
class SuperclassResetToSubclass < M
end
}.should raise_error(TypeError, /superclass mismatch/)
@@ -79,7 +97,7 @@ describe "A class definition" do
end
SuperclassReopenedBasicObject.superclass.should == A
- lambda {
+ -> {
class SuperclassReopenedBasicObject < BasicObject
end
}.should raise_error(TypeError, /superclass mismatch/)
@@ -94,7 +112,7 @@ describe "A class definition" do
end
SuperclassReopenedObject.superclass.should == A
- lambda {
+ -> {
class SuperclassReopenedObject < Object
end
}.should raise_error(TypeError, /superclass mismatch/)
@@ -119,7 +137,7 @@ describe "A class definition" do
class NoSuperclassSet
end
- lambda {
+ -> {
class NoSuperclassSet < String
end
}.should raise_error(TypeError, /superclass mismatch/)
@@ -129,7 +147,7 @@ describe "A class definition" do
it "allows using self as the superclass if self is a class" do
ClassSpecs::I::J.superclass.should == ClassSpecs::I
- lambda {
+ -> {
class ShouldNotWork < self; end
}.should raise_error(TypeError)
end
@@ -150,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
- lambda { class ClassSpecs::MetaclassSuper < meta; end }.should raise_error(TypeError)
+ -> { class ClassSpecs::MetaclassSuper < meta; end }.should raise_error(TypeError)
end
it "allows the declaration of class variables in the body" do
@@ -212,16 +230,16 @@ describe "A class definition" do
describe "within a block creates a new class in the lexical scope" do
it "for named classes at the toplevel" do
klass = Class.new do
- class Howdy
+ class CS_CONST_CLASS_SPECS
end
def self.get_class_name
- Howdy.name
+ CS_CONST_CLASS_SPECS.name
end
end
- Howdy.name.should == 'Howdy'
- klass.get_class_name.should == 'Howdy'
+ klass.get_class_name.should == 'CS_CONST_CLASS_SPECS'
+ ::CS_CONST_CLASS_SPECS.name.should == 'CS_CONST_CLASS_SPECS'
end
it "for named classes in a module" do
@@ -260,6 +278,8 @@ describe "A class definition" do
AnonWithConstant.name.should == 'AnonWithConstant'
klass.get_class_name.should == 'AnonWithConstant'
+ ensure
+ Object.send(:remove_const, :AnonWithConstant)
end
end
end
@@ -276,7 +296,7 @@ describe "A class definition extending an object (sclass)" do
end
it "raises a TypeError when trying to extend numbers" do
- lambda {
+ -> {
eval <<-CODE
class << 1
def xyz
@@ -287,8 +307,20 @@ describe "A class definition extending an object (sclass)" do
}.should raise_error(TypeError)
end
- it "allows accessing the block of the original scope" do
- ClassSpecs.sclass_with_block { 123 }.should == 123
+ 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
+
+ it "does not allow accessing the block of the original scope" do
+ -> {
+ ClassSpecs.sclass_with_block { 123 }
+ }.should raise_error(SyntaxError)
end
it "can use return to cause the enclosing method to return" do
@@ -308,11 +340,11 @@ describe "Reopening a class" do
end
it "raises a TypeError when superclasses mismatch" do
- lambda { class ClassSpecs::A < Array; end }.should raise_error(TypeError)
+ -> { class ClassSpecs::A < Array; end }.should raise_error(TypeError)
end
it "adds new methods to subclasses" do
- lambda { ClassSpecs::M.m }.should raise_error(NoMethodError)
+ -> { ClassSpecs::M.m }.should raise_error(NoMethodError)
class ClassSpecs::L
def self.m
1
@@ -321,6 +353,39 @@ describe "Reopening a class" do
ClassSpecs::M.m.should == 1
ClassSpecs::L.singleton_class.send(:remove_method, :m)
end
+
+ it "does not reopen a class included in Object" do
+ ruby_exe(<<~RUBY).should == "false"
+ module IncludedInObject
+ class IncludedClass
+ end
+ end
+ class Object
+ include IncludedInObject
+ end
+ class IncludedClass
+ end
+ print IncludedInObject::IncludedClass == Object::IncludedClass
+ RUBY
+ end
+
+ it "does not reopen a class included in non-Object modules" do
+ ruby_exe(<<~RUBY).should == "false/false"
+ module Included
+ module IncludedClass; end
+ end
+ module M
+ include Included
+ module IncludedClass; end
+ end
+ class C
+ include Included
+ module IncludedClass; end
+ end
+ print Included::IncludedClass == M::IncludedClass, "/",
+ Included::IncludedClass == C::IncludedClass
+ RUBY
+ end
end
describe "class provides hooks" do
diff --git a/spec/ruby/language/class_variable_spec.rb b/spec/ruby/language/class_variable_spec.rb
index 463f731a93..a26a3fb8de 100644
--- a/spec/ruby/language/class_variable_spec.rb
+++ b/spec/ruby/language/class_variable_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/class_variables', __FILE__)
+require_relative '../spec_helper'
+require_relative '../fixtures/class_variables'
describe "A class variable" do
after :each do
@@ -70,15 +70,45 @@ describe 'A class variable definition' do
c = Class.new(b)
b.class_variable_set(:@@cv, :value)
- lambda { a.class_variable_get(:@@cv) }.should raise_error(NameError)
+ -> { a.class_variable_get(:@@cv) }.should raise_error(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)
- lambda { a.class_variable_get(:@@cv) }.should raise_error(NameError)
+ -> { a.class_variable_get(:@@cv) }.should raise_error(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_error(RuntimeError, 'class variable access from toplevel')
+ -> {
+ eval "@@cvar_toplevel2 = 2"
+ }.should raise_error(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_error
+ 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_error(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
new file mode 100644
index 0000000000..dd788e681c
--- /dev/null
+++ b/spec/ruby/language/comment_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../spec_helper'
+
+describe "The comment" do
+ it "can be placed between fluent dot now" do
+ code = <<~CODE
+ 10
+ # some comment
+ .to_s
+ CODE
+
+ eval(code).should == '10'
+ end
+end
diff --git a/spec/ruby/language/constants_spec.rb b/spec/ruby/language/constants_spec.rb
index 1f1e254fb8..063c52c422 100644
--- a/spec/ruby/language/constants_spec.rb
+++ b/spec/ruby/language/constants_spec.rb
@@ -1,7 +1,7 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/constants', __FILE__)
-require File.expand_path('../fixtures/constants_sclass', __FILE__)
-require File.expand_path('../fixtures/constant_visibility', __FILE__)
+require_relative '../spec_helper'
+require_relative '../fixtures/constants'
+require_relative 'fixtures/constants_sclass'
+require_relative 'fixtures/constant_visibility'
# Read the documentation in fixtures/constants.rb for the guidelines and
# rationale for the structure and organization of these specs.
@@ -49,10 +49,10 @@ describe "Literal (A::X) constant resolution" do
end
it "does not search the singleton class of the class or module" do
- lambda do
+ -> do
ConstantSpecs::ContainerA::ChildA::CS_CONST14
end.should raise_error(NameError)
- lambda { ConstantSpecs::CS_CONST14 }.should raise_error(NameError)
+ -> { ConstantSpecs::CS_CONST14 }.should raise_error(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
@@ -112,7 +133,7 @@ describe "Literal (A::X) constant resolution" do
CS_CONST108 = :const108_1
end
- lambda do
+ -> do
ConstantSpecs::ContainerB::ChildB::CS_CONST108
end.should raise_error(NameError)
@@ -122,7 +143,10 @@ describe "Literal (A::X) constant resolution" do
end
end
- lambda { ConstantSpecs::CS_CONST108 }.should raise_error(NameError)
+ -> { ConstantSpecs::CS_CONST108 }.should raise_error(NameError)
+ ensure
+ ConstantSpecs::ContainerB::ChildB.singleton_class.send(:remove_const, :CS_CONST108)
+ ConstantSpecs.singleton_class.send(:remove_const, :CS_CONST108)
end
it "returns the updated value when a constant is reassigned" do
@@ -133,25 +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
- lambda { ConstantSpecs::ParentA::CS_CONSTX }.should raise_error(NameError)
+ -> { ConstantSpecs::ParentA::CS_CONSTX }.should raise_error(NameError)
+ end
+
+ 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
+ end
+
+ -> { mod::DOES_NOT_EXIST }.should raise_error(NameError, /uninitialized constant ModuleName::DOES_NOT_EXIST/)
+ 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
+
+ def self.inspect
+ "<unusable info>"
+ end
+ end
+
+ -> { mod::DOES_NOT_EXIST }.should raise_error(NameError, /uninitialized constant <unusable info>::DOES_NOT_EXIST/)
end
it "sends #const_missing to the original class or module scope" do
@@ -163,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
- lambda { CS_CONST1::CS_CONST }.should raise_error(TypeError)
- lambda { 1::CS_CONST }.should raise_error(TypeError)
- lambda { "mod"::CS_CONST }.should raise_error(TypeError)
- lambda { false::CS_CONST }.should raise_error(TypeError)
+ -> { 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)
end
end
@@ -212,7 +264,7 @@ describe "Constant resolution within methods" do
end
it "does not search the lexical scope of the caller" do
- lambda { ConstantSpecs::ClassA.const16 }.should raise_error(NameError)
+ -> { ConstantSpecs::ClassA.const16 }.should raise_error(NameError)
end
it "searches the lexical scope of a block" do
@@ -225,7 +277,7 @@ describe "Constant resolution within methods" do
end
it "does not search the lexical scope of qualifying modules" do
- lambda do
+ -> do
ConstantSpecs::ContainerA::ChildA.const23
end.should raise_error(NameError)
end
@@ -248,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
@@ -256,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
@@ -264,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
@@ -272,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
@@ -279,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
@@ -290,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
@@ -297,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
- lambda { ConstantSpecs::ClassB.const209 }.should raise_error(NameError)
+ -> { ConstantSpecs::ClassB.const209 }.should raise_error(NameError)
+ ensure
+ ConstantSpecs::ClassB.send(:remove_const, :CS_CONST209)
end
it "searches the lexical scope of a block" do
@@ -310,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
@@ -320,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
@@ -332,71 +417,29 @@ 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
ConstantSpecs::ContainerB::CS_CONST214 = :const214
- lambda do
+ -> do
ConstantSpecs::ContainerB::ChildB.const214
end.should raise_error(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
- lambda { ConstantSpecs::ParentA.constx }.should raise_error(NameError)
+ -> { ConstantSpecs::ParentA.constx }.should raise_error(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
@@ -404,46 +447,34 @@ describe "Constant resolution within a singleton class (class << obj)" do
ConstantSpecs::CS_SINGLETON1.foo.should == 1
end
- ruby_version_is "2.3" do
- it "uses its own namespace for each object" do
- a = ConstantSpecs::CS_SINGLETON2[0].foo
- b = ConstantSpecs::CS_SINGLETON2[1].foo
- [a, b].should == [1, 2]
- end
+ it "uses its own namespace for each object" do
+ a = ConstantSpecs::CS_SINGLETON2[0].foo
+ b = ConstantSpecs::CS_SINGLETON2[1].foo
+ [a, b].should == [1, 2]
+ end
- 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)
- end
+ 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)
+ end
- it "allows nested modules to have proper resolution" do
- a = ConstantSpecs::CS_SINGLETON4_CLASSES[0].new
- b = ConstantSpecs::CS_SINGLETON4_CLASSES[1].new
- [a.foo, b.foo].should == [1, 2]
- end
+ it "allows nested modules to have proper resolution" do
+ a = ConstantSpecs::CS_SINGLETON4_CLASSES[0].new
+ b = ConstantSpecs::CS_SINGLETON4_CLASSES[1].new
+ [a.foo, b.foo].should == [1, 2]
end
end
describe "top-level constant lookup" do
context "on a class" do
- ruby_version_is "" ... "2.5" do
- it "searches Object successfully after searching other scopes" do
- ->() {
- String::Hash.should == Hash
- }.should complain(/toplevel constant Hash referenced by/)
- end
- end
-
- ruby_version_is "2.5" do
- it "does not search Object after searching other scopes" do
- ->() { String::Hash }.should raise_error(NameError)
- end
+ it "does not search Object after searching other scopes" do
+ -> { String::Hash }.should raise_error(NameError)
end
end
it "searches Object unsuccessfully when searches on a module" do
- ->() { Enumerable::Hash }.should raise_error(NameError)
+ -> { Enumerable::Hash }.should raise_error(NameError)
end
end
@@ -457,24 +488,35 @@ describe "Module#private_constant marked constants" do
mod.const_set :Foo, false
}.should complain(/already initialized constant/)
- lambda {mod::Foo}.should raise_error(NameError)
+ -> {mod::Foo}.should raise_error(NameError)
+ end
+
+ 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
- lambda do
+ -> do
ConstantVisibility::PrivConstModule::PRIVATE_CONSTANT_MODULE
end.should raise_error(NameError)
end
it "cannot be reopened as a module from scope where constant would be private" do
- lambda do
+ -> do
module ConstantVisibility::ModuleContainer::PrivateModule; end
end.should raise_error(NameError)
end
it "cannot be reopened as a class from scope where constant would be private" do
- lambda do
+ -> do
class ConstantVisibility::ModuleContainer::PrivateClass; end
end.should raise_error(NameError)
end
@@ -487,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
@@ -497,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
@@ -520,29 +570,42 @@ 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 be_true
+ end
+
+ it "can be accessed from modules that include the module" do
+ ConstantVisibility::ModuleIncludingPrivConstModule.private_constant_from_include.should be_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_error(NameError)
+ -> do
+ ConstantVisibility::ModuleIncludingPrivConstModule.private_constant_named_from_include
+ end.should raise_error(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
describe "in a class" do
it "cannot be accessed from outside the class" do
- lambda do
+ -> do
ConstantVisibility::PrivConstClass::PRIVATE_CONSTANT_CLASS
end.should raise_error(NameError)
end
it "cannot be reopened as a module" do
- lambda do
+ -> do
module ConstantVisibility::ClassContainer::PrivateModule; end
end.should raise_error(NameError)
end
it "cannot be reopened as a class" do
- lambda do
+ -> do
class ConstantVisibility::ClassContainer::PrivateClass; end
end.should raise_error(NameError)
end
@@ -555,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
@@ -565,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
@@ -598,7 +669,7 @@ describe "Module#private_constant marked constants" do
describe "in Object" do
it "cannot be accessed using ::Const form" do
- lambda do
+ -> do
::PRIVATE_CONSTANT_IN_OBJECT
end.should raise_error(NameError)
end
@@ -615,6 +686,40 @@ describe "Module#private_constant marked constants" do
defined?(PRIVATE_CONSTANT_IN_OBJECT).should == "constant"
end
end
+
+ describe "NameError by #private_constant" do
+ it "has :receiver and :name attributes" do
+ -> do
+ ConstantVisibility::PrivConstClass::PRIVATE_CONSTANT_CLASS
+ end.should raise_error(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|
+ e.receiver.should == ConstantVisibility::PrivConstModule
+ e.name.should == :PRIVATE_CONSTANT_MODULE
+ }
+ end
+
+ it "has the defined class as the :name attribute" do
+ -> do
+ ConstantVisibility::PrivConstClassChild::PRIVATE_CONSTANT_CLASS
+ end.should raise_error(NameError) {|e|
+ e.receiver.should == ConstantVisibility::PrivConstClass
+ e.name.should == :PRIVATE_CONSTANT_CLASS
+ }
+
+ -> do
+ ConstantVisibility::ClassIncludingPrivConstModule::PRIVATE_CONSTANT_MODULE
+ end.should raise_error(NameError) {|e|
+ e.receiver.should == ConstantVisibility::PrivConstModule
+ e.name.should == :PRIVATE_CONSTANT_MODULE
+ }
+ end
+ end
end
describe "Module#public_constant marked constants" do
@@ -666,3 +771,39 @@ describe "Module#public_constant marked constants" do
end
end
end
+
+describe 'Allowed characters' do
+ it 'allows not ASCII characters in the middle of a name' do
+ mod = Module.new
+ mod.const_set("BBἍBB", 1)
+
+ eval("mod::BBἍBB").should == 1
+ end
+
+ 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
+
+ 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
+
+describe 'Assignment' do
+ context 'dynamic assignment' do
+ it 'raises SyntaxError' do
+ -> do
+ eval <<-CODE
+ def test
+ B = 1
+ end
+ CODE
+ end.should raise_error(SyntaxError, /dynamic constant assignment/)
+ end
+ end
+end
diff --git a/spec/ruby/language/def_spec.rb b/spec/ruby/language/def_spec.rb
index 55ee283b90..0cf1790791 100644
--- a/spec/ruby/language/def_spec.rb
+++ b/spec/ruby/language/def_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/def', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/def'
# Language-level method behaviour
describe "Redefining a method" do
@@ -79,6 +79,40 @@ describe "Defining a method" do
end
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)')
+ 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)')
+ end
+
+ it "raises FrozenError with the correct class name" do
+ -> {
+ Module.new do
+ self.freeze
+ def foo; end
+ end
+ }.should raise_error(FrozenError) { |e|
+ msg_class = ruby_version_is("4.0") ? "Module" : "module"
+ e.message.should == "can't modify frozen #{msg_class}: #{e.receiver}"
+ }
+
+ -> {
+ Class.new do
+ self.freeze
+ def foo; end
+ end
+ }.should raise_error(FrozenError){ |e|
+ msg_class = ruby_version_is("4.0") ? "Class" : "class"
+ e.message.should == "can't modify frozen #{msg_class}: #{e.receiver}"
+ }
+ end
+end
+
describe "An instance method definition with a splat" do
it "accepts an unnamed '*' argument" do
def foo(*); end;
@@ -101,12 +135,12 @@ describe "An instance method definition with a splat" do
end
it "allows only a single * argument" do
- lambda { eval 'def foo(a, *b, *c); end' }.should raise_error(SyntaxError)
+ -> { eval 'def foo(a, *b, *c); end' }.should raise_error(SyntaxError)
end
it "requires the presence of any arguments that precede the *" do
def foo(a, b, *c); end
- lambda { foo 1 }.should raise_error(ArgumentError)
+ -> { foo 1 }.should raise_error(ArgumentError, 'wrong number of arguments (given 1, expected 2+)')
end
end
@@ -139,7 +173,7 @@ describe "An instance method with a default argument" do
def foo(a, b = 2)
[a,b]
end
- lambda { foo }.should raise_error(ArgumentError)
+ -> { foo }.should raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1..2)')
foo(1).should == [1, 2]
end
@@ -147,7 +181,7 @@ describe "An instance method with a default argument" do
def foo(a, b = 2, *c)
[a,b,c]
end
- lambda { foo }.should raise_error(ArgumentError)
+ -> { foo }.should raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1+)')
foo(1).should == [1,2,[]]
end
@@ -165,17 +199,25 @@ describe "An instance method with a default argument" do
foo(2,3,3).should == [2,3,[3]]
end
- it "shadows an existing method with the same name as the local" do
- def bar
- 1
+ 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 raise_error(SyntaxError)
+ end
+ 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
+ foo"
+ }.call.should == nil
end
- -> {
- eval "def foo(bar = bar)
- bar
- end"
- }.should complain(/circular argument reference/)
- foo.should == nil
- foo(2).should == 2
end
it "calls a method with the same name as the local when explicitly using ()" do
@@ -208,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
@@ -234,10 +276,30 @@ describe "A singleton method definition" do
(obj==2).should == 2
end
- it "raises RuntimeError if frozen" do
+ it "raises FrozenError if frozen" do
+ obj = Object.new
+ obj.freeze
+ -> { def obj.foo; end }.should raise_error(FrozenError)
+ end
+
+ it "raises FrozenError with the correct class name" do
obj = Object.new
obj.freeze
- lambda { def obj.foo; end }.should raise_error(RuntimeError)
+ msg_class = ruby_version_is("4.0") ? "Object" : "object"
+ -> { def obj.foo; end }.should raise_error(FrozenError, "can't modify frozen #{msg_class}: #{obj}")
+
+ obj = Object.new
+ c = obj.singleton_class
+ c.singleton_class.freeze
+ -> { def c.foo; end }.should raise_error(FrozenError, "can't modify frozen Class: #{c}")
+
+ c = Class.new
+ c.freeze
+ -> { def c.foo; end }.should raise_error(FrozenError, "can't modify frozen Class: #{c}")
+
+ m = Module.new
+ m.freeze
+ -> { def m.foo; end }.should raise_error(FrozenError, "can't modify frozen Module: #{m}")
end
end
@@ -310,7 +372,7 @@ describe "A method defined with extreme default arguments" do
end
it "may use a lambda as a default" do
- def foo(output = 'a', prc = lambda {|n| output * n})
+ def foo(output = 'a', prc = -> n { output * n })
prc.call(5)
end
foo.should == 'aaaaa'
@@ -356,7 +418,7 @@ describe "A singleton method defined with extreme default arguments" do
it "may use a lambda as a default" do
a = Object.new
- def a.foo(output = 'a', prc = lambda {|n| output * n})
+ def a.foo(output = 'a', prc = -> n { output * n })
prc.call(5)
end
a.foo.should == 'aaaaa'
@@ -372,7 +434,7 @@ describe "A method definition inside a metaclass scope" do
end
DefSpecSingleton.a_class_method.should == DefSpecSingleton
- lambda { Object.a_class_method }.should raise_error(NoMethodError)
+ -> { Object.a_class_method }.should raise_error(NoMethodError)
end
it "can create a singleton method" do
@@ -382,15 +444,15 @@ describe "A method definition inside a metaclass scope" do
end
obj.a_singleton_method.should == obj
- lambda { Object.new.a_singleton_method }.should raise_error(NoMethodError)
+ -> { Object.new.a_singleton_method }.should raise_error(NoMethodError)
end
- it "raises RuntimeError if frozen" do
+ it "raises FrozenError if frozen" do
obj = Object.new
obj.freeze
class << obj
- lambda { def foo; end }.should raise_error(RuntimeError)
+ -> { def foo; end }.should raise_error(FrozenError)
end
end
end
@@ -426,11 +488,11 @@ describe "A nested method definition" do
end
end
- lambda { DefSpecNested.a_class_method }.should raise_error(NoMethodError)
+ -> { DefSpecNested.a_class_method }.should raise_error(NoMethodError)
DefSpecNested.create_class_method.should == DefSpecNested
DefSpecNested.a_class_method.should == DefSpecNested
- lambda { Object.a_class_method }.should raise_error(NoMethodError)
- lambda { DefSpecNested.new.a_class_method }.should raise_error(NoMethodError)
+ -> { Object.a_class_method }.should raise_error(NoMethodError)
+ -> { DefSpecNested.new.a_class_method }.should raise_error(NoMethodError)
end
it "creates a singleton method when evaluated in the metaclass of an instance" do
@@ -448,7 +510,7 @@ describe "A nested method definition" do
obj.a_singleton_method.should == obj
other = DefSpecNested.new
- lambda { other.a_singleton_method }.should raise_error(NoMethodError)
+ -> { other.a_singleton_method }.should raise_error(NoMethodError)
end
it "creates a method in the surrounding context when evaluated in a def expr.method" do
@@ -465,6 +527,8 @@ describe "A nested method definition" do
obj = DefSpecNested.new
obj.inherited_method.should == obj
+ ensure
+ DefSpecNested.send(:remove_const, :TARGET)
end
# See http://yugui.jp/articles/846#label-3
@@ -486,9 +550,11 @@ describe "A nested method definition" do
DefSpecNested.should_not have_instance_method :arg_method
DefSpecNested.should_not have_instance_method :body_method
+ ensure
+ DefSpecNested.send(:remove_const, :OBJ)
end
- it "defines methods as public by default" do
+ it "creates an instance method inside Class.new" do
cls = Class.new do
def do_def
def new_def
@@ -500,6 +566,41 @@ describe "A nested method definition" do
obj = cls.new
obj.do_def
obj.new_def.should == 1
+
+ cls.new.new_def.should == 1
+
+ -> { Object.new.new_def }.should raise_error(NoMethodError)
+ end
+end
+
+describe "A method definition always resets the visibility to public for nested definitions" do
+ it "in Class.new" do
+ cls = Class.new do
+ private
+ def do_def
+ def new_def
+ 1
+ end
+ end
+ end
+
+ obj = cls.new
+ -> { obj.do_def }.should raise_error(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)
+ end
+
+ it "at the toplevel" do
+ obj = Object.new
+ -> { obj.toplevel_define_other_method }.should raise_error(NoMethodError, /private/)
+ toplevel_define_other_method
+ nested_method_in_toplevel_method.should == 42
+
+ Object.new.nested_method_in_toplevel_method.should == 42
end
end
@@ -512,7 +613,7 @@ describe "A method definition inside an instance_eval" do
obj.an_instance_eval_method.should == obj
other = Object.new
- lambda { other.an_instance_eval_method }.should raise_error(NoMethodError)
+ -> { other.an_instance_eval_method }.should raise_error(NoMethodError)
end
it "creates a singleton method when evaluated inside a metaclass" do
@@ -525,7 +626,7 @@ describe "A method definition inside an instance_eval" do
obj.a_metaclass_eval_method.should == obj
other = Object.new
- lambda { other.a_metaclass_eval_method }.should raise_error(NoMethodError)
+ -> { other.a_metaclass_eval_method }.should raise_error(NoMethodError)
end
it "creates a class method when the receiver is a class" do
@@ -534,7 +635,7 @@ describe "A method definition inside an instance_eval" do
end
DefSpecNested.an_instance_eval_class_method.should == DefSpecNested
- lambda { Object.an_instance_eval_class_method }.should raise_error(NoMethodError)
+ -> { Object.an_instance_eval_class_method }.should raise_error(NoMethodError)
end
it "creates a class method when the receiver is an anonymous class" do
@@ -546,7 +647,7 @@ describe "A method definition inside an instance_eval" do
end
m.klass_method.should == :test
- lambda { Object.klass_method }.should raise_error(NoMethodError)
+ -> { Object.klass_method }.should raise_error(NoMethodError)
end
it "creates a class method when instance_eval is within class" do
@@ -559,7 +660,7 @@ describe "A method definition inside an instance_eval" do
end
m.klass_method.should == :test
- lambda { Object.klass_method }.should raise_error(NoMethodError)
+ -> { Object.klass_method }.should raise_error(NoMethodError)
end
end
@@ -572,7 +673,7 @@ describe "A method definition inside an instance_exec" do
end
DefSpecNested.an_instance_exec_class_method.should == 1
- lambda { Object.an_instance_exec_class_method }.should raise_error(NoMethodError)
+ -> { Object.an_instance_exec_class_method }.should raise_error(NoMethodError)
end
it "creates a class method when the receiver is an anonymous class" do
@@ -586,7 +687,7 @@ describe "A method definition inside an instance_exec" do
end
m.klass_method.should == 1
- lambda { Object.klass_method }.should raise_error(NoMethodError)
+ -> { Object.klass_method }.should raise_error(NoMethodError)
end
it "creates a class method when instance_exec is within class" do
@@ -601,7 +702,7 @@ describe "A method definition inside an instance_exec" do
end
m.klass_method.should == 2
- lambda { Object.klass_method }.should raise_error(NoMethodError)
+ -> { Object.klass_method }.should raise_error(NoMethodError)
end
end
@@ -621,7 +722,7 @@ describe "A method definition in an eval" do
other = DefSpecNested.new
other.an_eval_instance_method.should == other
- lambda { Object.new.an_eval_instance_method }.should raise_error(NoMethodError)
+ -> { Object.new.an_eval_instance_method }.should raise_error(NoMethodError)
end
it "creates a class method" do
@@ -637,8 +738,8 @@ describe "A method definition in an eval" do
DefSpecNestedB.eval_class_method.should == DefSpecNestedB
DefSpecNestedB.an_eval_class_method.should == DefSpecNestedB
- lambda { Object.an_eval_class_method }.should raise_error(NoMethodError)
- lambda { DefSpecNestedB.new.an_eval_class_method}.should raise_error(NoMethodError)
+ -> { Object.an_eval_class_method }.should raise_error(NoMethodError)
+ -> { DefSpecNestedB.new.an_eval_class_method}.should raise_error(NoMethodError)
end
it "creates a singleton method" do
@@ -656,7 +757,7 @@ describe "A method definition in an eval" do
obj.an_eval_singleton_method.should == obj
other = DefSpecNested.new
- lambda { other.an_eval_singleton_method }.should raise_error(NoMethodError)
+ -> { other.an_eval_singleton_method }.should raise_error(NoMethodError)
end
end
@@ -682,7 +783,7 @@ describe "a method definition that sets more than one default parameter all to t
end
it "only allows overriding the default value of the first such parameter in each set" do
- lambda { foo(1,2) }.should raise_error(ArgumentError)
+ -> { foo(1,2) }.should raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 0..1)')
end
def bar(a=b=c=1,d=2)
@@ -693,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]
- lambda { bar(3,4,5) }.should raise_error(ArgumentError)
+ -> { bar(3,4,5) }.should raise_error(ArgumentError, 'wrong number of arguments (given 3, expected 0..2)')
end
end
@@ -703,7 +804,7 @@ describe "The def keyword" do
module DefSpecsLambdaVisibility
private
- lambda {
+ -> {
def some_method; end
}.call
end
diff --git a/spec/ruby/language/defined_spec.rb b/spec/ruby/language/defined_spec.rb
index 9fe460a9de..80ad1818b1 100644
--- a/spec/ruby/language/defined_spec.rb
+++ b/spec/ruby/language/defined_spec.rb
@@ -1,25 +1,29 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/defined', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/defined'
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,15 +31,16 @@ 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
- ret = defined?([NonExistantConstant, Array])
+ ret = defined?([NonExistentConstant, Array])
ret.should == nil
end
it "returns nil if all elements are not defined" do
- ret = defined?([NonExistantConstant, AnotherNonExistantConstant])
+ ret = defined?([NonExistentConstant, AnotherNonExistentConstant])
ret.should == nil
end
@@ -43,9 +48,25 @@ 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
@@ -95,6 +116,11 @@ 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
@@ -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
@@ -275,9 +435,7 @@ describe "The defined? keyword for an expression" do
end
it "returns nil for an expression with '!' and an unset class variable" do
- -> {
- @result = defined?(!@@defined_specs_undefined_class_variable)
- }.should complain(/class variable access from toplevel/)
+ @result = eval("class singleton_class::A; defined?(!@@doesnt_exist) end", binding, __FILE__, __LINE__)
@result.should be_nil
end
@@ -286,9 +444,7 @@ describe "The defined? keyword for an expression" do
end
it "returns nil for an expression with 'not' and an unset class variable" do
- -> {
- @result = defined?(not @@defined_specs_undefined_class_variable)
- }.should complain(/class variable access from toplevel/)
+ @result = eval("class singleton_class::A; defined?(not @@doesnt_exist) end", binding, __FILE__, __LINE__)
@result.should be_nil
end
@@ -474,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
@@ -490,7 +648,9 @@ describe "The defined? keyword for variables" do
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
@@ -505,6 +665,12 @@ describe "The defined? keyword for variables" do
DefinedSpecs::Basic.new.global_variable_read.should be_nil
end
+ it "returns 'global-variable' for a global variable that has been assigned nil" do
+ 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
# 'global-variable' even when they are not set (or they are always "set"
# but the value may be nil). In other words, 'defined?($~)' will return
@@ -543,16 +709,12 @@ describe "The defined? keyword for variables" do
defined?($+).should be_nil
end
- it "returns nil for $1-$9" do
+ it "returns nil for any last match global" do
defined?($1).should be_nil
- defined?($2).should be_nil
- defined?($3).should be_nil
defined?($4).should be_nil
- defined?($5).should be_nil
- defined?($6).should be_nil
defined?($7).should be_nil
- defined?($8).should be_nil
- defined?($9).should be_nil
+ defined?($10).should be_nil
+ defined?($200).should be_nil
end
end
@@ -587,13 +749,10 @@ describe "The defined? keyword for variables" do
end
it "returns nil for non-captures" do
- defined?($3).should be_nil
defined?($4).should be_nil
- defined?($5).should be_nil
- defined?($6).should be_nil
defined?($7).should be_nil
- defined?($8).should be_nil
- defined?($9).should be_nil
+ defined?($10).should be_nil
+ defined?($200).should be_nil
end
end
@@ -622,16 +781,12 @@ describe "The defined? keyword for variables" do
defined?($+).should be_nil
end
- it "returns nil for $1-$9" do
+ it "returns nil for any last match global" do
defined?($1).should be_nil
- defined?($2).should be_nil
- defined?($3).should be_nil
defined?($4).should be_nil
- defined?($5).should be_nil
- defined?($6).should be_nil
defined?($7).should be_nil
- defined?($8).should be_nil
- defined?($9).should be_nil
+ defined?($10).should be_nil
+ defined?($200).should be_nil
end
end
@@ -666,13 +821,10 @@ describe "The defined? keyword for variables" do
end
it "returns nil for non-captures" do
- defined?($3).should be_nil
defined?($4).should be_nil
- defined?($5).should be_nil
- defined?($6).should be_nil
defined?($7).should be_nil
- defined?($8).should be_nil
- defined?($9).should be_nil
+ defined?($10).should be_nil
+ defined?($200).should be_nil
end
end
it "returns 'global-variable' for a global variable that has been assigned" do
@@ -688,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
@@ -699,7 +853,9 @@ 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
@@ -744,6 +900,10 @@ describe "The defined? keyword for a scoped constant" do
defined?(DefinedSpecs::Undefined).should be_nil
end
+ it "returns nil when the constant is not defined and the outer module implements .const_missing" do
+ defined?(DefinedSpecs::ModuleWithConstMissing::Undefined).should be_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
@@ -766,16 +926,8 @@ describe "The defined? keyword for a scoped constant" do
defined?(DefinedSpecs::String).should be_nil
end
- ruby_version_is ""..."2.5" do
- it "returns 'constant' when a constant is defined on top-level but not on the class" do
- defined?(DefinedSpecs::Basic::String).should == 'constant'
- end
- end
-
- ruby_version_is "2.5" do
- it "returns nil when a constant is defined on top-level but not on the class" do
- defined?(DefinedSpecs::Basic::String).should be_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
end
it "returns 'constant' if the scoped-scoped constant is defined" do
@@ -915,17 +1067,21 @@ describe "The defined? keyword for a variable scoped constant" do
end
it "returns nil if the class scoped constant is not defined" do
- -> {
- @@defined_specs_obj = DefinedSpecs::Basic
- defined?(@@defined_specs_obj::Undefined).should be_nil
- }.should complain(/class variable access from toplevel/)
+ eval(<<-END, binding, __FILE__, __LINE__)
+ class singleton_class::A
+ @@defined_specs_obj = DefinedSpecs::Basic
+ defined?(@@defined_specs_obj::Undefined).should be_nil
+ end
+ END
end
it "returns 'constant' if the constant is defined in the scope of the class variable" do
- -> {
- @@defined_specs_obj = DefinedSpecs::Basic
- defined?(@@defined_specs_obj::A).should == "constant"
- }.should complain(/class variable access from toplevel/)
+ eval(<<-END, binding, __FILE__, __LINE__)
+ class singleton_class::A
+ @@defined_specs_obj = DefinedSpecs::Basic
+ defined?(@@defined_specs_obj::A).should == "constant"
+ end
+ END
end
it "returns nil if the local scoped constant is not defined" do
@@ -959,12 +1115,26 @@ describe "The defined? keyword for yield" do
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
@@ -990,7 +1160,9 @@ describe "The defined? keyword for super" do
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
diff --git a/spec/ruby/language/delegation_spec.rb b/spec/ruby/language/delegation_spec.rb
new file mode 100644
index 0000000000..c711a536c2
--- /dev/null
+++ b/spec/ruby/language/delegation_spec.rb
@@ -0,0 +1,158 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/delegation'
+
+# 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 "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], 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
+
+ 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
+
+ a.new.delegate(0, 1).should == [[0, 1], {}, nil]
+ end
+
+ ruby_version_is "3.3" do
+ context "within a block that accepts anonymous rest within a method that accepts anonymous rest" do
+ it "does not allow delegating rest" do
+ -> {
+ eval "def m(*); proc { |*| n(*) } end"
+ }.should raise_error(SyntaxError, /anonymous rest parameter is also used within block/)
+ end
+ end
+ end
+end
+
+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
+
+ ruby_version_is "3.3" do
+ context "within a block that accepts anonymous kwargs within a method that accepts anonymous kwargs" do
+ it "does not allow delegating kwargs" do
+ -> {
+ eval "def m(**); proc { |**| n(**) } end"
+ }.should raise_error(SyntaxError, /anonymous keyword rest parameter is also used within block/)
+ end
+ end
+ end
+end
+
+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
+
+ ruby_version_is "3.3" do
+ context "within a block that accepts anonymous block within a method that accepts anonymous block" do
+ it "does not allow delegating a block" do
+ -> {
+ eval "def m(&); proc { |&| n(&) } end"
+ }.should raise_error(SyntaxError, /anonymous block parameter is also used within block/)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/language/encoding_spec.rb b/spec/ruby/language/encoding_spec.rb
index 070fa52bba..e761a53cb6 100644
--- a/spec/ruby/language/encoding_spec.rb
+++ b/spec/ruby/language/encoding_spec.rb
@@ -1,7 +1,7 @@
# -*- encoding: us-ascii -*-
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/coding_us_ascii', __FILE__)
-require File.expand_path('../fixtures/coding_utf_8', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/coding_us_ascii'
+require_relative 'fixtures/coding_utf_8'
describe "The __ENCODING__ pseudo-variable" do
it "is an instance of Encoding" 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("ASCII-8BIT")).should == Encoding::ASCII_8BIT
+ 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: ASCII-8BIT\n__ENCODING__".force_encoding("US-ASCII")
- eval(code).should == Encoding::ASCII_8BIT
+ code = "# encoding: BINARY\n__ENCODING__".dup.force_encoding("US-ASCII")
+ eval(code).should == Encoding::BINARY
- code = "# encoding: us-ascii\n__ENCODING__".force_encoding("ASCII-8BIT")
+ 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
- lambda { eval("__ENCODING__ = 1") }.should raise_error(SyntaxError)
+ -> { eval("__ENCODING__ = 1") }.should raise_error(SyntaxError)
end
end
diff --git a/spec/ruby/language/ensure_spec.rb b/spec/ruby/language/ensure_spec.rb
index 1d99dcf5f2..b76292c007 100644
--- a/spec/ruby/language/ensure_spec.rb
+++ b/spec/ruby/language/ensure_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/ensure', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/ensure'
describe "An ensure block inside a begin block" do
before :each do
@@ -7,7 +7,7 @@ describe "An ensure block inside a begin block" do
end
it "is executed when an exception is raised in it's corresponding begin block" do
- lambda {
+ -> {
begin
ScratchPad << :begin
raise EnsureSpec::Error
@@ -66,6 +66,18 @@ describe "An ensure block inside a begin block" do
:ensure
end.should == :begin
end
+
+ it "sets exception cause if raises exception in block and in ensure" do
+ -> {
+ begin
+ raise "from block"
+ ensure
+ raise "from ensure"
+ end
+ }.should raise_error(RuntimeError, "from ensure") { |e|
+ e.cause.message.should == "from block"
+ }
+ end
end
describe "The value of an ensure expression," do
@@ -96,7 +108,7 @@ describe "An ensure block inside a method" do
end
it "is executed when an exception is raised in the method" do
- lambda { @obj.raise_in_method_with_ensure }.should raise_error(EnsureSpec::Error)
+ -> { @obj.raise_in_method_with_ensure }.should raise_error(EnsureSpec::Error)
@obj.executed.should == [:method, :ensure]
end
@@ -117,6 +129,34 @@ describe "An ensure block inside a method" do
it "has an impact on the method's explicit return value" do
@obj.explicit_return_in_method_with_ensure.should == :ensure
end
+
+ it "has an impact on the method's explicit return value from rescue if returns explicitly" do
+ @obj.explicit_return_in_rescue_and_explicit_return_in_ensure.should == "returned in ensure"
+ end
+
+ it "has no impact on the method's explicit return value from rescue if returns implicitly" do
+ @obj.explicit_return_in_rescue_and_implicit_return_in_ensure.should == "returned in rescue"
+ end
+
+ it "suppresses exception raised in method if returns value explicitly" do
+ @obj.raise_and_explicit_return_in_ensure.should == "returned in ensure"
+ end
+
+ it "suppresses exception raised in rescue if returns value explicitly" do
+ @obj.raise_in_rescue_and_explicit_return_in_ensure.should == "returned in ensure"
+ end
+
+ 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")
+ 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")
+ end
end
describe "An ensure block inside a class" do
@@ -125,7 +165,7 @@ describe "An ensure block inside a class" do
end
it "is executed when an exception is raised" do
- lambda {
+ -> {
eval <<-ruby
class EnsureInClassExample
ScratchPad << :class
@@ -200,7 +240,7 @@ end
describe "An ensure block inside {} block" do
it "is not allowed" do
- lambda {
+ -> {
eval <<-ruby
lambda {
raise
@@ -211,83 +251,96 @@ describe "An ensure block inside {} block" do
end
end
-ruby_version_is "2.5" do
- describe "An ensure block inside 'do end' block" do
- before :each do
- ScratchPad.record []
- end
-
- it "is executed when an exception is raised in it's corresponding begin block" do
- lambda {
- eval(<<-ruby).call
- lambda do
- ScratchPad << :begin
- raise EnsureSpec::Error
- ensure
- ScratchPad << :ensure
- end
- ruby
- }.should raise_error(EnsureSpec::Error)
-
- ScratchPad.recorded.should == [:begin, :ensure]
- end
+describe "An ensure block inside 'do end' block" do
+ before :each do
+ ScratchPad.record []
+ 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 in it's corresponding begin block" do
+ -> {
eval(<<-ruby).call
lambda do
ScratchPad << :begin
- raise "An exception occurred!"
- rescue
- ScratchPad << :rescue
+ raise EnsureSpec::Error
ensure
ScratchPad << :ensure
end
ruby
+ }.should raise_error(EnsureSpec::Error)
- ScratchPad.recorded.should == [:begin, :rescue, :ensure]
- end
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
- it "is executed even when a symbol is thrown in it's corresponding begin block" do
- catch(:symbol) do
- eval(<<-ruby).call
- lambda do
- ScratchPad << :begin
- throw(:symbol)
- rescue
- ScratchPad << :rescue
- ensure
- ScratchPad << :ensure
- end
- ruby
+ it "is executed when an exception is raised and rescued in it's corresponding begin block" do
+ eval(<<-ruby).call
+ lambda do
+ ScratchPad << :begin
+ raise "An exception occurred!"
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
end
+ ruby
- ScratchPad.recorded.should == [:begin, :ensure]
- end
+ ScratchPad.recorded.should == [:begin, :rescue, :ensure]
+ end
- it "is executed when nothing is raised or thrown in it's corresponding begin block" do
+ it "is executed even when a symbol is thrown in it's corresponding begin block" do
+ catch(:symbol) do
eval(<<-ruby).call
lambda do
ScratchPad << :begin
+ throw(:symbol)
rescue
ScratchPad << :rescue
ensure
ScratchPad << :ensure
end
ruby
-
- ScratchPad.recorded.should == [:begin, :ensure]
end
- it "has no return value" do
- result = eval(<<-ruby).call
- lambda do
- :begin
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "is executed when nothing is raised or thrown in it's corresponding begin block" do
+ eval(<<-ruby).call
+ lambda do
+ ScratchPad << :begin
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "has no return value" do
+ result = eval(<<-ruby).call
+ lambda do
+ :begin
+ ensure
+ :ensure
+ end
+ ruby
+
+ result.should == :begin
+ end
+
+ ruby_version_is "3.4" do
+ it "does not introduce extra backtrace entries" do
+ def foo
+ begin
+ raise "oops"
ensure
- :ensure
+ return caller(0, 2) # rubocop:disable Lint/EnsureReturn
end
- ruby
-
- result.should == :begin
+ 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 3e6e7ff48c..51bcde62e8 100644
--- a/spec/ruby/language/execution_spec.rb
+++ b/spec/ruby/language/execution_spec.rb
@@ -1,10 +1,49 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "``" do
it "returns the output of the executed sub-process" 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 409400ca83..59563d9642 100644
--- a/spec/ruby/language/file_spec.rb
+++ b/spec/ruby/language/file_spec.rb
@@ -1,29 +1,29 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/code_loading', __FILE__)
-require File.expand_path('../shared/__FILE__', __FILE__)
+require_relative '../spec_helper'
+require_relative '../fixtures/code_loading'
+require_relative 'shared/__FILE__'
describe "The __FILE__ pseudo-variable" do
it "raises a SyntaxError if assigned to" do
- lambda { eval("__FILE__ = 1") }.should raise_error(SyntaxError)
+ -> { eval("__FILE__ = 1") }.should raise_error(SyntaxError)
end
- it "equals (eval) inside an eval" do
- eval("__FILE__").should == "(eval)"
+ ruby_version_is ""..."3.3" do
+ it "equals (eval) inside an eval" do
+ eval("__FILE__").should == "(eval)"
+ end
end
-end
-describe "The __FILE__ pseudo-variable" do
- it_behaves_like :language___FILE__, :require, CodeLoadingSpecs::Method.new
+ ruby_version_is "3.3" do
+ it "equals (eval at __FILE__:__LINE__) inside an eval" do
+ eval("__FILE__").should == "(eval at #{__FILE__}:#{__LINE__})"
+ end
+ end
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/array.rb b/spec/ruby/language/fixtures/array.rb
index 4d8ce74ed6..c1036575ff 100644
--- a/spec/ruby/language/fixtures/array.rb
+++ b/spec/ruby/language/fixtures/array.rb
@@ -8,4 +8,25 @@ module ArraySpec
[a, b, c, d]
end
end
+
+ class SideEffect
+ def initialize()
+ @call_count = 0
+ end
+
+ attr_reader :call_count
+
+ def array_result(a_number)
+ [result(a_number), result(a_number)]
+ end
+
+ def result(a_number)
+ @call_count += 1
+ if a_number
+ 1
+ else
+ :thing
+ end
+ end
+ end
end
diff --git a/spec/ruby/language/fixtures/block.rb b/spec/ruby/language/fixtures/block.rb
index 9848d18776..33baac6aeb 100644
--- a/spec/ruby/language/fixtures/block.rb
+++ b/spec/ruby/language/fixtures/block.rb
@@ -15,6 +15,10 @@ module BlockSpecs
def r(a)
yield(*a)
end
+
+ def k(*a)
+ yield(*a, b: true)
+ end
end
# TODO: rewrite all specs that use Yield to use Yielder
diff --git a/spec/ruby/language/fixtures/break.rb b/spec/ruby/language/fixtures/break.rb
index 2d07cc3d48..217c20a2c0 100644
--- a/spec/ruby/language/fixtures/break.rb
+++ b/spec/ruby/language/fixtures/break.rb
@@ -163,7 +163,7 @@ module BreakSpecs
# on the call stack when the lambda is invoked.
def break_in_defining_scope(value=true)
note :a
- note lambda {
+ note -> {
note :b
if value
break :break
@@ -177,7 +177,7 @@ module BreakSpecs
def break_in_nested_scope
note :a
- l = lambda do
+ l = -> do
note :b
break :break
note :c
@@ -197,7 +197,7 @@ module BreakSpecs
def break_in_nested_scope_yield
note :a
- l = lambda do
+ l = -> do
note :b
break :break
note :c
@@ -217,7 +217,7 @@ module BreakSpecs
def break_in_nested_scope_block
note :a
- l = lambda do
+ l = -> do
note :b
break :break
note :c
@@ -251,7 +251,7 @@ module BreakSpecs
# active on the call stack when the lambda is invoked.
def create_lambda
note :la
- l = lambda do
+ l = -> do
note :lb
break :break
note :lc
diff --git a/spec/ruby/language/fixtures/break_lambda_toplevel.rb b/spec/ruby/language/fixtures/break_lambda_toplevel.rb
index 05af1d3fdc..da5abbaf00 100644
--- a/spec/ruby/language/fixtures/break_lambda_toplevel.rb
+++ b/spec/ruby/language/fixtures/break_lambda_toplevel.rb
@@ -1,6 +1,6 @@
print "a,"
-print lambda {
+print -> {
print "b,"
break "break,"
print "c,"
diff --git a/spec/ruby/language/fixtures/break_lambda_toplevel_block.rb b/spec/ruby/language/fixtures/break_lambda_toplevel_block.rb
index a35cb8a8a1..3dcee62424 100644
--- a/spec/ruby/language/fixtures/break_lambda_toplevel_block.rb
+++ b/spec/ruby/language/fixtures/break_lambda_toplevel_block.rb
@@ -1,6 +1,6 @@
print "a,"
-l = lambda {
+l = -> {
print "b,"
break "break,"
print "c,"
diff --git a/spec/ruby/language/fixtures/break_lambda_toplevel_method.rb b/spec/ruby/language/fixtures/break_lambda_toplevel_method.rb
index 200040d614..a5936a3d70 100644
--- a/spec/ruby/language/fixtures/break_lambda_toplevel_method.rb
+++ b/spec/ruby/language/fixtures/break_lambda_toplevel_method.rb
@@ -1,6 +1,6 @@
print "a,"
-l = lambda {
+l = -> {
print "b,"
break "break,"
print "c,"
diff --git a/spec/ruby/language/fixtures/bytes_magic_comment.rb b/spec/ruby/language/fixtures/bytes_magic_comment.rb
new file mode 100644
index 0000000000..2bc2bcfb07
--- /dev/null
+++ b/spec/ruby/language/fixtures/bytes_magic_comment.rb
@@ -0,0 +1,2 @@
+# encoding: big5
+$magic_comment_result = 'An'.bytes.inspect
diff --git a/spec/ruby/language/fixtures/case_magic_comment.rb b/spec/ruby/language/fixtures/case_magic_comment.rb
new file mode 100644
index 0000000000..96f35a7c94
--- /dev/null
+++ b/spec/ruby/language/fixtures/case_magic_comment.rb
@@ -0,0 +1,2 @@
+# CoDiNg: bIg5
+$magic_comment_result = __ENCODING__.name
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/def.rb b/spec/ruby/language/fixtures/def.rb
index 81bfce73d0..e07060ed74 100644
--- a/spec/ruby/language/fixtures/def.rb
+++ b/spec/ruby/language/fixtures/def.rb
@@ -1,3 +1,9 @@
+def toplevel_define_other_method
+ def nested_method_in_toplevel_method
+ 42
+ end
+end
+
def some_toplevel_method
end
diff --git a/spec/ruby/language/fixtures/defined.rb b/spec/ruby/language/fixtures/defined.rb
index d26e553c4b..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
@@ -94,6 +97,11 @@ module DefinedSpecs
defined? $defined_specs_global_variable_defined
end
+ def global_variable_defined_as_nil
+ $defined_specs_global_variable_defined_as_nil = nil
+ defined? $defined_specs_global_variable_defined_as_nil
+ end
+
def class_variable_undefined
defined? @@class_variable_undefined
end
@@ -277,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
new file mode 100644
index 0000000000..da2b024791
--- /dev/null
+++ b/spec/ruby/language/fixtures/delegation.rb
@@ -0,0 +1,11 @@
+module DelegationSpecs
+ class Target
+ def target(*args, **kwargs, &block)
+ [args, kwargs, block]
+ end
+
+ def target_block(*args, **kwargs)
+ yield [kwargs, args]
+ end
+ end
+end
diff --git a/spec/ruby/language/fixtures/emacs_magic_comment.rb b/spec/ruby/language/fixtures/emacs_magic_comment.rb
new file mode 100644
index 0000000000..2b09f3e74c
--- /dev/null
+++ b/spec/ruby/language/fixtures/emacs_magic_comment.rb
@@ -0,0 +1,2 @@
+# -*- encoding: big5 -*-
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/ensure.rb b/spec/ruby/language/fixtures/ensure.rb
index d1a9da37b8..6047ac5bc0 100644
--- a/spec/ruby/language/fixtures/ensure.rb
+++ b/spec/ruby/language/fixtures/ensure.rb
@@ -40,6 +40,50 @@ module EnsureSpec
ensure
return :ensure
end
+
+ def explicit_return_in_rescue_and_explicit_return_in_ensure
+ raise
+ rescue
+ return 2
+ ensure
+ return "returned in ensure"
+ end
+
+ def explicit_return_in_rescue_and_implicit_return_in_ensure
+ raise
+ rescue
+ return "returned in rescue"
+ ensure
+ 3
+ end
+
+ def raise_and_explicit_return_in_ensure
+ raise
+ ensure
+ return "returned in ensure"
+ end
+
+ def raise_in_rescue_and_explicit_return_in_ensure
+ raise
+ rescue
+ raise
+ ensure
+ return "returned in ensure"
+ end
+
+ def raise_in_rescue_and_raise_in_ensure
+ raise
+ rescue
+ raise "raised in rescue"
+ ensure
+ raise "raised in ensure"
+ end
+
+ def raise_in_method_and_raise_in_ensure
+ raise
+ ensure
+ raise "raised in ensure"
+ end
end
end
diff --git a/spec/ruby/language/fixtures/for_scope.rb b/spec/ruby/language/fixtures/for_scope.rb
new file mode 100644
index 0000000000..9c44a23a2c
--- /dev/null
+++ b/spec/ruby/language/fixtures/for_scope.rb
@@ -0,0 +1,15 @@
+module ForSpecs
+ class ForInClassMethod
+ m = :same_variable_set_outside
+
+ def self.foo
+ all = []
+ for m in [:bar, :baz]
+ all << m
+ end
+ all
+ end
+
+ READER = -> { m }
+ end
+end
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
index d0558a2251..f72a32e879 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
Binary files differ
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/hash_strings_ascii8bit.rb b/spec/ruby/language/fixtures/hash_strings_binary.rb
index 4ac11b9930..44b99cbf80 100644
--- a/spec/ruby/language/fixtures/hash_strings_ascii8bit.rb
+++ b/spec/ruby/language/fixtures/hash_strings_binary.rb
@@ -1,6 +1,6 @@
-# encoding: ascii-8bit
+# encoding: binary
-module HashStringsASCII8BIT
+module HashStringsBinary
def self.literal_hash
{"foo" => "bar"}
end
diff --git a/spec/ruby/language/fixtures/magic_comment.rb b/spec/ruby/language/fixtures/magic_comment.rb
new file mode 100644
index 0000000000..120ef6ff4a
--- /dev/null
+++ b/spec/ruby/language/fixtures/magic_comment.rb
@@ -0,0 +1,2 @@
+# encoding: big5
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/metaclass.rb b/spec/ruby/language/fixtures/metaclass.rb
index a1990b9225..a8f837e701 100644
--- a/spec/ruby/language/fixtures/metaclass.rb
+++ b/spec/ruby/language/fixtures/metaclass.rb
@@ -31,4 +31,3 @@ module MetaClassSpecs
class D < C; end
end
-
diff --git a/spec/ruby/language/fixtures/module.rb b/spec/ruby/language/fixtures/module.rb
index 33d323846e..75eee77791 100644
--- a/spec/ruby/language/fixtures/module.rb
+++ b/spec/ruby/language/fixtures/module.rb
@@ -12,13 +12,4 @@ module ModuleSpecs
module Anonymous
end
-
- module IncludedInObject
- module IncludedModuleSpecs
- end
- end
-end
-
-class Object
- include ModuleSpecs::IncludedInObject
end
diff --git a/spec/ruby/language/fixtures/no_magic_comment.rb b/spec/ruby/language/fixtures/no_magic_comment.rb
new file mode 100644
index 0000000000..743a0f9503
--- /dev/null
+++ b/spec/ruby/language/fixtures/no_magic_comment.rb
@@ -0,0 +1 @@
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/print_magic_comment_result_at_exit.rb b/spec/ruby/language/fixtures/print_magic_comment_result_at_exit.rb
new file mode 100644
index 0000000000..aa82cf4471
--- /dev/null
+++ b/spec/ruby/language/fixtures/print_magic_comment_result_at_exit.rb
@@ -0,0 +1,3 @@
+at_exit {
+ print $magic_comment_result
+}
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.rb b/spec/ruby/language/fixtures/rescue.rb
index 3fa5df1eb5..b906e17a2f 100644
--- a/spec/ruby/language/fixtures/rescue.rb
+++ b/spec/ruby/language/fixtures/rescue.rb
@@ -60,4 +60,8 @@ module RescueSpecs
ScratchPad << :outside_begin
:return_val
end
+
+ def self.raise_standard_error
+ raise StandardError, "an error occurred"
+ 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/rescue_captures.rb b/spec/ruby/language/fixtures/rescue_captures.rb
new file mode 100644
index 0000000000..69f9b83904
--- /dev/null
+++ b/spec/ruby/language/fixtures/rescue_captures.rb
@@ -0,0 +1,107 @@
+module RescueSpecs
+ class Captor
+ attr_accessor :captured_error
+
+ def self.should_capture_exception
+ captor = new
+ captor.capture('some text').should == :caught # Ensure rescue body still runs
+ captor.captured_error.message.should == 'some text'
+ end
+ end
+
+ class ClassVariableCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => @@captured_error
+ :caught
+ end
+
+ def captured_error
+ self.class.remove_class_variable(:@@captured_error)
+ end
+ end
+
+ class ConstantCaptor < Captor
+ # Using lambda gets around the dynamic constant assignment warning
+ CAPTURE = -> msg {
+ begin
+ raise msg
+ rescue => CapturedError
+ :caught
+ end
+ }
+
+ def capture(msg)
+ CAPTURE.call(msg)
+ end
+
+ def captured_error
+ self.class.send(:remove_const, :CapturedError)
+ end
+ end
+
+ class GlobalVariableCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => $captured_error
+ :caught
+ end
+
+ def captured_error
+ $captured_error.tap do
+ $captured_error = nil # Can't remove globals, only nil them out
+ end
+ end
+ end
+
+ class InstanceVariableCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => @captured_error
+ :caught
+ end
+ end
+
+ class LocalVariableCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => captured_error
+ @captured_error = captured_error
+ :caught
+ end
+ end
+
+ class SafeNavigationSetterCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => self&.captured_error
+ :caught
+ end
+ end
+
+ class SetterCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => self.captured_error
+ :caught
+ end
+ end
+
+ class SquareBracketsCaptor < Captor
+ def capture(msg)
+ @hash = {}
+
+ raise msg
+ rescue => self[:error]
+ :caught
+ end
+
+ def []=(key, value)
+ @hash[key] = value
+ end
+
+ def captured_error
+ @hash[:error]
+ end
+ end
+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/second_line_magic_comment.rb b/spec/ruby/language/fixtures/second_line_magic_comment.rb
new file mode 100644
index 0000000000..a3dd50393b
--- /dev/null
+++ b/spec/ruby/language/fixtures/second_line_magic_comment.rb
@@ -0,0 +1,3 @@
+
+# encoding: big5
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/second_token_magic_comment.rb b/spec/ruby/language/fixtures/second_token_magic_comment.rb
new file mode 100644
index 0000000000..8d443e68f3
--- /dev/null
+++ b/spec/ruby/language/fixtures/second_token_magic_comment.rb
@@ -0,0 +1,2 @@
+1 + 1 # encoding: big5
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/send.rb b/spec/ruby/language/fixtures/send.rb
index c3013616b2..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
@@ -53,8 +53,9 @@ module LangSendSpecs
end
class PrivateGetter
- attr_reader :foo
+ attr_accessor :foo
private :foo
+ private :foo=
def call_self_foo
self.foo
@@ -80,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/shebang_magic_comment.rb b/spec/ruby/language/fixtures/shebang_magic_comment.rb
new file mode 100755
index 0000000000..f8e5e7d8e4
--- /dev/null
+++ b/spec/ruby/language/fixtures/shebang_magic_comment.rb
@@ -0,0 +1,3 @@
+#!/usr/bin/ruby
+# encoding: big5
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/squiggly_heredoc.rb b/spec/ruby/language/fixtures/squiggly_heredoc.rb
index afc87514c7..984a629e5b 100644
--- a/spec/ruby/language/fixtures/squiggly_heredoc.rb
+++ b/spec/ruby/language/fixtures/squiggly_heredoc.rb
@@ -29,6 +29,22 @@ module SquigglyHeredocSpecs
HERE
end
+ def self.backslash
+ <<~HERE
+ a
+ b\
+ c
+ HERE
+ end
+
+ def self.least_indented_on_the_first_line
+ <<~HERE
+ a
+ b
+ c
+ HERE
+ end
+
def self.least_indented_on_the_last_line
<<~HERE
a
@@ -36,4 +52,20 @@ module SquigglyHeredocSpecs
c
HERE
end
+
+ def self.least_indented_on_the_first_line_single
+ <<~'HERE'
+ a
+ b
+ c
+ HERE
+ end
+
+ def self.least_indented_on_the_last_line_single
+ <<~'HERE'
+ a
+ b
+ c
+ HERE
+ end
end
diff --git a/spec/ruby/language/fixtures/super.rb b/spec/ruby/language/fixtures/super.rb
index 09a454bdf4..c5bdcf0e40 100644
--- a/spec/ruby/language/fixtures/super.rb
+++ b/spec/ruby/language/fixtures/super.rb
@@ -1,4 +1,4 @@
-module Super
+module SuperSpecs
module S1
class A
def foo(a)
@@ -282,7 +282,7 @@ module Super
#
# When name3 is called then, Alias2 (NOT Alias3) is presented as the
# current module to Alias2#name, so that when super is called,
- # Alias2->superclass is next.
+ # Alias2's superclass is next.
#
# Otherwise, Alias2 is next, which is where name was to begin with,
# causing the wrong #name method to be called.
@@ -377,12 +377,12 @@ module Super
end
def b
- block_ref = lambda { 15 }
+ block_ref = -> { 15 }
[super { 14 }, super(&block_ref)]
end
def c
- block_ref = lambda { 16 }
+ block_ref = -> { 16 }
super(&block_ref)
end
end
@@ -455,12 +455,114 @@ module Super
end
end
+ module ZSuperWithRestAndPost
+ class A
+ def m(*args, a, b)
+ args
+ end
+
+ def m_modified(*args, a, b)
+ args
+ end
+ end
+
+ class B < A
+ def m(*args, a, b)
+ super
+ end
+
+ def m_modified(*args, a, b)
+ args[1] = 14
+ super
+ end
+ end
+ end
+
+ module ZSuperWithRestOthersAndPost
+ class A
+ def m(a, *args, b)
+ args
+ end
+
+ def m_modified(a, *args, b)
+ args
+ end
+ end
+
+ class B < A
+ def m(a, *args, b)
+ super
+ end
+
+ def m_modified(a, *args, b)
+ args[1] = 14
+ super
+ end
+ end
+ end
+
+ module ZSuperWithRestReassigned
+ class A
+ def a(*args)
+ args
+ end
+ end
+
+ class B < A
+ def a(*args)
+ args = ["foo"]
+
+ super
+ end
+ end
+ end
+
+ module ZSuperWithRestReassignedWithScalar
+ class A
+ def a(*args)
+ args
+ end
+ end
+
+ class B < A
+ def a(*args)
+ args = "foo"
+
+ super
+ end
+ end
+ end
+
module ZSuperWithUnderscores
class A
def m(*args)
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
@@ -471,6 +573,30 @@ module Super
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
@@ -478,6 +604,20 @@ module Super
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/utf16-be-nobom.rb b/spec/ruby/language/fixtures/utf16-be-nobom.rb
new file mode 100644
index 0000000000..99e2ce8ce8
--- /dev/null
+++ b/spec/ruby/language/fixtures/utf16-be-nobom.rb
Binary files differ
diff --git a/spec/ruby/language/fixtures/utf16-le-nobom.rb b/spec/ruby/language/fixtures/utf16-le-nobom.rb
new file mode 100644
index 0000000000..98de9697ca
--- /dev/null
+++ b/spec/ruby/language/fixtures/utf16-le-nobom.rb
Binary files differ
diff --git a/spec/ruby/language/fixtures/utf8-bom.rb b/spec/ruby/language/fixtures/utf8-bom.rb
new file mode 100644
index 0000000000..50c223a922
--- /dev/null
+++ b/spec/ruby/language/fixtures/utf8-bom.rb
@@ -0,0 +1,2 @@
+# encoding: utf-8
+puts 'hello'
diff --git a/spec/ruby/language/fixtures/utf8-nobom.rb b/spec/ruby/language/fixtures/utf8-nobom.rb
new file mode 100644
index 0000000000..75f5563b95
--- /dev/null
+++ b/spec/ruby/language/fixtures/utf8-nobom.rb
@@ -0,0 +1,2 @@
+# encoding: utf-8
+puts 'hello'
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/fixtures/vim_magic_comment.rb b/spec/ruby/language/fixtures/vim_magic_comment.rb
new file mode 100644
index 0000000000..60cbe7a3bf
--- /dev/null
+++ b/spec/ruby/language/fixtures/vim_magic_comment.rb
@@ -0,0 +1,2 @@
+# vim: filetype=ruby, fileencoding=big5, tabsize=3, shiftwidth=3
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/yield.rb b/spec/ruby/language/fixtures/yield.rb
index a195616640..9f7a2ba238 100644
--- a/spec/ruby/language/fixtures/yield.rb
+++ b/spec/ruby/language/fixtures/yield.rb
@@ -21,6 +21,10 @@ module YieldSpecs
yield(*a)
end
+ def k(a)
+ yield(*a, b: true)
+ end
+
def rs(a, b, c)
yield(a, b, *c)
end
diff --git a/spec/ruby/language/for_spec.rb b/spec/ruby/language/for_spec.rb
index c9d043fa25..b8ddfe5f0d 100644
--- a/spec/ruby/language/for_spec.rb
+++ b/spec/ruby/language/for_spec.rb
@@ -1,4 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/for_scope'
# for name[, name]... in expr [do]
# body
@@ -18,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
@@ -32,14 +54,13 @@ describe "The for expression" do
end
it "iterates over any object responding to 'each'" do
- class XYZ
- def each
- (0..10).each { |i| yield i }
- end
+ obj = Object.new
+ def obj.each
+ (0..10).each { |i| yield i }
end
j = 0
- for i in XYZ.new
+ for i in obj
j += i
end
j.should == 55
@@ -81,6 +102,88 @@ 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
+
+ # Segfault in MRI 3.3 and lower: https://bugs.ruby-lang.org/issues/20468
+ ruby_bug "#20468", ""..."3.4" do
+ 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 be_nil
+ n.should == 3
+ end
+ 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
@@ -131,6 +234,11 @@ describe "The for expression" do
a.should == 123
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
+ end
+
it "returns expr" do
for i in 1..3; end.should == (1..3)
for i,j in { 1 => 10, 2 => 20 }; end.should == { 1 => 10, 2 => 20 }
diff --git a/spec/ruby/language/hash_spec.rb b/spec/ruby/language/hash_spec.rb
index edd9d4fbb2..668716e2e3 100644
--- a/spec/ruby/language/hash_spec.rb
+++ b/spec/ruby/language/hash_spec.rb
@@ -1,7 +1,7 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/hash_strings_ascii8bit', __FILE__)
-require File.expand_path('../fixtures/hash_strings_utf8', __FILE__)
-require File.expand_path('../fixtures/hash_strings_usascii', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/hash_strings_binary'
+require_relative 'fixtures/hash_strings_utf8'
+require_relative 'fixtures/hash_strings_usascii'
describe "Hash literal" do
it "{} should return an empty hash" do
@@ -33,12 +33,12 @@ 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"
h.keys.first.should == "foo"
- h.keys.first.frozen?.should == true
+ h.keys.first.should.frozen?
key.should == "oof"
end
@@ -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
- lambda { eval("{:a ==> 1}") }.should raise_error(SyntaxError)
+ -> { eval("{:a ==> 1}") }.should raise_error(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_error(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_error(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,44 @@ 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_error(TypeError, "no implicit conversion of nil into Hash")
+
+ -> { {a: 1, **nil} }.should raise_error(TypeError, "no implicit conversion of nil into Hash")
+ end
+ end
+
+ ruby_version_is "3.4" do
+ it "expands nil using ** into {}" do
+ h = nil
+ {**h}.should == {}
+ {a: 1, **h}.should == {a: 1}
+
+ {**nil}.should == {}
+ {a: 1, **nil}.should == {a: 1}
+ end
+ end
+
+ it "expands an '**{}' or '**obj' element with the last key/value pair taking precedence" do
-> {
@h = eval "{a: 1, **{a: 2, b: 3, c: 1}, c: 3}"
}.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,27 +203,122 @@ describe "Hash literal" do
{a: 1, **obj, c: 3}.should == {a:1, b: 2, c: 3, d: 4}
end
- it "raises a TypeError if any splatted elements keys are not symbols" do
+ it "allows splatted elements keys that are not symbols" do
h = {1 => 2, b: 3}
- lambda { {a: 1, **h} }.should raise_error(TypeError)
+ {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)
- lambda { {**obj} }.should raise_error(TypeError)
+ -> { {**obj} }.should raise_error(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)
end
it "does not change encoding of literal string keys during creation" do
- ascii8bit_hash = HashStringsASCII8BIT.literal_hash
+ binary_hash = HashStringsBinary.literal_hash
utf8_hash = HashStringsUTF8.literal_hash
usascii_hash = HashStringsUSASCII.literal_hash
- ascii8bit_hash.keys.first.encoding.should == Encoding::ASCII_8BIT
- ascii8bit_hash.keys.first.should == utf8_hash.keys.first
+ binary_hash.keys.first.encoding.should == Encoding::BINARY
+ binary_hash.keys.first.should == utf8_hash.keys.first
utf8_hash.keys.first.encoding.should == Encoding::UTF_8
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_error(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_error(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
+
+ ruby_bug "#20012", ""..."3.3" do
+ 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
+ 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_error(SyntaxError, /identifier a! is not valid to get/)
+ end
+
+ it "raises a SyntaxError when the hash key ends with `?`" do
+ -> { eval("{a?:}") }.should raise_error(SyntaxError, /identifier a\? is not valid to get/)
+ end
+ end
end
diff --git a/spec/ruby/language/heredoc_spec.rb b/spec/ruby/language/heredoc_spec.rb
index a57a7b0bb9..47ee9c2c51 100644
--- a/spec/ruby/language/heredoc_spec.rb
+++ b/spec/ruby/language/heredoc_spec.rb
@@ -1,6 +1,6 @@
# -*- encoding: us-ascii -*-
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "Heredoc string" do
@@ -13,6 +13,7 @@ describe "Heredoc string" do
foo bar#{@ip}
HERE
s.should == "foo barxxx\n"
+ s.encoding.should == Encoding::US_ASCII
end
it 'allow HEREDOC with <<"identifier", interpolated' do
@@ -20,6 +21,7 @@ HERE
foo bar#{@ip}
HERE
s.should == "foo barxxx\n"
+ s.encoding.should == Encoding::US_ASCII
end
it "allows HEREDOC with <<'identifier', no interpolation" do
@@ -27,6 +29,7 @@ HERE
foo bar#{@ip}
HERE
s.should == 'foo bar#{@ip}' + "\n"
+ s.encoding.should == Encoding::US_ASCII
end
it "allows HEREDOC with <<-identifier, allowing to indent identifier, interpolated" do
@@ -35,6 +38,7 @@ HERE
HERE
s.should == " foo barxxx\n"
+ s.encoding.should == Encoding::US_ASCII
end
it 'allows HEREDOC with <<-"identifier", allowing to indent identifier, interpolated' do
@@ -43,6 +47,7 @@ HERE
HERE
s.should == " foo barxxx\n"
+ s.encoding.should == Encoding::US_ASCII
end
it "allows HEREDOC with <<-'identifier', allowing to indent identifier, no interpolation" do
@@ -51,37 +56,64 @@ HERE
HERE
s.should == ' foo bar#{@ip}' + "\n"
+ s.encoding.should == Encoding::US_ASCII
end
- ruby_version_is "2.3" do
- it "allows HEREDOC with <<~'identifier', allowing to indent identifier and content" do
- require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
- SquigglyHeredocSpecs.message.should == "character density, n.:\n The number of very weird people in the office.\n"
- end
-
- it "trims trailing newline character for blank HEREDOC with <<~'identifier'" do
- require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
- SquigglyHeredocSpecs.blank.should == ""
- end
-
- it 'allows HEREDOC with <<~identifier, interpolated' do
- require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
- SquigglyHeredocSpecs.unquoted.should == "unquoted interpolated\n"
- end
-
- it 'allows HEREDOC with <<~"identifier", interpolated' do
- require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
- SquigglyHeredocSpecs.doublequoted.should == "doublequoted interpolated\n"
- end
-
- it "allows HEREDOC with <<~'identifier', no interpolation" do
- require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
- SquigglyHeredocSpecs.singlequoted.should == "singlequoted \#{\"interpolated\"}\n"
- end
-
- it "selects the least-indented line and removes its indentation from all the lines" do
- require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
- SquigglyHeredocSpecs.least_indented_on_the_last_line.should == " a\n b\nc\n"
- end
+ 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
+
+ it "allows HEREDOC with <<~'identifier', allowing to indent identifier and content" do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.message.should == "character density, n.:\n The number of very weird people in the office.\n"
+ end
+
+ it "trims trailing newline character for blank HEREDOC with <<~'identifier'" do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.blank.should == ""
+ end
+
+ it 'allows HEREDOC with <<~identifier, interpolated' do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.unquoted.should == "unquoted interpolated\n"
+ end
+
+ it 'allows HEREDOC with <<~"identifier", interpolated' do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.doublequoted.should == "doublequoted interpolated\n"
+ end
+
+ it "allows HEREDOC with <<~'identifier', no interpolation" do
+ require_relative 'fixtures/squiggly_heredoc'
+ 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"
+ SquigglyHeredocSpecs.least_indented_on_the_last_line.should == " a\n b\nc\n"
+ end
+
+ it "selects the least-indented line and removes its indentation from all the lines for <<~'identifier'" do
+ require_relative 'fixtures/squiggly_heredoc'
+ 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_error(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 284d852462..2d1a89f081 100644
--- a/spec/ruby/language/if_spec.rb
+++ b/spec/ruby/language/if_spec.rb
@@ -1,22 +1,20 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The if expression" do
- ruby_version_is '2.4' do
- describe "accepts multiple assignments in conditional expression" do
- before(:each) { ScratchPad.record([]) }
- after(:each) { ScratchPad.clear }
-
- it 'with non-nil values' do
- ary = [1, 2]
- eval "if (a, b = ary); ScratchPad.record [a, b]; end"
- ScratchPad.recorded.should == [1, 2]
- end
+ describe "accepts multiple assignments in conditional expression" do
+ before(:each) { ScratchPad.record([]) }
+ after(:each) { ScratchPad.clear }
+
+ it 'with non-nil values' do
+ ary = [1, 2]
+ eval "if (a, b = ary); ScratchPad.record [a, b]; end"
+ ScratchPad.recorded.should == [1, 2]
+ end
- it 'with nil values' do
- ary = nil
- eval "if (a, b = ary); else; ScratchPad.record [a, b]; end"
- ScratchPad.recorded.should == [nil, nil]
- end
+ it 'with nil values' do
+ ary = nil
+ eval "if (a, b = ary); else; ScratchPad.record [a, b]; end"
+ ScratchPad.recorded.should == [nil, nil]
end
end
@@ -307,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_error(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..72023180d9
--- /dev/null
+++ b/spec/ruby/language/it_parameter_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../spec_helper'
+
+ruby_version_is "3.4" do
+ describe "The `it` parameter" do
+ it "provides it in a block" do
+ -> { it }.call("a").should == "a"
+ proc { it }.call("a").should == "a"
+ lambda { it }.call("a").should == "a"
+ ["a"].map { it }.should == ["a"]
+ end
+
+ it "assigns nil to not passed parameters" do
+ proc { it }.call().should == nil
+ end
+
+ it "can be used in both outer and nested blocks at the same time" do
+ -> { it + -> { it * it }.call(2) }.call(3).should == 7
+ end
+
+ it "is a regular local variable if there is already a 'it' local variable" do
+ it = 0
+ proc { it }.call("a").should == 0
+ end
+
+ it "raises SyntaxError when block parameters are specified explicitly" do
+ -> { eval("-> () { it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ -> { eval("-> (x) { it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+
+ -> { eval("proc { || it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ -> { eval("proc { |x| it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+
+ -> { eval("lambda { || it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ -> { eval("lambda { |x| it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+
+ -> { eval("['a'].map { || it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ -> { eval("['a'].map { |x| it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ end
+
+ it "affects block arity" do
+ -> {}.arity.should == 0
+ -> { it }.arity.should == 1
+ end
+
+ it "affects block parameters" do
+ -> { it }.parameters.should == [[:req]]
+
+ ruby_version_is ""..."4.0" do
+ proc { it }.parameters.should == [[:opt, nil]]
+ end
+ ruby_version_is "4.0" do
+ proc { it }.parameters.should == [[:opt]]
+ end
+ end
+
+ it "does not affect binding local variables" do
+ -> { it; binding.local_variables }.call("a").should == []
+ end
+
+ it "does not work in methods" do
+ obj = Object.new
+ def obj.foo; it; end
+
+ -> { obj.foo("a") }.should raise_error(ArgumentError, /wrong number of arguments/)
+ end
+ end
+end
diff --git a/spec/ruby/language/keyword_arguments_spec.rb b/spec/ruby/language/keyword_arguments_spec.rb
new file mode 100644
index 0000000000..4f6370d419
--- /dev/null
+++ b/spec/ruby/language/keyword_arguments_spec.rb
@@ -0,0 +1,386 @@
+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_error(ArgumentError, 'unknown keyword: :kw2')
+ -> { m(kw: 1, true => false) }.should raise_error(ArgumentError, 'unknown keyword: true')
+ -> { m(kw: 1, a: 1, b: 2, c: 3) }.should raise_error(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_error(ArgumentError, /missing keyword: :c/)
+ -> { m() }.should raise_error(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_error(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 "**" do
+ ruby_version_is "3.3" 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
+ 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 43e2d60ae3..ed5a1c69e8 100644
--- a/spec/ruby/language/lambda_spec.rb
+++ b/spec/ruby/language/lambda_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/classes'
describe "A lambda literal -> () { }" do
SpecEvaluate.desc = "for definition"
@@ -7,7 +7,7 @@ describe "A lambda literal -> () { }" do
it "returns a Proc object when used in a BasicObject method" do
klass = Class.new(BasicObject) do
def create_lambda
- -> () { }
+ -> { }
end
end
@@ -15,11 +15,19 @@ describe "A lambda literal -> () { }" do
end
it "does not execute the block" do
- ->() { fail }.should be_an_instance_of(Proc)
+ -> { fail }.should be_an_instance_of(Proc)
end
it "returns a lambda" do
- -> () { }.lambda?.should be_true
+ -> { }.lambda?.should be_true
+ end
+
+ 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 ensure clause" do
+ eval('-> do 1; ensure; 2; end').should be_an_instance_of(Proc)
end
it "has its own scope for local variables" do
@@ -101,7 +109,7 @@ describe "A lambda literal -> () { }" do
@a = -> (a:) { a }
ruby
- lambda { @a.() }.should raise_error(ArgumentError)
+ -> { @a.() }.should raise_error(ArgumentError)
@a.(a: 1).should == 1
end
@@ -119,7 +127,7 @@ describe "A lambda literal -> () { }" do
@a.().should be_nil
@a.(a: 1, b: 2).should be_nil
- lambda { @a.(1) }.should raise_error(ArgumentError)
+ -> { @a.(1) }.should raise_error(ArgumentError)
end
evaluate <<-ruby do
@@ -143,8 +151,8 @@ describe "A lambda literal -> () { }" do
ruby
@a.(1, 2).should == [1, 2]
- lambda { @a.() }.should raise_error(ArgumentError)
- lambda { @a.(1) }.should raise_error(ArgumentError)
+ -> { @a.() }.should raise_error(ArgumentError)
+ -> { @a.(1) }.should raise_error(ArgumentError)
end
evaluate <<-ruby do
@@ -177,8 +185,8 @@ describe "A lambda literal -> () { }" do
@a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
h = mock("keyword splat")
- h.should_receive(:to_hash).and_return({a: 1})
- @a.(h).should == {a: 1}
+ h.should_not_receive(:to_hash)
+ @a.(h).should == {}
end
evaluate <<-ruby do
@@ -255,25 +263,26 @@ describe "A lambda literal -> () { }" do
end
describe "with circular optional argument reference" do
- it "shadows an existing local with the same name as the argument" do
- a = 1
- -> {
- @proc = eval "-> (a=a) { a }"
- }.should complain(/circular argument reference/)
- @proc.call.should == nil
+ ruby_version_is ""..."3.4" do
+ it "raises a SyntaxError if using the argument in its default value" do
+ a = 1
+ -> {
+ eval "-> (a=a) { a }"
+ }.should raise_error(SyntaxError)
+ end
end
- it "shadows an existing method with the same name as the argument" do
- def a; 1; end
- -> {
- @proc = eval "-> (a=a) { a }"
- }.should complain(/circular argument reference/)
- @proc.call.should == nil
+ ruby_version_is "3.4" do
+ it "is nil if using the argument in its default value" do
+ -> {
+ eval "-> (a=a) { a }.call"
+ }.call.should == nil
+ end
end
it "calls an existing method with the same name as the argument if explicitly using ()" do
def a; 1; end
- -> (a=a()) { a }.call.should == 1
+ -> a=a() { a }.call.should == 1
end
end
end
@@ -302,7 +311,13 @@ describe "A lambda expression 'lambda { ... }'" do
end
it "requires a block" do
- lambda { lambda }.should raise_error(ArgumentError)
+ suppress_warning do
+ lambda { lambda }.should raise_error(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)
end
context "with an implicit block" do
@@ -310,14 +325,13 @@ describe "A lambda expression 'lambda { ... }'" do
def meth; lambda; end
end
- it "can be created" do
+ it "raises ArgumentError" do
implicit_lambda = nil
- -> {
- 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
+ suppress_warning do
+ -> {
+ meth { 1 }
+ }.should raise_error(ArgumentError, /tried to create Proc object without a block/)
+ end
end
end
@@ -493,8 +507,8 @@ describe "A lambda expression 'lambda { ... }'" do
@a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
h = mock("keyword splat")
- h.should_receive(:to_hash).and_return({a: 1})
- @a.(h).should == {a: 1}
+ h.should_not_receive(:to_hash)
+ @a.(h).should == {}
end
evaluate <<-ruby do
diff --git a/spec/ruby/language/line_spec.rb b/spec/ruby/language/line_spec.rb
index d9fd307dab..fcadaa71d7 100644
--- a/spec/ruby/language/line_spec.rb
+++ b/spec/ruby/language/line_spec.rb
@@ -1,10 +1,10 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/code_loading', __FILE__)
-require File.expand_path('../shared/__LINE__', __FILE__)
+require_relative '../spec_helper'
+require_relative '../fixtures/code_loading'
+require_relative 'shared/__LINE__'
describe "The __LINE__ pseudo-variable" do
it "raises a SyntaxError if assigned to" do
- lambda { eval("__LINE__ = 1") }.should raise_error(SyntaxError)
+ -> { eval("__LINE__ = 1") }.should raise_error(SyntaxError)
end
before :each do
diff --git a/spec/ruby/language/loop_spec.rb b/spec/ruby/language/loop_spec.rb
index 4e60e0d8e6..fd17b53910 100644
--- a/spec/ruby/language/loop_spec.rb
+++ b/spec/ruby/language/loop_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The loop expression" do
it "repeats the given block until a break is called" do
@@ -15,7 +15,7 @@ describe "The loop expression" do
inner_loop = 123
break
end
- lambda { inner_loop }.should raise_error(NameError)
+ -> { inner_loop }.should raise_error(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 2f6e3b5c3a..af9c9dbfd0 100644
--- a/spec/ruby/language/magic_comment_spec.rb
+++ b/spec/ruby/language/magic_comment_spec.rb
@@ -1,62 +1,93 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
-describe "Magic comment" do
- it "is optional" do
- eval("__ENCODING__").should be_an_instance_of(Encoding)
+# See core/kernel/eval_spec.rb for more magic comments specs for eval()
+describe :magic_comments, shared: true do
+ before :each do
+ @default = @method == :locale ? Encoding.find('locale') : Encoding::UTF_8
end
- it "determines __ENCODING__" do
- eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
-# encoding: ASCII-8BIT
-__ENCODING__
-EOS
+ it "are optional" do
+ @object.call('no_magic_comment.rb').should == @default.name
end
- it "is case-insensitive" do
- eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
-# CoDiNg: aScIi-8bIt
-__ENCODING__
-EOS
+ it "are case-insensitive" do
+ @object.call('case_magic_comment.rb').should == Encoding::Big5.name
end
it "must be at the first line" do
- eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::US_ASCII
-
-# encoding: ASCII-8BIT
-__ENCODING__
-EOS
+ @object.call('second_line_magic_comment.rb').should == @default.name
end
it "must be the first token of the line" do
- eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::US_ASCII
-1+1 # encoding: ASCII-8BIT
-__ENCODING__
-EOS
- eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
- # encoding: ASCII-8BIT
-__ENCODING__
-EOS
+ @object.call('second_token_magic_comment.rb').should == @default.name
end
it "can be after the shebang" do
- eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
-#!/usr/bin/ruby -Ku
-# encoding: ASCII-8BIT
-__ENCODING__
-EOS
+ @object.call('shebang_magic_comment.rb').should == Encoding::Big5.name
end
it "can take Emacs style" do
- eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
-# -*- encoding: ascii-8bit -*-
-__ENCODING__
-EOS
+ @object.call('emacs_magic_comment.rb').should == Encoding::Big5.name
end
it "can take vim style" do
- eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
-# vim: filetype=ruby, fileencoding=ascii-8bit, tabsize=3, shiftwidth=3
-__ENCODING__
-EOS
+ @object.call('vim_magic_comment.rb').should == Encoding::Big5.name
+ end
+
+ it "determine __ENCODING__" do
+ @object.call('magic_comment.rb').should == Encoding::Big5.name
+ end
+
+ it "do not cause bytes to be mangled by passing them through the wrong encoding" do
+ @object.call('bytes_magic_comment.rb').should == [167, 65, 166, 110].inspect
+ end
+end
+
+describe "Magic comments" do
+ describe "in stdin" do
+ 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}")
+ }
+ end
+
+ platform_is_not :windows do
+ describe "in an -e argument" do
+ it_behaves_like :magic_comments, :locale, -> file {
+ print_at_exit = fixture(__FILE__, "print_magic_comment_result_at_exit.rb")
+ # Use UTF-8, as it is the default source encoding for files
+ code = File.read(fixture(__FILE__, file), encoding: 'utf-8')
+ IO.popen([*ruby_exe, "-r", print_at_exit, "-e", code], &:read)
+ }
+ end
+ end
+
+ describe "in the main file" do
+ it_behaves_like :magic_comments, :UTF8, -> file {
+ print_at_exit = fixture(__FILE__, "print_magic_comment_result_at_exit.rb")
+ ruby_exe(fixture(__FILE__, file), options: "-r#{print_at_exit}")
+ }
+ end
+
+ describe "in a loaded file" do
+ it_behaves_like :magic_comments, :UTF8, -> file {
+ load fixture(__FILE__, file)
+ $magic_comment_result
+ }
+ end
+
+ describe "in a required file" do
+ it_behaves_like :magic_comments, :UTF8, -> file {
+ require fixture(__FILE__, file)
+ $magic_comment_result
+ }
+ end
+
+ describe "in an eval" do
+ it_behaves_like :magic_comments, :UTF8, -> file {
+ # Use UTF-8, as it is the default source encoding for files
+ eval(File.read(fixture(__FILE__, file), encoding: 'utf-8'))
+ }
end
end
diff --git a/spec/ruby/language/match_spec.rb b/spec/ruby/language/match_spec.rb
index 81604e94b2..ebf677cabc 100644
--- a/spec/ruby/language/match_spec.rb
+++ b/spec/ruby/language/match_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/match_operators', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/match_operators'
describe "The !~ operator" do
before :each do
@@ -48,6 +48,13 @@ describe "The =~ operator with named captures" do
end
end
+ describe "on syntax of 'string_literal' =~ /regexp/" do
+ it "does not set local variables" do
+ 'string literal' =~ /(?<matched>str)(?<unmatched>lit)?/
+ local_variables.should == []
+ end
+ end
+
describe "on syntax of string_variable =~ /regexp/" do
it "does not set local variables" do
@string =~ /(?<matched>foo)(?<unmatched>bar)?/
diff --git a/spec/ruby/language/metaclass_spec.rb b/spec/ruby/language/metaclass_spec.rb
index b3bcd9ef18..fc83067977 100644
--- a/spec/ruby/language/metaclass_spec.rb
+++ b/spec/ruby/language/metaclass_spec.rb
@@ -1,6 +1,6 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/class', __FILE__)
-require File.expand_path('../fixtures/metaclass', __FILE__)
+require_relative '../spec_helper'
+require_relative '../fixtures/class'
+require_relative 'fixtures/metaclass'
describe "self in a metaclass body (class << obj)" do
it "is TrueClass for true" do
@@ -16,11 +16,11 @@ describe "self in a metaclass body (class << obj)" do
end
it "raises a TypeError for numbers" do
- lambda { class << 1; self; end }.should raise_error(TypeError)
+ -> { class << 1; self; end }.should raise_error(TypeError)
end
it "raises a TypeError for symbols" do
- lambda { class << :symbol; self; end }.should raise_error(TypeError)
+ -> { class << :symbol; self; end }.should raise_error(TypeError)
end
it "is a singleton Class instance" do
@@ -64,11 +64,11 @@ describe "A constant on a metaclass" do
class << @object
CONST
end
- lambda { CONST }.should raise_error(NameError)
+ -> { CONST }.should raise_error(NameError)
end
it "cannot be accessed via object::CONST" do
- lambda do
+ -> do
@object::CONST
end.should raise_error(TypeError)
end
@@ -79,7 +79,7 @@ describe "A constant on a metaclass" do
CONST = 100
end
- lambda do
+ -> do
@object::CONST
end.should raise_error(NameError)
end
@@ -96,7 +96,7 @@ describe "A constant on a metaclass" do
it "is not preserved when the object is duped" do
@object = @object.dup
- lambda do
+ -> do
class << @object; CONST; end
end.should raise_error(NameError)
end
diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb
index ca939dbab6..8f72bd45ed 100644
--- a/spec/ruby/language/method_spec.rb
+++ b/spec/ruby/language/method_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "A method send" do
evaluate <<-ruby do
@@ -40,7 +40,7 @@ describe "A method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { m(*x) }.should raise_error(TypeError)
+ -> { m(*x) }.should raise_error(TypeError)
end
end
@@ -74,7 +74,7 @@ describe "A method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { m(*x, 2, 3) }.should raise_error(TypeError)
+ -> { m(*x, 2, 3) }.should raise_error(TypeError)
end
end
@@ -108,7 +108,7 @@ describe "A method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { m(1, *x, 2, 3) }.should raise_error(TypeError)
+ -> { m(1, *x, 2, 3) }.should raise_error(TypeError)
end
it "copies the splatted array" do
@@ -153,7 +153,27 @@ describe "A method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { m(1, 2, *x) }.should raise_error(TypeError)
+ -> { m(1, 2, *x) }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a block argument" do
+ before :all do
+ def m(x)
+ if block_given?
+ [true, yield(x + 'b')]
+ else
+ [false]
+ end
+ end
+ end
+
+ it "that refers to a proc passes the proc as the block" do
+ m('a', &-> y { y + 'c'}).should == [true, 'abc']
+ end
+
+ it "that is nil passes no block" do
+ m('a', &nil).should == [false]
end
end
end
@@ -197,7 +217,7 @@ describe "An element assignment method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { @o[*x] = 1 }.should raise_error(TypeError)
+ -> { @o[*x] = 1 }.should raise_error(TypeError)
end
end
@@ -235,7 +255,7 @@ describe "An element assignment method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { @o[*x, 2, 3] = 4 }.should raise_error(TypeError)
+ -> { @o[*x, 2, 3] = 4 }.should raise_error(TypeError)
end
end
@@ -273,7 +293,7 @@ describe "An element assignment method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { @o[1, 2, *x, 3] = 4 }.should raise_error(TypeError)
+ -> { @o[1, 2, *x, 3] = 4 }.should raise_error(TypeError)
end
end
@@ -311,7 +331,7 @@ describe "An element assignment method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { @o[1, 2, 3, *x] = 4 }.should raise_error(TypeError)
+ -> { @o[1, 2, 3, *x] = 4 }.should raise_error(TypeError)
end
end
end
@@ -348,7 +368,7 @@ describe "An attribute assignment method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { @o.send :m=, *x, 1 }.should raise_error(TypeError)
+ -> { @o.send :m=, *x, 1 }.should raise_error(TypeError)
end
end
@@ -383,7 +403,7 @@ describe "An attribute assignment method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { @o.send :m=, *x, 2, 3, 4 }.should raise_error(TypeError)
+ -> { @o.send :m=, *x, 2, 3, 4 }.should raise_error(TypeError)
end
end
@@ -418,7 +438,7 @@ describe "An attribute assignment method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { @o.send :m=, 1, 2, *x, 3, 4 }.should raise_error(TypeError)
+ -> { @o.send :m=, 1, 2, *x, 3, 4 }.should raise_error(TypeError)
end
end
@@ -453,7 +473,7 @@ describe "An attribute assignment method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { @o.send :m=, 1, 2, 3, *x, 4 }.should raise_error(TypeError)
+ -> { @o.send :m=, 1, 2, 3, *x, 4 }.should raise_error(TypeError)
end
end
end
@@ -512,6 +532,15 @@ describe "A method" do
end
evaluate <<-ruby do
+ def m() end
+ ruby
+
+ m().should be_nil
+ m(*[]).should be_nil
+ m(**{}).should be_nil
+ end
+
+ evaluate <<-ruby do
def m(*) end
ruby
@@ -527,15 +556,26 @@ describe "A method" do
m().should == []
m(1).should == [1]
m(1, 2, 3).should == [1, 2, 3]
+ m(*[]).should == []
+ m(**{}).should == []
end
evaluate <<-ruby do
def m(a:) a end
ruby
- lambda { m() }.should raise_error(ArgumentError)
+ -> { m() }.should raise_error(ArgumentError)
m(a: 1).should == 1
- lambda { m("a" => 1, a: 1) }.should raise_error(ArgumentError)
+ suppress_keyword_warning do
+ -> { m("a" => 1, a: 1) }.should raise_error(ArgumentError)
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a:, **kw) [a, kw] end
+ ruby
+
+ -> { m(b: 1) }.should raise_error(ArgumentError)
end
evaluate <<-ruby do
@@ -552,7 +592,7 @@ describe "A method" do
m().should be_nil
m(a: 1, b: 2).should be_nil
- lambda { m(1) }.should raise_error(ArgumentError)
+ -> { m(1) }.should raise_error(ArgumentError)
end
evaluate <<-ruby do
@@ -561,7 +601,19 @@ describe "A method" do
m().should == {}
m(a: 1, b: 2).should == { a: 1, b: 2 }
- lambda { m(2) }.should raise_error(ArgumentError)
+ m(*[]).should == {}
+ m(**{}).should == {}
+ 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)
+ end
+
+ evaluate <<-ruby do
+ def m(**k); k end;
+ ruby
+
+ m("a" => 1).should == { "a" => 1 }
end
evaluate <<-ruby do
@@ -600,7 +652,7 @@ describe "A method" do
m(2, 3).should be_nil
m([2, 3, 4], [5, 6]).should be_nil
- lambda { m a: 1 }.should raise_error(ArgumentError)
+ -> { m a: 1 }.should raise_error(ArgumentError)
end
evaluate <<-ruby do
@@ -692,7 +744,9 @@ describe "A method" do
ruby
m(1, b: 2).should == [1, 2]
- lambda { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ suppress_keyword_warning do
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
end
evaluate <<-ruby do
@@ -701,7 +755,7 @@ describe "A method" do
m(2).should == [2, 1]
m(1, b: 2).should == [1, 2]
- m("a" => 1, b: 2).should == [{"a" => 1, b: 2}, 1]
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
end
evaluate <<-ruby do
@@ -710,7 +764,7 @@ describe "A method" do
m(1).should == 1
m(1, a: 2, b: 3).should == 1
- m("a" => 1, b: 2).should == {"a" => 1, b: 2}
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
end
evaluate <<-ruby do
@@ -719,7 +773,7 @@ describe "A method" do
m(1).should == [1, {}]
m(1, a: 2, b: 3).should == [1, {a: 2, b: 3}]
- m("a" => 1, b: 2).should == [{"a" => 1, b: 2}, {}]
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
end
evaluate <<-ruby do
@@ -794,8 +848,8 @@ describe "A method" do
def m(a=1, (*b), (*c)) [a, b, c] end
ruby
- lambda { m() }.should raise_error(ArgumentError)
- lambda { m(2) }.should raise_error(ArgumentError)
+ -> { m() }.should raise_error(ArgumentError)
+ -> { m(2) }.should raise_error(ArgumentError)
m(2, 3).should == [1, [2], [3]]
m(2, [3, 4], [5, 6]).should == [2, [3, 4], [5, 6]]
end
@@ -836,7 +890,7 @@ describe "A method" do
m(b: 2).should == [1, 2]
m(2, b: 1).should == [2, 1]
- m("a" => 1, b: 2).should == [{"a" => 1}, 2]
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
end
evaluate <<-ruby do
@@ -846,7 +900,7 @@ describe "A method" do
m().should == [1, 2]
m(2).should == [2, 2]
m(b: 3).should == [1, 3]
- m("a" => 1, b: 2).should == [{"a" => 1}, 2]
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
end
evaluate <<-ruby do
@@ -855,7 +909,7 @@ describe "A method" do
m().should == 1
m(2, a: 1, b: 0).should == 2
- m("a" => 1, a: 2).should == {"a" => 1}
+ m("a" => 1, a: 2).should == 1
end
evaluate <<-ruby do
@@ -896,151 +950,6 @@ describe "A method" do
end
evaluate <<-ruby do
- def m(*, a:) a end
- ruby
-
- m(a: 1).should == 1
- m(1, 2, a: 3).should == 3
- m("a" => 1, a: 2).should == 2
- 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]
- m("a" => 1, b: 2).should == [[{"a" => 1}], 2]
- 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
- m("a" => 1, a: 2).should == 2
- 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]
- m("a" => 1, b: 2).should == [[{"a" => 1}], 2]
-
- 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})
- m(h).should be_nil
-
- h = mock("keyword splat")
- error = RuntimeError.new("error while converting to a hash")
- h.should_receive(:to_hash).and_raise(error)
- lambda { 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)
- lambda { 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
-
- evaluate <<-ruby do
def m(*, &b) b end
ruby
@@ -1063,7 +972,9 @@ describe "A method" do
ruby
m(a: 1, b: 2).should == [1, 2]
- lambda { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ suppress_keyword_warning do
+ -> { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ end
end
evaluate <<-ruby do
@@ -1072,7 +983,9 @@ describe "A method" do
m(a: 1).should == [1, 1]
m(a: 1, b: 2).should == [1, 2]
- lambda { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ suppress_keyword_warning do
+ -> { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ end
end
evaluate <<-ruby do
@@ -1081,7 +994,7 @@ describe "A method" do
m(a: 1).should == 1
m(a: 1, b: 2).should == 1
- lambda { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ m("a" => 1, a: 1, b: 2).should == 1
end
evaluate <<-ruby do
@@ -1090,7 +1003,7 @@ describe "A method" do
m(a: 1).should == [1, {}]
m(a: 1, b: 2, c: 3).should == [1, {b: 2, c: 3}]
- lambda { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ m("a" => 1, a: 1, b: 2).should == [1, {"a" => 1, b: 2}]
end
evaluate <<-ruby do
@@ -1188,21 +1101,103 @@ describe "A method" do
end
evaluate <<-ruby do
+ def m(a, **nil); a end;
+ ruby
+
+ m({a: 1}).should == {a: 1}
+ m({"a" => 1}).should == {"a" => 1}
+
+ -> { m(a: 1) }.should raise_error(ArgumentError, 'no keywords accepted')
+ -> { m(**{a: 1}) }.should raise_error(ArgumentError, 'no keywords accepted')
+ -> { m("a" => 1) }.should raise_error(ArgumentError, 'no keywords accepted')
+ end
+
+ evaluate <<-ruby do
def m(a, b = nil, c = nil, d, e: nil, **f)
[a, b, c, d, e, f]
end
- ruby
+ ruby
result = m(1, 2)
result.should == [1, nil, nil, 2, nil, {}]
result = m(1, 2, {foo: :bar})
- result.should == [1, nil, nil, 2, nil, {foo: :bar}]
+ result.should == [1, 2, nil, {foo: :bar}, nil, {}]
result = m(1, {foo: :bar})
result.should == [1, nil, nil, {foo: :bar}, nil, {}]
end
end
+
+ 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
+ end
+
+ 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 raise_error(ArgumentError)
+ end
+ end
+
+ 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
+ m(options)
+ end.should raise_error(ArgumentError)
+ end
+ end
+
+ 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
+
+ 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_error(TypeError, "no implicit conversion of nil into Hash")
+ -> { m(a: 1, **nil) }.should raise_error(TypeError, "no implicit conversion of nil into Hash")
+ end
+ end
+
+ ruby_version_is "3.4" do
+ it "expands nil using ** into {}" do
+ def m(**kw) kw; end
+
+ h = nil
+ m(**h).should == {}
+ m(a: 1, **h).should == {a: 1}
+
+ m(**nil).should == {}
+ m(a: 1, **nil).should == {a: 1}
+ end
+ end
end
describe "A method call with a space between method name and parentheses" do
@@ -1223,20 +1218,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
+
+ ruby_version_is "3.3" do
+ it "supports multiple statements" do
+ eval("m (1; 2)").should == [2]
+ end
+ 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
- lambda {
+ -> {
eval("m (1, 2)")
}.should raise_error(SyntaxError)
- lambda {
+ -> {
eval("m (1, 2, 3)")
}.should raise_error(SyntaxError)
end
@@ -1294,3 +1317,333 @@ describe "An array-dereference method ([])" do
end
end
end
+
+describe "An endless method definition" do
+ context "without arguments" do
+ evaluate <<-ruby do
+ 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
+
+ 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_error(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_error(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 d5ca71b3b4..fba4aa8c6e 100644
--- a/spec/ruby/language/module_spec.rb
+++ b/spec/ruby/language/module_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/module', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/module'
describe "The module keyword" do
it "creates a new module without semicolon" do
@@ -26,37 +26,65 @@ describe "The module keyword" do
it "reopens an existing module" do
module ModuleSpecs; Reopened = true; end
ModuleSpecs::Reopened.should be_true
+ ensure
+ ModuleSpecs.send(:remove_const, :Reopened)
end
- it "reopens a module included in Object" do
- module IncludedModuleSpecs; Reopened = true; end
- ModuleSpecs::IncludedInObject::IncludedModuleSpecs::Reopened.should be_true
+ 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
- lambda do
+ -> do
module ModuleSpecs::Modules::Klass; end
end.should raise_error(TypeError)
end
it "raises a TypeError if the constant is a String" do
- lambda { module ModuleSpecs::Modules::A; end }.should raise_error(TypeError)
+ -> { module ModuleSpecs::Modules::A; end }.should raise_error(TypeError)
end
- it "raises a TypeError if the constant is a Fixnum" do
- lambda { module ModuleSpecs::Modules::B; end }.should raise_error(TypeError)
+ it "raises a TypeError if the constant is an Integer" do
+ -> { module ModuleSpecs::Modules::B; end }.should raise_error(TypeError)
end
it "raises a TypeError if the constant is nil" do
- lambda { module ModuleSpecs::Modules::C; end }.should raise_error(TypeError)
+ -> { module ModuleSpecs::Modules::C; end }.should raise_error(TypeError)
end
it "raises a TypeError if the constant is true" do
- lambda { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError)
+ -> { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError)
end
it "raises a TypeError if the constant is false" do
- lambda { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError)
+ -> { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError)
end
end
@@ -67,12 +95,14 @@ describe "Assigning an anonymous module to a constant" do
::ModuleSpecs_CS1 = mod
mod.name.should == "ModuleSpecs_CS1"
+ ensure
+ Object.send(:remove_const, :ModuleSpecs_CS1)
end
- it "does not set the name of a module scoped by an anonymous module" 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 be_nil
+ b.name.should.end_with? '::B'
end
it "sets the name of contained modules when assigning a toplevel anonymous module" do
@@ -87,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 67da5224dc..6fbfc4a54d 100644
--- a/spec/ruby/language/next_spec.rb
+++ b/spec/ruby/language/next_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/next', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/next'
describe "The next statement from within the block" do
before :each do
@@ -8,7 +8,7 @@ describe "The next statement from within the block" do
it "ends block execution" do
a = []
- lambda {
+ -> {
a << 1
next
a << 2
@@ -17,15 +17,15 @@ describe "The next statement from within the block" do
end
it "causes block to return nil if invoked without arguments" do
- lambda { 123; next; 456 }.call.should == nil
+ -> { 123; next; 456 }.call.should == nil
end
it "causes block to return nil if invoked with an empty expression" do
- lambda { next (); 456 }.call.should be_nil
+ -> { next (); 456 }.call.should be_nil
end
it "returns the argument passed" do
- lambda { 123; next 234; 345 }.call.should == 234
+ -> { 123; next 234; 345 }.call.should == 234
end
it "returns to the invoking method" do
@@ -102,14 +102,14 @@ describe "The next statement from within the block" do
it "passes the value returned by a method with omitted parenthesis and passed block" do
obj = NextSpecs::Block.new
- lambda { next obj.method :value do |x| x end }.call.should == :value
+ -> { next obj.method :value do |x| x end }.call.should == :value
end
end
describe "The next statement" do
describe "in a method" do
it "is invalid and raises a SyntaxError" do
- lambda {
+ -> {
eval("def m; next; end")
}.should raise_error(SyntaxError)
end
diff --git a/spec/ruby/language/not_spec.rb b/spec/ruby/language/not_spec.rb
index a0cf6b15a6..052af9b256 100644
--- a/spec/ruby/language/not_spec.rb
+++ b/spec/ruby/language/not_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The not keyword" do
it "negates a `true' value" do
diff --git a/spec/ruby/language/numbered_parameters_spec.rb b/spec/ruby/language/numbered_parameters_spec.rb
new file mode 100644
index 0000000000..de532c326d
--- /dev/null
+++ b/spec/ruby/language/numbered_parameters_spec.rb
@@ -0,0 +1,113 @@
+require_relative '../spec_helper'
+
+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 "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 "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 "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 "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/)
+
+ -> { eval("proc { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ -> { eval("proc { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+
+ -> { eval("lambda { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ -> { eval("lambda { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+
+ -> { 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/)
+ end
+
+ describe "assigning to a numbered parameter" do
+ it "raises SyntaxError" do
+ -> { eval("proc { _1 = 0 }") }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/)
+ 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
+ end
+
+ it "affects block parameters" do
+ -> { _1 }.parameters.should == [[:req, :_1]]
+ -> { _2 }.parameters.should == [[:req, :_1], [:req, :_2]]
+
+ proc { _1 }.parameters.should == [[:opt, :_1]]
+ proc { _2 }.parameters.should == [[:opt, :_1], [:opt, :_2]]
+ end
+
+ ruby_version_is ""..."4.0" do
+ it "affects binding local variables" do
+ -> { _1; binding.local_variables }.call("a").should == [:_1]
+ -> { _2; binding.local_variables }.call("a", "b").should == [:_1, :_2]
+ end
+ end
+
+ ruby_version_is "4.0" do
+ it "does not affect binding local variables" do
+ -> { _1; binding.local_variables }.call("a").should == []
+ -> { _2; binding.local_variables }.call("a", "b").should == []
+ end
+ end
+
+ it "does not work in methods" do
+ obj = Object.new
+ def obj.foo; _1 end
+
+ -> { obj.foo("a") }.should raise_error(ArgumentError, /wrong number of arguments/)
+ end
+end
diff --git a/spec/ruby/language/numbers_spec.rb b/spec/ruby/language/numbers_spec.rb
index e8c82f09a7..a8e023efb6 100644
--- a/spec/ruby/language/numbers_spec.rb
+++ b/spec/ruby/language/numbers_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "A number literal" do
@@ -11,7 +11,7 @@ describe "A number literal" do
end
it "cannot have a leading underscore" do
- lambda { eval("_4_2") }.should raise_error(NameError)
+ -> { eval("_4_2") }.should raise_error(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
- lambda { eval(".75") }.should raise_error(SyntaxError)
- lambda { eval("-.75") }.should raise_error(SyntaxError)
+ -> { eval(".75") }.should raise_error(SyntaxError)
+ -> { eval("-.75") }.should raise_error(SyntaxError)
end
it "can have an exponent" do
@@ -45,7 +45,15 @@ describe "A number literal" do
eval('-3r').should == Rational(-3, 1)
end
- it "can be an bignum literal with trailing 'r' to represent a Rational" do
+ 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 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 0ab28985ed..5fe3e3671b 100644
--- a/spec/ruby/language/optional_assignments_spec.rb
+++ b/spec/ruby/language/optional_assignments_spec.rb
@@ -1,4 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
+require_relative '../fixtures/constants'
describe 'Optional variable assignments' do
describe 'using ||=' do
@@ -42,9 +43,21 @@ describe 'Optional variable assignments' do
a.should == 10
end
+
+ it 'returns the new value if set to false' do
+ a = false
+
+ (a ||= 20).should == 20
+ end
+
+ it 'returns the original value if truthy' do
+ a = 10
+
+ (a ||= 20).should == 10
+ end
end
- describe 'using a accessor' do
+ describe 'using an accessor' do
before do
klass = Class.new { attr_accessor :b }
@a = klass.new
@@ -89,6 +102,178 @@ 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
+ end
+
+ @a.b = false
+ (@a.b ||= 20).should == 20
+ end
+
+ it 'returns the original value if truthy' do
+ def @a.b=(x)
+ @b = x
+ :v
+ end
+
+ @a.b = 10
+ (@a.b ||= 20).should == 10
+ 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(false)
+ a.public_method(10).should == 10
+ end
+ end
+
+ describe 'using a #[]' do
+ before do
+ @a = {}
+ 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 '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
+
+ (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
+
+ 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
@@ -135,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
@@ -180,11 +365,221 @@ 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
+ before do
+ @a = {}
+ 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 'leaves new variable unassigned' do
+ @a[:k] &&= 10
+
+ @a.key?(:k).should == false
+ end
+
+ it 'leaves false' do
+ @a[:k] = false
+ @a[:k] &&= 10
+
+ @a[:k].should == false
+ end
+
+ it 'leaves nil' do
+ @a[:k] = nil
+ @a[:k] &&= 10
+
+ @a[:k].should == nil
+ end
+
+ it 'does not evaluate the right side when not needed' do
+ @a[:k] = nil
+ @a[:k] &&= raise('should not be executed')
+ @a[:k].should == nil
+ end
+
+ it 'does re-assign a variable with a truthy value' do
+ @a[:k] = 10
+ @a[:k] &&= 20
+
+ @a[:k].should == 20
+ end
+
+ it 'does re-assign a variable with a truthy value when using an inline rescue' do
+ @a[:k] = 10
+ @a[:k] &&= 20 rescue 30
+
+ @a[:k].should == 20
+ end
+
+ it 'returns the assigned value, not the result of the []= method with ||=' do
+ @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
- describe 'using compunded constants' do
- before do
+ describe 'using compounded constants' do
+ before :each do
+ Object.send(:remove_const, :A) if defined? Object::A
+ end
+
+ after :each do
Object.send(:remove_const, :A) if defined? Object::A
end
@@ -208,7 +603,7 @@ describe 'Optional variable assignments' do
end
it 'with &&= assignments will fail with non-existent constants' do
- lambda { Object::A &&= 10 }.should raise_error(NameError)
+ -> { Object::A &&= 10 }.should raise_error(NameError)
end
it 'with operator assignments' do
@@ -220,7 +615,128 @@ describe 'Optional variable assignments' do
end
it 'with operator assignments will fail with non-existent constants' do
- lambda { Object::A += 10 }.should raise_error(NameError)
+ -> { Object::A += 10 }.should raise_error(NameError)
+ end
+ end
+end
+
+describe 'Optional constant assignment' do
+ 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
+
+ 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_error(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_error(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 150d693996..fb75e788f1 100644
--- a/spec/ruby/language/or_spec.rb
+++ b/spec/ruby/language/or_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The || operator" do
it "evaluates to true if any of its operands are true" do
@@ -35,15 +35,15 @@ describe "The || operator" do
it "has a higher precedence than 'break' in 'break true || false'" do
# see also 'break true or false' below
- lambda { break false || true }.call.should be_true
+ -> { break false || true }.call.should be_true
end
it "has a higher precedence than 'next' in 'next true || false'" do
- lambda { next false || true }.call.should be_true
+ -> { next false || true }.call.should be_true
end
it "has a higher precedence than 'return' in 'return true || false'" do
- lambda { return false || true }.call.should be_true
+ -> { return false || true }.call.should be_true
end
end
@@ -77,14 +77,14 @@ describe "The or operator" do
it "has a lower precedence than 'break' in 'break true or false'" do
# see also 'break true || false' above
- lambda { eval "break true or false" }.should raise_error(SyntaxError, /void value expression/)
+ -> { eval "break true or false" }.should raise_error(SyntaxError, /void value expression/)
end
it "has a lower precedence than 'next' in 'next true or false'" do
- lambda { eval "next true or false" }.should raise_error(SyntaxError, /void value expression/)
+ -> { eval "next true or false" }.should raise_error(SyntaxError, /void value expression/)
end
it "has a lower precedence than 'return' in 'return true or false'" do
- lambda { eval "return true or false" }.should raise_error(SyntaxError, /void value expression/)
+ -> { eval "return true or false" }.should raise_error(SyntaxError, /void value expression/)
end
end
diff --git a/spec/ruby/language/order_spec.rb b/spec/ruby/language/order_spec.rb
index d02bf04077..d550f6b3f4 100644
--- a/spec/ruby/language/order_spec.rb
+++ b/spec/ruby/language/order_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "A method call" do
before :each do
diff --git a/spec/ruby/language/pattern_matching_spec.rb b/spec/ruby/language/pattern_matching_spec.rb
new file mode 100644
index 0000000000..c1a6f0e4d6
--- /dev/null
+++ b/spec/ruby/language/pattern_matching_spec.rb
@@ -0,0 +1,1310 @@
+require_relative '../spec_helper'
+
+describe "Pattern matching" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ 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
+
+ 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 "can work with keywords" do
+ { a: 0, b: 1 } => { a:, b: }
+ [a, b].should == [0, 1]
+ end
+ end
+
+ 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
+
+ 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
+ @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_error(SyntaxError, /syntax error, unexpected `in'|\(eval\):3: syntax error, unexpected keyword_in|unexpected 'in'/)
+
+ -> {
+ eval <<~RUBY
+ case []
+ in []
+ when 1 == 1
+ end
+ RUBY
+ }.should raise_error(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
+
+ it "raises NoMatchingPatternError if no pattern matches and no else clause" do
+ -> {
+ case [0, 1]
+ in [0]
+ end
+ }.should raise_error(NoMatchingPatternError, /\[0, 1\]/)
+
+ 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_error(NoMatchingPatternError, error_pattern)
+ end
+
+ it "raises NoMatchingPatternError if no pattern matches and evaluates the expression only once" do
+ evals = 0
+ -> {
+ case (evals += 1; [0, 1])
+ in [0]
+ end
+ }.should raise_error(NoMatchingPatternError, /\[0, 1\]/)
+ evals.should == 1
+ end
+
+ it "does not allow calculation or method calls in a pattern" do
+ -> {
+ eval <<~RUBY
+ case 0
+ in 1 + 1
+ true
+ end
+ RUBY
+ }.should raise_error(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 "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
+
+ it "makes bound variables visible in guard" do
+ case [0, 1]
+ in [a, 1] if a >= 0
+ true
+ end.should == true
+ end
+
+ 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 "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 "executes else clause if no guarded pattern matches" do
+ case 0
+ in 0 if false
+ true
+ else
+ false
+ end.should == false
+ end
+
+ it "raises NoMatchingPatternError if no guarded pattern matches and no else clause" do
+ -> {
+ case [0, 1]
+ in [0, 1] if false
+ end
+ }.should raise_error(NoMatchingPatternError, /\[0, 1\]/)
+ end
+ end
+
+ 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"
+
+ case "x"
+ in "#{x + ""}"
+ true
+ end.should == true
+ end
+ end
+
+ 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
+
+ it "makes bounded variable visible outside a case statement scope" do
+ case 0
+ in a
+ end
+
+ a.should == 0
+ end
+
+ it "create local variables even if a pattern doesn't match" do
+ case 0
+ in a
+ in b
+ in c
+ end
+
+ [a, b, c].should == [0, nil, nil]
+ end
+
+ 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 [a, a]
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /duplicated variable name/)
+ end
+
+ it "supports existing variables in a pattern specified with ^ operator" do
+ a = 0
+
+ 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 "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
+ end
+
+ describe "alternative pattern" do
+ it "matches if any of patterns matches" do
+ case 0
+ in 0 | 1 | 2
+ true
+ end.should == true
+ end
+
+ it "does not support variable binding" do
+ -> {
+ eval <<~RUBY
+ case [0, 1]
+ in [0, 0] | [0, a]
+ end
+ RUBY
+ }.should raise_error(SyntaxError)
+ end
+
+ it "support underscore prefixed variables in alternation" do
+ case [0, 1]
+ in [1, _]
+ false
+ in [0, 0] | [0, _a]
+ true
+ end.should == true
+ 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
+
+ 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
+
+ it "can be used as a nested pattern" do
+ case [1, [2, 3]]
+ in [1, Array => ary]
+ ary
+ end.should == [2, 3]
+ end
+ end
+
+ 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
+
+ it "supports form Constant[pat, pat, ...]" do
+ case [0, 1, 2]
+ in Array[0, 1, 2]
+ true
+ end.should == true
+ end
+
+ it "supports form [pat, pat, ...]" do
+ case [0, 1, 2]
+ in [0, 1, 2]
+ true
+ end.should == true
+ 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
+
+ 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
+
+ case obj
+ in [Integer, Integer]
+ true
+ end.should == true
+ end
+
+ it "calls #deconstruct once for multiple patterns, caching the result" do
+ obj = Object.new
+
+ def obj.deconstruct
+ ScratchPad << :deconstruct
+ [0, 1]
+ end
+
+ case obj
+ in [1, 2]
+ false
+ in [0, 1]
+ true
+ end.should == true
+
+ ScratchPad.recorded.should == [:deconstruct]
+ end
+
+ it "calls #deconstruct even on objects that are already an array" do
+ obj = [1, 2]
+
+ def obj.deconstruct
+ ScratchPad << :deconstruct
+ [3, 4]
+ end
+
+ case obj
+ in [3, 4]
+ true
+ else
+ false
+ end.should == true
+
+ ScratchPad.recorded.should == [:deconstruct]
+ end
+
+ 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
+
+ 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 "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
+
+ it "raises TypeError if #deconstruct method does not return array" do
+ obj = Object.new
+
+ def obj.deconstruct
+ ""
+ end
+
+ -> {
+ case obj
+ in Object[]
+ else
+ end
+ }.should raise_error(TypeError, /deconstruct must return Array/)
+ end
+
+ it "accepts a subclass of Array from #deconstruct" do
+ obj = Object.new
+
+ def obj.deconstruct
+ Class.new(Array).new([0, 1])
+ end
+
+ case obj
+ in [1, 2]
+ false
+ in [0, 1]
+ true
+ end.should == true
+ end
+
+ 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 obj
+ in Object[0]
+ true
+ else
+ false
+ end.should == false
+ end
+
+ it "binds variables" do
+ case [0, 1, 2]
+ in [a, b, c]
+ [a, b, c]
+ end.should == [0, 1, 2]
+ end
+
+ it "supports splat operator *rest" do
+ case [0, 1, 2]
+ in [0, *rest]
+ rest
+ end.should == [1, 2]
+ end
+
+ it "does not match partially by default" do
+ case [0, 1, 2, 3]
+ in [1, 2]
+ true
+ else
+ false
+ end.should == false
+ end
+
+ 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
+
+ case [0, 1, 2, 3]
+ in 0, 1,;
+ true
+ end.should == true
+ end
+
+ it "matches [] with []" do
+ case []
+ in []
+ true
+ end.should == true
+ end
+
+ it "matches anything with *" do
+ case [0, 1]
+ in *;
+ true
+ end.should == true
+ 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
+
+ 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
+
+ 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 "supports form {id: pat, id: pat, ...}" do
+ case {a: 0, b: 1}
+ in {a: 0, b: 1}
+ true
+ end.should == true
+ end
+
+ 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
+
+ it "supports a: which means a: a" do
+ case {a: 0, b: 1}
+ in Hash(a:, b:)
+ [a, b]
+ end.should == [0, 1]
+
+ a = b = nil
+
+ case {a: 0, b: 1}
+ in Hash[a:, b:]
+ [a, b]
+ end.should == [0, 1]
+
+ a = b = nil
+
+ case {a: 0, b: 1}
+ in {a:, b:}
+ [a, b]
+ end.should == [0, 1]
+
+ a = nil
+
+ case {a: 0, b: 1, c: 2}
+ in {a:, **rest}
+ [a, rest]
+ end.should == [0, {b: 1, c: 2}]
+
+ a = b = nil
+
+ case {a: 0, b: 1}
+ in a:, b:
+ [a, b]
+ end.should == [0, 1]
+ end
+
+ 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
+
+ it "supports 'string': key literal" do
+ case {a: 0}
+ in {"a": 0}
+ true
+ end.should == true
+ end
+
+ it "does not support non-symbol keys" do
+ -> {
+ eval <<~RUBY
+ case {a: 1}
+ in {"a" => 1}
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /unexpected|expected a label as the key in the hash pattern/)
+ end
+
+ it "does not support string interpolation in keys" do
+ -> {
+ eval <<~'RUBY'
+ case {a: 1}
+ in {"#{x}": 1}
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /symbol literal with interpolation is not allowed|expected a label as the key in the hash pattern/)
+ end
+
+ 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/)
+ 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 {a: 1}
+ true
+ end.should == true
+ end
+
+ it "calls #deconstruct_keys per pattern" do
+ obj = Object.new
+
+ def obj.deconstruct_keys(*)
+ ScratchPad << :deconstruct_keys
+ {a: 1}
+ end
+
+ case obj
+ in {b: 1}
+ false
+ in {a: 1}
+ true
+ end.should == true
+
+ ScratchPad.recorded.should == [:deconstruct_keys, :deconstruct_keys]
+ end
+
+ 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
+
+ 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
+
+ it "does not match object without #deconstruct_keys method" do
+ obj = Object.new
+ obj.should_receive(:respond_to?).with(:deconstruct_keys)
+
+ case obj
+ in Object[a: 1]
+ true
+ else
+ false
+ end.should == false
+ end
+
+ it "does not match object if #deconstruct_keys method does not return Hash" do
+ obj = Object.new
+
+ def obj.deconstruct_keys(*)
+ ""
+ end
+
+ -> {
+ case obj
+ in Object[a: 1]
+ end
+ }.should raise_error(TypeError, /deconstruct_keys must return Hash/)
+ 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
+
+ case obj
+ in Object[a: 1]
+ true
+ else
+ false
+ end.should == false
+ end
+
+ 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
+
+ case obj
+ in Object[a: 2]
+ true
+ else
+ false
+ end.should == false
+ end
+
+ it "passes keys specified in pattern as arguments to #deconstruct_keys method" do
+ obj = Object.new
+
+ def obj.deconstruct_keys(*args)
+ ScratchPad << args
+ {a: 1, b: 2, c: 3}
+ end
+
+ case obj
+ in Object[a: 1, b: 2, c: 3]
+ end
+
+ ScratchPad.recorded.sort.should == [[[:a, :b, :c]]]
+ end
+
+ 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
+
+ case obj
+ in Object[a: 1, b: 2, **]
+ end
+
+ ScratchPad.recorded.sort.should == [[[:a, :b]]]
+ end
+
+ it "passes nil to #deconstruct_keys method if pattern contains double splat operator **rest" do
+ obj = Object.new
+
+ def obj.deconstruct_keys(*args)
+ ScratchPad << args
+ {a: 1, b: 2}
+ end
+
+ case obj
+ in Object[a: 1, **rest]
+ end
+
+ ScratchPad.recorded.should == [[nil]]
+ 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
+
+ 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
+
+ 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 "can match partially" do
+ case {a: 1, b: 2}
+ in {a: 1}
+ true
+ end.should == true
+ end
+
+ it "matches {} with {}" do
+ case {}
+ in {}
+ true
+ end.should == true
+ end
+
+ it "in {} only matches empty hashes" do
+ case {a: 1}
+ in {}
+ true
+ else
+ false
+ end.should == false
+ 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 "matches anything with **" do
+ case {a: 1}
+ in **;
+ true
+ end.should == true
+ 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
+
+ describe "refinements" do
+ it "are used for #deconstruct" do
+ refinery = Module.new do
+ refine Array do
+ def deconstruct
+ [0]
+ end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinery
+
+ result =
+ case []
+ in [0]
+ true
+ end
+ end
+
+ result.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
+
+ result = nil
+ Module.new do
+ using refinery
+
+ result =
+ case {}
+ in a: 0
+ true
+ end
+ end
+
+ 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
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinery
+
+ result =
+ case {}
+ in Array
+ true
+ end
+ end
+
+ 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
+
+ it "supports pinning global variables" do
+ $a = /a/
+ case 'abc'
+ in ^$a
+ true
+ end.should == true
+ end
+
+ it "supports pinning expressions" do
+ case 'abc'
+ in ^(/a/)
+ true
+ end.should == true
+
+ case 0
+ in ^(0 + 0)
+ true
+ end.should == true
+ end
+
+ it "supports pinning expressions in array pattern" do
+ case [3]
+ in [^(1 + 2)]
+ true
+ end.should == true
+ end
+
+ it "supports pinning expressions in hash pattern" do
+ case {name: '2.6', released_at: Time.new(2018, 12, 25)}
+ in {released_at: ^(Time.new(2010)..Time.new(2020))}
+ true
+ end.should == true
+ end
+ end
+
+ describe "value in pattern" do
+ it "returns true if the pattern matches" do
+ (1 in 1).should == true
+
+ (1 in Integer).should == true
+
+ e = nil
+ ([1, 2] in [1, e]).should == true
+ e.should == 2
+
+ k = nil
+ ({k: 1} in {k:}).should == true
+ k.should == 1
+ end
+
+ it "returns false if the pattern does not match" do
+ (1 in 2).should == false
+
+ (1 in Float).should == false
+
+ ([1, 2] in [2, e]).should == false
+
+ ({k: 1} in {k: 2}).should == false
+ end
+ end
+end
diff --git a/spec/ruby/language/precedence_spec.rb b/spec/ruby/language/precedence_spec.rb
index 90734022ff..5e606c16d8 100644
--- a/spec/ruby/language/precedence_spec.rb
+++ b/spec/ruby/language/precedence_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/precedence', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/precedence'
# Specifying the behavior of operators in combination could
# lead to combinatorial explosion. A better way seems to be
@@ -14,46 +14,44 @@ require File.expand_path('../fixtures/precedence', __FILE__)
# 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
@@ -136,7 +134,7 @@ describe "Operators" do
# Guard against the Mathn library
# TODO: Make these specs not rely on specific behaviour / result values
# by using mocks.
- conflicts_with :Prime do
+ guard -> { !defined?(Math.rsqrt) } do
(2*1/2).should_not == 2*(1/2)
end
@@ -253,12 +251,12 @@ describe "Operators" do
end
it "<=> == === != =~ !~ are non-associative" do
- lambda { eval("1 <=> 2 <=> 3") }.should raise_error(SyntaxError)
- lambda { eval("1 == 2 == 3") }.should raise_error(SyntaxError)
- lambda { eval("1 === 2 === 3") }.should raise_error(SyntaxError)
- lambda { eval("1 != 2 != 3") }.should raise_error(SyntaxError)
- lambda { eval("1 =~ 2 =~ 3") }.should raise_error(SyntaxError)
- lambda { 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_error(SyntaxError)
end
it "<=> == === != =~ !~ have higher precedence than &&" do
@@ -292,19 +290,18 @@ describe "Operators" do
end
it ".. ... are non-associative" do
- lambda { eval("1..2..3") }.should raise_error(SyntaxError)
- lambda { eval("1...2...3") }.should raise_error(SyntaxError)
+ -> { eval("1..2..3") }.should raise_error(SyntaxError)
+ -> { eval("1...2...3") }.should raise_error(SyntaxError)
end
-# XXX: this is commented now due to a bug in compiler, which cannot
-# distinguish between range and flip-flop operator so far. zenspider is
-# currently working on a new lexer, which will be able to do that.
-# As soon as it's done, these piece should be reenabled.
-#
-# it ".. ... have higher precedence than ? :" do
-# (1..2 ? 3 : 4).should == 3
-# (1...2 ? 3 : 4).should == 3
-# 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/data_spec.rb b/spec/ruby/language/predefined/data_spec.rb
index f616879527..921d236ee9 100644
--- a/spec/ruby/language/predefined/data_spec.rb
+++ b/spec/ruby/language/predefined/data_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "The DATA constant" do
it "exists when the main script contains __END__" do
@@ -23,6 +23,25 @@ describe "The DATA constant" do
str.chomp.should == "data only"
end
+ it "returns a File object with the right offset" do
+ ruby_exe(fixture(__FILE__, "data_offset.rb")).should == "File\n121\n"
+ end
+
+ it "is set even if there is no data after __END__" do
+ ruby_exe(fixture(__FILE__, "empty_data.rb")).should == "31\n\"\"\n"
+ end
+
+ it "is set even if there is no newline after __END__" do
+ path = tmp("no_newline_data.rb")
+ code = File.binread(fixture(__FILE__, "empty_data.rb"))
+ touch(path, "wb") { |f| f.write code.chomp }
+ begin
+ ruby_exe(path).should == "30\n\"\"\n"
+ ensure
+ rm_r path
+ end
+ end
+
it "rewinds to the head of the main script" do
ruby_exe(fixture(__FILE__, "data5.rb")).chomp.should == "DATA.rewind"
end
diff --git a/spec/ruby/language/predefined/fixtures/data2.rb b/spec/ruby/language/predefined/fixtures/data2.rb
index 0f714b06d4..a764ca56d1 100644
--- a/spec/ruby/language/predefined/fixtures/data2.rb
+++ b/spec/ruby/language/predefined/fixtures/data2.rb
@@ -1,4 +1,3 @@
-
-require File.expand_path("../data4.rb", __FILE__)
+require_relative 'data4'
p Object.const_defined?(:DATA)
diff --git a/spec/ruby/language/predefined/fixtures/data3.rb b/spec/ruby/language/predefined/fixtures/data3.rb
index 6cbf63dae6..e37313c68b 100644
--- a/spec/ruby/language/predefined/fixtures/data3.rb
+++ b/spec/ruby/language/predefined/fixtures/data3.rb
@@ -1,5 +1,4 @@
-
-require File.expand_path("../data4.rb", __FILE__)
+require_relative 'data4'
puts DATA.read
diff --git a/spec/ruby/language/predefined/fixtures/data_offset.rb b/spec/ruby/language/predefined/fixtures/data_offset.rb
new file mode 100644
index 0000000000..9829b3f87f
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data_offset.rb
@@ -0,0 +1,12 @@
+# some comment
+
+foo = <<HEREDOC
+some heredoc to make the
+spec more interesting
+HEREDOC
+
+p DATA.class
+p DATA.pos
+
+__END__
+data offset
diff --git a/spec/ruby/language/predefined/fixtures/empty_data.rb b/spec/ruby/language/predefined/fixtures/empty_data.rb
new file mode 100644
index 0000000000..c6d9bc6f1f
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/empty_data.rb
@@ -0,0 +1,3 @@
+p DATA.pos
+p DATA.read
+__END__
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic.rb
new file mode 100644
index 0000000000..f7809109fa
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic.rb
@@ -0,0 +1,4 @@
+p TOPLEVEL_BINDING.local_variables.sort
+TOPLEVEL_BINDING.local_variable_set(:dynamic_set_main, 2)
+p TOPLEVEL_BINDING.local_variables.sort
+main_script = 3
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic_required.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic_required.rb
new file mode 100644
index 0000000000..7ccf329680
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic_required.rb
@@ -0,0 +1,2 @@
+TOPLEVEL_BINDING.local_variable_set(:dynamic_set_required, 1)
+p TOPLEVEL_BINDING.local_variables
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_id.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_id.rb
new file mode 100644
index 0000000000..3626ea1f10
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_id.rb
@@ -0,0 +1,4 @@
+a = TOPLEVEL_BINDING.object_id
+require_relative 'toplevel_binding_id_required'
+c = eval('TOPLEVEL_BINDING.object_id')
+p [a, $b, c].uniq.size
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_id_required.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_id_required.rb
new file mode 100644
index 0000000000..b31b6e32a0
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_id_required.rb
@@ -0,0 +1 @@
+$b = TOPLEVEL_BINDING.object_id
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_required_before.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_required_before.rb
new file mode 100644
index 0000000000..58924a5800
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_required_before.rb
@@ -0,0 +1,2 @@
+required = true
+p [:required_before, TOPLEVEL_BINDING.local_variables]
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_values.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_values.rb
new file mode 100644
index 0000000000..42bd67f347
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_values.rb
@@ -0,0 +1,9 @@
+p TOPLEVEL_BINDING.local_variable_get(:a)
+p TOPLEVEL_BINDING.local_variable_get(:b)
+a = 1
+p TOPLEVEL_BINDING.local_variable_get(:a)
+p TOPLEVEL_BINDING.local_variable_get(:b)
+b = 2
+a = 3
+p TOPLEVEL_BINDING.local_variable_get(:a)
+p TOPLEVEL_BINDING.local_variable_get(:b)
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_variables.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_variables.rb
new file mode 100644
index 0000000000..151f4340ef
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_variables.rb
@@ -0,0 +1,4 @@
+main_script = 1
+require_relative 'toplevel_binding_variables_required'
+eval('eval_var = 3')
+p TOPLEVEL_BINDING.local_variables
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_variables_required.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_variables_required.rb
new file mode 100644
index 0000000000..614547fe16
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_variables_required.rb
@@ -0,0 +1,2 @@
+required = 2
+p [:required_after, TOPLEVEL_BINDING.local_variables]
diff --git a/spec/ruby/language/predefined/toplevel_binding_spec.rb b/spec/ruby/language/predefined/toplevel_binding_spec.rb
new file mode 100644
index 0000000000..69ac28618c
--- /dev/null
+++ b/spec/ruby/language/predefined/toplevel_binding_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+
+describe "The TOPLEVEL_BINDING constant" do
+ it "only includes local variables defined in the main script, not in required files or eval" do
+ binding_toplevel_variables = ruby_exe(fixture(__FILE__, "toplevel_binding_variables.rb"))
+ binding_toplevel_variables.should == "[:required_after, [:main_script]]\n[:main_script]\n"
+ end
+
+ it "has no local variables in files required before the main script" do
+ required = fixture(__FILE__, 'toplevel_binding_required_before.rb')
+ out = ruby_exe("a=1; p TOPLEVEL_BINDING.local_variables.sort; b=2", options: "-r#{required}")
+ out.should == "[:required_before, []]\n[:a, :b]\n"
+ end
+
+ it "merges local variables of the main script with dynamically-defined Binding variables" do
+ required = fixture(__FILE__, 'toplevel_binding_dynamic_required.rb')
+ out = ruby_exe(fixture(__FILE__, 'toplevel_binding_dynamic.rb'), options: "-r#{required}")
+ out.should == <<EOS
+[:dynamic_set_required]
+[:dynamic_set_required, :main_script]
+[:dynamic_set_main, :dynamic_set_required, :main_script]
+EOS
+ end
+
+ it "gets updated variables values as they are defined and set" do
+ out = ruby_exe(fixture(__FILE__, "toplevel_binding_values.rb"))
+ out.should == "nil\nnil\n1\nnil\n3\n2\n"
+ end
+
+ it "is always the same object for all top levels" do
+ binding_toplevel_id = ruby_exe(fixture(__FILE__, "toplevel_binding_id.rb"))
+ binding_toplevel_id.should == "1\n"
+ end
+end
diff --git a/spec/ruby/language/predefined_spec.rb b/spec/ruby/language/predefined_spec.rb
index f827fb2eb5..fc1667a38f 100644
--- a/spec/ruby/language/predefined_spec.rb
+++ b/spec/ruby/language/predefined_spec.rb
@@ -1,4 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
+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,56 +8,52 @@ 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 $9 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 $9 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)
- $~.object_id.should == md.object_id
+ $~.should equal md
/bar/ =~ 'bar'
$~.should be_kind_of(MatchData)
- $~.object_id.should_not == md.object_id
+ $~.should_not equal md
end
it "is set to nil if the last match was unsuccessful" do
/foo/ =~ 'foo'
- $~.nil?.should == false
+ $~.should_not.nil?
/foo/ =~ 'bar'
- $~.nil?.should == true
+ $~.should.nil?
end
it "is set at the method-scoped level rather than block-scoped" 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
@@ -92,8 +89,8 @@ describe "Predefined global $~" do
$~ = /foo/.match("foo")
$~.should be_an_instance_of(MatchData)
- lambda { $~ = Object.new }.should raise_error(TypeError)
- lambda { $~ = 1 }.should raise_error(TypeError)
+ -> { $~ = Object.new }.should raise_error(TypeError, 'wrong argument type Object (expected MatchData)')
+ -> { $~ = 1 }.should raise_error(TypeError, 'wrong argument type Integer (expected MatchData)')
end
it "changes the value of derived capture globals when assigned" do
@@ -136,11 +133,22 @@ describe "Predefined global $&" do
$&.should == 'foo'
end
- with_feature :encoding do
- 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)
- end
+ it "sets the encoding to the encoding of the source String" do
+ "abc".dup.force_encoding(Encoding::EUC_JP) =~ /b/
+ $&.encoding.should equal(Encoding::EUC_JP)
+ end
+
+ it "is read-only" do
+ -> {
+ eval %q{$& = ""}
+ }.should raise_error(SyntaxError, /Can't set variable \$&/)
+ end
+
+ it "is read-only when aliased" do
+ alias $predefined_spec_ampersand $&
+ -> {
+ $predefined_spec_ampersand = ""
+ }.should raise_error(NameError, '$predefined_spec_ampersand is a read-only variable')
end
end
@@ -151,16 +159,27 @@ describe "Predefined global $`" do
$`.should == 'bar'
end
- with_feature :encoding do
- 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)
- end
+ it "sets the encoding to the encoding of the source String" do
+ "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)
- end
+ it "sets an empty result to the encoding of the source String" do
+ "abc".dup.force_encoding(Encoding::ISO_8859_1) =~ /a/
+ $`.encoding.should equal(Encoding::ISO_8859_1)
+ end
+
+ it "is read-only" do
+ -> {
+ eval %q{$` = ""}
+ }.should raise_error(SyntaxError, /Can't set variable \$`/)
+ end
+
+ it "is read-only when aliased" do
+ alias $predefined_spec_backquote $`
+ -> {
+ $predefined_spec_backquote = ""
+ }.should raise_error(NameError, '$predefined_spec_backquote is a read-only variable')
end
end
@@ -171,16 +190,27 @@ describe "Predefined global $'" do
$'.should == 'baz'
end
- with_feature :encoding do
- 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)
- end
+ it "sets the encoding to the encoding of the source String" do
+ "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)
- end
+ it "sets an empty result to the encoding of the source String" do
+ "abc".dup.force_encoding(Encoding::ISO_8859_1) =~ /c/
+ $'.encoding.should equal(Encoding::ISO_8859_1)
+ end
+
+ it "is read-only" do
+ -> {
+ eval %q{$' = ""}
+ }.should raise_error(SyntaxError, /Can't set variable \$'/)
+ end
+
+ it "is read-only when aliased" do
+ alias $predefined_spec_single_quote $'
+ -> {
+ $predefined_spec_single_quote = ""
+ }.should raise_error(NameError, '$predefined_spec_single_quote is a read-only variable')
end
end
@@ -196,11 +226,22 @@ describe "Predefined global $+" do
$+.should == 'a'
end
- with_feature :encoding do
- 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)
- end
+ it "sets the encoding to the encoding of the source String" do
+ "abc".dup.force_encoding(Encoding::EUC_JP) =~ /(b)/
+ $+.encoding.should equal(Encoding::EUC_JP)
+ end
+
+ it "is read-only" do
+ -> {
+ eval %q{$+ = ""}
+ }.should raise_error(SyntaxError, /Can't set variable \$\+/)
+ end
+
+ it "is read-only when aliased" do
+ alias $predefined_spec_plus $+
+ -> {
+ $predefined_spec_plus = ""
+ }.should raise_error(NameError, '$predefined_spec_plus is a read-only variable')
end
end
@@ -225,11 +266,9 @@ describe "Predefined globals $1..N" do
test("-").should == nil
end
- with_feature :encoding do
- 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)
- end
+ it "sets the encoding to the encoding of the source String" do
+ "abc".dup.force_encoding(Encoding::EUC_JP) =~ /(b)/
+ $1.encoding.should equal(Encoding::EUC_JP)
end
end
@@ -243,12 +282,12 @@ describe "Predefined global $stdout" do
end
it "raises TypeError error if assigned to nil" do
- lambda { $stdout = nil }.should raise_error(TypeError)
+ -> { $stdout = nil }.should raise_error(TypeError, '$stdout must have write method, NilClass given')
end
it "raises TypeError error if assigned to object that doesn't respond to #write" do
obj = mock('object')
- lambda { $stdout = obj }.should raise_error(TypeError)
+ -> { $stdout = obj }.should raise_error(TypeError)
obj.stub!(:write)
$stdout = obj
@@ -257,6 +296,22 @@ describe "Predefined global $stdout" do
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_error(NameError, '$! is a read-only variable')
+ end
+
# See http://jira.codehaus.org/browse/JRUBY-5550
it "remains nil after a failed core class \"checked\" coercion against a class that defines method_missing" do
$!.should == nil
@@ -408,6 +463,22 @@ describe "Predefined global $!" do
$!.should == nil
end
+ it "should be cleared when an exception is rescued even when a non-local return from block" do
+ def foo
+ [ 1 ].each do
+ begin
+ raise StandardError.new('err')
+ rescue => e
+ $!.should == e
+ return
+ end
+ end
+ end
+
+ foo
+ $!.should == nil
+ end
+
it "should not be cleared when an exception is not rescued" do
e = StandardError.new
begin
@@ -500,44 +571,112 @@ 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.
-$. Fixnum 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 be_an_instance_of(Array)
+ $@.should == $!.backtrace
+ end
+ end
+
+ it "is cleared when an exception is rescued" do
+ begin
+ raise
+ rescue
+ end
+
+ $@.should == nil
+ end
+
+ it "is not set when there is no current exception" do
+ $@.should == nil
+ end
+
+ it "is set to a backtrace of a rescued exception" do
+ begin
+ raise
+ rescue
+ $@.should be_an_instance_of(Array)
+ $@.should == $!.backtrace
+ end
+ end
+
+ it "is not read-only" do
+ begin
+ raise
+ rescue
+ $@ = []
+ $@.should == []
+ end
+ end
+
+ it_behaves_like :exception_set_backtrace, -> backtrace {
+ exception = nil
+ begin
+ raise
+ rescue
+ $@ = backtrace
+ exception = $!
+ end
+ exception
+ }
+
+ it "cannot be assigned when there is no a rescued exception" do
+ -> {
+ $@ = []
+ }.should raise_error(ArgumentError, '$! not set')
+ end
+end
+
+# Input/Output Variables
+# ---------------------------------------------------------------------------------------------------
+#
+# $/ 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
+ @verbose, $VERBOSE = $VERBOSE, nil
@dollar_slash = $/
@dollar_dash_zero = $-0
end
@@ -545,14 +684,42 @@ describe "Predefined global $/" do
after :each do
$/ = @dollar_slash
$-0 = @dollar_dash_zero
+ $VERBOSE = @verbose
end
- it "can be assigned a String" do
- str = "abc"
- $/ = str
- $/.should equal(str)
+ ruby_version_is ""..."4.0" do
+ it "can be assigned a String" do
+ str = +"abc"
+ $/ = str
+ $/.should equal(str)
+ end
end
+ ruby_version_is "4.0" do
+ it "makes a new frozen String from the assigned String" do
+ string_subclass = Class.new(String)
+ str = string_subclass.new("abc")
+ str.instance_variable_set(:@ivar, 1)
+ $/ = str
+ $/.should.frozen?
+ $/.should be_an_instance_of(String)
+ $/.should_not.instance_variable_defined?(:@ivar)
+ $/.should == str
+ end
+
+ it "makes a new frozen String if it's not frozen" do
+ str = +"abc"
+ $/ = str
+ $/.should.frozen?
+ $/.should == str
+ end
+
+ it "assigns the given String if it's frozen and has no instance variables" do
+ str = "abc".freeze
+ $/ = str
+ $/.should equal(str)
+ end
+ end
it "can be assigned nil" do
$/ = nil
$/.should be_nil
@@ -562,7 +729,6 @@ describe "Predefined global $/" do
($/ = "xyz").should == "xyz"
end
-
it "changes $-0" do
$/ = "xyz"
$-0.should equal($/)
@@ -572,20 +738,25 @@ describe "Predefined global $/" do
obj = mock("$/ value")
obj.should_not_receive(:to_str)
- lambda { $/ = obj }.should raise_error(TypeError)
+ -> { $/ = obj }.should raise_error(TypeError, 'value of $/ must be String')
end
- it "raises a TypeError if assigned a Fixnum" do
- lambda { $/ = 1 }.should raise_error(TypeError)
+ it "raises a TypeError if assigned an Integer" do
+ -> { $/ = 1 }.should raise_error(TypeError, 'value of $/ must be String')
end
it "raises a TypeError if assigned a boolean" do
- lambda { $/ = true }.should raise_error(TypeError)
+ -> { $/ = true }.should raise_error(TypeError, 'value of $/ must be String')
+ end
+
+ it "warns if assigned non-nil" do
+ -> { $/ = "_" }.should complain(/warning: (?:non-nil )?[`']\$\/' is deprecated/)
end
end
describe "Predefined global $-0" do
before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
@dollar_slash = $/
@dollar_dash_zero = $-0
end
@@ -593,12 +764,41 @@ describe "Predefined global $-0" do
after :each do
$/ = @dollar_slash
$-0 = @dollar_dash_zero
+ $VERBOSE = @verbose
end
- it "can be assigned a String" do
- str = "abc"
- $-0 = str
- $-0.should equal(str)
+ ruby_version_is ""..."4.0" do
+ it "can be assigned a String" do
+ str = +"abc"
+ $-0 = str
+ $-0.should equal(str)
+ end
+ end
+
+ ruby_version_is "4.0" do
+ it "makes a new frozen String from the assigned String" do
+ string_subclass = Class.new(String)
+ str = string_subclass.new("abc")
+ str.instance_variable_set(:@ivar, 1)
+ $-0 = str
+ $-0.should.frozen?
+ $-0.should be_an_instance_of(String)
+ $-0.should_not.instance_variable_defined?(:@ivar)
+ $-0.should == str
+ end
+
+ it "makes a new frozen String if it's not frozen" do
+ str = +"abc"
+ $-0 = str
+ $-0.should.frozen?
+ $-0.should == str
+ end
+
+ it "assigns the given String if it's frozen and has no instance variables" do
+ str = "abc".freeze
+ $-0 = str
+ $-0.should equal(str)
+ end
end
it "can be assigned nil" do
@@ -619,15 +819,62 @@ describe "Predefined global $-0" do
obj = mock("$-0 value")
obj.should_not_receive(:to_str)
- lambda { $-0 = obj }.should raise_error(TypeError)
+ -> { $-0 = obj }.should raise_error(TypeError, 'value of $-0 must be String')
end
- it "raises a TypeError if assigned a Fixnum" do
- lambda { $-0 = 1 }.should raise_error(TypeError)
+ it "raises a TypeError if assigned an Integer" do
+ -> { $-0 = 1 }.should raise_error(TypeError, 'value of $-0 must be String')
end
it "raises a TypeError if assigned a boolean" do
- lambda { $-0 = true }.should raise_error(TypeError)
+ -> { $-0 = true }.should raise_error(TypeError, 'value of $-0 must be String')
+ end
+
+ it "warns if assigned non-nil" do
+ -> { $-0 = "_" }.should complain(/warning: (?:non-nil )?[`']\$-0' is deprecated/)
+ end
+end
+
+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 be_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_error(TypeError, 'value of $\ must be String')
+ end
+
+ it "raises a TypeError if assigned not String" do
+ -> { $\ = 1 }.should raise_error(TypeError, 'value of $\ must be String')
+ -> { $\ = true }.should raise_error(TypeError, 'value of $\ must be String')
+ end
+
+ it "warns if assigned non-nil" do
+ -> { $\ = "_" }.should complain(/warning: (?:non-nil )?[`']\$\\' is deprecated/)
end
end
@@ -641,7 +888,48 @@ describe "Predefined global $," do
end
it "raises TypeError if assigned a non-String" do
- lambda { $, = Object.new }.should raise_error(TypeError)
+ -> { $, = Object.new }.should raise_error(TypeError, 'value of $, must be String')
+ end
+
+ it "warns if assigned non-nil" do
+ -> { $, = "_" }.should complain(/warning: (?:non-nil )?[`']\$,' is deprecated/)
+ end
+end
+
+describe "Predefined global $." do
+ it "can be assigned an Integer" do
+ $. = 123
+ $..should == 123
+ end
+
+ it "can be assigned a Float" do
+ $. = 123.5
+ $..should == 123
+ end
+
+ it "should call #to_int to convert the object to an Integer" do
+ obj = mock("good-value")
+ obj.should_receive(:to_int).and_return(321)
+
+ $. = obj
+ $..should == 321
+ end
+
+ it "raises TypeError if object can't be converted to an Integer" do
+ obj = mock("bad-value")
+ obj.should_receive(:to_int).and_return('abc')
+
+ -> { $. = obj }.should raise_error(TypeError)
+ end
+end
+
+describe "Predefined global $;" do
+ after :each do
+ $; = nil
+ end
+
+ it "warns if assigned non-nil" do
+ -> { $; = "_" }.should complain(/warning: (?:non-nil )?[`']\$;' is deprecated/)
end
end
@@ -675,7 +963,7 @@ describe "Predefined global $_" do
match.should == "bar\n"
$_.should == match
- eval 'match = stdin.gets'
+ match = stdin.gets
match.should == "baz\n"
$_.should == match
@@ -714,56 +1002,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]
-$$ Fixnum 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]
-$SAFE Fixnum The current safe level. This variable’s value may never be
- reduced by assignment. [thread] (Not implemented in Rubinius)
-$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
@@ -782,60 +1066,78 @@ describe "Execution variable $:" do
it "can be changed via <<" do
$: << "foo"
$:.should include("foo")
+ ensure
+ $:.delete("foo")
end
it "is read-only" do
- lambda {
+ -> {
$: = []
- }.should raise_error(NameError)
+ }.should raise_error(NameError, '$: is a read-only variable')
- lambda {
+ -> {
$LOAD_PATH = []
- }.should raise_error(NameError)
+ }.should raise_error(NameError, '$LOAD_PATH is a read-only variable')
- lambda {
+ -> {
$-I = []
- }.should raise_error(NameError)
+ }.should raise_error(NameError, '$-I is a read-only variable')
+ end
+
+ it "default $LOAD_PATH entries until sitelibdir included have @gem_prelude_index set" do
+ skip "no sense in ruby itself" if MSpecScript.instance_variable_defined?(:@testing_ruby)
+
+ if platform_is :windows
+ # See https://github.com/ruby/setup-ruby/pull/762#issuecomment-2917460440
+ $:.should.find { |e| File.realdirpath(e) == RbConfig::CONFIG['sitelibdir'] }
+ idx = $:.index { |e| File.realdirpath(e) == RbConfig::CONFIG['sitelibdir'] }
+ else
+ $:.should.include?(RbConfig::CONFIG['sitelibdir'])
+ idx = $:.index(RbConfig::CONFIG['sitelibdir'])
+ end
+
+ $:[idx..-1].all? { |p| p.instance_variable_defined?(:@gem_prelude_index) }.should be_true
+ $:[0...idx].all? { |p| !p.instance_variable_defined?(:@gem_prelude_index) }.should be_true
end
end
describe "Global variable $\"" do
it "is an alias for $LOADED_FEATURES" do
- $".object_id.should == $LOADED_FEATURES.object_id
+ $".should equal $LOADED_FEATURES
end
it "is read-only" do
- lambda {
+ -> {
$" = []
- }.should raise_error(NameError)
+ }.should raise_error(NameError, '$" is a read-only variable')
- lambda {
+ -> {
$LOADED_FEATURES = []
- }.should raise_error(NameError)
+ }.should raise_error(NameError, '$LOADED_FEATURES is a read-only variable')
end
end
describe "Global variable $<" do
it "is read-only" do
- lambda {
+ -> {
$< = nil
- }.should raise_error(NameError)
+ }.should raise_error(NameError, '$< is a read-only variable')
end
end
describe "Global variable $FILENAME" do
it "is read-only" do
- lambda {
+ -> {
$FILENAME = "-"
- }.should raise_error(NameError)
+ }.should raise_error(NameError, '$FILENAME is a read-only variable')
end
end
describe "Global variable $?" do
it "is read-only" do
- lambda {
+ -> {
$? = nil
- }.should raise_error(NameError)
+ }.should raise_error(NameError, '$? is a read-only variable')
end
it "is thread-local" do
@@ -846,19 +1148,19 @@ end
describe "Global variable $-a" do
it "is read-only" do
- lambda { $-a = true }.should raise_error(NameError)
+ -> { $-a = true }.should raise_error(NameError, '$-a is a read-only variable')
end
end
describe "Global variable $-l" do
it "is read-only" do
- lambda { $-l = true }.should raise_error(NameError)
+ -> { $-l = true }.should raise_error(NameError, '$-l is a read-only variable')
end
end
describe "Global variable $-p" do
it "is read-only" do
- lambda { $-p = true }.should raise_error(NameError)
+ -> { $-p = true }.should raise_error(NameError, '$-p is a read-only variable')
end
end
@@ -880,6 +1182,18 @@ describe "Global variable $-d" do
end
describe "Global variable $VERBOSE" do
+ before :each do
+ @verbose = $VERBOSE
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it "is false by default" do
+ $VERBOSE.should be_false
+ end
+
it "converts truthy values to true" do
[true, 1, 0, [], ""].each do |true_value|
$VERBOSE = true_value
@@ -934,7 +1248,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
@@ -957,26 +1271,24 @@ describe "Global variable $0" do
end
it "raises a TypeError when not given an object that can be coerced to a String" do
- lambda { $0 = nil }.should raise_error(TypeError)
+ -> { $0 = nil }.should raise_error(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
@@ -999,7 +1311,7 @@ describe "The predefined standard object nil" do
end
it "raises a SyntaxError if assigned to" do
- lambda { eval("nil = true") }.should raise_error(SyntaxError)
+ -> { eval("nil = true") }.should raise_error(SyntaxError, /Can't assign to nil/)
end
end
@@ -1009,7 +1321,7 @@ describe "The predefined standard object true" do
end
it "raises a SyntaxError if assigned to" do
- lambda { eval("true = false") }.should raise_error(SyntaxError)
+ -> { eval("true = false") }.should raise_error(SyntaxError, /Can't assign to true/)
end
end
@@ -1019,85 +1331,61 @@ describe "The predefined standard object false" do
end
it "raises a SyntaxError if assigned to" do
- lambda { eval("false = nil") }.should raise_error(SyntaxError)
+ -> { eval("false = nil") }.should raise_error(SyntaxError, /Can't assign to false/)
end
end
describe "The self pseudo-variable" do
it "raises a SyntaxError if assigned to" do
- lambda { eval("self = 1") }.should raise_error(SyntaxError)
+ -> { eval("self = 1") }.should raise_error(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.
-FALSE FalseClass Synonym for false.
-NIL NilClass Synonym for nil.
-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.
-=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
- ruby_version_is ""..."2.4" do
- it "includes TRUE" do
- Object.const_defined?(:TRUE).should == true
- TRUE.should equal(true)
- end
-
- it "includes FALSE" do
- Object.const_defined?(:FALSE).should == true
- FALSE.should equal(false)
- end
-
- it "includes NIL" do
- Object.const_defined?(:NIL).should == true
- NIL.should equal(nil)
+ describe "TRUE" do
+ it "is no longer defined" do
+ Object.const_defined?(:TRUE).should == false
end
end
- ruby_version_is "2.4" do
- it "includes TRUE" do
- Object.const_defined?(:TRUE).should == true
- -> {
- TRUE.should equal(true)
- }.should complain(/constant ::TRUE is deprecated/)
- end
-
- it "includes FALSE" do
- Object.const_defined?(:FALSE).should == true
- -> {
- FALSE.should equal(false)
- }.should complain(/constant ::FALSE is deprecated/)
+ describe "FALSE" do
+ it "is no longer defined" do
+ Object.const_defined?(:FALSE).should == false
end
+ end
- it "includes NIL" do
- Object.const_defined?(:NIL).should == true
- -> {
- NIL.should equal(nil)
- }.should complain(/constant ::NIL is deprecated/)
+ describe "NIL" do
+ it "is no longer defined" do
+ Object.const_defined?(:NIL).should == false
end
end
@@ -1128,22 +1416,21 @@ describe "The predefined global constants" do
it "includes TOPLEVEL_BINDING" do
Object.const_defined?(:TOPLEVEL_BINDING).should == true
end
-
end
-with_feature :encoding do
- describe "The predefined global constant" do
- before :each do
- @external = Encoding.default_external
- @internal = Encoding.default_internal
- end
+describe "The predefined global constant" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+ end
- after :each do
- Encoding.default_external = @external
- Encoding.default_internal = @internal
- end
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
- describe "STDIN" do
+ describe "STDIN" do
+ 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
@@ -1153,19 +1440,6 @@ with_feature :encoding do
STDIN.external_encoding.should equal(Encoding::ISO_8859_16)
end
- it "has the encodings set by #set_encoding" do
- code = "STDIN.set_encoding Encoding::IBM775, Encoding::IBM866; " \
- "p [STDIN.external_encoding.name, STDIN.internal_encoding.name]"
- ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
- end
-
- it "retains the encoding set by #set_encoding when Encoding.default_external is changed" do
- code = "STDIN.set_encoding Encoding::IBM775, Encoding::IBM866; " \
- "Encoding.default_external = Encoding::ISO_8859_16;" \
- "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
@@ -1176,65 +1450,125 @@ with_feature :encoding do
end
end
- describe "STDOUT" do
- it "has nil for the external encoding" do
- STDOUT.external_encoding.should be_nil
- end
+ it "has the encodings set by #set_encoding" do
+ code = "STDIN.set_encoding Encoding::IBM775, Encoding::IBM866; " \
+ "p [STDIN.external_encoding.name, STDIN.internal_encoding.name]"
+ ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
+ 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
- end
+ it "retains the encoding set by #set_encoding when Encoding.default_external is changed" do
+ code = "STDIN.set_encoding Encoding::IBM775, Encoding::IBM866; " \
+ "Encoding.default_external = Encoding::ISO_8859_16;" \
+ "p [STDIN.external_encoding.name, STDIN.internal_encoding.name]"
+ ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
+ end
+ end
- it "has the encodings set by #set_encoding" do
- code = "STDOUT.set_encoding Encoding::IBM775, Encoding::IBM866; " \
- "p [STDOUT.external_encoding.name, STDOUT.internal_encoding.name]"
- ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
- end
+ describe "STDOUT" do
+ it "has nil for the external encoding" do
+ STDOUT.external_encoding.should be_nil
+ end
- it "has nil for the internal encoding" do
- STDOUT.internal_encoding.should be_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
+ 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
- end
+ it "has the encodings set by #set_encoding" do
+ code = "STDOUT.set_encoding Encoding::IBM775, Encoding::IBM866; " \
+ "p [STDOUT.external_encoding.name, STDOUT.internal_encoding.name]"
+ ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
end
- describe "STDERR" do
- it "has nil for the external encoding" do
- STDERR.external_encoding.should be_nil
- end
+ it "has nil for the internal encoding" do
+ STDOUT.internal_encoding.should be_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
- 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
+ end
+ end
- it "has the encodings set by #set_encoding" do
- code = "STDERR.set_encoding Encoding::IBM775, Encoding::IBM866; " \
- "p [STDERR.external_encoding.name, STDERR.internal_encoding.name]"
- ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
- end
+ describe "STDERR" do
+ it "has nil for the external encoding" do
+ STDERR.external_encoding.should be_nil
+ end
- it "has nil for the internal encoding" do
- STDERR.internal_encoding.should be_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
+ 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
- end
+ it "has the encodings set by #set_encoding" do
+ code = "STDERR.set_encoding Encoding::IBM775, Encoding::IBM866; " \
+ "p [STDERR.external_encoding.name, STDERR.internal_encoding.name]"
+ ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
end
- describe "ARGV" do
- it "contains Strings encoded in locale Encoding" do
- code = fixture __FILE__, "argv_encoding.rb"
- result = ruby_exe(code, args: "a b")
- encoding = Encoding.default_external
- result.chomp.should == %{["#{encoding}", "#{encoding}"]}
- end
+ it "has nil for the internal encoding" do
+ STDERR.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
+ STDERR.internal_encoding.should be_nil
end
end
+
+ describe "ARGV" do
+ it "contains Strings encoded in locale Encoding" do
+ code = fixture __FILE__, "argv_encoding.rb"
+ result = ruby_exe(code, args: "a b")
+ encoding = Encoding.default_external
+ result.chomp.should == %{["#{encoding}", "#{encoding}"]}
+ end
+ end
+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'
+ 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
+
+ it "return nil if feature cannot be found" do
+ $LOAD_PATH.resolve_feature_path('noop').should be_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 796c0c1711..b04aa25c9e 100644
--- a/spec/ruby/language/private_spec.rb
+++ b/spec/ruby/language/private_spec.rb
@@ -1,15 +1,15 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/private', __FILE__)
+require_relative '../spec_helper'
+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)
- lambda { a.bar }.should raise_error(NoMethodError)
+ -> { a.bar }.should raise_error(NoMethodError)
b = Private::B.new
b.methods.should_not include(:bar)
- lambda { b.bar }.should raise_error(NoMethodError)
+ -> { b.bar }.should raise_error(NoMethodError)
end
# def expr.meth() methods are always public
@@ -22,7 +22,7 @@ describe "The private keyword" do
c.methods.should include(:baz)
c.baz
Private::B.public_class_method1.should == 1
- lambda { Private::B.private_class_method1 }.should raise_error(NoMethodError)
+ -> { Private::B.private_class_method1 }.should raise_error(NoMethodError)
end
it "is no longer in effect when the class is closed" do
@@ -34,7 +34,7 @@ describe "The private keyword" do
it "changes visibility of previously called method" do
klass = Class.new do
def foo
- "foo"
+ "foo"
end
end
f = klass.new
@@ -42,12 +42,12 @@ describe "The private keyword" do
klass.class_eval do
private :foo
end
- lambda { f.foo }.should raise_error(NoMethodError)
+ -> { f.foo }.should raise_error(NoMethodError)
end
- it "changes visiblity of previously called methods with same send/call site" do
+ it "changes visibility of previously called methods with same send/call site" do
g = ::Private::G.new
- lambda {
+ -> {
2.times do
g.foo
module ::Private
@@ -61,7 +61,7 @@ describe "The private keyword" do
it "changes the visibility of the existing method in the subclass" do
::Private::A.new.foo.should == 'foo'
- lambda {::Private::H.new.foo}.should raise_error(NoMethodError)
+ -> { ::Private::H.new.foo }.should raise_error(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 bbef318826..ca9a13aa61 100644
--- a/spec/ruby/language/proc_spec.rb
+++ b/spec/ruby/language/proc_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "A Proc" do
it "captures locals from the surrounding scope" do
@@ -21,7 +21,7 @@ describe "A Proc" do
@l.call.should == 1
end
- it "raises an ArgumentErro if a value is passed" do
+ it "raises an ArgumentError if a value is passed" do
lambda { @l.call(0) }.should raise_error(ArgumentError)
end
end
@@ -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,6 +161,18 @@ 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 }
@@ -217,4 +229,21 @@ describe "A Proc" do
lambda { @l.call(obj) }.should raise_error(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_error(ArgumentError)
+ end
+ end
end
diff --git a/spec/ruby/language/range_spec.rb b/spec/ruby/language/range_spec.rb
new file mode 100644
index 0000000000..ccc9f55537
--- /dev/null
+++ b/spec/ruby/language/range_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Literal Ranges" do
+ it "creates range object" do
+ (1..10).should == Range.new(1, 10)
+ end
+
+ it "creates range with excluded right boundary" do
+ (1...10).should == Range.new(1, 10, true)
+ end
+
+ 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
+
+ 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 53fd30b4f2..57532553b3 100644
--- a/spec/ruby/language/redo_spec.rb
+++ b/spec/ruby/language/redo_spec.rb
@@ -1,9 +1,9 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The redo statement" do
it "restarts block execution if used within block" do
a = []
- lambda {
+ -> {
a << 1
redo if a.size < 2
a << 2
@@ -58,7 +58,7 @@ describe "The redo statement" do
describe "in a method" do
it "is invalid and raises a SyntaxError" do
- lambda {
+ -> {
eval("def m; redo; end")
}.should raise_error(SyntaxError)
end
diff --git a/spec/ruby/language/regexp/anchors_spec.rb b/spec/ruby/language/regexp/anchors_spec.rb
index c6a620a221..cdc06c0b4d 100644
--- a/spec/ruby/language/regexp/anchors_spec.rb
+++ b/spec/ruby/language/regexp/anchors_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
describe "Regexps with anchors" do
it "supports ^ (line start anchor)" do
@@ -124,10 +124,10 @@ describe "Regexps with anchors" do
/foo\b/.match("foo").to_a.should == ["foo"]
/foo\b/.match("foo\n").to_a.should == ["foo"]
LanguageSpecs.white_spaces.scan(/./).each do |c|
- /foo\b/.match("foo" + c).to_a.should == ["foo"]
+ /foo\b/.match("foo" + c).to_a.should == ["foo"]
end
LanguageSpecs.non_alphanum_non_space.scan(/./).each do |c|
- /foo\b/.match("foo" + c).to_a.should == ["foo"]
+ /foo\b/.match("foo" + c).to_a.should == ["foo"]
end
/foo\b/.match("foo\0").to_a.should == ["foo"]
# Basic non-matching
@@ -145,10 +145,10 @@ describe "Regexps with anchors" do
/foo\B/.match("foo").should be_nil
/foo\B/.match("foo\n").should be_nil
LanguageSpecs.white_spaces.scan(/./).each do |c|
- /foo\B/.match("foo" + c).should be_nil
+ /foo\B/.match("foo" + c).should be_nil
end
LanguageSpecs.non_alphanum_non_space.scan(/./).each do |c|
- /foo\B/.match("foo" + c).should be_nil
+ /foo\B/.match("foo" + c).should be_nil
end
/foo\B/.match("foo\0").should be_nil
end
diff --git a/spec/ruby/language/regexp/back-references_spec.rb b/spec/ruby/language/regexp/back-references_spec.rb
index 607f4463fd..627c8daace 100644
--- a/spec/ruby/language/regexp/back-references_spec.rb
+++ b/spec/ruby/language/regexp/back-references_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
describe "Regexps with back-references" do
it "saves match data in the $~ pseudo-global variable" do
@@ -7,7 +7,7 @@ describe "Regexps with back-references" do
$~.to_a.should == ["ll"]
end
- it "saves captures in numbered $[1-9] variables" do
+ it "saves captures in numbered $[1-N] variables" do
"1234567890" =~ /(1)(2)(3)(4)(5)(6)(7)(8)(9)(0)/
$~.to_a.should == ["1234567890", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
$1.should == "1"
@@ -19,6 +19,16 @@ describe "Regexps with back-references" do
$7.should == "7"
$8.should == "8"
$9.should == "9"
+ $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
@@ -45,4 +55,95 @@ describe "Regexps with back-references" do
it "resets nested \<n> backreference before match of outer subexpression" do
/(a\1?){2}/.match("aaaa").to_a.should == ["aa", "a"]
end
+
+ it "does not reset enclosed capture groups" do
+ /((a)|(b))+/.match("ab").captures.should == [ "b", "a", "b" ]
+ end
+
+ it "can match an optional quote, followed by content, followed by a matching quote, as the whole string" do
+ /^("|)(.*)\1$/.match('x').to_a.should == ["x", "", "x"]
+ end
+
+ it "allows forward references" do
+ /(?:(\2)|(.))+/.match("aa").to_a.should == [ "aa", "a", "a" ]
+ end
+
+ it "disallows forward references >= 10" 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)
+ end
+
+ it "allows numeric conditional backreferences" do
+ /(a)(?(1)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?(<1>)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?('1')a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+
+ it "allows either <> or '' in named conditional backreferences" do
+ -> { Regexp.new("(?<a>a)(?(a)a|b)") }.should raise_error(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
+
+ it "allows negative numeric backreferences" do
+ /(a)\k<-1>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)\g<-1>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?(<-1>)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?('-1')a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+
+ it "delimited numeric backreferences can start with 0" do
+ /(a)\k<01>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)\g<01>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?(01)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?(<01>)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?('01')a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+
+ it "regular numeric backreferences cannot start with 0" do
+ /(a)\01/.match("aa").should == nil
+ /(a)\01/.match("a\x01").to_a.should == [ "a\x01", "a" ]
+ 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)
+ 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)
+ end
end
diff --git a/spec/ruby/language/regexp/character_classes_spec.rb b/spec/ruby/language/regexp/character_classes_spec.rb
index ce66d8e65f..018757db41 100644
--- a/spec/ruby/language/regexp/character_classes_spec.rb
+++ b/spec/ruby/language/regexp/character_classes_spec.rb
@@ -1,6 +1,6 @@
# coding: utf-8
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
describe "Regexp with character classes" do
it "supports \\w (word character)" 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
- lambda { eval('/[[:alpha:]-[:digit:]]/') }.should raise_error(SyntaxError) # can't use character class as a start value of range
+ -> { eval('/[[:alpha:]-[:digit:]]/') }.should raise_error(SyntaxError) # can't use character class as a start value of range
end
it "matches ASCII characters with [[:ascii:]]" do
@@ -113,7 +113,7 @@ describe "Regexp with character classes" do
end
it "doesn't matches Unicode marks with [[:alnum:]]" do
- "\u{36F}".match(/[[:alnum:]]/).should be_nil
+ "\u{3099}".match(/[[:alnum:]]/).should be_nil
end
it "doesn't match Unicode control characters with [[:alnum:]]" do
@@ -133,7 +133,7 @@ describe "Regexp with character classes" do
end
it "doesn't matches Unicode marks with [[:alpha:]]" do
- "\u{36F}".match(/[[:alpha:]]/).should be_nil
+ "\u{3099}".match(/[[:alpha:]]/).should be_nil
end
it "doesn't match Unicode control characters with [[:alpha:]]" do
@@ -226,7 +226,7 @@ describe "Regexp with character classes" do
end
it "matches Unicode letter characters with [[:graph:]]" do
- "à".match(/[[:graph:]]/).to_a.should == ["à"]
+ "à".match(/[[:graph:]]/).to_a.should == ["à"]
end
it "matches Unicode digits with [[:graph:]]" do
@@ -562,6 +562,13 @@ describe "Regexp with character classes" do
"\u{16EE}".match(/[[:word:]]/).to_a.should == ["\u{16EE}"]
end
+ ruby_bug "#19417", ""..."3.4.6" do
+ it "matches Unicode join control characters with [[:word:]]" do
+ "\u{200C}".match(/[[:word:]]/).to_a.should == ["\u{200C}"]
+ "\u{200D}".match(/[[:word:]]/).to_a.should == ["\u{200D}"]
+ end
+ end
+
it "doesn't match Unicode No characters with [[:word:]]" do
"\u{17F0}".match(/[[:word:]]/).should be_nil
end
@@ -609,25 +616,32 @@ describe "Regexp with character classes" do
"루비(Ruby)".match(/\p{Hangul}+/u).to_a.should == ["루비"]
end
- ruby_version_is "2.4" do
- it "supports \\X (unicode 9.0 with UTR #51 workarounds)" do
- # simple emoji without any fancy modifier or ZWJ
- /\X/.match("\u{1F98A}").to_a.should == ["🦊"]
+ it "supports negated property condition" do
+ "a".match(eval("/\P{L}/")).should be_nil
+ "1".match(eval("/\P{N}/")).should be_nil
+ end
+
+ it "raises a RegexpError for an unterminated unicode property" do
+ -> { Regexp.new('\p{') }.should raise_error(RegexpError)
+ end
+
+ it "supports \\X (unicode 9.0 with UTR #51 workarounds)" do
+ # simple emoji without any fancy modifier or ZWJ
+ /\X/.match("\u{1F98A}").to_a.should == ["🦊"]
- # skin tone modifier
- /\X/.match("\u{1F918}\u{1F3FD}").to_a.should == ["🤘🏽"]
+ # skin tone modifier
+ /\X/.match("\u{1F918}\u{1F3FD}").to_a.should == ["🤘🏽"]
- # emoji joined with ZWJ
- /\X/.match("\u{1F3F3}\u{FE0F}\u{200D}\u{1F308}").to_a.should == ["🏳️‍🌈"]
- /\X/.match("\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}").to_a.should == ["👩‍👩‍👧‍👦"]
+ # emoji joined with ZWJ
+ /\X/.match("\u{1F3F3}\u{FE0F}\u{200D}\u{1F308}").to_a.should == ["🏳️‍🌈"]
+ /\X/.match("\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}").to_a.should == ["👩‍👩‍👧‍👦"]
- # without the ZWJ
- /\X+/.match("\u{1F3F3}\u{FE0F}\u{1F308}").to_a.should == ["🏳️🌈"]
- /\X+/.match("\u{1F469}\u{1F469}\u{1F467}\u{1F466}").to_a.should == ["👩👩👧👦"]
+ # without the ZWJ
+ /\X+/.match("\u{1F3F3}\u{FE0F}\u{1F308}").to_a.should == ["🏳️🌈"]
+ /\X+/.match("\u{1F469}\u{1F469}\u{1F467}\u{1F466}").to_a.should == ["👩👩👧👦"]
- # both of the ZWJ combined
- /\X+/.match("\u{1F3F3}\u{FE0F}\u{200D}\u{1F308}\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}")
- .to_a.should == ["🏳️‍🌈👩‍👩‍👧‍👦"]
- end
+ # both of the ZWJ combined
+ /\X+/.match("\u{1F3F3}\u{FE0F}\u{200D}\u{1F308}\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}")
+ .to_a.should == ["🏳️‍🌈👩‍👩‍👧‍👦"]
end
end
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 1f62244a28..ceb9cf823a 100644
--- a/spec/ruby/language/regexp/encoding_spec.rb
+++ b/spec/ruby/language/regexp/encoding_spec.rb
@@ -1,21 +1,21 @@
-# -*- encoding: binary -*-
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+# 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,35 +38,47 @@ 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
- it 'uses ASCII-8BIT as /n encoding if not all chars are 7-bit' do
- /\xFF/n.encoding.should == Encoding::ASCII_8BIT
+ it 'uses BINARY when is not initialized' do
+ Regexp.allocate.encoding.should == Encoding::BINARY
+ end
+
+ it 'uses BINARY as /n encoding if not all chars are 7-bit' do
+ /\xFF/n.encoding.should == Encoding::BINARY
end
it 'preserves US-ASCII as /n encoding through interpolation if all chars are 7-bit' do
/.#{/./}/n.encoding.should == Encoding::US_ASCII
end
- it 'preserves ASCII-8BIT as /n encoding through interpolation if all chars are 7-bit' do
- /\xFF#{/./}/n.encoding.should == Encoding::ASCII_8BIT
+ it 'preserves BINARY as /n encoding through interpolation if all chars are 7-bit' do
+ /\xFF#{/./}/n.encoding.should == Encoding::BINARY
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
@@ -78,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
@@ -100,4 +112,41 @@ describe "Regexps with encoding modifiers" do
it "selects last of multiple encoding specifiers" do
/foo/ensuensuens.should == /foo/s
end
+
+ it "raises Encoding::CompatibilityError when trying match against different encodings" do
+ -> { /\A[[:space:]]*\z/.match(" ".encode("UTF-16LE")) }.should raise_error(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)
+ end
+
+ it "raises Encoding::CompatibilityError when trying =~ against different encodings" do
+ -> { /\A[[:space:]]*\z/ =~ " ".encode("UTF-16LE") }.should raise_error(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_error(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_error(Encoding::CompatibilityError)
+ end
+
+ it "raises ArgumentError when trying to match a broken String" do
+ s = "\x80".dup.force_encoding('UTF-8')
+ -> { s =~ /./ }.should raise_error(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é".dup.force_encoding(Encoding::UTF_8))
+ r.should.fixed_encoding?
+ r.encoding.should == 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
end
diff --git a/spec/ruby/language/regexp/escapes_spec.rb b/spec/ruby/language/regexp/escapes_spec.rb
index 50ac22e51e..541998b937 100644
--- a/spec/ruby/language/regexp/escapes_spec.rb
+++ b/spec/ruby/language/regexp/escapes_spec.rb
@@ -1,9 +1,11 @@
-# -*- encoding: binary -*-
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+# 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
/\x0AA/.match("\nA").to_a.should == ["\nA"]
/\xAG/.match("\nG").to_a.should == ["\nG"]
# Non-matches
- lambda { eval('/\xG/') }.should raise_error(SyntaxError)
+ -> { eval('/\xG/') }.should raise_error(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"]
@@ -67,15 +139,31 @@ describe "Regexps with escape characters" do
/\cJ/.match("\r").should be_nil
# Parsing precedence
- /\cJ+/.match("\n\n").to_a.should == ["\n\n"] # Quantifers apply to entire escape sequence
+ /\cJ+/.match("\n\n").to_a.should == ["\n\n"] # Quantifiers apply to entire escape sequence
/\\cJ/.match("\\cJ").to_a.should == ["\\cJ"]
- lambda { eval('/[abc\x]/') }.should raise_error(SyntaxError) # \x is treated as a escape sequence even inside a character class
+ -> { eval('/[abc\x]/') }.should raise_error(SyntaxError) # \x is treated as a escape sequence even inside a character class
# Syntax error
- lambda { eval('/\c/') }.should raise_error(SyntaxError)
+ -> { eval('/\c/') }.should raise_error(SyntaxError)
# \cx control char (character code point value)
# \C-x control char (character code point value)
# \M-x meta (x|0x80) (character code point value)
# \M-\C-x meta control char (character code point value)
end
+
+ it "handles three digit octal escapes starting with 0" do
+ /[\000-\b]/.match("\x00")[0].should == "\x00"
+ end
+
+ it "handles control escapes with \\C-x syntax" do
+ /\C-*\C-J\C-j/.match("\n\n\n")[0].should == "\n\n\n"
+ end
+
+ it "supports the \\K keep operator" do
+ /a\Kb/.match("ab")[0].should == "b"
+ end
+
+ it "supports the \\R line break escape" do
+ /\R/.match("\n")[0].should == "\n"
+ end
end
diff --git a/spec/ruby/language/regexp/grouping_spec.rb b/spec/ruby/language/regexp/grouping_spec.rb
index 443cab7ee0..313858f714 100644
--- a/spec/ruby/language/regexp/grouping_spec.rb
+++ b/spec/ruby/language/regexp/grouping_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
describe "Regexps with grouping" do
it "support ()" do
@@ -12,7 +12,7 @@ describe "Regexps with grouping" do
end
it "raises a SyntaxError when parentheses aren't balanced" do
- lambda { eval "/(hay(st)ack/" }.should raise_error(SyntaxError)
+ -> { eval "/(hay(st)ack/" }.should raise_error(SyntaxError)
end
it "supports (?: ) (non-capturing group)" do
@@ -20,4 +20,44 @@ describe "Regexps with grouping" do
# Parsing precedence
/(?:xdigit:)/.match("xdigit:").to_a.should == ["xdigit:"]
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)
+ 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 5536c718f1..6951fd38ca 100644
--- a/spec/ruby/language/regexp/interpolation_spec.rb
+++ b/spec/ruby/language/regexp/interpolation_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
describe "Regexps with interpolation" do
@@ -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 = ""
- lambda { /(#{s}/ }.should raise_error(RegexpError)
+ -> { /(#{s}/ }.should raise_error(RegexpError)
s = "("
- lambda { /#{s}/ }.should raise_error(RegexpError)
+ -> { /#{s}/ }.should raise_error(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 a7052a941c..2f5522bc8a 100644
--- a/spec/ruby/language/regexp/modifiers_spec.rb
+++ b/spec/ruby/language/regexp/modifiers_spec.rb
@@ -1,7 +1,7 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
-describe "Regexps with modifers" do
+describe "Regexps with modifiers" do
it "supports /i (case-insensitive)" do
/foo/i.match("FOO").to_a.should == ["FOO"]
end
@@ -36,14 +36,12 @@ describe "Regexps with modifers" do
/foo/imox.match("foo").to_a.should == ["foo"]
/foo/imoximox.match("foo").to_a.should == ["foo"]
- lambda { eval('/foo/a') }.should raise_error(SyntaxError)
+ -> { eval('/foo/a') }.should raise_error(SyntaxError)
end
- ruby_version_is "2.4" do
- it "supports (?~) (absent operator)" do
- Regexp.new("(?~foo)").match("hello").to_a.should == ["hello"]
- "foo".scan(Regexp.new("(?~foo)")).should == ["fo","o",""]
- end
+ it "supports (?~) (absent operator)" do
+ Regexp.new("(?~foo)").match("hello").to_a.should == ["hello"]
+ "foo".scan(Regexp.new("(?~foo)")).should == ["fo","o",""]
end
it "supports (?imx-imx) (inline modifiers)" do
@@ -78,7 +76,7 @@ describe "Regexps with modifers" do
/(?i-i)foo/.match("FOO").should be_nil
/(?ii)foo/.match("FOO").to_a.should == ["FOO"]
/(?-)foo/.match("foo").to_a.should == ["foo"]
- lambda { eval('/(?o)/') }.should raise_error(SyntaxError)
+ -> { eval('/(?o)/') }.should raise_error(SyntaxError)
end
it "supports (?imx-imx:expr) (scoped inline modifiers)" do
@@ -98,7 +96,7 @@ describe "Regexps with modifers" do
/(?i-i:foo)/.match("FOO").should be_nil
/(?ii:foo)/.match("FOO").to_a.should == ["FOO"]
/(?-:)foo/.match("foo").to_a.should == ["foo"]
- lambda { eval('/(?o:)/') }.should raise_error(SyntaxError)
+ -> { eval('/(?o:)/') }.should raise_error(SyntaxError)
end
it "supports . with /m" do
@@ -106,7 +104,7 @@ describe "Regexps with modifers" do
/./m.match("\n").to_a.should == ["\n"]
end
- it "supports ASII/Unicode modifiers" do
+ it "supports ASCII/Unicode modifiers" do
eval('/(?a)[[:alpha:]]+/').match("a\u3042").to_a.should == ["a"]
eval('/(?d)[[:alpha:]]+/').match("a\u3042").to_a.should == ["a\u3042"]
eval('/(?u)[[:alpha:]]+/').match("a\u3042").to_a.should == ["a\u3042"]
diff --git a/spec/ruby/language/regexp/repetition_spec.rb b/spec/ruby/language/regexp/repetition_spec.rb
index 2fc8a74a47..d76619688f 100644
--- a/spec/ruby/language/regexp/repetition_spec.rb
+++ b/spec/ruby/language/regexp/repetition_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
describe "Regexps with repetition" do
it "supports * (0 or more of previous subexpression)" do
@@ -34,24 +34,105 @@ describe "Regexps with repetition" do
/.([0-9]){3,5}?foo/.match("9876543210foo").to_a.should == ["543210foo", "0"]
end
- ruby_version_is ""..."2.4" do
- it "does not treat {m,n}+ as possessive" do
+ it "does not treat {m,n}+ as possessive" do
+ -> {
@regexp = eval "/foo(A{0,1}+)Abar/"
- @regexp.match("fooAAAbar").to_a.should == ["fooAAAbar", "AA"]
- end
- end
-
- ruby_version_is "2.4" do
- it "does not treat {m,n}+ as possessive" do
- -> {
- @regexp = eval "/foo(A{0,1}+)Abar/"
- }.should complain(/nested repeat operato/)
- @regexp.match("fooAAAbar").to_a.should == ["fooAAAbar", "AA"]
- end
+ }.should complain(/nested repeat operator/)
+ @regexp.match("fooAAAbar").to_a.should == ["fooAAAbar", "AA"]
end
it "supports ? (0 or 1 of previous subexpression)" do
/a?/.match("aaa").to_a.should == ["a"]
/a?/.match("bbb").to_a.should == [""]
end
+
+ it "handles incomplete range quantifiers" do
+ /a{}/.match("a{}")[0].should == "a{}"
+ /a{,}/.match("a{,}")[0].should == "a{,}"
+ /a{1/.match("a{1")[0].should == "a{1"
+ /a{1,2/.match("a{1,2")[0].should == "a{1,2"
+ /a{,5}/.match("aaa")[0].should == "aaa"
+ end
+
+ it "lets us use quantifiers on assertions" do
+ /a^?b/.match("ab")[0].should == "ab"
+ /a$?b/.match("ab")[0].should == "ab"
+ /a\A?b/.match("ab")[0].should == "ab"
+ /a\Z?b/.match("ab")[0].should == "ab"
+ /a\z?b/.match("ab")[0].should == "ab"
+ /a\G?b/.match("ab")[0].should == "ab"
+ /a\b?b/.match("ab")[0].should == "ab"
+ /a\B?b/.match("ab")[0].should == "ab"
+ /a(?=c)?b/.match("ab")[0].should == "ab"
+ /a(?!=b)?b/.match("ab")[0].should == "ab"
+ /a(?<=c)?b/.match("ab")[0].should == "ab"
+ /a(?<!a)?b/.match("ab")[0].should == "ab"
+ end
+
+ it "does not delete optional assertions" do
+ /(?=(a))?/.match("a").to_a.should == [ "", "a" ]
+ end
+
+ it "supports nested quantifiers" do
+ suppress_warning do
+ eval <<-RUBY
+ /a***/.match("aaa")[0].should == "aaa"
+
+ # a+?* should not be reduced, it should be equivalent to (a+?)*
+ # NB: the capture group prevents regex engines from reducing the two quantifiers
+ # https://bugs.ruby-lang.org/issues/17341
+ /a+?*/.match("")[0].should == ""
+ /(a+?)*/.match("")[0].should == ""
+
+ /a+?*/.match("a")[0].should == "a"
+ /(a+?)*/.match("a")[0].should == "a"
+
+ /a+?*/.match("aa")[0].should == "aa"
+ /(a+?)*/.match("aa")[0].should == "aa"
+
+ # a+?+ should not be reduced, it should be equivalent to (a+?)+
+ # https://bugs.ruby-lang.org/issues/17341
+ /a+?+/.match("").should == nil
+ /(a+?)+/.match("").should == nil
+
+ /a+?+/.match("a")[0].should == "a"
+ /(a+?)+/.match("a")[0].should == "a"
+
+ /a+?+/.match("aa")[0].should == "aa"
+ /(a+?)+/.match("aa")[0].should == "aa"
+
+ # both a**? and a+*? should be equivalent to (a+)??
+ # this quantifier would rather match nothing, but if that's not possible,
+ # it will greedily take everything
+ /a**?/.match("")[0].should == ""
+ /(a*)*?/.match("")[0].should == ""
+ /a+*?/.match("")[0].should == ""
+ /(a+)*?/.match("")[0].should == ""
+ /(a+)??/.match("")[0].should == ""
+
+ /a**?/.match("aaa")[0].should == ""
+ /(a*)*?/.match("aaa")[0].should == ""
+ /a+*?/.match("aaa")[0].should == ""
+ /(a+)*?/.match("aaa")[0].should == ""
+ /(a+)??/.match("aaa")[0].should == ""
+
+ /b.**?b/.match("baaabaaab")[0].should == "baaabaaab"
+ /b(.*)*?b/.match("baaabaaab")[0].should == "baaabaaab"
+ /b.+*?b/.match("baaabaaab")[0].should == "baaabaaab"
+ /b(.+)*?b/.match("baaabaaab")[0].should == "baaabaaab"
+ /b(.+)??b/.match("baaabaaab")[0].should == "baaabaaab"
+ 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/subexpression_call_spec.rb b/spec/ruby/language/regexp/subexpression_call_spec.rb
new file mode 100644
index 0000000000..16b64cb327
--- /dev/null
+++ b/spec/ruby/language/regexp/subexpression_call_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexps with subexpression calls" do
+ it "allows numeric subexpression calls" do
+ /(a)\g<1>/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+
+ it "treats subexpression calls as distinct from simple back-references" do
+ # Back-references only match a string which is equal to the original captured string.
+ /(?<three_digits>[0-9]{3})-\k<three_digits>/.match("123-123")[0].should == "123-123"
+ /(?<three_digits>[0-9]{3})-\k<three_digits>/.match("123-456").should == nil
+ # However, subexpression calls reuse the previous expression and can match a different
+ # string.
+ /(?<three_digits>[0-9]{3})-\g<three_digits>/.match("123-456")[0].should == "123-456"
+ end
+
+ it "allows recursive subexpression calls" do
+ # This pattern matches well-nested parenthesized expression.
+ parens = /^ (?<parens> (?: \( \g<parens> \) | [^()] )* ) $/x
+ parens.match("((a)(b))c(d)")[0].should == "((a)(b))c(d)"
+ parens.match("((a)(b)c(d)").should == nil
+ end
+
+ it "allows access to back-references from the current level" do
+ # Using \\k<first_char-0> accesses the last value captured in first_char
+ # on the current stack level.
+ mirror = /^ (?<mirror> (?: (?<first_char>.) \g<mirror> \k<first_char-0> )? ) $/x
+ mirror.match("abccba")[0].should == "abccba"
+ mirror.match("abccbd").should == nil
+
+ # OTOH, using \\k<first_char> accesses the last value captured in first_char,
+ # regardless of the stack level. Therefore, it can't be used to implement
+ # the mirror language.
+ broken_mirror = /^ (?<mirror> (?: (?<first_char>.) \g<mirror> \k<first_char> )? ) $/x
+ broken_mirror.match("abccba").should == nil
+ # This matches because the 'c' is captured in first_char and that value is
+ # then used for all subsequent back-references, regardless of nesting.
+ broken_mirror.match("abcccc")[0].should == "abcccc"
+ end
+
+ it "allows + and - in group names and referential constructs that don't use levels, i.e. subexpression calls" do
+ /(?<a+>a)\g<a+>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a+b>a)\g<a+b>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a+1>a)\g<a+1>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a->a)\g<a->/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a-b>a)\g<a-b>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a-1>a)\g<a-1>/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+end
diff --git a/spec/ruby/language/regexp_spec.rb b/spec/ruby/language/regexp_spec.rb
index d4b0c81128..ce344b5b05 100644
--- a/spec/ruby/language/regexp_spec.rb
+++ b/spec/ruby/language/regexp_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/classes'
describe "Literal Regexps" do
it "matches against $_ (last input) in a conditional if no explicit matchee provided" do
@@ -18,6 +18,10 @@ describe "Literal Regexps" do
/Hello/.should be_kind_of(Regexp)
end
+ it "is frozen" do
+ /Hello/.should.frozen?
+ end
+
it "caches the Regexp object" do
rs = []
2.times do |i|
@@ -27,7 +31,7 @@ describe "Literal Regexps" do
end
it "throws SyntaxError for malformed literals" do
- lambda { eval('/(/') }.should raise_error(SyntaxError)
+ -> { eval('/(/') }.should raise_error(SyntaxError)
end
#############################################################################
@@ -54,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|
- lambda { eval("%r#{p0} foo #{p0}") }.should raise_error(SyntaxError)
+ -> { eval("%r#{p0} foo #{p0}") }.should raise_error(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
- lambda { eval('%ra foo a') }.should raise_error(SyntaxError)
+ -> { eval('%ra foo a') }.should raise_error(SyntaxError)
end
it "disallows spaces after %r and delimiter" do
- lambda { eval('%r !foo!') }.should raise_error(SyntaxError)
+ -> { eval('%r !foo!') }.should raise_error(SyntaxError)
end
it "allows unescaped / to be used with %r" do
@@ -90,14 +94,13 @@ describe "Literal Regexps" do
/./.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 possesive
+ /(?>foo*)obar/.match("foooooooobar").should be_nil # it is possessive
end
it "supports (?# )" do
@@ -109,6 +112,13 @@ describe "Literal Regexps" do
/foo.(?<=\d)/.match("fooA foo1").to_a.should == ["foo1"]
end
+ 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
/foo.(?<!\d)/.match("foo1 fooA").to_a.should == ["fooA"]
end
@@ -147,4 +157,11 @@ describe "Literal Regexps" do
pattern.should_not =~ 'fooF'
pattern.should_not =~ 'T'
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
+ char_uppercase = "\u{104B0}" # OSAGE CAPITAL LETTER A
+ /[[:upper:]]/.match(char_uppercase).to_s.should == char_uppercase
+ end
end
diff --git a/spec/ruby/language/rescue_spec.rb b/spec/ruby/language/rescue_spec.rb
index 0dc8894740..6be3bfd023 100644
--- a/spec/ruby/language/rescue_spec.rb
+++ b/spec/ruby/language/rescue_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/rescue', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/rescue'
class SpecificExampleException < StandardError
end
@@ -23,11 +23,127 @@ describe "The rescue keyword" do
end.should == :caught
end
- it "can capture the raised exception in a local variable" do
- begin
- raise SpecificExampleException, "some text"
- rescue SpecificExampleException => e
- e.message.should == "some text"
+ describe 'can capture the raised exception' do
+ before :all do
+ require_relative 'fixtures/rescue_captures'
+ end
+
+ it 'in a local variable' do
+ RescueSpecs::LocalVariableCaptor.should_capture_exception
+ end
+
+ it 'in a class variable' do
+ RescueSpecs::ClassVariableCaptor.should_capture_exception
+ end
+
+ it 'in a constant' do
+ RescueSpecs::ConstantCaptor.should_capture_exception
+ end
+
+ it 'in a global variable' do
+ RescueSpecs::GlobalVariableCaptor.should_capture_exception
+ end
+
+ it 'in an instance variable' do
+ RescueSpecs::InstanceVariableCaptor.should_capture_exception
+ end
+
+ it 'using a safely navigated setter method' 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 be_nil
+ end
+
+ it 'using a setter method' do
+ RescueSpecs::SetterCaptor.should_capture_exception
+ end
+
+ it 'using a square brackets setter' do
+ RescueSpecs::SquareBracketsCaptor.should_capture_exception
+ 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
@@ -54,7 +170,7 @@ describe "The rescue keyword" do
end
it "can rescue multiple raised exceptions with a single rescue block" do
- [lambda{raise ArbitraryException}, lambda{raise SpecificExampleException}].map do |block|
+ [->{raise ArbitraryException}, ->{raise SpecificExampleException}].map do |block|
begin
block.call
rescue SpecificExampleException, ArbitraryException
@@ -72,7 +188,7 @@ describe "The rescue keyword" do
end
caught_it.should be_true
caught = []
- [lambda{raise ArbitraryException}, lambda{raise SpecificExampleException}].each do |block|
+ [->{raise ArbitraryException}, ->{raise SpecificExampleException}].each do |block|
begin
block.call
rescue *exception_list
@@ -85,6 +201,18 @@ describe "The rescue keyword" do
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 be_true
+ end
+
it "can combine a splatted list of exceptions with a literal list of exceptions" do
caught_it = false
begin
@@ -94,7 +222,7 @@ describe "The rescue keyword" do
end
caught_it.should be_true
caught = []
- [lambda{raise ArbitraryException}, lambda{raise SpecificExampleException}].each do |block|
+ [->{raise ArbitraryException}, ->{raise SpecificExampleException}].each do |block|
begin
block.call
rescue ArbitraryException, *exception_list
@@ -108,7 +236,7 @@ describe "The rescue keyword" do
end
it "will only rescue the specified exceptions when doing a splat rescue" do
- lambda do
+ -> do
begin
raise OtherCustomException, "not rescued!"
rescue *exception_list
@@ -142,6 +270,19 @@ describe "The rescue keyword" do
ScratchPad.recorded.should == [:standard_error]
end
+ it "rescues the exception in the deepest rescue block declared to handle the appropriate exception type" do
+ begin
+ begin
+ RescueSpecs.raise_standard_error
+ rescue ArgumentError
+ end
+ rescue StandardError => e
+ e.backtrace.first.should =~ /:in [`'](?:RescueSpecs\.)?raise_standard_error'/
+ else
+ fail("exception wasn't handled by the correct rescue block")
+ end
+ end
+
it "will execute an else block only if no exceptions were raised" do
result = begin
ScratchPad << :one
@@ -195,8 +336,8 @@ describe "The rescue keyword" do
ScratchPad.recorded.should == [:one, :else_ran, :ensure_ran, :outside_begin]
end
- it "will execute an else block even without rescue and ensure" do
- lambda {
+ it "raises SyntaxError when else is used without rescue and ensure" do
+ -> {
eval <<-ruby
begin
ScratchPad << :begin
@@ -204,9 +345,7 @@ describe "The rescue keyword" do
ScratchPad << :else
end
ruby
- }.should complain(/else without rescue is useless/)
-
- ScratchPad.recorded.should == [:begin, :else]
+ }.should raise_error(SyntaxError, /else without rescue is useless/)
end
it "will not execute an else block if an exception was raised" do
@@ -265,7 +404,7 @@ describe "The rescue keyword" do
end
it "will not rescue errors raised in an else block in the rescue block above it" do
- lambda do
+ -> do
begin
ScratchPad << :one
rescue Exception
@@ -300,7 +439,7 @@ describe "The rescue keyword" do
[ Exception.new, NoMemoryError.new, ScriptError.new, SecurityError.new,
SignalException.new('INT'), SystemExit.new, SystemStackError.new
].each do |exception|
- lambda {
+ -> {
begin
raise exception
rescue
@@ -332,7 +471,7 @@ describe "The rescue keyword" do
it "only accepts Module or Class in rescue clauses" do
rescuer = 42
- lambda {
+ -> {
begin
raise "error"
rescue rescuer
@@ -344,7 +483,7 @@ describe "The rescue keyword" do
it "only accepts Module or Class in splatted rescue clauses" do
rescuer = [42]
- lambda {
+ -> {
begin
raise "error"
rescue *rescuer
@@ -355,11 +494,23 @@ describe "The rescue keyword" do
end
it "evaluates rescue expressions only when needed" do
- invalid_rescuer = Object.new
begin
- :foo
- rescue invalid_rescuer
- end.should == :foo
+ ScratchPad << :foo
+ rescue -> { ScratchPad << :bar; StandardError }.call
+ end
+
+ ScratchPad.recorded.should == [:foo]
+ end
+
+ it "suppresses exception from block when raises one from rescue expression" do
+ -> {
+ begin
+ raise "from block"
+ rescue (raise "from rescue expression")
+ end
+ }.should raise_error(RuntimeError, "from rescue expression") { |e|
+ e.cause.message.should == "from block"
+ }
end
it "should splat the handling Error classes" do
@@ -383,7 +534,7 @@ describe "The rescue keyword" do
end
it "does not allow rescue in {} block" do
- lambda {
+ -> {
eval <<-ruby
lambda {
raise SpecificExampleException
@@ -394,35 +545,40 @@ describe "The rescue keyword" do
}.should raise_error(SyntaxError)
end
- ruby_version_is "2.5" do
- it "allows rescue in 'do end' block" do
- lambda = eval <<-ruby
- lambda do
- raise SpecificExampleException
- rescue SpecificExampleException
- ScratchPad << :caught
- end.call
- ruby
+ it "allows rescue in 'do end' block" do
+ lambda = eval <<-ruby
+ lambda do
+ raise SpecificExampleException
+ rescue SpecificExampleException
+ ScratchPad << :caught
+ end.call
+ ruby
- ScratchPad.recorded.should == [:caught]
- end
+ ScratchPad.recorded.should == [:caught]
end
- ruby_version_is ""..."2.4" do
- it "fails when using 'rescue' in method arguments" do
- lambda { eval '1.+ (1 rescue 1)' }.should raise_error(SyntaxError)
- end
+ it "allows 'rescue' in method arguments" do
+ two = eval '1.+ (raise("Error") rescue 1)'
+ two.should == 2
end
- ruby_version_is "2.4" do
- it "allows 'rescue' in method arguments" do
- two = eval '1.+ (raise("Error") rescue 1)'
- two.should == 2
- 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 == 2
+ end
- it "requires the 'rescue' in method arguments to be wrapped in parens" do
- lambda { eval '1.+(1 rescue 1)' }.should raise_error(SyntaxError)
- eval('1.+((1 rescue 1))').should == 2
+ 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
@@ -433,7 +589,7 @@ describe "The rescue keyword" do
end
it "doesn't except rescue expression" do
- lambda {
+ -> {
eval <<-ruby
a = 1 rescue RuntimeError 2
ruby
@@ -444,9 +600,17 @@ describe "The rescue keyword" do
a = raise(StandardError) rescue 1
a.should == 1
- lambda {
+ -> {
a = raise(Exception) rescue 1
}.should raise_error(Exception)
end
+
+ it "rescues with multiple assignment" do
+
+ a, b = raise rescue [1, 2]
+
+ 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..6c40e34ccc
--- /dev/null
+++ b/spec/ruby/language/reserved_keywords.rb
@@ -0,0 +1,149 @@
+require_relative '../spec_helper'
+
+describe "Ruby's reserved keywords" do
+ # Copied from https://github.com/ruby/ruby/blob/master/defs/keywords
+ keywords = %w[
+ alias
+ and
+ begin
+ BEGIN
+ break
+ case
+ class
+ def
+ defined?
+ do
+ else
+ elsif
+ end
+ END
+ ensure
+ false
+ for
+ if
+ in
+ module
+ next
+ nil
+ not
+ or
+ redo
+ rescue
+ retry
+ return
+ self
+ super
+ then
+ true
+ undef
+ unless
+ until
+ when
+ while
+ yield
+ __ENCODING__
+ __FILE__
+ __LINE__
+ ]
+
+ keywords.each do |name|
+ describe "keyword '#{name}'" do
+ it "can't be used as local variable name" do
+ -> { eval(<<~RUBY) }.should raise_error(SyntaxError)
+ #{name} = :local_variable
+ RUBY
+ end
+
+ if name == "defined?"
+ it "can't be used as an instance variable name" do
+ -> { eval(<<~RUBY) }.should raise_error(SyntaxError)
+ @#{name} = :instance_variable
+ RUBY
+ end
+
+ it "can't be used as a class variable name" do
+ -> { eval(<<~RUBY) }.should raise_error(SyntaxError)
+ class C
+ @@#{name} = :class_variable
+ end
+ RUBY
+ end
+
+ it "can't be used as a global variable name" do
+ -> { eval(<<~RUBY) }.should raise_error(SyntaxError)
+ $#{name} = :global_variable
+ RUBY
+ end
+ else
+ it "can be used as an instance variable name" do
+ result = eval <<~RUBY
+ @#{name} = :instance_variable
+ @#{name}
+ RUBY
+
+ result.should == :instance_variable
+ end
+
+ it "can be used as a class variable name" do
+ result = eval <<~RUBY
+ class C
+ @@#{name} = :class_variable
+ @@#{name}
+ end
+ RUBY
+
+ result.should == :class_variable
+ end
+
+ it "can be used as a global variable name" do
+ result = eval <<~RUBY
+ $#{name} = :global_variable
+ $#{name}
+ RUBY
+
+ result.should == :global_variable
+ end
+ end
+
+ it "can't be used as a positional parameter name" do
+ -> { eval(<<~RUBY) }.should raise_error(SyntaxError)
+ def x(#{name}); end
+ RUBY
+ end
+
+ invalid_kw_param_names = ["BEGIN","END","defined?"]
+
+ if invalid_kw_param_names.include?(name)
+ it "can't be used a keyword parameter name" do
+ -> { eval(<<~RUBY) }.should raise_error(SyntaxError)
+ def m(#{name}:); end
+ RUBY
+ end
+ else
+ it "can be used a keyword parameter name" do
+ result = instance_eval <<~RUBY
+ def m(#{name}:)
+ binding.local_variable_get(:#{name})
+ end
+
+ m(#{name}: :argument)
+ RUBY
+
+ result.should == :argument
+ end
+ end
+
+ it "can be used as a method name" do
+ result = instance_eval <<~RUBY
+ def #{name}
+ :method_return_value
+ end
+
+ send(:#{name})
+ RUBY
+
+ result.should == :method_return_value
+ end
+ end
+ end
+end
diff --git a/spec/ruby/language/retry_spec.rb b/spec/ruby/language/retry_spec.rb
index 96e69b763a..669d5f0ff5 100644
--- a/spec/ruby/language/retry_spec.rb
+++ b/spec/ruby/language/retry_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The retry statement" do
it "re-executes the closest block" do
@@ -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
- lambda { eval 'retry' }.should raise_error(SyntaxError)
+ it "raises a SyntaxError when used outside of a rescue statement" do
+ -> { eval 'retry' }.should raise_error(SyntaxError)
+ -> { eval 'begin; retry; end' }.should raise_error(SyntaxError)
+ -> { eval 'def m; retry; end' }.should raise_error(SyntaxError)
+ -> { eval 'module RetrySpecs; retry; end' }.should raise_error(SyntaxError)
end
end
diff --git a/spec/ruby/language/return_spec.rb b/spec/ruby/language/return_spec.rb
index ba4bbfb5f3..a62ed1242d 100644
--- a/spec/ruby/language/return_spec.rb
+++ b/spec/ruby/language/return_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/return', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/return'
describe "The return keyword" do
it "returns any object directly" do
@@ -159,7 +159,7 @@ describe "The return keyword" do
end
it "executes the ensure clause when begin/ensure are inside a lambda" do
- lambda do
+ -> do
begin
return
ensure
@@ -176,15 +176,15 @@ describe "The return keyword" do
end
it "causes lambda to return nil if invoked without any arguments" do
- lambda { return; 456 }.call.should be_nil
+ -> { return; 456 }.call.should be_nil
end
it "causes lambda to return nil if invoked with an empty expression" do
- lambda { return (); 456 }.call.should be_nil
+ -> { return (); 456 }.call.should be_nil
end
it "causes lambda to return the value passed to return" do
- lambda { return 123; 456 }.call.should == 123
+ -> { return 123; 456 }.call.should == 123
end
it "causes the method that lexically encloses the block to return" do
@@ -250,233 +250,240 @@ describe "The return keyword" do
end
end
- ruby_version_is '2.4.2' do
- describe "at top level" do
- before :each do
- @filename = tmp("top_return.rb")
- ScratchPad.record []
- end
+ describe "at top level" do
+ before :each do
+ @filename = tmp("top_return.rb")
+ ScratchPad.record []
+ end
- after do
- rm_r @filename
- end
+ after do
+ rm_r @filename
+ end
- it "stops file execution" do
- ruby_exe(<<-END_OF_CODE).should == "before return\n"
- puts "before return"
- return
+ it "stops file execution" do
+ ruby_exe(<<-END_OF_CODE).should == "before return\n"
+ puts "before return"
+ return
+
+ puts "after return"
+ END_OF_CODE
+
+ $?.exitstatus.should == 0
+ end
+
+ describe "within if" do
+ it "is allowed" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before if"
+ if true
+ return
+ end
- puts "after return"
+ ScratchPad << "after if"
END_OF_CODE
- $?.exitstatus.should == 0
+ load @filename
+ ScratchPad.recorded.should == ["before if"]
end
+ end
- describe "within if" do
- it "is allowed" do
- File.write(@filename, <<-END_OF_CODE)
- ScratchPad << "before if"
- if true
- return
- end
+ describe "within while loop" do
+ it "is allowed" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before while"
+ while true
+ return
+ end
- ScratchPad << "after if"
- END_OF_CODE
+ ScratchPad << "after while"
+ END_OF_CODE
- load @filename
- ScratchPad.recorded.should == ["before if"]
- end
+ load @filename
+ ScratchPad.recorded.should == ["before while"]
end
+ end
- describe "within while loop" do
- it "is allowed" do
- File.write(@filename, <<-END_OF_CODE)
- ScratchPad << "before while"
- while true
- return
- end
+ describe "within a begin" do
+ it "is allowed in begin block" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before begin"
+ begin
+ return
+ end
- ScratchPad << "after while"
- END_OF_CODE
+ ScratchPad << "after begin"
+ END_OF_CODE
- load @filename
- ScratchPad.recorded.should == ["before while"]
- end
+ load @filename
+ ScratchPad.recorded.should == ["before begin"]
end
- describe "within a begin" do
- it "is allowed in begin block" do
- File.write(@filename, <<-END_OF_CODE)
- ScratchPad << "before begin"
- begin
- return
- end
+ it "is allowed in ensure block" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before begin"
+ begin
+ ensure
+ return
+ end
- ScratchPad << "after begin"
- END_OF_CODE
+ ScratchPad << "after begin"
+ END_OF_CODE
- load @filename
- ScratchPad.recorded.should == ["before begin"]
- end
+ load @filename
+ ScratchPad.recorded.should == ["before begin"]
+ end
- it "is allowed in ensure block" do
- File.write(@filename, <<-END_OF_CODE)
- ScratchPad << "before begin"
- begin
- ensure
- return
- end
+ it "is allowed in rescue block" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before begin"
+ begin
+ raise
+ rescue RuntimeError
+ return
+ end
- ScratchPad << "after begin"
- END_OF_CODE
+ ScratchPad << "after begin"
+ END_OF_CODE
- load @filename
- ScratchPad.recorded.should == ["before begin"]
- end
+ load @filename
+ ScratchPad.recorded.should == ["before begin"]
+ end
- it "is allowed in rescue block" do
- File.write(@filename, <<-END_OF_CODE)
- ScratchPad << "before begin"
- begin
- raise
- rescue RuntimeError
- return
- end
+ it "fires ensure block before returning" do
+ ruby_exe(<<-END_OF_CODE).should == "within ensure\n"
+ begin
+ return
+ ensure
+ puts "within ensure"
+ end
- ScratchPad << "after begin"
- END_OF_CODE
+ puts "after begin"
+ END_OF_CODE
+ end
- load @filename
- ScratchPad.recorded.should == ["before begin"]
- end
+ it "fires ensure block before returning while loads file" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before begin"
+ begin
+ return
+ ensure
+ ScratchPad << "within ensure"
+ end
- it "fires ensure block before returning" do
- ruby_exe(<<-END_OF_CODE).should == "within ensure\n"
- begin
- return
- ensure
- puts "within ensure"
- end
+ ScratchPad << "after begin"
+ END_OF_CODE
- puts "after begin"
- END_OF_CODE
- end
+ load @filename
+ ScratchPad.recorded.should == ["before begin", "within ensure"]
+ end
- ruby_bug "#14061", "2.4"..."2.6" do
- it "fires ensure block before returning while loads file" do
- File.write(@filename, <<-END_OF_CODE)
- ScratchPad << "before begin"
- begin
- return
- ensure
- ScratchPad << "within ensure"
- end
-
- ScratchPad << "after begin"
- END_OF_CODE
-
- load @filename
- ScratchPad.recorded.should == ["before begin", "within ensure"]
+ it "swallows exception if returns in ensure block" do
+ File.write(@filename, <<-END_OF_CODE)
+ begin
+ raise
+ ensure
+ ScratchPad << "before return"
+ return
end
- end
+ END_OF_CODE
- it "swallows exception if returns in ensure block" do
- File.write(@filename, <<-END_OF_CODE)
- begin
- raise
- ensure
- ScratchPad << "before return"
- return
- end
- END_OF_CODE
-
- load @filename
- ScratchPad.recorded.should == ["before return"]
- end
+ load @filename
+ ScratchPad.recorded.should == ["before return"]
end
+ end
- describe "within a block" do
- it "is allowed" do
- File.write(@filename, <<-END_OF_CODE)
- ScratchPad << "before call"
- proc { return }.call
+ describe "within a block" do
+ it "is allowed" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before call"
+ proc { return }.call
- ScratchPad << "after call"
- END_OF_CODE
+ ScratchPad << "after call"
+ END_OF_CODE
- load @filename
- ScratchPad.recorded.should == ["before call"]
- end
+ load @filename
+ ScratchPad.recorded.should == ["before call"]
end
+ end
- describe "within a class" do
- ruby_version_is ""..."2.5" do
- it "is allowed" do
- File.write(@filename, <<-END_OF_CODE)
- class A
- ScratchPad << "before return"
- return
-
- ScratchPad << "after return"
- end
- END_OF_CODE
+ describe "within a class" do
+ it "raises a SyntaxError" do
+ File.write(@filename, <<-END_OF_CODE)
+ class ReturnSpecs::A
+ ScratchPad << "before return"
+ return
- load @filename
- ScratchPad.recorded.should == ["before return"]
+ ScratchPad << "after return"
end
- end
-
- ruby_version_is "2.5" do
- it "raises a SyntaxError" do
- File.write(@filename, <<-END_OF_CODE)
- class A
- ScratchPad << "before return"
- return
-
- ScratchPad << "after return"
- end
- END_OF_CODE
+ END_OF_CODE
- -> { load @filename }.should raise_error(SyntaxError)
- end
- end
+ -> { load @filename }.should raise_error(SyntaxError)
end
+ end
- describe "file loading" do
- it "stops file loading and execution" do
- File.write(@filename, <<-END_OF_CODE)
+ describe "within a block within a class" do
+ it "is not allowed" do
+ File.write(@filename, <<-END_OF_CODE)
+ class ReturnSpecs::A
ScratchPad << "before return"
- return
+ 1.times { return }
ScratchPad << "after return"
- END_OF_CODE
+ end
+ END_OF_CODE
- load @filename
- ScratchPad.recorded.should == ["before return"]
- end
+ -> { load @filename }.should raise_error(LocalJumpError)
end
+ end
- describe "file requiring" do
- it "stops file loading and execution" do
- File.write(@filename, <<-END_OF_CODE)
- ScratchPad << "before return"
+ describe "within BEGIN" do
+ it "is allowed" do
+ File.write(@filename, <<-END_OF_CODE)
+ BEGIN {
+ ScratchPad << "before call"
return
- ScratchPad << "after return"
- END_OF_CODE
+ ScratchPad << "after call"
+ }
+ END_OF_CODE
- require @filename
- ScratchPad.recorded.should == ["before return"]
- end
+ load @filename
+ ScratchPad.recorded.should == ["before call"]
end
+ end
- describe "return with argument" do
- # https://bugs.ruby-lang.org/issues/14062
- it "does not affect exit status" do
- ruby_exe(<<-END_OF_CODE).should == ""
- return 10
- END_OF_CODE
+ describe "file loading" do
+ it "stops file loading and execution" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before return"
+ return
+ ScratchPad << "after return"
+ END_OF_CODE
- $?.exitstatus.should == 0
- end
+ load @filename
+ ScratchPad.recorded.should == ["before return"]
+ end
+ end
+
+ describe "file requiring" do
+ it "stops file loading and execution" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before return"
+ return
+ ScratchPad << "after return"
+ END_OF_CODE
+
+ require @filename
+ ScratchPad.recorded.should == ["before return"]
+ end
+ end
+
+ describe "return with argument" 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
+
+ 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 a8b29dc5a3..b1e28c3963 100644
--- a/spec/ruby/language/safe_navigator_spec.rb
+++ b/spec/ruby/language/safe_navigator_spec.rb
@@ -1,101 +1,147 @@
-require File.expand_path("../../spec_helper", __FILE__)
+require_relative '../spec_helper'
-ruby_version_is "2.3" do
- describe "Safe navigator" do
- it "requires a method name to be provided" do
- lambda { eval("obj&. {}") }.should raise_error(SyntaxError)
+describe "Safe navigator" do
+ it "requires a method name to be provided" do
+ -> { eval("obj&. {}") }.should raise_error(SyntaxError)
+ end
+
+ context "when context is nil" do
+ it "always returns nil" do
+ nil&.unknown.should == nil
+ [][10]&.unknown.should == nil
end
- context "when context is nil" do
- it "always returns nil" do
- eval("nil&.unknown").should == nil
- eval("[][10]&.unknown").should == nil
- end
+ it "can be chained" do
+ nil&.one&.two&.three.should == nil
+ end
- it "can be chained" do
- eval("nil&.one&.two&.three").should == nil
- end
+ it "doesn't evaluate arguments" do
+ obj = Object.new
+ obj.should_not_receive(:m)
+ nil&.unknown(obj.m) { obj.m }
+ end
+ end
- it "doesn't evaluate arguments" do
- obj = Object.new
- obj.should_not_receive(:m)
- eval("nil&.unknown(obj.m) { obj.m }")
- end
+ context "when context is false" do
+ it "calls the method" do
+ false&.to_s.should == "false"
+
+ -> { false&.unknown }.should raise_error(NoMethodError)
end
+ end
- context "when context is false" do
- it "calls the method" do
- eval("false&.to_s").should == "false"
+ context "when context is truthy" do
+ it "calls the method" do
+ 1&.to_s.should == "1"
- lambda { eval("false&.unknown") }.should raise_error(NoMethodError)
- end
+ -> { 1&.unknown }.should raise_error(NoMethodError)
end
+ end
- context "when context is truthy" do
- it "calls the method" do
- eval("1&.to_s").should == "1"
+ it "takes a list of arguments" do
+ [1,2,3]&.first(2).should == [1,2]
+ end
+
+ it "takes a block" do
+ [1,2]&.map { |i| i * 2 }.should == [2, 4]
+ end
- lambda { eval("1&.unknown") }.should raise_error(NoMethodError)
+ it "allows assignment methods" do
+ klass = Class.new do
+ attr_reader :foo
+ def foo=(val)
+ @foo = val
+ 42
end
end
+ obj = klass.new
- it "takes a list of arguments" do
- eval("[1,2,3]&.first(2)").should == [1,2]
- end
+ (obj&.foo = 3).should == 3
+ obj.foo.should == 3
- it "takes a block" do
- eval("[1,2]&.map { |i| i * 2 }").should == [2, 4]
- end
+ obj = nil
+ (obj&.foo = 3).should == nil
+ end
- it "allows assignment methods" do
- klass = Class.new do
- attr_reader :foo
- def foo=(val)
- @foo = val
- 42
- end
- end
- obj = klass.new
+ it "allows assignment operators" do
+ klass = Class.new do
+ attr_reader :m
- eval("obj&.foo = 3").should == 3
- obj.foo.should == 3
+ def initialize
+ @m = 0
+ end
- obj = nil
- eval("obj&.foo = 3").should == nil
+ def m=(v)
+ @m = v
+ 42
+ end
end
- it "allows assignment operators" do
- klass = Class.new do
- attr_accessor :m
+ obj = klass.new
- def initialize
- @m = 0
- end
- end
+ obj&.m += 3
+ obj.m.should == 3
+
+ obj = nil
+ (obj&.m += 3).should == nil
+ end
- obj = klass.new
+ it "allows ||= operator" do
+ klass = Class.new do
+ attr_reader :m
- eval("obj&.m += 3")
- obj.m.should == 3
+ def initialize
+ @m = false
+ end
- obj = nil
- eval("obj&.m += 3").should == nil
+ def m=(v)
+ @m = v
+ 42
+ end
end
- it "does not call the operator method lazily with an assignment operator" do
- klass = Class.new do
- attr_writer :foo
- def foo
- nil
- 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
- obj = klass.new
+ end
+
+ obj = klass.new
+
+ (obj&.m &&= false).should == false
+ obj.m.should == false
- lambda {
- eval("obj&.foo += 3")
- }.should raise_error(NoMethodError) { |e|
- e.name.should == :+
- }
+ obj = nil
+ (obj&.m &&= false).should == nil
+ obj.should == nil
+ end
+
+ it "does not call the operator method lazily with an assignment operator" do
+ klass = Class.new do
+ attr_writer :foo
+ def foo
+ nil
+ end
end
+ obj = klass.new
+
+ -> {
+ obj&.foo += 3
+ }.should raise_error(NoMethodError) { |e|
+ e.name.should == :+
+ }
end
end
diff --git a/spec/ruby/language/safe_spec.rb b/spec/ruby/language/safe_spec.rb
new file mode 100644
index 0000000000..03ae96148e
--- /dev/null
+++ b/spec/ruby/language/safe_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../spec_helper'
+
+describe "The $SAFE variable" do
+ 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 646a700785..5d6340ffc5 100644
--- a/spec/ruby/language/send_spec.rb
+++ b/spec/ruby/language/send_spec.rb
@@ -1,15 +1,15 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/send', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/send'
# Why so many fixed arg tests? JRuby and I assume other Ruby impls have
# separate call paths for simple fixed arity methods. Testing up to five
# will verify special and generic arity code paths for all impls.
#
# Method naming conventions:
-# M - Manditory Args
+# M - Mandatory Args
# O - Optional Arg
# R - Rest Arg
-# Q - Post Manditory Args
+# Q - Post Mandatory Args
specs = LangSendSpecs
@@ -20,7 +20,7 @@ describe "Invoking a method" do
end
it "raises ArgumentError if the method has a positive arity" do
- lambda {
+ -> {
specs.fooM1
}.should raise_error(ArgumentError)
end
@@ -36,14 +36,14 @@ describe "Invoking a method" do
end
it "raises ArgumentError if the methods arity doesn't match" do
- lambda {
+ -> {
specs.fooM1(1,2)
}.should raise_error(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
@@ -52,7 +52,7 @@ describe "Invoking a method" do
end
it "raises ArgumentError if extra arguments are passed" do
- lambda {
+ -> {
specs.fooM0O1(2,3)
}.should raise_error(ArgumentError)
end
@@ -64,13 +64,13 @@ describe "Invoking a method" do
end
it "raises an ArgumentError if there are no values for the mandatory args" do
- lambda {
+ -> {
specs.fooM1O1
}.should raise_error(ArgumentError)
end
it "raises an ArgumentError if too many values are passed" do
- lambda {
+ -> {
specs.fooM1O1(1,2,3)
}.should raise_error(ArgumentError)
end
@@ -106,8 +106,26 @@ describe "Invoking a method" do
specs.yield_now(&o).should == :from_to_proc
end
+ ruby_version_is "4.0" do
+ it "raises TypeError if 'to_proc' doesn't return a Proc" do
+ o = LangSendSpecs::RawToProc.new(42)
+
+ -> {
+ specs.makeproc(&o)
+ }.should raise_error(TypeError, "can't convert LangSendSpecs::RawToProc to Proc (LangSendSpecs::RawToProc#to_proc gives Integer)")
+ end
+
+ it "raises TypeError if block object isn't a Proc and doesn't respond to `to_proc`" do
+ o = Object.new
+
+ -> {
+ specs.makeproc(&o)
+ }.should raise_error(TypeError, "no implicit conversion of Object into Proc")
+ end
+ end
+
it "raises a SyntaxError with both a literal block and an object as block" do
- lambda {
+ -> {
eval "specs.oneb(10, &l){ 42 }"
}.should raise_error(SyntaxError)
end
@@ -195,12 +213,20 @@ describe "Invoking a method" do
end
it "raises NameError if invoked as a vcall" do
- lambda { no_such_method }.should raise_error NameError
+ -> { no_such_method }.should raise_error NameError
+ 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") }
end
it "raises NoMethodError if invoked as an unambiguous method call" do
- lambda { no_such_method() }.should raise_error NoMethodError
- lambda { no_such_method(1,2,3) }.should raise_error NoMethodError
+ -> { no_such_method() }.should raise_error NoMethodError
+ -> { no_such_method(1,2,3) }.should raise_error NoMethodError
+ 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") }
end
end
@@ -250,10 +276,10 @@ describe "Invoking a private setter method" do
end
describe "Invoking a private getter method" do
- it "does not permit self as a receiver" do
+ it "permits self as a receiver" do
receiver = LangSendSpecs::PrivateGetter.new
- lambda { receiver.call_self_foo }.should raise_error(NoMethodError)
- lambda { receiver.call_self_foo_or_equals(6) }.should raise_error(NoMethodError)
+ receiver.call_self_foo_or_equals(6)
+ receiver.call_self_foo.should == 6
end
end
@@ -403,6 +429,29 @@ describe "Invoking a method" do
specs.rest_len(0,*a,4,*5,6,7,*c,-1).should == 11
end
+ 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]
+ 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
+ def self.m(*args, &block)
+ [args, block]
+ end
+
+ args = [1, nil]
+ m(*args, 2, &args.pop).should == [[1, nil, 2], nil]
+ end
+
it "expands an array to arguments grouped in parentheses" do
specs.destructure2([40,2]).should == 42
end
diff --git a/spec/ruby/language/singleton_class_spec.rb b/spec/ruby/language/singleton_class_spec.rb
index 837f479440..45e1f7f3ad 100644
--- a/spec/ruby/language/singleton_class_spec.rb
+++ b/spec/ruby/language/singleton_class_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/class', __FILE__)
+require_relative '../spec_helper'
+require_relative '../fixtures/class'
describe "A singleton class" do
it "is TrueClass for true" do
@@ -14,12 +14,12 @@ describe "A singleton class" do
nil.singleton_class.should == NilClass
end
- it "raises a TypeError for Fixnum's" do
- lambda { 1.singleton_class }.should raise_error(TypeError)
+ it "raises a TypeError for Integer's" do
+ -> { 1.singleton_class }.should raise_error(TypeError)
end
it "raises a TypeError for symbols" do
- lambda { :symbol.singleton_class }.should raise_error(TypeError)
+ -> { :symbol.singleton_class }.should raise_error(TypeError)
end
it "is a singleton Class instance" do
@@ -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
- lambda { bignum_value.singleton_class.superclass.should == Bignum }.should raise_error(TypeError)
+ -> { bignum_value.singleton_class }.should raise_error(TypeError)
end
end
@@ -112,11 +112,11 @@ describe "A constant on a singleton class" do
class << @object
CONST
end
- lambda { CONST }.should raise_error(NameError)
+ -> { CONST }.should raise_error(NameError)
end
it "cannot be accessed via object::CONST" do
- lambda do
+ -> do
@object::CONST
end.should raise_error(TypeError)
end
@@ -127,7 +127,7 @@ describe "A constant on a singleton class" do
CONST = 100
end
- lambda do
+ -> do
@object::CONST
end.should raise_error(NameError)
end
@@ -143,7 +143,7 @@ describe "A constant on a singleton class" do
it "is not preserved when the object is duped" do
@object = @object.dup
- lambda do
+ -> do
class << @object; CONST; end
end.should raise_error(NameError)
end
@@ -280,14 +280,38 @@ end
describe "Instantiating a singleton class" do
it "raises a TypeError when new is called" do
- lambda {
+ -> {
Object.new.singleton_class.new
}.should raise_error(TypeError)
end
it "raises a TypeError when allocate is called" do
- lambda {
+ -> {
Object.new.singleton_class.allocate
}.should raise_error(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
new file mode 100644
index 0000000000..7135bc0a70
--- /dev/null
+++ b/spec/ruby/language/source_encoding_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../spec_helper'
+
+describe "Source files" do
+
+ describe "encoded in UTF-8 without a BOM" do
+ it "can be parsed" do
+ ruby_exe(fixture(__FILE__, "utf8-nobom.rb"), args: "2>&1").should == "hello\n"
+ end
+ end
+
+ describe "encoded in UTF-8 with a BOM" do
+ it "can be parsed" do
+ ruby_exe(fixture(__FILE__, "utf8-bom.rb"), args: "2>&1").should == "hello\n"
+ end
+ end
+
+ describe "encoded in UTF-16 LE without a BOM" 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
+
+ describe "encoded in UTF-16 LE with a BOM" do
+ it "are invalid because they contain an invalid UTF-8 sequence before the encoding comment" do
+ bom = "\xFF\xFE".b
+ source = "# encoding: utf-16le\nputs 'hello'\n"
+ source = bom + source.bytes.zip([0]*source.bytesize).flatten.pack('C*')
+ path = tmp("utf16-le-bom.rb")
+
+ touch(path, "wb") { |f| f.write source }
+ begin
+ ruby_exe(path, args: "2>&1", exit_status: 1).should =~ /invalid multibyte char/
+ ensure
+ rm_r path
+ end
+ end
+ end
+
+ describe "encoded in UTF-16 BE without a BOM" do
+ it "are parsed as empty because they contain a NUL byte before the encoding comment" do
+ ruby_exe(fixture(__FILE__, "utf16-be-nobom.rb"), args: "2>&1").should == ""
+ end
+ end
+
+ describe "encoded in UTF-16 BE with a BOM" do
+ it "are invalid because they contain an invalid UTF-8 sequence before the encoding comment" do
+ bom = "\xFE\xFF".b
+ source = "# encoding: utf-16be\nputs 'hello'\n"
+ source = bom + ([0]*source.bytesize).zip(source.bytes).flatten.pack('C*')
+ path = tmp("utf16-be-bom.rb")
+
+ touch(path, "wb") { |f| f.write source }
+ begin
+ ruby_exe(path, args: "2>&1", exit_status: 1).should =~ /invalid multibyte char/
+ ensure
+ rm_r path
+ end
+ end
+ end
+
+end
diff --git a/spec/ruby/language/string_spec.rb b/spec/ruby/language/string_spec.rb
index dbec2652ed..f287731bed 100644
--- a/spec/ruby/language/string_spec.rb
+++ b/spec/ruby/language/string_spec.rb
@@ -1,6 +1,7 @@
-# -*- encoding: binary -*-
+# encoding: binary
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/class_with_class_variable'
# TODO: rewrite these horrid specs. it "are..." seriously?!
@@ -27,11 +28,21 @@ 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'
end
+ it "does not interpolate invalid variable names" do
+ "#@".should == '#@'
+ "#$%".should == '#$%'
+ end
+
it "has characters [.(=?!# end simple # interpolation" do
"#@ip[".should == 'xxx['
"#@ip.".should == 'xxx.'
@@ -42,24 +53,13 @@ describe "Ruby character strings" do
"#@ip#@ip".should == 'xxxxxx'
end
- 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
+ it "don't get confused by partial interpolation character sequences" do
+ "#@".should == '#@'
+ "#@ ".should == '#@ '
+ "#@@".should == '#@@'
+ "#@@ ".should == '#@@ '
+ "#$ ".should == '#$ '
+ "#\$".should == '#$'
end
it "allows using non-alnum characters as string delimiters" do
@@ -186,11 +186,11 @@ describe "Ruby character strings" do
# TODO: spec other source encodings
describe "with ASCII_8BIT source encoding" do
it "produces an ASCII string when escaping ASCII characters via \\u" do
- "\u0000".encoding.should == Encoding::ASCII_8BIT
+ "\u0000".encoding.should == Encoding::BINARY
end
it "produces an ASCII string when escaping ASCII characters via \\u{}" do
- "\u{0000}".encoding.should == Encoding::ASCII_8BIT
+ "\u{0000}".encoding.should == Encoding::BINARY
end
it "produces a UTF-8-encoded string when escaping non-ASCII characters via \\u" do
@@ -224,59 +224,78 @@ describe "Ruby String literals" do
long_string_literals.should == "Beautiful is better than ugly.Explicit is better than implicit."
end
- ruby_version_is "2.3" do
- describe "with a magic frozen comment" do
- it "produce the same object each time" do
- ruby_exe(fixture(__FILE__, "freeze_magic_comment_one_literal.rb")).chomp.should == "true"
- end
+ describe "with a magic frozen comment" do
+ it "produce the same object each time" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_one_literal.rb")).chomp.should == "true"
+ end
- it "produce the same object for literals with the same content" do
- ruby_exe(fixture(__FILE__, "freeze_magic_comment_two_literals.rb")).chomp.should == "true"
- end
+ it "produce the same object for literals with the same content" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_two_literals.rb")).chomp.should == "true"
+ end
- it "produce the same object for literals with the same content in different files" do
- ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files.rb")).chomp.should == "true"
- end
+ it "produce the same object for literals with the same content in different files" 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
+ 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
- it "produce different objects for literals with the same content in different files if they have different encodings" do
- ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_diff_enc.rb")).chomp.should == "true"
+ 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
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_diff_enc.rb")).chomp.should == "true"
+ end
end
end
-with_feature :encoding do
- describe "Ruby String interpolation" do
- it "creates a String having an Encoding compatible with all components" do
- a = "\u3042"
- b = "abc".encode("ascii-8bit")
+describe "Ruby String interpolation" do
+ it "permits an empty expression" do
+ s = "#{}" # rubocop:disable Lint/EmptyInterpolation
+ s.should.empty?
+ s.should_not.frozen?
+ end
+
+ it "returns a string with the source encoding by default" do
+ "a#{"b"}c".encoding.should == Encoding::BINARY
+ 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
- str = "#{a} x #{b}"
+ it "returns a string with the source encoding, even if the components have another encoding" do
+ a = "abc".dup.force_encoding("euc-jp")
+ "#{a}".encoding.should == Encoding::BINARY
- str.should == "\xe3\x81\x82\x20\x78\x20\x61\x62\x63".force_encoding("utf-8")
- str.encoding.should == Encoding::UTF_8
- end
+ b = "abc".encode("utf-8")
+ "#{b}".encoding.should == Encoding::BINARY
+ end
- it "creates a String having the Encoding of the components when all are the same Encoding" do
- a = "abc".force_encoding("euc-jp")
- b = "def".force_encoding("euc-jp")
- str = '"#{a} x #{b}"'.force_encoding("euc-jp")
+ it "raises an Encoding::CompatibilityError if the Encodings are not compatible" do
+ a = "\u3042"
+ b = "\xff".dup.force_encoding "binary"
- result = eval(str)
- result.should == "\x61\x62\x63\x20\x78\x20\x64\x65\x66".force_encoding("euc-jp")
- result.encoding.should == Encoding::EUC_JP
- end
+ -> { "#{a} #{b}" }.should raise_error(Encoding::CompatibilityError)
+ end
- it "raises an Encoding::CompatibilityError if the Encodings are not compatible" do
- a = "\u3042"
- b = "\xff".force_encoding "ascii-8bit"
+ it "creates a non-frozen String" do
+ code = <<~'RUBY'
+ "a#{6*7}c"
+ RUBY
+ eval(code).should_not.frozen?
+ end
- lambda { "#{a} #{b}" }.should raise_error(Encoding::CompatibilityError)
- end
+ it "creates a non-frozen String when # frozen-string-literal: true is used" do
+ code = <<~'RUBY'
+ # frozen-string-literal: true
+ "a#{6*7}c"
+ 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 3d3f5d6f74..7d9e896d8b 100644
--- a/spec/ruby/language/super_spec.rb
+++ b/spec/ruby/language/super_spec.rb
@@ -1,76 +1,76 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/super', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/super'
describe "The super keyword" do
it "calls the method on the calling class" do
- Super::S1::A.new.foo([]).should == ["A#foo","A#bar"]
- Super::S1::A.new.bar([]).should == ["A#bar"]
- Super::S1::B.new.foo([]).should == ["B#foo","A#foo","B#bar","A#bar"]
- Super::S1::B.new.bar([]).should == ["B#bar","A#bar"]
+ SuperSpecs::S1::A.new.foo([]).should == ["A#foo","A#bar"]
+ SuperSpecs::S1::A.new.bar([]).should == ["A#bar"]
+ SuperSpecs::S1::B.new.foo([]).should == ["B#foo","A#foo","B#bar","A#bar"]
+ SuperSpecs::S1::B.new.bar([]).should == ["B#bar","A#bar"]
end
it "searches the full inheritance chain" do
- Super::S2::B.new.foo([]).should == ["B#foo","A#baz"]
- Super::S2::B.new.baz([]).should == ["A#baz"]
- Super::S2::C.new.foo([]).should == ["B#foo","C#baz","A#baz"]
- Super::S2::C.new.baz([]).should == ["C#baz","A#baz"]
+ SuperSpecs::S2::B.new.foo([]).should == ["B#foo","A#baz"]
+ SuperSpecs::S2::B.new.baz([]).should == ["A#baz"]
+ SuperSpecs::S2::C.new.foo([]).should == ["B#foo","C#baz","A#baz"]
+ SuperSpecs::S2::C.new.baz([]).should == ["C#baz","A#baz"]
end
it "searches class methods" do
- Super::S3::A.new.foo([]).should == ["A#foo"]
- Super::S3::A.foo([]).should == ["A.foo"]
- Super::S3::A.bar([]).should == ["A.bar","A.foo"]
- Super::S3::B.new.foo([]).should == ["A#foo"]
- Super::S3::B.foo([]).should == ["B.foo","A.foo"]
- Super::S3::B.bar([]).should == ["B.bar","A.bar","B.foo","A.foo"]
+ SuperSpecs::S3::A.new.foo([]).should == ["A#foo"]
+ SuperSpecs::S3::A.foo([]).should == ["A.foo"]
+ SuperSpecs::S3::A.bar([]).should == ["A.bar","A.foo"]
+ SuperSpecs::S3::B.new.foo([]).should == ["A#foo"]
+ SuperSpecs::S3::B.foo([]).should == ["B.foo","A.foo"]
+ SuperSpecs::S3::B.bar([]).should == ["B.bar","A.bar","B.foo","A.foo"]
end
it "calls the method on the calling class including modules" do
- Super::MS1::A.new.foo([]).should == ["ModA#foo","ModA#bar"]
- Super::MS1::A.new.bar([]).should == ["ModA#bar"]
- Super::MS1::B.new.foo([]).should == ["B#foo","ModA#foo","ModB#bar","ModA#bar"]
- Super::MS1::B.new.bar([]).should == ["ModB#bar","ModA#bar"]
+ SuperSpecs::MS1::A.new.foo([]).should == ["ModA#foo","ModA#bar"]
+ SuperSpecs::MS1::A.new.bar([]).should == ["ModA#bar"]
+ SuperSpecs::MS1::B.new.foo([]).should == ["B#foo","ModA#foo","ModB#bar","ModA#bar"]
+ SuperSpecs::MS1::B.new.bar([]).should == ["ModB#bar","ModA#bar"]
end
it "searches the full inheritance chain including modules" do
- Super::MS2::B.new.foo([]).should == ["ModB#foo","A#baz"]
- Super::MS2::B.new.baz([]).should == ["A#baz"]
- Super::MS2::C.new.baz([]).should == ["C#baz","A#baz"]
- Super::MS2::C.new.foo([]).should == ["ModB#foo","C#baz","A#baz"]
+ SuperSpecs::MS2::B.new.foo([]).should == ["ModB#foo","A#baz"]
+ SuperSpecs::MS2::B.new.baz([]).should == ["A#baz"]
+ SuperSpecs::MS2::C.new.baz([]).should == ["C#baz","A#baz"]
+ SuperSpecs::MS2::C.new.foo([]).should == ["ModB#foo","C#baz","A#baz"]
end
it "can resolve to different methods in an included module method" do
- Super::MultiSuperTargets::A.new.foo.should == :BaseA
- Super::MultiSuperTargets::B.new.foo.should == :BaseB
+ SuperSpecs::MultiSuperTargets::A.new.foo.should == :BaseA
+ SuperSpecs::MultiSuperTargets::B.new.foo.should == :BaseB
end
it "searches class methods including modules" do
- Super::MS3::A.new.foo([]).should == ["A#foo"]
- Super::MS3::A.foo([]).should == ["ModA#foo"]
- Super::MS3::A.bar([]).should == ["ModA#bar","ModA#foo"]
- Super::MS3::B.new.foo([]).should == ["A#foo"]
- Super::MS3::B.foo([]).should == ["B.foo","ModA#foo"]
- Super::MS3::B.bar([]).should == ["B.bar","ModA#bar","B.foo","ModA#foo"]
+ SuperSpecs::MS3::A.new.foo([]).should == ["A#foo"]
+ SuperSpecs::MS3::A.foo([]).should == ["ModA#foo"]
+ SuperSpecs::MS3::A.bar([]).should == ["ModA#bar","ModA#foo"]
+ SuperSpecs::MS3::B.new.foo([]).should == ["A#foo"]
+ SuperSpecs::MS3::B.foo([]).should == ["B.foo","ModA#foo"]
+ SuperSpecs::MS3::B.bar([]).should == ["B.bar","ModA#bar","B.foo","ModA#foo"]
end
it "searches BasicObject from a module for methods defined there" do
- Super::IncludesFromBasic.new.__send__(:foobar).should == 43
+ SuperSpecs::IncludesFromBasic.new.__send__(:foobar).should == 43
end
it "searches BasicObject through another module for methods defined there" do
- Super::IncludesIntermediate.new.__send__(:foobar).should == 42
+ SuperSpecs::IncludesIntermediate.new.__send__(:foobar).should == 42
end
it "calls the correct method when the method visibility is modified" do
- Super::MS4::A.new.example.should == 5
+ SuperSpecs::MS4::A.new.example.should == 5
end
it "calls the correct method when the superclass argument list is different from the subclass" do
- Super::S4::A.new.foo([]).should == ["A#foo"]
- Super::S4::B.new.foo([],"test").should == ["B#foo(a,test)", "A#foo"]
+ SuperSpecs::S4::A.new.foo([]).should == ["A#foo"]
+ 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
- lambda {sub_normal.new.foo}.should raise_error(NoMethodError, /super/)
- lambda {sub_zsuper.new.foo}.should raise_error(NoMethodError, /super/)
+ -> {sub_normal.new.foo}.should raise_error(NoMethodError, /super/)
+ -> {sub_zsuper.new.foo}.should raise_error(NoMethodError, /super/)
end
it "uses given block even if arguments are passed explicitly" do
@@ -102,16 +102,66 @@ describe "The super keyword" do
c2.new.m(:dump) { :value }.should == :value
end
+ it "can pass an explicit block" do
+ c1 = Class.new do
+ def m(v)
+ yield(v)
+ end
+ end
+ c2 = Class.new(c1) do
+ def m(v)
+ block = -> w { yield(w + 'b') }
+ super(v, &block)
+ end
+ end
+
+ c2.new.m('a') { |x| x + 'c' }.should == 'abc'
+ end
+
+ it "can pass no block using &nil" do
+ c1 = Class.new do
+ def m(v)
+ block_given?
+ end
+ end
+ c2 = Class.new(c1) do
+ def m(v)
+ super(v, &nil)
+ end
+ end
+
+ c2.new.m('a') { raise }.should be_false
+ end
+
+ it "uses block argument given to method when used in a block" do
+ c1 = Class.new do
+ def m
+ yield
+ end
+ end
+ c2 = Class.new(c1) do
+ def m(v)
+ ary = []
+ 1.times do
+ ary << super()
+ end
+ ary
+ end
+ end
+
+ c2.new.m(:dump) { :value }.should == [ :value ]
+ end
+
it "calls the superclass method when in a block" do
- Super::S6.new.here.should == :good
+ SuperSpecs::S6.new.here.should == :good
end
it "calls the superclass method when initial method is defined_method'd" do
- Super::S7.new.here.should == :good
+ SuperSpecs::S7.new.here.should == :good
end
it "can call through a define_method multiple times (caching check)" do
- obj = Super::S7.new
+ obj = SuperSpecs::S7.new
2.times do
obj.here.should == :good
@@ -150,25 +200,44 @@ describe "The super keyword" do
end
end
- lambda { klass.new.a(:a_called) }.should raise_error(RuntimeError)
+ -> { klass.new.a(:a_called) }.should raise_error(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
it "calls method_missing when a superclass method is not found" do
- Super::MM_B.new.is_a?(Hash).should == false
+ SuperSpecs::MM_B.new.is_a?(Hash).should == false
end
# Rubinius ticket github#180
it "respects the original module a method is aliased from" do
- Super::Alias3.new.name3.should == [:alias2, :alias1]
+ SuperSpecs::Alias3.new.name3.should == [:alias2, :alias1]
end
it "sees the included version of a module a method is alias from" do
- Super::AliasWithSuper::Trigger.foo.should == [:b, :a]
+ SuperSpecs::AliasWithSuper::Trigger.foo.should == [:b, :a]
end
it "find super from a singleton class" do
- obj = Super::SingletonCase::Foo.new
+ obj = SuperSpecs::SingletonCase::Foo.new
def obj.foobar(array)
array << :singleton
super
@@ -177,87 +246,123 @@ describe "The super keyword" do
end
it "finds super on other objects if a singleton class aliased the method" do
- orig_obj = Super::SingletonAliasCase::Foo.new
+ orig_obj = SuperSpecs::SingletonAliasCase::Foo.new
orig_obj.alias_on_singleton
orig_obj.new_foobar([]).should == [:foo, :base]
- Super::SingletonAliasCase::Foo.new.foobar([]).should == [:foo, :base]
+ SuperSpecs::SingletonAliasCase::Foo.new.foobar([]).should == [:foo, :base]
end
it "passes along modified rest args when they weren't originally empty" do
- Super::RestArgsWithSuper::B.new.a("bar").should == ["bar", "foo"]
+ SuperSpecs::RestArgsWithSuper::B.new.a("bar").should == ["bar", "foo"]
end
it "passes along modified rest args when they were originally empty" do
- Super::RestArgsWithSuper::B.new.a.should == ["foo"]
+ SuperSpecs::RestArgsWithSuper::B.new.a.should == ["foo"]
+ end
+
+ # https://bugs.ruby-lang.org/issues/14279
+ it "passes along reassigned rest args" do
+ SuperSpecs::ZSuperWithRestReassigned::B.new.a("bar").should == ["foo"]
+ end
+
+ # https://bugs.ruby-lang.org/issues/14279
+ it "wraps into array and passes along reassigned rest args with non-array scalar value" do
+ SuperSpecs::ZSuperWithRestReassignedWithScalar::B.new.a("bar").should == ["foo"]
end
it "invokes methods from a chain of anonymous modules" do
- Super::AnonymousModuleIncludedTwice.new.a([]).should == ["anon", "anon", "non-anon"]
+ SuperSpecs::AnonymousModuleIncludedTwice.new.a([]).should == ["anon", "anon", "non-anon"]
end
it "without explicit arguments can accept a block but still pass the original arguments" do
- Super::ZSuperWithBlock::B.new.a.should == 14
+ SuperSpecs::ZSuperWithBlock::B.new.a.should == 14
end
it "passes along block via reference to method expecting a reference" do
- Super::ZSuperWithBlock::B.new.b.should == [14, 15]
+ SuperSpecs::ZSuperWithBlock::B.new.b.should == [14, 15]
end
it "passes along a block via reference to a method that yields" do
- Super::ZSuperWithBlock::B.new.c.should == 16
+ SuperSpecs::ZSuperWithBlock::B.new.c.should == 16
end
it "without explicit arguments passes optional arguments that have a default value" do
- Super::ZSuperWithOptional::B.new.m(1, 2).should == 14
+ SuperSpecs::ZSuperWithOptional::B.new.m(1, 2).should == 14
end
it "without explicit arguments passes optional arguments that have a non-default value" do
- Super::ZSuperWithOptional::B.new.m(1, 2, 3).should == 3
+ SuperSpecs::ZSuperWithOptional::B.new.m(1, 2, 3).should == 3
end
it "without explicit arguments passes optional arguments that have a default value but were modified" do
- Super::ZSuperWithOptional::C.new.m(1, 2).should == 100
+ SuperSpecs::ZSuperWithOptional::C.new.m(1, 2).should == 100
end
it "without explicit arguments passes optional arguments that have a non-default value but were modified" do
- Super::ZSuperWithOptional::C.new.m(1, 2, 3).should == 100
+ SuperSpecs::ZSuperWithOptional::C.new.m(1, 2, 3).should == 100
end
it "without explicit arguments passes rest arguments" do
- Super::ZSuperWithRest::B.new.m(1, 2, 3).should == [1, 2, 3]
+ SuperSpecs::ZSuperWithRest::B.new.m(1, 2, 3).should == [1, 2, 3]
end
it "without explicit arguments passes rest arguments including any modifications" do
- Super::ZSuperWithRest::B.new.m_modified(1, 2, 3).should == [1, 14, 3]
+ SuperSpecs::ZSuperWithRest::B.new.m_modified(1, 2, 3).should == [1, 14, 3]
end
it "without explicit arguments passes arguments and rest arguments" do
- Super::ZSuperWithRestAndOthers::B.new.m(1, 2, 3, 4, 5).should == [3, 4, 5]
+ SuperSpecs::ZSuperWithRestAndOthers::B.new.m(1, 2, 3, 4, 5).should == [3, 4, 5]
+ SuperSpecs::ZSuperWithRestAndOthers::B.new.m(1, 2).should == []
+ end
+
+ it "without explicit arguments passes arguments, rest arguments, and post arguments" do
+ SuperSpecs::ZSuperWithRestAndPost::B.new.m(1, 2, 3, 4, 5).should == [1, 2, 3]
+ SuperSpecs::ZSuperWithRestOthersAndPost::B.new.m(1, 2, 3, 4, 5).should == [2, 3, 4]
+ SuperSpecs::ZSuperWithRestAndPost::B.new.m(1, 2).should == []
+ SuperSpecs::ZSuperWithRestOthersAndPost::B.new.m(1, 2).should == []
+ end
+
+ it "without explicit arguments passes arguments, rest arguments including modifications, and post arguments" do
+ SuperSpecs::ZSuperWithRestAndPost::B.new.m_modified(1, 2, 3, 4, 5).should == [1, 14, 3]
+ SuperSpecs::ZSuperWithRestOthersAndPost::B.new.m_modified(1, 2, 3, 4, 5).should == [2, 14, 4]
+ SuperSpecs::ZSuperWithRestAndPost::B.new.m_modified(1, 2).should == [nil, 14]
+ SuperSpecs::ZSuperWithRestOthersAndPost::B.new.m_modified(1, 2).should == [nil, 14]
end
it "without explicit arguments passes arguments and rest arguments including any modifications" do
- Super::ZSuperWithRestAndOthers::B.new.m_modified(1, 2, 3, 4, 5).should == [3, 14, 5]
+ SuperSpecs::ZSuperWithRestAndOthers::B.new.m_modified(1, 2, 3, 4, 5).should == [3, 14, 5]
end
it "without explicit arguments that are '_'" do
- Super::ZSuperWithUnderscores::B.new.m(1, 2).should == [1, 2]
+ 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
- Super::ZSuperWithUnderscores::B.new.m_modified(1, 2).should == [14, 2]
+ 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 = Super::Keywords::RequiredArguments.new
- @opts = Super::Keywords::OptionalArguments.new
- @etc = Super::Keywords::PlaceholderArguments.new
+ @req = SuperSpecs::Keywords::RequiredArguments.new
+ @opts = SuperSpecs::Keywords::OptionalArguments.new
+ @etc = SuperSpecs::Keywords::PlaceholderArguments.new
- @req_and_opts = Super::Keywords::RequiredAndOptionalArguments.new
- @req_and_etc = Super::Keywords::RequiredAndPlaceholderArguments.new
- @opts_and_etc = Super::Keywords::OptionalAndPlaceholderArguments.new
+ @req_and_opts = SuperSpecs::Keywords::RequiredAndOptionalArguments.new
+ @req_and_etc = SuperSpecs::Keywords::RequiredAndPlaceholderArguments.new
+ @opts_and_etc = SuperSpecs::Keywords::OptionalAndPlaceholderArguments.new
- @req_and_opts_and_etc = Super::Keywords::RequiredAndOptionalAndPlaceholderArguments.new
+ @req_and_opts_and_etc = SuperSpecs::Keywords::RequiredAndOptionalAndPlaceholderArguments.new
end
it 'does not pass any arguments to the parent when none are given' do
@@ -293,15 +398,15 @@ describe "The super keyword" do
describe 'when using regular and keyword arguments' do
before :each do
- @req = Super::RegularAndKeywords::RequiredArguments.new
- @opts = Super::RegularAndKeywords::OptionalArguments.new
- @etc = Super::RegularAndKeywords::PlaceholderArguments.new
+ @req = SuperSpecs::RegularAndKeywords::RequiredArguments.new
+ @opts = SuperSpecs::RegularAndKeywords::OptionalArguments.new
+ @etc = SuperSpecs::RegularAndKeywords::PlaceholderArguments.new
- @req_and_opts = Super::RegularAndKeywords::RequiredAndOptionalArguments.new
- @req_and_etc = Super::RegularAndKeywords::RequiredAndPlaceholderArguments.new
- @opts_and_etc = Super::RegularAndKeywords::OptionalAndPlaceholderArguments.new
+ @req_and_opts = SuperSpecs::RegularAndKeywords::RequiredAndOptionalArguments.new
+ @req_and_etc = SuperSpecs::RegularAndKeywords::RequiredAndPlaceholderArguments.new
+ @opts_and_etc = SuperSpecs::RegularAndKeywords::OptionalAndPlaceholderArguments.new
- @req_and_opts_and_etc = Super::RegularAndKeywords::RequiredAndOptionalAndPlaceholderArguments.new
+ @req_and_opts_and_etc = SuperSpecs::RegularAndKeywords::RequiredAndOptionalAndPlaceholderArguments.new
end
it 'passes only required regular arguments to the parent when no optional keyword arguments are given' do
@@ -337,7 +442,7 @@ describe "The super keyword" do
describe 'when using splat and keyword arguments' do
before :each do
- @all = Super::SplatAndKeywords::AllArguments.new
+ @all = SuperSpecs::SplatAndKeywords::AllArguments.new
end
it 'does not pass any arguments to the parent when none are given' do
diff --git a/spec/ruby/language/symbol_spec.rb b/spec/ruby/language/symbol_spec.rb
index 90540f7d1d..0801d3223e 100644
--- a/spec/ruby/language/symbol_spec.rb
+++ b/spec/ruby/language/symbol_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "A Symbol literal" do
it "is a ':' followed by any number of valid characters" do
@@ -38,7 +38,7 @@ describe "A Symbol literal" do
it 'inherits the encoding of the magic comment and can have a binary encoding' do
ruby_exe(fixture(__FILE__, "binary_symbol.rb"))
- .should == "[105, 108, 95, 195, 169, 116, 97, 105, 116]\nASCII-8BIT\n"
+ .should == "[105, 108, 95, 195, 169, 116, 97, 105, 116]\n#{Encoding::BINARY.name}\n"
end
it "may contain '::' in the string" do
@@ -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_error(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 92f699350c..d723843688 100644
--- a/spec/ruby/language/throw_spec.rb
+++ b/spec/ruby/language/throw_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The throw keyword" do
it "abandons processing" do
@@ -45,7 +45,7 @@ describe "The throw keyword" do
end
it "does not convert strings to a symbol" do
- lambda { catch(:exit) { throw "exit" } }.should raise_error(ArgumentError)
+ -> { catch(:exit) { throw "exit" } }.should raise_error(ArgumentError)
end
it "unwinds stack from within a method" do
@@ -59,13 +59,13 @@ describe "The throw keyword" do
end
it "unwinds stack from within a lambda" do
- c = lambda { throw :foo, :msg }
+ c = -> { throw :foo, :msg }
catch(:foo) { c.call }.should == :msg
end
it "raises an ArgumentError if outside of scope of a matching catch" do
- lambda { throw :test, 5 }.should raise_error(ArgumentError)
- lambda { catch(:different) { throw :test, 5 } }.should raise_error(ArgumentError)
+ -> { throw :test, 5 }.should raise_error(ArgumentError)
+ -> { catch(:different) { throw :test, 5 } }.should raise_error(ArgumentError)
end
it "raises an UncaughtThrowError if used to exit a thread" do
diff --git a/spec/ruby/language/undef_spec.rb b/spec/ruby/language/undef_spec.rb
index 9e788f2a09..268c0b84c3 100644
--- a/spec/ruby/language/undef_spec.rb
+++ b/spec/ruby/language/undef_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The undef keyword" do
describe "undefines a method" do
@@ -14,35 +14,42 @@ describe "The undef keyword" do
@undef_class.class_eval do
undef meth
end
- lambda { @obj.meth(5) }.should raise_error(NoMethodError)
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
end
it "with a simple symbol" do
@undef_class.class_eval do
undef :meth
end
- lambda { @obj.meth(5) }.should raise_error(NoMethodError)
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
end
it "with a single quoted symbol" do
@undef_class.class_eval do
undef :'meth'
end
- lambda { @obj.meth(5) }.should raise_error(NoMethodError)
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
end
it "with a double quoted symbol" do
@undef_class.class_eval do
undef :"meth"
end
- lambda { @obj.meth(5) }.should raise_error(NoMethodError)
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
end
- it "with a interpolated symbol" do
+ it "with an interpolated symbol" do
@undef_class.class_eval do
undef :"#{'meth'}"
end
- lambda { @obj.meth(5) }.should raise_error(NoMethodError)
+ -> { @obj.meth(5) }.should raise_error(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_error(NoMethodError)
end
end
@@ -61,8 +68,8 @@ describe "The undef keyword" do
it "raises a NameError when passed a missing name" do
Class.new do
- lambda {
- undef not_exist
+ -> {
+ undef not_exist
}.should raise_error(NameError) { |e|
# a NameError and not a NoMethodError
e.class.should == NameError
diff --git a/spec/ruby/language/unless_spec.rb b/spec/ruby/language/unless_spec.rb
index 681f0adfdd..98acdc083b 100644
--- a/spec/ruby/language/unless_spec.rb
+++ b/spec/ruby/language/unless_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The unless expression" do
it "evaluates the unless body when the expression is false" do
diff --git a/spec/ruby/language/until_spec.rb b/spec/ruby/language/until_spec.rb
index 08898644ce..78c289ff56 100644
--- a/spec/ruby/language/until_spec.rb
+++ b/spec/ruby/language/until_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
# until bool-expr [do]
# body
@@ -220,7 +220,7 @@ describe "The until modifier with begin .. end block" do
a.should == [0, 1, 2, 4]
end
- it "restart the current iteration without reevaluting condition with redo" do
+ it "restart the current iteration without reevaluating condition with redo" do
a = []
i = 0
j = 0
diff --git a/spec/ruby/language/variables_spec.rb b/spec/ruby/language/variables_spec.rb
index 81ba54840a..e134271939 100644
--- a/spec/ruby/language/variables_spec.rb
+++ b/spec/ruby/language/variables_spec.rb
@@ -1,5 +1,50 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/variables', __FILE__)
+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
@@ -46,7 +91,7 @@ describe "Multiple assignment" do
x = mock("multi-assign single RHS")
x.should_receive(:to_ary).and_return(1)
- lambda { a, b, c = x }.should raise_error(TypeError)
+ -> { a, b, c = x }.should raise_error(TypeError)
end
it "does not call #to_a to convert an Object RHS when assigning a simple MLHS" do
@@ -127,7 +172,7 @@ describe "Multiple assignment" do
x = mock("multi-assign splat")
x.should_receive(:to_ary).and_return(1)
- lambda { *a = x }.should raise_error(TypeError)
+ -> { *a = x }.should raise_error(TypeError)
end
it "does not call #to_ary on an Array subclass" do
@@ -160,7 +205,7 @@ describe "Multiple assignment" do
x = mock("multi-assign splat")
x.should_receive(:to_ary).and_return(1)
- lambda { a, *b, c = x }.should raise_error(TypeError)
+ -> { a, *b, c = x }.should raise_error(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)
- lambda { a, b = x.m }.should raise_error(TypeError)
+ -> { a, b = x.m }.should raise_error(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
@@ -354,6 +418,16 @@ describe "Multiple assignment" do
a.should be_an_instance_of(Array)
end
+ it "unfreezes the array returned from calling 'to_a' on the splatted value" do
+ obj = Object.new
+ def obj.to_a
+ [1,2].freeze
+ end
+ res = *obj
+ res.should == [1,2]
+ res.should_not.frozen?
+ end
+
it "consumes values for an anonymous splat" do
a = 1
(* = *a).should == [1]
@@ -407,7 +481,7 @@ describe "Multiple assignment" do
x = mock("multi-assign RHS splat")
x.should_receive(:to_a).and_return(1)
- lambda { *a = *x }.should raise_error(TypeError)
+ -> { *a = *x }.should raise_error(TypeError)
end
it "does not call #to_ary to convert an Object RHS with a single splat LHS" do
@@ -453,7 +527,7 @@ describe "Multiple assignment" do
x = mock("multi-assign splat")
x.should_receive(:to_a).and_return(1)
- lambda { a = *x }.should raise_error(TypeError)
+ -> { a = *x }.should raise_error(TypeError)
end
it "calls #to_a to convert an Object splat RHS when assigned to a simple MLHS" do
@@ -468,7 +542,7 @@ describe "Multiple assignment" do
x = mock("multi-assign splat")
x.should_receive(:to_a).and_return(1)
- lambda { a, b, c = *x }.should raise_error(TypeError)
+ -> { a, b, c = *x }.should raise_error(TypeError)
end
it "does not call #to_ary to convert an Object splat RHS when assigned to a simple MLHS" do
@@ -491,7 +565,7 @@ describe "Multiple assignment" do
x = mock("multi-assign splat")
x.should_receive(:to_a).and_return(1)
- lambda { a, *b, c = *x }.should raise_error(TypeError)
+ -> { a, *b, c = *x }.should raise_error(TypeError)
end
it "does not call #to_ary to convert an Object RHS with a MLHS" do
@@ -524,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
@@ -569,7 +645,7 @@ describe "Multiple assignment" do
x = mock("multi-assign splat MRHS")
x.should_receive(:to_a).and_return(1)
- lambda { a, *b = 1, *x }.should raise_error(TypeError)
+ -> { a, *b = 1, *x }.should raise_error(TypeError)
end
it "does not call #to_ary to convert a splatted Object as part of a MRHS with a splat MRHS" do
@@ -592,7 +668,7 @@ describe "Multiple assignment" do
x = mock("multi-assign splat MRHS")
x.should_receive(:to_a).and_return(1)
- lambda { a, *b = *x, 1 }.should raise_error(TypeError)
+ -> { a, *b = *x, 1 }.should raise_error(TypeError)
end
it "does not call #to_ary to convert a splatted Object with a splat MRHS" do
@@ -641,7 +717,7 @@ describe "Multiple assignment" do
x = mock("multi-assign mixed RHS")
x.should_receive(:to_ary).and_return(x)
- lambda { a, (b, c), d = 1, x, 3, 4 }.should raise_error(TypeError)
+ -> { a, (b, c), d = 1, x, 3, 4 }.should raise_error(TypeError)
end
it "calls #to_a to convert a splatted Object value in a MRHS" do
@@ -665,7 +741,7 @@ describe "Multiple assignment" do
x = mock("multi-assign mixed splatted RHS")
x.should_receive(:to_ary).and_return(x)
- lambda { a, *b, (c, d) = 1, 2, 3, *x }.should raise_error(TypeError)
+ -> { a, *b, (c, d) = 1, 2, 3, *x }.should raise_error(TypeError)
end
it "does not call #to_ary to convert an Object when the position receiving the value is a simple variable" do
@@ -705,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
@@ -718,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
@@ -758,3 +851,80 @@ describe "A local variable assigned only within a conditional block" do
end
end
end
+
+describe 'Local variable shadowing' do
+ 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)
+
+ result.should == [3, 3, 3]
+ end
+end
+
+describe 'Allowed characters' do
+ it 'allows non-ASCII lowercased characters at the beginning' do
+ result = nil
+
+ eval <<-CODE
+ def test
+ μ = 1
+ end
+
+ result = test
+ CODE
+
+ 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_error(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 00e948e41f..e172453ca6 100644
--- a/spec/ruby/language/while_spec.rb
+++ b/spec/ruby/language/while_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
# while bool-expr [do]
# body
@@ -330,7 +330,7 @@ describe "The while modifier with begin .. end block" do
a.should == [0, 1, 2, 4]
end
- it "restarts the current iteration without reevaluting condition with redo" do
+ it "restarts the current iteration without reevaluating condition with redo" do
a = []
i = 0
j = 0
diff --git a/spec/ruby/language/yield_spec.rb b/spec/ruby/language/yield_spec.rb
index 663110cbe6..e125cf8e73 100644
--- a/spec/ruby/language/yield_spec.rb
+++ b/spec/ruby/language/yield_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/yield', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/yield'
# Note that these specs use blocks defined as { |*a| ... } to capture the
# arguments with which the block is invoked. This is slightly confusing
@@ -13,18 +13,22 @@ describe "The yield call" do
describe "taking no arguments" do
it "raises a LocalJumpError when the method is not passed a block" do
- lambda { @y.z }.should raise_error(LocalJumpError)
+ -> { @y.z }.should raise_error(LocalJumpError)
end
it "ignores assignment to the explicit block argument and calls the passed block" do
@y.ze { 42 }.should == 42
end
+
+ it "does not pass a named block to the block being yielded to" do
+ @y.z() { |&block| block == nil }.should == true
+ end
end
describe "taking a single argument" do
describe "when no block is given" do
it "raises a LocalJumpError" do
- lambda { @y.s(1) }.should raise_error(LocalJumpError)
+ -> { @y.s(1) }.should raise_error(LocalJumpError)
end
end
@@ -48,40 +52,38 @@ describe "The yield call" do
describe "yielding to a lambda" do
it "passes an empty Array when the argument is an empty Array" do
- @y.s([], &lambda { |*a| a }).should == [[]]
+ @y.s([], &-> *a { a }).should == [[]]
end
it "passes nil as a value" do
- @y.s(nil, &lambda { |*a| a }).should == [nil]
+ @y.s(nil, &-> *a { a }).should == [nil]
end
it "passes a single value" do
- @y.s(1, &lambda { |*a| a }).should == [1]
+ @y.s(1, &-> *a { a }).should == [1]
end
it "passes a single, multi-value Array" do
- @y.s([1, 2, 3], &lambda { |*a| a }).should == [[1, 2, 3]]
+ @y.s([1, 2, 3], &-> *a { a }).should == [[1, 2, 3]]
end
it "raises an ArgumentError if too few arguments are passed" do
- lambda {
- @y.s(1, &lambda { |a,b| [a,b] })
+ -> {
+ @y.s(1, &-> a, b { [a,b] })
}.should raise_error(ArgumentError)
end
- ruby_bug "#12705", "2.2"..."2.5" do
- it "should not destructure an Array into multiple arguments" do
- lambda {
- @y.s([1, 2], &lambda { |a,b| [a,b] })
- }.should raise_error(ArgumentError)
- end
+ it "should not destructure an Array into multiple arguments" do
+ -> {
+ @y.s([1, 2], &-> a, b { [a,b] })
+ }.should raise_error(ArgumentError)
end
end
end
describe "taking multiple arguments" do
it "raises a LocalJumpError when the method is not passed a block" do
- lambda { @y.m(1, 2, 3) }.should raise_error(LocalJumpError)
+ -> { @y.m(1, 2, 3) }.should raise_error(LocalJumpError)
end
it "passes the arguments to the block" do
@@ -93,21 +95,21 @@ describe "The yield call" do
end
it "raises an ArgumentError if too many arguments are passed to a lambda" do
- lambda {
- @y.m(1, 2, 3, &lambda { |a| })
+ -> {
+ @y.m(1, 2, 3, &-> a { })
}.should raise_error(ArgumentError)
end
it "raises an ArgumentError if too few arguments are passed to a lambda" do
- lambda {
- @y.m(1, 2, 3, &lambda { |a,b,c,d| })
+ -> {
+ @y.m(1, 2, 3, &-> a, b, c, d { })
}.should raise_error(ArgumentError)
end
end
describe "taking a single splatted argument" do
it "raises a LocalJumpError when the method is not passed a block" do
- lambda { @y.r(0) }.should raise_error(LocalJumpError)
+ -> { @y.r(0) }.should raise_error(LocalJumpError)
end
it "passes a single value" do
@@ -139,7 +141,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
- lambda { @y.rs(1, 2, [3, 4]) }.should raise_error(LocalJumpError)
+ -> { @y.rs(1, 2, [3, 4]) }.should raise_error(LocalJumpError)
end
it "passes the arguments to the block" do
@@ -164,7 +166,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
- lambda { @y.rs(1, 2, [3, 4]) }.should raise_error(LocalJumpError)
+ -> { @y.rs(1, 2, [3, 4]) }.should raise_error(LocalJumpError)
end
it "passes the arguments to the block" do
@@ -172,8 +174,47 @@ describe "The yield call" do
end
end
+ describe "taking a splat and a keyword argument" do
+ it "passes it as an array of the values and a hash" do
+ @y.k([1, 2]) { |*a| a }.should == [1, 2, {:b=>true}]
+ end
+ end
+
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_error(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_error(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_error(SyntaxError, /Invalid yield/)
+ end
end