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.rb41
-rw-r--r--spec/ruby/language/END_spec.rb33
-rw-r--r--spec/ruby/language/README30
-rw-r--r--spec/ruby/language/alias_spec.rb294
-rw-r--r--spec/ruby/language/and_spec.rb80
-rw-r--r--spec/ruby/language/array_spec.rb162
-rw-r--r--spec/ruby/language/assignments_spec.rb590
-rw-r--r--spec/ruby/language/block_spec.rb1138
-rw-r--r--spec/ruby/language/break_spec.rb402
-rw-r--r--spec/ruby/language/case_spec.rb509
-rw-r--r--spec/ruby/language/class_spec.rb395
-rw-r--r--spec/ruby/language/class_variable_spec.rb114
-rw-r--r--spec/ruby/language/comment_spec.rb13
-rw-r--r--spec/ruby/language/constants_spec.rb809
-rw-r--r--spec/ruby/language/def_spec.rb815
-rw-r--r--spec/ruby/language/defined_spec.rb1312
-rw-r--r--spec/ruby/language/delegation_spec.rb158
-rw-r--r--spec/ruby/language/encoding_spec.rb36
-rw-r--r--spec/ruby/language/ensure_spec.rb346
-rw-r--r--spec/ruby/language/execution_spec.rb93
-rw-r--r--spec/ruby/language/file_spec.rb29
-rw-r--r--spec/ruby/language/fixtures/argv_encoding.rb1
-rw-r--r--spec/ruby/language/fixtures/array.rb32
-rw-r--r--spec/ruby/language/fixtures/begin_file.rb3
-rw-r--r--spec/ruby/language/fixtures/binary_symbol.rb4
-rw-r--r--spec/ruby/language/fixtures/block.rb61
-rw-r--r--spec/ruby/language/fixtures/break.rb291
-rw-r--r--spec/ruby/language/fixtures/break_lambda_toplevel.rb9
-rw-r--r--spec/ruby/language/fixtures/break_lambda_toplevel_block.rb23
-rw-r--r--spec/ruby/language/fixtures/break_lambda_toplevel_method.rb17
-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/classes.rb31
-rw-r--r--spec/ruby/language/fixtures/coding_us_ascii.rb11
-rw-r--r--spec/ruby/language/fixtures/coding_utf_8.rb11
-rw-r--r--spec/ruby/language/fixtures/constant_visibility.rb114
-rw-r--r--spec/ruby/language/fixtures/constants_sclass.rb54
-rw-r--r--spec/ruby/language/fixtures/def.rb14
-rw-r--r--spec/ruby/language/fixtures/defined.rb312
-rw-r--r--spec/ruby/language/fixtures/delegation.rb11
-rw-r--r--spec/ruby/language/fixtures/dollar_zero.rb6
-rw-r--r--spec/ruby/language/fixtures/emacs_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/ensure.rb121
-rw-r--r--spec/ruby/language/fixtures/file.rb1
-rw-r--r--spec/ruby/language/fixtures/for_scope.rb15
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_across_files.rb5
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb5
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_across_files_no_comment.rb5
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_one_literal.rb4
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_required.rb3
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb4
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_required_no_comment.rb1
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb3
-rw-r--r--spec/ruby/language/fixtures/hash_strings_binary.rb7
-rw-r--r--spec/ruby/language/fixtures/hash_strings_usascii.rb7
-rw-r--r--spec/ruby/language/fixtures/hash_strings_utf8.rb7
-rw-r--r--spec/ruby/language/fixtures/magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/match_operators.rb9
-rw-r--r--spec/ruby/language/fixtures/metaclass.rb33
-rw-r--r--spec/ruby/language/fixtures/module.rb15
-rw-r--r--spec/ruby/language/fixtures/next.rb134
-rw-r--r--spec/ruby/language/fixtures/no_magic_comment.rb1
-rw-r--r--spec/ruby/language/fixtures/precedence.rb16
-rw-r--r--spec/ruby/language/fixtures/print_magic_comment_result_at_exit.rb3
-rw-r--r--spec/ruby/language/fixtures/private.rb59
-rw-r--r--spec/ruby/language/fixtures/rescue.rb67
-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.rb135
-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.rb151
-rwxr-xr-xspec/ruby/language/fixtures/shebang_magic_comment.rb3
-rw-r--r--spec/ruby/language/fixtures/squiggly_heredoc.rb71
-rw-r--r--spec/ruby/language/fixtures/super.rb804
-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.rb157
-rw-r--r--spec/ruby/language/fixtures/vim_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/yield.rb41
-rw-r--r--spec/ruby/language/for_spec.rb285
-rw-r--r--spec/ruby/language/hash_spec.rb324
-rw-r--r--spec/ruby/language/heredoc_spec.rb119
-rw-r--r--spec/ruby/language/if_spec.rb424
-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.rb587
-rw-r--r--spec/ruby/language/line_spec.rb45
-rw-r--r--spec/ruby/language/loop_spec.rb67
-rw-r--r--spec/ruby/language/magic_comment_spec.rb93
-rw-r--r--spec/ruby/language/match_spec.rb81
-rw-r--r--spec/ruby/language/metaclass_spec.rb143
-rw-r--r--spec/ruby/language/method_spec.rb1649
-rw-r--r--spec/ruby/language/module_spec.rb123
-rw-r--r--spec/ruby/language/next_spec.rb410
-rw-r--r--spec/ruby/language/not_spec.rb51
-rw-r--r--spec/ruby/language/numbered_parameters_spec.rb113
-rw-r--r--spec/ruby/language/numbers_spec.rb105
-rw-r--r--spec/ruby/language/optional_assignments_spec.rb742
-rw-r--r--spec/ruby/language/or_spec.rb90
-rw-r--r--spec/ruby/language/order_spec.rb75
-rw-r--r--spec/ruby/language/pattern_matching_spec.rb1310
-rw-r--r--spec/ruby/language/precedence_spec.rb445
-rw-r--r--spec/ruby/language/predefined/data_spec.rb48
-rw-r--r--spec/ruby/language/predefined/fixtures/data1.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/data2.rb3
-rw-r--r--spec/ruby/language/predefined/fixtures/data3.rb6
-rw-r--r--spec/ruby/language/predefined/fixtures/data4.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/data5.rb5
-rw-r--r--spec/ruby/language/predefined/fixtures/data_offset.rb12
-rw-r--r--spec/ruby/language/predefined/fixtures/data_only.rb2
-rw-r--r--spec/ruby/language/predefined/fixtures/empty_data.rb3
-rw-r--r--spec/ruby/language/predefined/fixtures/print_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.rb1574
-rw-r--r--spec/ruby/language/private_spec.rb67
-rw-r--r--spec/ruby/language/proc_spec.rb249
-rw-r--r--spec/ruby/language/range_spec.rb30
-rw-r--r--spec/ruby/language/redo_spec.rb66
-rw-r--r--spec/ruby/language/regexp/anchors_spec.rb179
-rw-r--r--spec/ruby/language/regexp/back-references_spec.rb149
-rw-r--r--spec/ruby/language/regexp/character_classes_spec.rb647
-rw-r--r--spec/ruby/language/regexp/empty_checks_spec.rb135
-rw-r--r--spec/ruby/language/regexp/encoding_spec.rb152
-rw-r--r--spec/ruby/language/regexp/escapes_spec.rb169
-rw-r--r--spec/ruby/language/regexp/grouping_spec.rb63
-rw-r--r--spec/ruby/language/regexp/interpolation_spec.rb58
-rw-r--r--spec/ruby/language/regexp/modifiers_spec.rb115
-rw-r--r--spec/ruby/language/regexp/repetition_spec.rb138
-rw-r--r--spec/ruby/language/regexp/subexpression_call_spec.rb50
-rw-r--r--spec/ruby/language/regexp_spec.rb167
-rw-r--r--spec/ruby/language/rescue_spec.rb616
-rw-r--r--spec/ruby/language/reserved_keywords.rb149
-rw-r--r--spec/ruby/language/retry_spec.rb55
-rw-r--r--spec/ruby/language/return_spec.rb490
-rw-r--r--spec/ruby/language/safe_navigator_spec.rb147
-rw-r--r--spec/ruby/language/safe_spec.rb11
-rw-r--r--spec/ruby/language/send_spec.rb570
-rw-r--r--spec/ruby/language/shared/__FILE__.rb23
-rw-r--r--spec/ruby/language/shared/__LINE__.rb15
-rw-r--r--spec/ruby/language/singleton_class_spec.rb317
-rw-r--r--spec/ruby/language/source_encoding_spec.rb61
-rw-r--r--spec/ruby/language/string_spec.rb301
-rw-r--r--spec/ruby/language/super_spec.rb464
-rw-r--r--spec/ruby/language/symbol_spec.rb108
-rw-r--r--spec/ruby/language/throw_spec.rb81
-rw-r--r--spec/ruby/language/undef_spec.rb79
-rw-r--r--spec/ruby/language/unless_spec.rb43
-rw-r--r--spec/ruby/language/until_spec.rb234
-rw-r--r--spec/ruby/language/variables_spec.rb930
-rw-r--r--spec/ruby/language/while_spec.rb344
-rw-r--r--spec/ruby/language/yield_spec.rb220
163 files changed, 27584 insertions, 0 deletions
diff --git a/spec/ruby/language/BEGIN_spec.rb b/spec/ruby/language/BEGIN_spec.rb
new file mode 100644
index 0000000000..5aef5a1d7c
--- /dev/null
+++ b/spec/ruby/language/BEGIN_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../spec_helper'
+
+describe "The BEGIN keyword" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "runs in a shared scope" do
+ eval("BEGIN { var_in_begin = 'foo' }; var_in_begin").should == "foo"
+ end
+
+ it "accesses variables outside the eval scope" do
+ outside_var = 'foo'
+ eval("BEGIN { var_in_begin = outside_var }; var_in_begin").should == "foo"
+ end
+
+ it "must appear in a top-level context" do
+ -> { 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
+ eval "ScratchPad << 'foo'; BEGIN { ScratchPad << 'bar' }"
+
+ ScratchPad.recorded.should == ['bar', 'foo']
+ end
+
+ it "runs multiple begins in FIFO order" do
+ eval "BEGIN { ScratchPad << 'foo' }; BEGIN { ScratchPad << 'bar' }"
+
+ ScratchPad.recorded.should == ['foo', 'bar']
+ end
+
+ it "returns the top-level script's filename for __FILE__" do
+ ruby_exe(fixture(__FILE__, "begin_file.rb")).chomp.should =~ /begin_file\.rb$/
+ end
+end
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
new file mode 100644
index 0000000000..ae08e17fb1
--- /dev/null
+++ b/spec/ruby/language/README
@@ -0,0 +1,30 @@
+There are numerous possible way of categorizing the entities and concepts that
+make up a programming language. Ruby has a fairly large number of reserved
+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 Integer.
+
+Behavioral specifications describe the behavior of concrete entities. Rather
+than using concepts of computation to organize these spec files, we use
+entities of the Ruby language. Consider looking at any syntactic element of a
+Ruby program. With (almost) no ambiguity, one can identify it as a literal,
+reserved word, variable, etc. There is a spec file that corresponds to each
+literal construct and most reserved words, with the exceptions noted below.
+There are also several files that are more difficult to classify: all
+predefined variables, constants, and objects (predefined_spec.rb), the
+precedence of all operators (precedence_spec.rb), the behavior of assignment
+to variables (variables_spec.rb), the behavior of subprocess execution
+(execution_spec.rb), the behavior of the raise method as it impacts the
+execution of a Ruby program (raise_spec.rb), and the block entities like
+'begin', 'do', ' { ... }' (block_spec.rb).
+
+Several reserved words and other entities are combined with the primary
+reserved word or entity to which they are related:
+
+false, true, nil, self predefined_spec.rb
+in for_spec.rb
+then, elsif if_spec.rb
+when case_spec.rb
+catch throw_spec.rb
diff --git a/spec/ruby/language/alias_spec.rb b/spec/ruby/language/alias_spec.rb
new file mode 100644
index 0000000000..61fddb0184
--- /dev/null
+++ b/spec/ruby/language/alias_spec.rb
@@ -0,0 +1,294 @@
+require_relative '../spec_helper'
+
+class AliasObject
+ attr :foo
+ attr_reader :bar
+ attr_accessor :baz
+
+ def prep; @foo = 3; @bar = 4; end
+ def value; 5; end
+ def false_value; 6; end
+ def self.klass_method; 7; end
+end
+
+describe "The alias keyword" do
+ before :each do
+ @obj = AliasObject.new
+ @meta = class << @obj;self;end
+ end
+
+ it "creates a new name for an existing method" do
+ @meta.class_eval do
+ alias __value value
+ end
+ @obj.__value.should == 5
+ end
+
+ it "works with a simple symbol on the left-hand side" do
+ @meta.class_eval do
+ alias :a value
+ end
+ @obj.a.should == 5
+ end
+
+ it "works with a single quoted symbol on the left-hand side" do
+ @meta.class_eval do
+ alias :'a' value
+ end
+ @obj.a.should == 5
+ end
+
+ 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 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
+ end
+ @obj.a.should == 5
+ end
+
+ it "works with a single quoted symbol on the right-hand side" do
+ @meta.class_eval do
+ alias a :'value'
+ end
+ @obj.a.should == 5
+ end
+
+ 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 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
+ alias __value value
+ end
+ (@obj.methods - original_methods).map {|m| m.to_s }.should == ["__value"]
+ end
+
+ it "adds the new method to the list of public methods" do
+ original_methods = @obj.public_methods
+ @meta.class_eval do
+ alias __value value
+ end
+ (@obj.public_methods - original_methods).map {|m| m.to_s }.should == ["__value"]
+ end
+
+ it "overwrites an existing method with the target name" do
+ @meta.class_eval do
+ alias false_value value
+ end
+ @obj.false_value.should == 5
+ end
+
+ it "is reversible" do
+ @meta.class_eval do
+ alias __value value
+ alias value false_value
+ end
+ @obj.value.should == 6
+
+ @meta.class_eval do
+ alias value __value
+ end
+ @obj.value.should == 5
+ end
+
+ it "operates on the object's metaclass when used in instance_eval" do
+ @obj.instance_eval do
+ alias __value value
+ end
+
+ @obj.__value.should == 5
+ -> { AliasObject.new.__value }.should raise_error(NoMethodError)
+ end
+
+ it "operates on the class/module metaclass when used in instance_eval" do
+ AliasObject.instance_eval do
+ alias __klass_method klass_method
+ end
+
+ AliasObject.__klass_method.should == 7
+ -> { Object.__klass_method }.should raise_error(NoMethodError)
+ end
+
+ it "operates on the class/module metaclass when used in instance_exec" do
+ AliasObject.instance_exec do
+ alias __klass_method2 klass_method
+ end
+
+ AliasObject.__klass_method2.should == 7
+ -> { Object.__klass_method2 }.should raise_error(NoMethodError)
+ end
+
+ it "operates on methods defined via attr, attr_reader, and attr_accessor" do
+ @obj.prep
+ @obj.instance_eval do
+ alias afoo foo
+ alias abar bar
+ alias abaz baz
+ end
+
+ @obj.afoo.should == 3
+ @obj.abar.should == 4
+ @obj.baz = 5
+ @obj.abaz.should == 5
+ end
+
+ it "operates on methods with splat arguments" do
+ class AliasObject2;end
+ AliasObject2.class_eval do
+ def test(*args)
+ 4
+ end
+ def test_with_check(*args)
+ test_without_check(*args)
+ end
+ alias test_without_check test
+ alias test test_with_check
+ end
+ AliasObject2.new.test(1,2,3,4,5).should == 4
+ end
+
+ it "operates on methods with splat arguments on eigenclasses" do
+ @meta.class_eval do
+ def test(*args)
+ 4
+ end
+ def test_with_check(*args)
+ test_without_check(*args)
+ end
+ alias test_without_check test
+ alias test test_with_check
+ end
+ @obj.test(1,2,3,4,5).should == 4
+ end
+
+ it "operates on methods with splat arguments defined in a superclass" do
+ alias_class = Class.new
+ alias_class.class_eval do
+ def test(*args)
+ 4
+ end
+ def test_with_check(*args)
+ test_without_check(*args)
+ end
+ end
+ sub = Class.new(alias_class) do
+ alias test_without_check test
+ alias test test_with_check
+ end
+ sub.new.test(1,2,3,4,5).should == 4
+ end
+
+ it "operates on methods with splat arguments defined in a superclass using text block for class eval" do
+ subclass = Class.new(AliasObject)
+ AliasObject.class_eval <<-code
+ def test(*args)
+ 4
+ end
+ def test_with_check(*args)
+ test_without_check(*args)
+ end
+ alias test_without_check test
+ alias test test_with_check
+ code
+ subclass.new.test("testing").should == 4
+ end
+
+ 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)
+
+ -> do
+ :blah.instance_eval do
+ alias :foo :to_s
+ end
+ end.should raise_error(TypeError)
+ end
+
+ it "on top level defines the alias on Object" do
+ # because it defines on the default definee / current module
+ ruby_exe("def foo; end; alias bla foo; print method(:bla).owner").should == "Object"
+ end
+
+ it "raises a NameError when passed a missing name" do
+ -> { @meta.class_eval { alias undef_method not_exist } }.should raise_error(NameError) { |e|
+ # 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
new file mode 100644
index 0000000000..55a2a3103a
--- /dev/null
+++ b/spec/ruby/language/and_spec.rb
@@ -0,0 +1,80 @@
+require_relative '../spec_helper'
+
+describe "The '&&' statement" do
+
+ it "short-circuits evaluation at the first condition to be false" do
+ x = nil
+ true && false && x = 1
+ x.should be_nil
+ end
+
+ it "evaluates to the first condition not to be true" do
+ value = nil
+ (value && nil).should == nil
+ (value && false).should == nil
+ value = false
+ (value && nil).should == false
+ (value && false).should == false
+
+ ("yes" && 1 && nil && true).should == nil
+ ("yes" && 1 && false && true).should == false
+ end
+
+ it "evaluates to the last condition if all are true" do
+ ("yes" && 1).should == 1
+ (1 && "yes").should == "yes"
+ end
+
+ it "evaluates the full set of chained conditions during assignment" do
+ x, y = nil
+ x = 1 && y = 2
+ # "1 && y = 2" is evaluated and then assigned to x
+ x.should == 2
+ end
+
+ it "treats empty expressions as nil" do
+ (() && true).should be_nil
+ (true && ()).should be_nil
+ (() && ()).should be_nil
+ end
+
+end
+
+describe "The 'and' statement" do
+ it "short-circuits evaluation at the first condition to be false" do
+ x = nil
+ true and false and x = 1
+ x.should be_nil
+ end
+
+ it "evaluates to the first condition not to be true" do
+ value = nil
+ (value and nil).should == nil
+ (value and false).should == nil
+ value = false
+ (value and nil).should == false
+ (value and false).should == false
+
+ ("yes" and 1 and nil and true).should == nil
+ ("yes" and 1 and false and true).should == false
+ end
+
+ it "evaluates to the last condition if all are true" do
+ ("yes" and 1).should == 1
+ (1 and "yes").should == "yes"
+ end
+
+ it "when used in assignment, evaluates and assigns expressions individually" do
+ x, y = nil
+ x = 1 and y = 2
+ # evaluates (x=1) and (y=2)
+ x.should == 1
+ end
+
+ it "treats empty expressions as nil" do
+ (() and true).should be_nil
+ (true and ()).should be_nil
+ (() and ()).should be_nil
+ end
+
+end
diff --git a/spec/ruby/language/array_spec.rb b/spec/ruby/language/array_spec.rb
new file mode 100644
index 0000000000..2583cffbf7
--- /dev/null
+++ b/spec/ruby/language/array_spec.rb
@@ -0,0 +1,162 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/array'
+
+describe "Array literals" do
+ it "[] should return a new array populated with the given elements" do
+ array = [1, 'a', nil]
+ array.should be_kind_of(Array)
+ array[0].should == 1
+ array[1].should == 'a'
+ array[2].should == nil
+ end
+
+ it "[] treats empty expressions as nil elements" do
+ array = [0, (), 2, (), 4]
+ array.should be_kind_of(Array)
+ array[0].should == 0
+ array[1].should == nil
+ array[2].should == 2
+ array[3].should == nil
+ array[4].should == 4
+ end
+
+ it "[] accepts a literal hash without curly braces as its only parameter" do
+ ["foo" => :bar, baz: 42].should == [{"foo" => :bar, baz: 42}]
+ end
+
+ it "[] accepts a literal hash without curly braces as its last parameter" do
+ ["foo", "bar" => :baz].should == ["foo", {"bar" => :baz}]
+ [1, 2, 3 => 6, 4 => 24].should == [1, 2, {3 => 6, 4 => 24}]
+ end
+
+ it "[] treats splatted nil as no element" do
+ [*nil].should == []
+ [1, *nil].should == [1]
+ [1, 2, *nil].should == [1, 2]
+ [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
+ it "%w() transforms unquoted barewords into an array" do
+ a = 3
+ %w(a #{3+a} 3).should == ["a", '#{3+a}', "3"]
+ end
+
+ it "%W() transforms unquoted barewords into an array, supporting interpolation" do
+ a = 3
+ %W(a #{3+a} 3).should == ["a", '6', "3"]
+ end
+
+ it "%W() always treats interpolated expressions as a single word" do
+ a = "hello world"
+ %W(a b c #{a} d e).should == ["a", "b", "c", "hello world", "d", "e"]
+ end
+
+ it "treats consecutive whitespace characters the same as one" do
+ %w(a b c d).should == ["a", "b", "c", "d"]
+ %W(hello
+ world).should == ["hello", "world"]
+ end
+
+ it "treats whitespace as literals characters when escaped by a backslash" do
+ %w(a b\ c d e).should == ["a", "b c", "d", "e"]
+ %w(a b\
+c d).should == ["a", "b\nc", "d"]
+ %W(a\ b\tc).should == ["a ", "b\tc"]
+ %W(white\ \ \ \ \ space).should == ["white ", " ", " ", " space"]
+ end
+end
+
+describe "The unpacking splat operator (*)" do
+ it "when applied to a literal nested array, unpacks its elements into the containing array" do
+ [1, 2, *[3, 4, 5]].should == [1, 2, 3, 4, 5]
+ end
+
+ it "when applied to a nested referenced array, unpacks its elements into the containing array" do
+ splatted_array = [3, 4, 5]
+ [1, 2, *splatted_array].should == [1, 2, 3, 4, 5]
+ end
+
+ it "returns a new array containing the same values when applied to an array inside an empty array" do
+ splatted_array = [3, 4, 5]
+ [*splatted_array].should == splatted_array
+ [*splatted_array].should_not equal(splatted_array)
+ end
+
+ it "unpacks the start and count arguments in an array slice assignment" do
+ alphabet_1 = ['a'..'z'].to_a
+ alphabet_2 = alphabet_1.dup
+ start_and_count_args = [1, 10]
+
+ alphabet_1[1, 10] = 'a'
+ alphabet_2[*start_and_count_args] = 'a'
+
+ alphabet_1.should == alphabet_2
+ end
+
+ it "unpacks arguments as if they were listed statically" do
+ static = [1,2,3,4]
+ receiver = static.dup
+ args = [0,1]
+ static[0,1] = []
+ static.should == [2,3,4]
+ receiver[*args] = []
+ receiver.should == static
+ end
+
+ it "unpacks a literal array into arguments in a method call" do
+ tester = ArraySpec::Splat.new
+ tester.unpack_3args(*[1, 2, 3]).should == [1, 2, 3]
+ tester.unpack_4args(1, 2, *[3, 4]).should == [1, 2, 3, 4]
+ tester.unpack_4args("a", %w(b c), *%w(d e)).should == ["a", ["b", "c"], "d", "e"]
+ end
+
+ it "unpacks a referenced array into arguments in a method call" do
+ args = [1, 2, 3]
+ tester = ArraySpec::Splat.new
+ tester.unpack_3args(*args).should == [1, 2, 3]
+ tester.unpack_4args(0, *args).should == [0, 1, 2, 3]
+ end
+
+ it "when applied to a non-Array value attempts to coerce it to Array if the object respond_to?(:to_a)" do
+ obj = mock("pseudo-array")
+ obj.should_receive(:to_a).and_return([2, 3, 4])
+ [1, *obj].should == [1, 2, 3, 4]
+ end
+
+ it "when applied to a non-Array value uses it unchanged if it does not respond_to?(:to_a)" do
+ obj = Object.new
+ obj.should_not respond_to(:to_a)
+ [1, *obj].should == [1, obj]
+ end
+
+ it "when applied to a BasicObject coerces it to Array if it respond_to?(:to_a)" do
+ obj = BasicObject.new
+ def obj.to_a; [2, 3, 4]; end
+ [1, *obj].should == [1, 2, 3, 4]
+ end
+
+ it "can be used before other non-splat elements" do
+ a = [1, 2]
+ [0, *a, 3].should == [0, 1, 2, 3]
+ end
+
+ it "can be used multiple times in the same containing array" do
+ a = [1, 2]
+ b = [1, 0]
+ [*a, 3, *a, *b].should == [1, 2, 3, 1, 2, 1, 0]
+ end
+end
+
+describe "The packing splat operator (*)" do
+
+end
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
new file mode 100644
index 0000000000..cc003b8946
--- /dev/null
+++ b/spec/ruby/language/block_spec.rb
@@ -0,0 +1,1138 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/block'
+
+describe "A block yielded a single" do
+ before :all do
+ def m(a) yield a end
+ end
+
+ context "Array" do
+ it "assigns the Array to a single argument" do
+ m([1, 2]) { |a| a }.should == [1, 2]
+ end
+
+ it "receives the identical Array object" do
+ ary = [1, 2]
+ m(ary) { |a| a }.should equal(ary)
+ end
+
+ it "assigns the Array to a single rest argument" do
+ m([1, 2, 3]) { |*a| a }.should == [[1, 2, 3]]
+ end
+
+ it "assigns the first element to a single argument with trailing comma" do
+ m([1, 2]) { |a, | a }.should == 1
+ end
+
+ it "assigns elements to required arguments" do
+ m([1, 2, 3]) { |a, b| [a, b] }.should == [1, 2]
+ end
+
+ it "assigns nil to unassigned required arguments" do
+ m([1, 2]) { |a, *b, c, d| [a, b, c, d] }.should == [1, [], 2, nil]
+ end
+
+ it "assigns elements to optional arguments" do
+ m([1, 2]) { |a=5, b=4, c=3| [a, b, c] }.should == [1, 2, 3]
+ end
+
+ 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 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 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 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 "does not autosplat single argument to required arguments when a keyword rest argument is present" do
+ m([1, 2]) { |a, **k| [a, k] }.should == [[1, 2], {}]
+ end
+
+ it "does not autosplat single argument to required arguments when keyword arguments are present" do
+ m([1, 2]) { |a, b: :b, c: :c| [a, b, c] }.should == [[1, 2], :b, :c]
+ end
+
+ it "raises error when required keyword arguments are present" do
+ -> {
+ m([1, 2]) { |a, b:, c:| [a, b, c] }
+ }.should raise_error(ArgumentError, "missing keywords: :b, :c")
+ end
+
+ it "assigns elements to mixed argument types" do
+ result = m([1, 2, 3, {x: 9}]) { |a, b=5, *c, d, e: 2, **k| [a, b, c, d, e, k] }
+ result.should == [1, 2, [3], {x: 9}, 2, {}]
+ end
+
+ it "does not treat final Hash as keyword arguments and does not autosplat" do
+ result = m(["a" => 1, a: 10]) { |a=nil, **b| [a, b] }
+ result.should == [[{"a" => 1, a: 10}], {}]
+ end
+
+ it "does not call #to_hash on final argument to get keyword arguments and does not autosplat" do
+ suppress_keyword_warning do
+ obj = mock("coerce block keyword arguments")
+ obj.should_not_receive(:to_hash)
+
+ result = m([obj]) { |a=nil, **b| [a, b] }
+ result.should == [[obj], {}]
+ end
+ end
+
+ it "does not call #to_hash on the argument when optional argument and keyword argument accepted and does not autosplat" do
+ obj = mock("coerce block keyword arguments")
+ obj.should_not_receive(:to_hash)
+
+ result = m([obj]) { |a=nil, **b| [a, b] }
+ result.should == [[obj], {}]
+ end
+
+ 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
+
+ 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 "does not call #to_hash on the last element if keyword arguments are present" do
+ obj = mock("destructure block keyword arguments")
+ obj.should_not_receive(:to_hash)
+
+ result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] }
+ result.should == [1, [2, 3], obj, {}]
+ end
+
+ it "does not call #to_hash on the last element when there are more arguments than parameters" do
+ x = mock("destructure matching block keyword argument")
+ x.should_not_receive(:to_hash)
+
+ result = m([1, 2, 3, {y: 9}, 4, 5, x]) { |a, b=5, c, **k| [a, b, c, k] }
+ result.should == [1, 2, 3, {}]
+ end
+
+ it "does not call #to_ary on the Array" do
+ ary = [1, 2]
+ ary.should_not_receive(:to_ary)
+
+ m(ary) { |a, b, c| [a, b, c] }.should == [1, 2, nil]
+ end
+ end
+
+ context "Object" do
+ it "calls #to_ary on the object when taking multiple arguments" do
+ obj = mock("destructure block arguments")
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil]
+ end
+
+ it "does not call #to_ary when not taking any arguments" do
+ obj = mock("destructure block arguments")
+ obj.should_not_receive(:to_ary)
+
+ m(obj) { 1 }.should == 1
+ end
+
+ it "does not call #to_ary on the object when taking a single argument" do
+ obj = mock("destructure block arguments")
+ obj.should_not_receive(:to_ary)
+
+ m(obj) { |a| a }.should == obj
+ end
+
+ it "does not call #to_ary on the object when taking a single rest argument" do
+ obj = mock("destructure block arguments")
+ obj.should_not_receive(:to_ary)
+
+ m(obj) { |*a| a }.should == [obj]
+ end
+
+ it "receives the object if #to_ary returns nil" do
+ obj = mock("destructure block arguments")
+ obj.should_receive(:to_ary).and_return(nil)
+
+ 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)
+
+ -> { 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
+
+# TODO: rewrite
+describe "A block" do
+ before :each do
+ @y = BlockSpecs::Yielder.new
+ end
+
+ it "captures locals from the surrounding scope" do
+ var = 1
+ @y.z { var }.should == 1
+ end
+
+ it "allows for a leading space before the arguments" do
+ res = @y.s (:a){ 1 }
+ res.should == 1
+ end
+
+ it "allows to define a block variable with the same name as the enclosing block" do
+ o = BlockSpecs::OverwriteBlockVariable.new
+ o.z { 1 }.should == 1
+ end
+
+ it "does not capture a local when an argument has the same name" do
+ var = 1
+ @y.s(2) { |var| var }.should == 2
+ var.should == 1
+ end
+
+ it "does not capture a local when the block argument has the same name" do
+ var = 1
+ proc { |&var|
+ var.call(2)
+ }.call { |x| x }.should == 2
+ var.should == 1
+ end
+
+ describe "taking zero arguments" do
+ it "does not raise an exception when no values are yielded" do
+ @y.z { 1 }.should == 1
+ end
+
+ 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
+ it "does not raise an exception when no values are yielded" do
+ @y.z { || 1 }.should == 1
+ end
+
+ 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
+ it "assigns nil to the argument when no values are yielded" do
+ @y.z { |a| a }.should be_nil
+ end
+
+ it "assigns the value yielded to the argument" do
+ @y.s(1) { |a| a }.should == 1
+ end
+
+ it "does not call #to_ary to convert a single yielded object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |a| a }.should equal(obj)
+ end
+
+ it "assigns the first value yielded to the argument" do
+ @y.m(1, 2) { |a| a }.should == 1
+ end
+
+ 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 "assigns nil to the arguments when no values are yielded" do
+ @y.z { |a, b| [a, b] }.should == [nil, nil]
+ end
+
+ it "assigns one value yielded to the first argument" do
+ @y.s(1) { |a, b| [a, b] }.should == [1, nil]
+ end
+
+ it "assigns the first two values yielded to the arguments" do
+ @y.m(1, 2, 3) { |a, b| [a, b] }.should == [1, 2]
+ end
+
+ it "does not destructure an Array value as one of several values yielded" do
+ @y.m([1, 2], 3, 4) { |a, b| [a, b] }.should == [[1, 2], 3]
+ end
+
+ it "assigns 'nil' and 'nil' to the arguments when a single, empty Array is yielded" do
+ @y.s([]) { |a, b| [a, b] }.should == [nil, nil]
+ end
+
+ it "assigns the element of a single element Array to the first argument" do
+ @y.s([1]) { |a, b| [a, b] }.should == [1, nil]
+ @y.s([nil]) { |a, b| [a, b] }.should == [nil, nil]
+ @y.s([[]]) { |a, b| [a, b] }.should == [[], nil]
+ end
+
+ it "destructures a single Array value yielded" do
+ @y.s([1, 2, 3]) { |a, b| [a, b] }.should == [1, 2]
+ end
+
+ it "destructures a splatted Array" do
+ @y.r([[]]) { |a, b| [a, b] }.should == [nil, nil]
+ @y.r([[1]]) { |a, b| [a, b] }.should == [1, nil]
+ end
+
+ it "calls #to_ary to convert a single yielded object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ @y.s(obj) { |a, b| [a, b] }.should == [1, 2]
+ end
+
+ it "does not call #to_ary if the single yielded object is an Array" do
+ obj = [1, 2]
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |a, b| [a, b] }.should == [1, 2]
+ end
+
+ it "does not call #to_ary if the object does not respond to #to_ary" do
+ obj = mock("block yield no to_ary")
+
+ @y.s(obj) { |a, b| [a, b] }.should == [obj, nil]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ obj = mock("block yield to_ary invalid")
+ obj.should_receive(:to_ary).and_return(1)
+
+ -> { @y.s(obj) { |a, b| } }.should raise_error(TypeError)
+ end
+
+ it "raises the original exception if #to_ary raises an exception" do
+ obj = mock("block yield to_ary raising an exception")
+ obj.should_receive(:to_ary).and_raise(ZeroDivisionError)
+
+ -> { @y.s(obj) { |a, b| } }.should raise_error(ZeroDivisionError)
+ end
+ end
+
+ describe "taking |a, *b| arguments" do
+ it "assigns 'nil' and '[]' to the arguments when no values are yielded" do
+ @y.z { |a, *b| [a, b] }.should == [nil, []]
+ end
+
+ it "assigns all yielded values after the first to the rest argument" do
+ @y.m(1, 2, 3) { |a, *b| [a, b] }.should == [1, [2, 3]]
+ end
+
+ it "assigns 'nil' and '[]' to the arguments when a single, empty Array is yielded" do
+ @y.s([]) { |a, *b| [a, b] }.should == [nil, []]
+ end
+
+ it "assigns the element of a single element Array to the first argument" do
+ @y.s([1]) { |a, *b| [a, b] }.should == [1, []]
+ @y.s([nil]) { |a, *b| [a, b] }.should == [nil, []]
+ @y.s([[]]) { |a, *b| [a, b] }.should == [[], []]
+ end
+
+ it "destructures a splatted Array" do
+ @y.r([[]]) { |a, *b| [a, b] }.should == [nil, []]
+ @y.r([[1]]) { |a, *b| [a, b] }.should == [1, []]
+ end
+
+ it "destructures a single Array value assigning the remaining values to the rest argument" do
+ @y.s([1, 2, 3]) { |a, *b| [a, b] }.should == [1, [2, 3]]
+ end
+
+ it "calls #to_ary to convert a single yielded object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ @y.s(obj) { |a, *b| [a, b] }.should == [1, [2]]
+ end
+
+ it "does not call #to_ary if the single yielded object is an Array" do
+ obj = [1, 2]
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |a, *b| [a, b] }.should == [1, [2]]
+ end
+
+ it "does not call #to_ary if the object does not respond to #to_ary" do
+ obj = mock("block yield no to_ary")
+
+ @y.s(obj) { |a, *b| [a, b] }.should == [obj, []]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ obj = mock("block yield to_ary invalid")
+ obj.should_receive(:to_ary).and_return(1)
+
+ -> { @y.s(obj) { |a, *b| } }.should raise_error(TypeError)
+ end
+ end
+
+ describe "taking |*| arguments" do
+ it "does not raise an exception when no values are yielded" do
+ @y.z { |*| 1 }.should == 1
+ end
+
+ it "does not raise an exception when values are yielded" do
+ @y.s(0) { |*| 1 }.should == 1
+ end
+
+ it "does not call #to_ary if the single yielded object is an Array" do
+ obj = [1, 2]
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |*| 1 }.should == 1
+ end
+
+ it "does not call #to_ary if the object does not respond to #to_ary" do
+ obj = mock("block yield no to_ary")
+
+ @y.s(obj) { |*| 1 }.should == 1
+ end
+
+ it "does not call #to_ary to convert a single yielded object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |*| 1 }.should == 1
+ end
+ end
+
+ describe "taking |*a| arguments" do
+ it "assigns '[]' to the argument when no values are yielded" do
+ @y.z { |*a| a }.should == []
+ end
+
+ it "assigns a single value yielded to the argument as an Array" do
+ @y.s(1) { |*a| a }.should == [1]
+ end
+
+ it "assigns all the values passed to the argument as an Array" do
+ @y.m(1, 2, 3) { |*a| a }.should == [1, 2, 3]
+ end
+
+ it "assigns '[[]]' to the argument when passed an empty Array" do
+ @y.s([]) { |*a| a }.should == [[]]
+ end
+
+ it "assigns a single Array value passed to the argument by wrapping it in an Array" do
+ @y.s([1, 2, 3]) { |*a| a }.should == [[1, 2, 3]]
+ end
+
+ it "does not call #to_ary if the single yielded object is an Array" do
+ obj = [1, 2]
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |*a| a }.should == [[1, 2]]
+ end
+
+ it "does not call #to_ary if the object does not respond to #to_ary" do
+ obj = mock("block yield no to_ary")
+
+ @y.s(obj) { |*a| a }.should == [obj]
+ end
+
+ it "does not call #to_ary to convert a single yielded object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |*a| a }.should == [obj]
+ end
+ end
+
+ describe "taking |a, | arguments" do
+ it "assigns nil to the argument when no values are yielded" do
+ @y.z { |a, | a }.should be_nil
+ end
+
+ it "assigns the argument a single value yielded" do
+ @y.s(1) { |a, | a }.should == 1
+ end
+
+ it "assigns the argument the first value yielded" do
+ @y.m(1, 2) { |a, | a }.should == 1
+ end
+
+ it "assigns the argument the first of several values yielded when it is an Array" do
+ @y.m([1, 2], 3) { |a, | a }.should == [1, 2]
+ end
+
+ it "assigns nil to the argument when passed an empty Array" do
+ @y.s([]) { |a, | a }.should be_nil
+ end
+
+ it "assigns the argument the first element of the Array when passed a single Array" do
+ @y.s([1, 2]) { |a, | a }.should == 1
+ end
+
+ it "calls #to_ary to convert a single yielded object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ @y.s(obj) { |a, | a }.should == 1
+ end
+
+ it "does not call #to_ary if the single yielded object is an Array" do
+ obj = [1, 2]
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |a, | a }.should == 1
+ end
+
+ it "does not call #to_ary if the object does not respond to #to_ary" do
+ obj = mock("block yield no to_ary")
+
+ @y.s(obj) { |a, | a }.should == obj
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ obj = mock("block yield to_ary invalid")
+ obj.should_receive(:to_ary).and_return(1)
+
+ -> { @y.s(obj) { |a, | } }.should raise_error(TypeError)
+ end
+ end
+
+ describe "taking |(a, b)| arguments" do
+ it "assigns nil to the arguments when yielded no values" do
+ @y.z { |(a, b)| [a, b] }.should == [nil, nil]
+ end
+
+ it "destructures a single Array value yielded" do
+ @y.s([1, 2]) { |(a, b)| [a, b] }.should == [1, 2]
+ end
+
+ it "destructures a single Array value yielded when shadowing an outer variable" do
+ a = 9
+ @y.s([1, 2]) { |(a, b)| [a, b] }.should == [1, 2]
+ end
+
+ it "calls #to_ary to convert a single yielded object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ @y.s(obj) { |(a, b)| [a, b] }.should == [1, 2]
+ end
+
+ it "does not call #to_ary if the single yielded object is an Array" do
+ obj = [1, 2]
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |(a, b)| [a, b] }.should == [1, 2]
+ end
+
+ it "does not call #to_ary if the object does not respond to #to_ary" do
+ obj = mock("block yield no to_ary")
+
+ @y.s(obj) { |(a, b)| [a, b] }.should == [obj, nil]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ obj = mock("block yield to_ary invalid")
+ obj.should_receive(:to_ary).and_return(1)
+
+ -> { @y.s(obj) { |(a, b)| } }.should raise_error(TypeError)
+ end
+ end
+
+ describe "taking |(a, b), c| arguments" do
+ it "assigns nil to the arguments when yielded no values" do
+ @y.z { |(a, b), c| [a, b, c] }.should == [nil, nil, nil]
+ end
+
+ it "destructures a single one-level Array value yielded" do
+ @y.s([1, 2]) { |(a, b), c| [a, b, c] }.should == [1, nil, 2]
+ end
+
+ it "destructures a single multi-level Array value yielded" do
+ @y.s([[1, 2, 3], 4]) { |(a, b), c| [a, b, c] }.should == [1, 2, 4]
+ end
+
+ it "calls #to_ary to convert a single yielded object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ @y.s(obj) { |(a, b), c| [a, b, c] }.should == [1, nil, 2]
+ end
+
+ it "does not call #to_ary if the single yielded object is an Array" do
+ obj = [1, 2]
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |(a, b), c| [a, b, c] }.should == [1, nil, 2]
+ end
+
+ it "does not call #to_ary if the object does not respond to #to_ary" do
+ obj = mock("block yield no to_ary")
+
+ @y.s(obj) { |(a, b), c| [a, b, c] }.should == [obj, nil, nil]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ obj = mock("block yield to_ary invalid")
+ obj.should_receive(:to_ary).and_return(1)
+
+ -> { @y.s(obj) { |(a, b), c| } }.should raise_error(TypeError)
+ end
+ end
+
+ describe "taking nested |a, (b, (c, d))|" do
+ it "assigns nil to the arguments when yielded no values" do
+ @y.m { |a, (b, (c, d))| [a, b, c, d] }.should == [nil, nil, nil, nil]
+ end
+
+ it "destructures separate yielded values" do
+ @y.m(1, 2) { |a, (b, (c, d))| [a, b, c, d] }.should == [1, 2, nil, nil]
+ end
+
+ it "destructures a nested Array value yielded" do
+ @y.m(1, [2, 3]) { |a, (b, (c, d))| [a, b, c, d] }.should == [1, 2, 3, nil]
+ end
+
+ it "destructures a single multi-level Array value yielded" do
+ @y.m(1, [2, [3, 4]]) { |a, (b, (c, d))| [a, b, c, d] }.should == [1, 2, 3, 4]
+ end
+ end
+
+ describe "taking nested |a, ((b, c), d)|" do
+ it "assigns nil to the arguments when yielded no values" do
+ @y.m { |a, ((b, c), d)| [a, b, c, d] }.should == [nil, nil, nil, nil]
+ end
+
+ it "destructures separate yielded values" do
+ @y.m(1, 2) { |a, ((b, c), d)| [a, b, c, d] }.should == [1, 2, nil, nil]
+ end
+
+ it "destructures a nested value yielded" do
+ @y.m(1, [2, 3]) { |a, ((b, c), d)| [a, b, c, d] }.should == [1, 2, nil, 3]
+ end
+
+ it "destructures a single multi-level Array value yielded" do
+ @y.m(1, [[2, 3], 4]) { |a, ((b, c), d)| [a, b, c, d] }.should == [1, 2, 3, 4]
+ 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
+ @y.m([1, [2, 3, 4]]) { |_, (_, a, _)| a }.should == 3
+ end
+
+ it "assigns the first variable named" do
+ @y.m(1, 2) { |_, _| _ }.should == 1
+ end
+ end
+
+ describe "taking identically-named arguments" do
+ it "raises a SyntaxError for standard arguments" do
+ -> { eval "lambda { |x,x| }" }.should raise_error(SyntaxError)
+ -> { eval "->(x,x) {}" }.should raise_error(SyntaxError)
+ -> { eval "Proc.new { |x,x| }" }.should raise_error(SyntaxError)
+ end
+
+ it "accepts unnamed arguments" do
+ 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
+
+describe "Block-local variables" do
+ it "are introduced with a semi-colon in the parameter list" do
+ [1].map {|one; bl| bl }.should == [nil]
+ end
+
+ it "can be specified in a comma-separated list after the semi-colon" do
+ [1].map {|one; bl, bl2| [bl, bl2] }.should == [[nil, nil]]
+ end
+
+ it "can not have the same name as one of the standard parameters" do
+ -> { eval "[1].each {|foo; foo| }" }.should raise_error(SyntaxError)
+ -> { eval "[1].each {|foo, bar; glark, bar| }" }.should raise_error(SyntaxError)
+ end
+
+ it "can not be prefixed with an asterisk" 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
+ -> { 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
+ -> { 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
+
+ it "need not be preceded by standard parameters" do
+ [1].map {|; foo| foo }.should == [nil]
+ [1].map {|; glark, bar| [glark, bar] }.should == [[nil, nil]]
+ end
+
+ it "only allow a single semi-colon in the parameter list" do
+ -> { eval "[1].each {|foo; bar; glark| }" }.should raise_error(SyntaxError)
+ -> { eval "[1].each {|; bar; glark| }" }.should raise_error(SyntaxError)
+ end
+
+ it "override shadowed variables from the outer scope" do
+ out = :out
+ [1].each {|; out| out = :in }
+ out.should == :out
+
+ a = :a
+ b = :b
+ c = :c
+ d = :d
+ {ant: :bee}.each_pair do |a, b; c, d|
+ a = :A
+ b = :B
+ c = :C
+ d = :D
+ end
+ a.should == :a
+ b.should == :b
+ c.should == :c
+ d.should == :d
+ end
+
+ it "are not automatically instantiated in the outer scope" do
+ defined?(glark).should be_nil
+ [1].each {|;glark| 1}
+ defined?(glark).should be_nil
+ end
+
+ it "are automatically instantiated in the block" do
+ [1].each do |;glark|
+ glark.should be_nil
+ end
+ end
+
+ it "are visible in deeper scopes before initialization" do
+ [1].each {|;glark|
+ [1].each {
+ defined?(glark).should_not be_nil
+ glark = 1
+ }
+ glark.should == 1
+ }
+ end
+end
+
+describe "Post-args" do
+ it "appear after a splat" do
+ proc do |*a, b|
+ [a, b]
+ end.call(1, 2, 3).should == [[1, 2], 3]
+
+ proc do |*a, b, c|
+ [a, b, c]
+ end.call(1, 2, 3).should == [[1], 2, 3]
+
+ proc do |*a, b, c, d|
+ [a, b, c, d]
+ end.call(1, 2, 3).should == [[], 1, 2, 3]
+ end
+
+ 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
+ proc do |a, *b, c|
+ [a, b, c]
+ end.call(1, 2, 3).should == [1, [2], 3]
+ end
+
+ it "has an empty splat when there are no remaining args" do
+ proc do |a, b, *c, d|
+ [a, b, c, d]
+ end.call(1, 2, 3).should == [1, 2, [], 3]
+
+ proc do |a, *b, c, d|
+ [a, b, c, d]
+ end.call(1, 2, 3).should == [1, [], 2, 3]
+ end
+ end
+
+ describe "with optional args" do
+
+ it "gathers remaining args in the splat" do
+ proc do |a=5, *b, c|
+ [a, b, c]
+ end.call(1, 2, 3).should == [1, [2], 3]
+ end
+
+ it "overrides the optional arg before gathering in the splat" do
+ proc do |a=5, *b, c|
+ [a, b, c]
+ end.call(2, 3).should == [2, [], 3]
+
+ proc do |a=5, b=6, *c, d|
+ [a, b, c, d]
+ end.call(1, 2, 3).should == [1, 2, [], 3]
+
+ proc do |a=5, *b, c, d|
+ [a, b, c, d]
+ end.call(1, 2, 3).should == [1, [], 2, 3]
+ end
+
+ it "uses the required arg before the optional and the splat" do
+ proc do |a=5, *b, c|
+ [a, b, c]
+ end.call(3).should == [5, [], 3]
+
+ proc do |a=5, b=6, *c, d|
+ [a, b, c, d]
+ end.call(3).should == [5, 6, [], 3]
+
+ proc do |a=5, *b, c, d|
+ [a, b, c, d]
+ end.call(2, 3).should == [5, [], 2, 3]
+ end
+
+ it "overrides the optional args from left to right before gathering the splat" do
+ proc do |a=5, b=6, *c, d|
+ [a, b, c, d]
+ end.call(2, 3).should == [2, 6, [], 3]
+ end
+
+ describe "with a circular argument reference" do
+ 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
+
+ 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
+ end
+
+ describe "with pattern matching" do
+ it "extracts matched blocks with post arguments" do
+ proc do |(a, *b, c), d, e|
+ [a, b, c, d, e]
+ end.call([1, 2, 3, 4], 5, 6).should == [1, [2, 3], 4, 5, 6]
+ end
+
+ it "allows empty splats" do
+ proc do |a, (*), b|
+ [a, b]
+ end.call([1, 2, 3]).should == [1, 3]
+ 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
new file mode 100644
index 0000000000..7e5b6fb328
--- /dev/null
+++ b/spec/ruby/language/break_spec.rb
@@ -0,0 +1,402 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/break'
+
+describe "The break statement in a block" do
+ before :each do
+ ScratchPad.record []
+ @program = BreakSpecs::Block.new
+ end
+
+ it "returns nil to method invoking the method yielding to the block when not passed an argument" do
+ @program.break_nil
+ ScratchPad.recorded.should == [:a, :aa, :b, nil, :d]
+ end
+
+ it "returns a value to the method invoking the method yielding to the block" do
+ @program.break_value
+ ScratchPad.recorded.should == [:a, :aa, :b, :break, :d]
+ end
+
+ describe "yielded inside a while" do
+ it "breaks out of the block" do
+ value = @program.break_in_block_in_while
+ ScratchPad.recorded.should == [:aa, :break]
+ value.should == :value
+ end
+ end
+
+ describe "captured and delegated to another method repeatedly" do
+ it "breaks out of the block" do
+ @program.looped_break_in_captured_block
+ ScratchPad.recorded.should == [:begin,
+ :preloop,
+ :predele,
+ :preyield,
+ :prebreak,
+ :postbreak,
+ :postyield,
+ :postdele,
+ :predele,
+ :preyield,
+ :prebreak,
+ :end]
+ end
+ end
+end
+
+describe "The break statement in a captured block" do
+ before :each do
+ ScratchPad.record []
+ @program = BreakSpecs::Block.new
+ end
+
+ describe "when the invocation of the scope creating the block is still active" do
+ it "raises a LocalJumpError when invoking the block from the scope creating the block" do
+ -> { @program.break_in_method }.should raise_error(LocalJumpError)
+ ScratchPad.recorded.should == [:a, :xa, :d, :b]
+ end
+
+ it "raises a LocalJumpError when invoking the block from a method" do
+ -> { @program.break_in_nested_method }.should raise_error(LocalJumpError)
+ ScratchPad.recorded.should == [:a, :xa, :cc, :aa, :b]
+ end
+
+ it "raises a LocalJumpError when yielding to the block" do
+ -> { @program.break_in_yielding_method }.should raise_error(LocalJumpError)
+ ScratchPad.recorded.should == [:a, :xa, :cc, :aa, :b]
+ end
+ end
+
+ describe "from a scope that has returned" do
+ it "raises a LocalJumpError when calling the block from a method" do
+ -> { @program.break_in_method_captured }.should raise_error(LocalJumpError)
+ ScratchPad.recorded.should == [:a, :za, :xa, :zd, :zb]
+ end
+
+ it "raises a LocalJumpError when yielding to the block" do
+ -> { @program.break_in_yield_captured }.should raise_error(LocalJumpError)
+ ScratchPad.recorded.should == [:a, :za, :xa, :zd, :aa, :zb]
+ end
+ end
+
+ describe "from another thread" do
+ it "raises a LocalJumpError when getting the value from another thread" do
+ thread_with_break = Thread.new do
+ begin
+ break :break
+ rescue LocalJumpError => e
+ e
+ end
+ end
+ thread_with_break.value.should be_an_instance_of(LocalJumpError)
+ end
+ end
+end
+
+describe "The break statement in a lambda" do
+ before :each do
+ ScratchPad.record []
+ @program = BreakSpecs::Lambda.new
+ end
+
+ it "returns from the lambda" do
+ l = -> {
+ ScratchPad << :before
+ break :foo
+ ScratchPad << :after
+ }
+ l.call.should == :foo
+ ScratchPad.recorded.should == [:before]
+ end
+
+ it "returns from the call site if the lambda is passed as a block" do
+ def mid(&b)
+ -> {
+ ScratchPad << :before
+ b.call
+ ScratchPad << :unreachable1
+ }.call
+ ScratchPad << :unreachable2
+ end
+
+ result = [1].each do |e|
+ mid {
+ break # This breaks from mid
+ ScratchPad << :unreachable3
+ }
+ ScratchPad << :after
+ end
+ result.should == [1]
+ ScratchPad.recorded.should == [:before, :after]
+ end
+
+ describe "when the invocation of the scope creating the lambda is still active" do
+ it "returns nil when not passed an argument" do
+ @program.break_in_defining_scope false
+ ScratchPad.recorded.should == [:a, :b, nil, :d]
+ end
+
+ it "returns a value to the scope creating and calling the lambda" do
+ @program.break_in_defining_scope
+ ScratchPad.recorded.should == [:a, :b, :break, :d]
+ end
+
+ it "returns a value to the method scope below invoking the lambda" do
+ @program.break_in_nested_scope
+ ScratchPad.recorded.should == [:a, :d, :aa, :b, :break, :bb, :e]
+ end
+
+ it "returns a value to a block scope invoking the lambda in a method below" do
+ @program.break_in_nested_scope_block
+ ScratchPad.recorded.should == [:a, :d, :aa, :aaa, :bb, :b, :break, :cc, :bbb, :dd, :e]
+ end
+
+ it "returns from the lambda" do
+ @program.break_in_nested_scope_yield
+ ScratchPad.recorded.should == [:a, :d, :aaa, :b, :bbb, :e]
+ end
+ end
+
+ describe "created at the toplevel" do
+ it "returns a value when invoking from the toplevel" do
+ code = fixture __FILE__, "break_lambda_toplevel.rb"
+ ruby_exe(code).chomp.should == "a,b,break,d"
+ end
+
+ it "returns a value when invoking from a method" do
+ code = fixture __FILE__, "break_lambda_toplevel_method.rb"
+ ruby_exe(code).chomp.should == "a,d,b,break,e,f"
+ end
+
+ it "returns a value when invoking from a block" do
+ code = fixture __FILE__, "break_lambda_toplevel_block.rb"
+ ruby_exe(code).chomp.should == "a,d,f,b,break,g,e,h"
+ end
+ end
+
+ describe "from a scope that has returned" do
+ it "returns a value to the method scope invoking the lambda" do
+ @program.break_in_method
+ ScratchPad.recorded.should == [:a, :la, :ld, :lb, :break, :b]
+ end
+
+ it "returns a value to the block scope invoking the lambda in a method" do
+ @program.break_in_block_in_method
+ ScratchPad.recorded.should == [:a, :aaa, :b, :la, :ld, :lb, :break, :c, :bbb, :d]
+ end
+
+ # By passing a lambda as a block argument, the user is requesting to treat
+ # the lambda as a block, which in this case means breaking to a scope that
+ # has returned. This is a subtle and confusing semantic where a block pass
+ # is removing the lambda-ness of a lambda.
+ it "raises a LocalJumpError when yielding to a lambda passed as a block argument" do
+ @program.break_in_method_yield
+ ScratchPad.recorded.should == [:a, :la, :ld, :aaa, :lb, :bbb, :b]
+ end
+ end
+end
+
+describe "Break inside a while loop" do
+ describe "with a value" do
+ it "exits the loop and returns the value" do
+ a = while true; break; end; a.should == nil
+ a = while true; break nil; end; a.should == nil
+ a = while true; break 1; end; a.should == 1
+ a = while true; break []; end; a.should == []
+ a = while true; break [1]; end; a.should == [1]
+ end
+
+ it "passes the value returned by a method with omitted parenthesis and passed block" do
+ obj = BreakSpecs::Block.new
+ -> { break obj.method :value do |x| x end }.call.should == :value
+ end
+ end
+
+ describe "with a splat" do
+ it "exits the loop and makes the splat an Array" do
+ a = while true; break *[1,2]; end; a.should == [1,2]
+ end
+
+ it "treats nil as an empty array" do
+ a = while true; break *nil; end; a.should == []
+ end
+
+ it "preserves an array as is" do
+ a = while true; break *[]; end; a.should == []
+ a = while true; break *[1,2]; end; a.should == [1,2]
+ a = while true; break *[nil]; end; a.should == [nil]
+ a = while true; break *[[]]; end; a.should == [[]]
+ end
+
+ it "wraps a non-Array in an Array" do
+ a = while true; break *1; end; a.should == [1]
+ end
+ end
+
+ it "stops a while loop when run" do
+ i = 0
+ while true
+ break if i == 2
+ i+=1
+ end
+ i.should == 2
+ end
+
+ it "causes a call with a block to return when run" do
+ at = 0
+ 0.upto(5) do |i|
+ at = i
+ break i if i == 2
+ end.should == 2
+ at.should == 2
+ 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.
+describe "Executing break from within a block" do
+
+ before :each do
+ ScratchPad.clear
+ end
+
+ # Discovered in JRuby (see JRUBY-2756)
+ it "returns from the original invoking method even in case of chained calls" do
+ class BreakTest
+ # case #1: yield
+ def self.meth_with_yield(&b)
+ yield
+ fail("break returned from yield to wrong place")
+ end
+ def self.invoking_method(&b)
+ meth_with_yield(&b)
+ fail("break returned from 'meth_with_yield' method to wrong place")
+ end
+
+ # case #2: block.call
+ def self.meth_with_block_call(&b)
+ b.call
+ fail("break returned from b.call to wrong place")
+ end
+ def self.invoking_method2(&b)
+ meth_with_block_call(&b)
+ fail("break returned from 'meth_with_block_call' method to wrong place")
+ end
+ end
+
+ # this calls a method that calls another method that yields to the block
+ BreakTest.invoking_method do
+ break
+ fail("break didn't, well, break")
+ end
+
+ # this calls a method that calls another method that calls the block
+ BreakTest.invoking_method2 do
+ break
+ fail("break didn't, well, break")
+ end
+
+ res = BreakTest.invoking_method do
+ break :return_value
+ fail("break didn't, well, break")
+ end
+ res.should == :return_value
+
+ res = BreakTest.invoking_method2 do
+ break :return_value
+ fail("break didn't, well, break")
+ end
+ res.should == :return_value
+
+ end
+
+ class BreakTest2
+ def one
+ two { yield }
+ end
+
+ def two
+ yield
+ ensure
+ ScratchPad << :two_ensure
+ end
+
+ def three
+ begin
+ one { break }
+ ScratchPad << :three_post
+ ensure
+ ScratchPad << :three_ensure
+ end
+ end
+ end
+
+ it "runs ensures when continuing upward" do
+ ScratchPad.record []
+
+ bt2 = BreakTest2.new
+ bt2.one { break }
+ ScratchPad.recorded.should == [:two_ensure]
+ end
+
+ it "runs ensures when breaking from a loop" do
+ ScratchPad.record []
+
+ while true
+ begin
+ ScratchPad << :begin
+ break if true
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "doesn't run ensures in the destination method" do
+ ScratchPad.record []
+
+ bt2 = BreakTest2.new
+ 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
new file mode 100644
index 0000000000..464d06e46a
--- /dev/null
+++ b/spec/ruby/language/case_spec.rb
@@ -0,0 +1,509 @@
+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
+ 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
+ end.should == true
+ end
+
+ it "evaluates the body of the when clause in left-to-right order if it's an array expression" do
+ @calls = []
+ def foo; @calls << :foo; end
+ def bar; @calls << :bar; end
+
+ case true
+ when foo, bar;
+ end
+
+ @calls.should == [:foo, :bar]
+ end
+
+ 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
+ end.should == true
+ end
+
+ it "returns nil when no 'then'-bodies are given" do
+ case "a"
+ 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'
+ 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
+ end.should == nil
+ end
+
+ it "returns 2 when a then body is empty" do
+ case Object.new
+ when Numeric then
+ 1
+ when String then
+ # ok
+ else
+ 2
+ end.should == 2
+ end
+
+ it "returns the statement following 'then'" do
+ case "a"
+ 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'
+ end.should == 'foo'
+ end
+
+ it "tests with matching regexps" do
+ case "hello"
+ 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"
+ end.should == "foo"
+ end
+
+ it "takes lists of values" do
+ case 'z'
+ 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"
+ 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"
+ 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"
+ end.should == "foo"
+
+ case 'b'
+ 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"
+ 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"
+ 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"
+ end.should == "foo"
+ end
+
+ it "expands multiple arrays from variables before additional listed values" do
+ a = ['a', 'b', 'c']
+ b = ['d', 'e', 'f']
+
+ case 'f'
+ when *a, *b, 'g', 'h'
+ "foo"
+ when 'x'
+ "bar"
+ end.should == "foo"
+ end
+
+ # MR: critical
+ it "concats arrays before expanding them" do
+ a = ['a', 'b', 'c', 'd']
+ b = ['f']
+
+ case 'f'
+ 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"
+ 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'
+ end.should == 'bar'
+ end
+
+ it "raises a SyntaxError when 'else' is used when no 'when' is given" do
+ -> {
+ eval <<-CODE
+ case 4
+ else
+ true
+ end
+ CODE
+ }.should raise_error(SyntaxError)
+ end
+
+ it "raises a SyntaxError when 'else' is used before a 'when' was given" do
+ -> {
+ eval <<-CODE
+ case 4
+ else
+ true
+ when 4; false
+ end
+ CODE
+ }.should raise_error(SyntaxError)
+ end
+
+ it "supports nested case statements" do
+ result = false
+ case :x
+ when Symbol
+ case :y
+ when Symbol
+ result = true
+ end
+ end
+ result.should == true
+ end
+
+ it "supports nested case statements followed by a when with a splatted array" do
+ result = false
+ case :x
+ when Symbol
+ case :y
+ when Symbol
+ result = true
+ end
+ when *[Symbol]
+ result = false
+ end
+ result.should == true
+ end
+
+ it "supports nested case statements followed by a when with a splatted non-array" do
+ result = false
+ case :x
+ when Symbol
+ case :y
+ when Symbol
+ result = true
+ end
+ when *Symbol
+ result = false
+ end
+ result.should == true
+ end
+
+ it "works even if there's only one when statement" do
+ case 1
+ when 1
+ 100
+ end.should == 100
+ end
+
+ it "evaluates true as only 'true' when true is the first clause" do
+ case 1
+ 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"
+ end.should == "good"
+ end
+
+ it "treats a literal array as its own when argument, rather than a list of arguments" do
+ case 'foo'
+ when ['foo', 'foo']; 'bad'
+ when 'foo'; 'good'
+ end.should == 'good'
+ end
+
+ it "takes multiple expanded arrays" do
+ a1 = ['f', 'o', 'o']
+ a2 = ['b', 'a', 'r']
+
+ case 'f'
+ 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"
+ end.should == "bar"
+ end
+
+ it "calls === even when private" do
+ klass = Class.new do
+ def ===(o)
+ true
+ end
+ private :===
+ end
+
+ case 1
+ when klass.new
+ :called
+ end.should == :called
+ end
+
+ it "accepts complex expressions within ()" do
+ case 'a'
+ when (raise if 2+2 == 3; /a/)
+ :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
new file mode 100644
index 0000000000..6fb785fd56
--- /dev/null
+++ b/spec/ruby/language/class_spec.rb
@@ -0,0 +1,395 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/class'
+
+ClassSpecsNumber = 12
+
+module ClassSpecs
+ Number = 12
+end
+
+describe "The class keyword" do
+ it "creates a new class with semicolon" do
+ class ClassSpecsKeywordWithSemicolon; end
+ ClassSpecsKeywordWithSemicolon.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
+
+describe "A class definition" do
+ it "creates a new class" do
+ ClassSpecs::A.should be_kind_of(Class)
+ ClassSpecs::A.new.should be_kind_of(ClassSpecs::A)
+ end
+
+ it "has no class variables" do
+ ClassSpecs::A.class_variables.should == []
+ end
+
+ it "raises TypeError if constant given as class name exists and is not a Module" do
+ -> {
+ class ClassSpecsNumber
+ end
+ }.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
+ -> {
+ class nil::Foo
+ end
+ }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError if any constant qualifying the class is not a Module" do
+ -> {
+ class ClassSpecs::Number::MyClass
+ end
+ }.should raise_error(TypeError)
+
+ -> {
+ class ClassSpecsNumber::MyClass
+ end
+ }.should raise_error(TypeError)
+ end
+
+ it "inherits from Object by default" do
+ ClassSpecs::A.superclass.should == Object
+ end
+
+ it "raises an error when trying to change the superclass" do
+ module ClassSpecs
+ class SuperclassResetToSubclass < L
+ end
+ -> {
+ class SuperclassResetToSubclass < M
+ end
+ }.should raise_error(TypeError, /superclass mismatch/)
+ end
+ end
+
+ it "raises an error when reopening a class with BasicObject as superclass" do
+ module ClassSpecs
+ class SuperclassReopenedBasicObject < A
+ end
+ SuperclassReopenedBasicObject.superclass.should == A
+
+ -> {
+ class SuperclassReopenedBasicObject < BasicObject
+ end
+ }.should raise_error(TypeError, /superclass mismatch/)
+ SuperclassReopenedBasicObject.superclass.should == A
+ end
+ end
+
+ # [Bug #12367] [ruby-core:75446]
+ it "raises an error when reopening a class with Object as superclass" do
+ module ClassSpecs
+ class SuperclassReopenedObject < A
+ end
+ SuperclassReopenedObject.superclass.should == A
+
+ -> {
+ class SuperclassReopenedObject < Object
+ end
+ }.should raise_error(TypeError, /superclass mismatch/)
+ SuperclassReopenedObject.superclass.should == A
+ end
+ end
+
+ it "allows reopening a class without specifying the superclass" do
+ module ClassSpecs
+ class SuperclassNotGiven < A
+ end
+ SuperclassNotGiven.superclass.should == A
+
+ class SuperclassNotGiven
+ end
+ SuperclassNotGiven.superclass.should == A
+ end
+ end
+
+ it "does not allow to set the superclass even if it was not specified by the first declaration" do
+ module ClassSpecs
+ class NoSuperclassSet
+ end
+
+ -> {
+ class NoSuperclassSet < String
+ end
+ }.should raise_error(TypeError, /superclass mismatch/)
+ end
+ end
+
+ it "allows using self as the superclass if self is a class" do
+ ClassSpecs::I::J.superclass.should == ClassSpecs::I
+
+ -> {
+ class ShouldNotWork < self; end
+ }.should raise_error(TypeError)
+ end
+
+ it "first evaluates the superclass before checking if the class already exists" do
+ module ClassSpecs
+ class SuperclassEvaluatedFirst
+ end
+ a = SuperclassEvaluatedFirst
+
+ class SuperclassEvaluatedFirst < remove_const(:SuperclassEvaluatedFirst)
+ end
+ b = SuperclassEvaluatedFirst
+ b.superclass.should == a
+ end
+ end
+
+ it "raises a TypeError if inheriting from a metaclass" do
+ obj = mock("metaclass super")
+ meta = obj.singleton_class
+ -> { class ClassSpecs::MetaclassSuper < meta; end }.should raise_error(TypeError)
+ end
+
+ it "allows the declaration of class variables in the body" do
+ ClassSpecs.string_class_variables(ClassSpecs::B).should == ["@@cvar"]
+ ClassSpecs::B.send(:class_variable_get, :@@cvar).should == :cvar
+ end
+
+ it "stores instance variables defined in the class body in the class object" do
+ ClassSpecs.string_instance_variables(ClassSpecs::B).should include("@ivar")
+ ClassSpecs::B.instance_variable_get(:@ivar).should == :ivar
+ end
+
+ it "allows the declaration of class variables in a class method" do
+ ClassSpecs::C.class_variables.should == []
+ ClassSpecs::C.make_class_variable
+ ClassSpecs.string_class_variables(ClassSpecs::C).should == ["@@cvar"]
+ ClassSpecs::C.remove_class_variable :@@cvar
+ end
+
+ it "allows the definition of class-level instance variables in a class method" do
+ ClassSpecs.string_instance_variables(ClassSpecs::C).should_not include("@civ")
+ ClassSpecs::C.make_class_instance_variable
+ ClassSpecs.string_instance_variables(ClassSpecs::C).should include("@civ")
+ ClassSpecs::C.remove_instance_variable :@civ
+ end
+
+ it "allows the declaration of class variables in an instance method" do
+ ClassSpecs::D.class_variables.should == []
+ ClassSpecs::D.new.make_class_variable
+ ClassSpecs.string_class_variables(ClassSpecs::D).should == ["@@cvar"]
+ ClassSpecs::D.remove_class_variable :@@cvar
+ end
+
+ it "allows the definition of instance methods" do
+ ClassSpecs::E.new.meth.should == :meth
+ end
+
+ it "allows the definition of class methods" do
+ ClassSpecs::E.cmeth.should == :cmeth
+ end
+
+ it "allows the definition of class methods using class << self" do
+ ClassSpecs::E.smeth.should == :smeth
+ end
+
+ it "allows the definition of Constants" do
+ Object.const_defined?('CONSTANT').should == false
+ ClassSpecs::E.const_defined?('CONSTANT').should == true
+ ClassSpecs::E::CONSTANT.should == :constant!
+ end
+
+ it "returns the value of the last statement in the body" do
+ class ClassSpecs::Empty; end.should == nil
+ class ClassSpecs::Twenty; 20; end.should == 20
+ class ClassSpecs::Plus; 10 + 20; end.should == 30
+ class ClassSpecs::Singleton; class << self; :singleton; end; end.should == :singleton
+ end
+
+ 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 CS_CONST_CLASS_SPECS
+ end
+
+ def self.get_class_name
+ CS_CONST_CLASS_SPECS.name
+ end
+ end
+
+ 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
+ klass = ClassSpecs::ANON_CLASS_FOR_NEW.call
+
+ ClassSpecs::NamedInModule.name.should == 'ClassSpecs::NamedInModule'
+ klass.get_class_name.should == 'ClassSpecs::NamedInModule'
+ end
+
+ it "for anonymous classes" do
+ klass = Class.new do
+ def self.get_class
+ Class.new do
+ def self.foo
+ 'bar'
+ end
+ end
+ end
+
+ def self.get_result
+ get_class.foo
+ end
+ end
+
+ klass.get_result.should == 'bar'
+ end
+
+ it "for anonymous classes assigned to a constant" do
+ klass = Class.new do
+ AnonWithConstant = Class.new
+
+ def self.get_class_name
+ AnonWithConstant.name
+ end
+ end
+
+ AnonWithConstant.name.should == 'AnonWithConstant'
+ klass.get_class_name.should == 'AnonWithConstant'
+ ensure
+ Object.send(:remove_const, :AnonWithConstant)
+ end
+ end
+end
+
+describe "An outer class definition" do
+ it "contains the inner classes" do
+ ClassSpecs::Container.constants.should include(:A, :B)
+ end
+end
+
+describe "A class definition extending an object (sclass)" do
+ it "allows adding methods" do
+ ClassSpecs::O.smeth.should == :smeth
+ end
+
+ it "raises a TypeError when trying to extend numbers" do
+ -> {
+ eval <<-CODE
+ class << 1
+ def xyz
+ self
+ end
+ end
+ CODE
+ }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when trying to extend non-Class" do
+ error_msg = /superclass must be a.* Class/
+ -> { class TestClass < ""; end }.should raise_error(TypeError, error_msg)
+ -> { class TestClass < 1; end }.should raise_error(TypeError, error_msg)
+ -> { class TestClass < :symbol; end }.should raise_error(TypeError, error_msg)
+ -> { class TestClass < mock('o'); end }.should raise_error(TypeError, error_msg)
+ -> { class TestClass < Module.new; end }.should raise_error(TypeError, error_msg)
+ -> { class TestClass < BasicObject.new; end }.should raise_error(TypeError, error_msg)
+ end
+
+ 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
+ ClassSpecs.sclass_with_return.should == :inner
+ end
+end
+
+describe "Reopening a class" do
+ it "extends the previous definitions" do
+ c = ClassSpecs::F.new
+ c.meth.should == :meth
+ c.another.should == :another
+ end
+
+ it "overwrites existing methods" do
+ ClassSpecs::G.new.override.should == :override
+ end
+
+ it "raises a TypeError when superclasses mismatch" do
+ -> { class ClassSpecs::A < Array; end }.should raise_error(TypeError)
+ end
+
+ it "adds new methods to subclasses" do
+ -> { ClassSpecs::M.m }.should raise_error(NoMethodError)
+ class ClassSpecs::L
+ def self.m
+ 1
+ end
+ end
+ 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
+ it "calls inherited when a class is created" do
+ ClassSpecs::H.track_inherited.should == [ClassSpecs::K]
+ end
+end
diff --git a/spec/ruby/language/class_variable_spec.rb b/spec/ruby/language/class_variable_spec.rb
new file mode 100644
index 0000000000..a26a3fb8de
--- /dev/null
+++ b/spec/ruby/language/class_variable_spec.rb
@@ -0,0 +1,114 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/class_variables'
+
+describe "A class variable" do
+ after :each do
+ ClassVariablesSpec::ClassA.new.cvar_a = :cvar_a
+ end
+
+ it "can be accessed from a subclass" do
+ ClassVariablesSpec::ClassB.new.cvar_a.should == :cvar_a
+ end
+
+ it "is set in the superclass" do
+ a = ClassVariablesSpec::ClassA.new
+ b = ClassVariablesSpec::ClassB.new
+ b.cvar_a = :new_val
+
+ a.cvar_a.should == :new_val
+ end
+end
+
+describe "A class variable defined in a module" do
+ after :each do
+ ClassVariablesSpec::ClassC.cvar_m = :value
+ ClassVariablesSpec::ClassC.remove_class_variable(:@@cvar) if ClassVariablesSpec::ClassC.cvar_defined?
+ end
+
+ it "can be accessed from classes that extend the module" do
+ ClassVariablesSpec::ClassC.cvar_m.should == :value
+ end
+
+ it "is not defined in these classes" do
+ ClassVariablesSpec::ClassC.cvar_defined?.should be_false
+ end
+
+ it "is only updated in the module a method defined in the module is used" do
+ ClassVariablesSpec::ClassC.cvar_m = "new value"
+ ClassVariablesSpec::ClassC.cvar_m.should == "new value"
+
+ ClassVariablesSpec::ClassC.cvar_defined?.should be_false
+ end
+
+ it "is updated in the class when a Method defined in the class is used" do
+ ClassVariablesSpec::ClassC.cvar_c = "new value"
+ ClassVariablesSpec::ClassC.cvar_defined?.should be_true
+ end
+
+ it "can be accessed inside the class using the module methods" do
+ ClassVariablesSpec::ClassC.cvar_c = "new value"
+ ClassVariablesSpec::ClassC.cvar_m.should == :value
+ end
+
+ it "can be accessed from modules that extend the module" do
+ ClassVariablesSpec::ModuleO.cvar_n.should == :value
+ end
+
+ it "is defined in the extended module" do
+ ClassVariablesSpec::ModuleN.class_variable_defined?(:@@cvar_n).should be_true
+ end
+
+ it "is not defined in the extending module" do
+ ClassVariablesSpec::ModuleO.class_variable_defined?(:@@cvar_n).should be_false
+ end
+end
+
+describe 'A class variable definition' do
+ it "is created in a module if any of the parents do not define it" do
+ a = Class.new
+ b = Class.new(a)
+ c = Class.new(b)
+ b.class_variable_set(:@@cv, :value)
+
+ -> { 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)
+
+ -> { 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
new file mode 100644
index 0000000000..063c52c422
--- /dev/null
+++ b/spec/ruby/language/constants_spec.rb
@@ -0,0 +1,809 @@
+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.
+
+describe "Literal (A::X) constant resolution" do
+ describe "with statically assigned constants" do
+ it "searches the immediate class or module scope first" do
+ ConstantSpecs::ClassA::CS_CONST10.should == :const10_10
+ ConstantSpecs::ModuleA::CS_CONST10.should == :const10_1
+ ConstantSpecs::ParentA::CS_CONST10.should == :const10_5
+ ConstantSpecs::ContainerA::CS_CONST10.should == :const10_2
+ ConstantSpecs::ContainerA::ChildA::CS_CONST10.should == :const10_3
+ end
+
+ it "searches a module included in the immediate class before the superclass" do
+ ConstantSpecs::ContainerA::ChildA::CS_CONST15.should == :const15_1
+ end
+
+ it "searches the superclass before a module included in the superclass" do
+ ConstantSpecs::ContainerA::ChildA::CS_CONST11.should == :const11_1
+ end
+
+ it "searches a module included in the superclass" do
+ ConstantSpecs::ContainerA::ChildA::CS_CONST12.should == :const12_1
+ end
+
+ it "searches the superclass chain" do
+ ConstantSpecs::ContainerA::ChildA::CS_CONST13.should == :const13
+ end
+
+ it "searches Object if no class or module qualifier is given" do
+ CS_CONST1.should == :const1
+ CS_CONST10.should == :const10_1
+ end
+
+ it "searches Object after searching other scopes" do
+ module ConstantSpecs::SpecAdded1
+ CS_CONST10.should == :const10_1
+ end
+ end
+
+ it "searches Object if a toplevel qualifier (::X) is given" do
+ ::CS_CONST1.should == :const1
+ ::CS_CONST10.should == :const10_1
+ end
+
+ it "does not search the singleton class of the class or module" do
+ -> do
+ ConstantSpecs::ContainerA::ChildA::CS_CONST14
+ end.should raise_error(NameError)
+ -> { ConstantSpecs::CS_CONST14 }.should raise_error(NameError)
+ end
+ end
+
+ describe "with dynamically assigned constants" do
+ it "searches the immediate class or module scope first" do
+ ConstantSpecs::ClassB::CS_CONST101 = :const101_1
+ ConstantSpecs::ClassB::CS_CONST101.should == :const101_1
+
+ ConstantSpecs::ParentB::CS_CONST101 = :const101_2
+ ConstantSpecs::ParentB::CS_CONST101.should == :const101_2
+
+ ConstantSpecs::ContainerB::CS_CONST101 = :const101_3
+ ConstantSpecs::ContainerB::CS_CONST101.should == :const101_3
+
+ ConstantSpecs::ContainerB::ChildB::CS_CONST101 = :const101_4
+ ConstantSpecs::ContainerB::ChildB::CS_CONST101.should == :const101_4
+
+ 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
+ class << ConstantSpecs::ContainerB::ChildB
+ CS_CONST108 = :const108_1
+ end
+
+ -> do
+ ConstantSpecs::ContainerB::ChildB::CS_CONST108
+ end.should raise_error(NameError)
+
+ module ConstantSpecs
+ class << self
+ CS_CONST108 = :const108_2
+ end
+ end
+
+ -> { ConstantSpecs::CS_CONST108 }.should raise_error(NameError)
+ ensure
+ ConstantSpecs::ContainerB::ChildB.singleton_class.send(:remove_const, :CS_CONST108)
+ ConstantSpecs.singleton_class.send(:remove_const, :CS_CONST108)
+ end
+
+ it "returns the updated value when a constant is reassigned" do
+ ConstantSpecs::ClassB::CS_CONST109 = :const109_1
+ ConstantSpecs::ClassB::CS_CONST109.should == :const109_1
+
+ -> {
+ 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 left-to-right" do
+ mod = Module.new
+
+ mod.module_eval <<-EOC
+ order = []
+ ConstantSpecsRHS = Module.new
+ (order << :lhs; ConstantSpecsRHS)::B = (order << :rhs)
+ EOC
+
+ mod::ConstantSpecsRHS::B.should == [:lhs, :rhs]
+ end
+ end
+
+ it "raises a NameError if no constant is defined in the search path" do
+ -> { ConstantSpecs::ParentA::CS_CONSTX }.should raise_error(NameError)
+ 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
+ ConstantSpecs::ClassA::CS_CONSTX.should == :CS_CONSTX
+ end
+
+ it "evaluates the qualifier" do
+ ConstantSpecs.get_const::CS_CONST2.should == :const2
+ end
+
+ it "raises a TypeError if a non-class or non-module qualifier is given" do
+ -> { CS_CONST1::CS_CONST }.should raise_error(TypeError)
+ -> { 1::CS_CONST }.should raise_error(TypeError)
+ -> { "mod"::CS_CONST }.should raise_error(TypeError)
+ -> { false::CS_CONST }.should raise_error(TypeError)
+ end
+end
+
+describe "Constant resolution within methods" do
+ describe "with statically assigned constants" do
+ it "searches the immediate class or module scope first" do
+ ConstantSpecs::ClassA.const10.should == :const10_10
+ ConstantSpecs::ParentA.const10.should == :const10_5
+ ConstantSpecs::ContainerA.const10.should == :const10_2
+ ConstantSpecs::ContainerA::ChildA.const10.should == :const10_3
+
+ ConstantSpecs::ClassA.new.const10.should == :const10_10
+ ConstantSpecs::ParentA.new.const10.should == :const10_5
+ ConstantSpecs::ContainerA::ChildA.new.const10.should == :const10_3
+ end
+
+ it "searches a module included in the immediate class before the superclass" do
+ ConstantSpecs::ContainerA::ChildA.const15.should == :const15_1
+ ConstantSpecs::ContainerA::ChildA.new.const15.should == :const15_1
+ end
+
+ it "searches the superclass before a module included in the superclass" do
+ ConstantSpecs::ContainerA::ChildA.const11.should == :const11_1
+ ConstantSpecs::ContainerA::ChildA.new.const11.should == :const11_1
+ end
+
+ it "searches a module included in the superclass" do
+ ConstantSpecs::ContainerA::ChildA.const12.should == :const12_1
+ ConstantSpecs::ContainerA::ChildA.new.const12.should == :const12_1
+ end
+
+ it "searches the superclass chain" do
+ ConstantSpecs::ContainerA::ChildA.const13.should == :const13
+ ConstantSpecs::ContainerA::ChildA.new.const13.should == :const13
+ end
+
+ it "searches the lexical scope of the method not the receiver's immediate class" do
+ ConstantSpecs::ContainerA::ChildA.const19.should == :const19_1
+ end
+
+ it "searches the lexical scope of a singleton method" do
+ ConstantSpecs::CS_CONST18.const17.should == :const17_1
+ end
+
+ it "does not search the lexical scope of the caller" do
+ -> { ConstantSpecs::ClassA.const16 }.should raise_error(NameError)
+ end
+
+ it "searches the lexical scope of a block" do
+ ConstantSpecs::ClassA.const22.should == :const22_1
+ end
+
+ it "searches Object as a lexical scope only if Object is explicitly opened" do
+ ConstantSpecs::ContainerA::ChildA.const20.should == :const20_1
+ ConstantSpecs::ContainerA::ChildA.const21.should == :const21_1
+ end
+
+ it "does not search the lexical scope of qualifying modules" do
+ -> do
+ ConstantSpecs::ContainerA::ChildA.const23
+ end.should raise_error(NameError)
+ end
+ end
+
+ describe "with dynamically assigned constants" do
+ it "searches the immediate class or module scope first" do
+ ConstantSpecs::ModuleA::CS_CONST201 = :const201_1
+
+ class ConstantSpecs::ClassB; CS_CONST201 = :const201_2; end
+ ConstantSpecs::ParentB::CS_CONST201 = :const201_3
+ ConstantSpecs::ContainerB::CS_CONST201 = :const201_4
+ ConstantSpecs::ContainerB::ChildB::CS_CONST201 = :const201_5
+
+ ConstantSpecs::ClassB.const201.should == :const201_2
+ ConstantSpecs::ParentB.const201.should == :const201_3
+ ConstantSpecs::ContainerB.const201.should == :const201_4
+ ConstantSpecs::ContainerB::ChildB.const201.should == :const201_5
+
+ 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
+ ConstantSpecs::ParentB::CS_CONST202 = :const202_2
+ ConstantSpecs::ContainerB::ChildB::CS_CONST202 = :const202_1
+
+ 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
+ ConstantSpecs::ParentB::CS_CONST203 = :const203_1
+ ConstantSpecs::ModuleE::CS_CONST203 = :const203_2
+
+ 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
+ ConstantSpecs::ModuleA::CS_CONST204 = :const204_2
+ ConstantSpecs::ModuleE::CS_CONST204 = :const204_1
+
+ 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
+ ConstantSpecs::ModuleA::CS_CONST205 = :const205
+
+ 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
+ ConstantSpecs::ContainerB::ChildB::CS_CONST206 = :const206_2
+ class ConstantSpecs::ContainerB::ChildB
+ class << self
+ CS_CONST206 = :const206_1
+ end
+ 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
+ ConstantSpecs::CS_CONST207 = :const207_1
+ ConstantSpecs::ClassB::CS_CONST207 = :const207_2
+
+ ConstantSpecs::CS_CONST208.const207.should == :const207_1
+ ensure
+ ConstantSpecs.send(:remove_const, :CS_CONST207)
+ ConstantSpecs::ClassB.send(:remove_const, :CS_CONST207)
+ end
+
+ it "does not search the lexical scope of the caller" do
+ ConstantSpecs::ClassB::CS_CONST209 = :const209
+
+ -> { ConstantSpecs::ClassB.const209 }.should raise_error(NameError)
+ ensure
+ ConstantSpecs::ClassB.send(:remove_const, :CS_CONST209)
+ end
+
+ it "searches the lexical scope of a block" do
+ ConstantSpecs::ClassB::CS_CONST210 = :const210_1
+ 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
+ Object::CS_CONST211 = :const211_1
+ ConstantSpecs::ParentB::CS_CONST211 = :const211_2
+ ConstantSpecs::ContainerB::ChildB.const211.should == :const211_1
+
+ 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
+ ConstantSpecs::ParentB::CS_CONST213 = :const213_1
+ ConstantSpecs::ContainerB::ChildB.const213.should == :const213_1
+ ConstantSpecs::ContainerB::ChildB.new.const213.should == :const213_1
+
+ -> {
+ ConstantSpecs::ParentB::CS_CONST213 = :const213_2
+ }.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
+
+ -> 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
+ -> { 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
+end
+
+describe "Constant resolution within a singleton class (class << obj)" do
+ it "works like normal classes or modules" do
+ ConstantSpecs::CS_SINGLETON1.foo.should == 1
+ 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 "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
+ 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)
+ end
+end
+
+describe "Module#private_constant marked constants" do
+
+ it "remain private even when updated" do
+ mod = Module.new
+ mod.const_set :Foo, true
+ mod.send :private_constant, :Foo
+ -> {
+ mod.const_set :Foo, false
+ }.should complain(/already initialized constant/)
+
+ -> {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
+ -> 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
+ -> 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
+ -> do
+ class ConstantVisibility::ModuleContainer::PrivateClass; end
+ end.should raise_error(NameError)
+ end
+
+ it "can be reopened as a module where constant is not private" do
+ module ::ConstantVisibility::ModuleContainer
+ module PrivateModule
+ X = 1
+ end
+
+ 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
+ module ::ConstantVisibility::ModuleContainer
+ class PrivateClass
+ X = 1
+ end
+
+ 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
+ defined?(ConstantVisibility::PrivConstModule::PRIVATE_CONSTANT_MODULE).should == nil
+ end
+
+ it "can be accessed from the module itself" do
+ ConstantVisibility::PrivConstModule.private_constant_from_self.should be_true
+ end
+
+ it "is defined? from the module itself" do
+ ConstantVisibility::PrivConstModule.defined_from_self.should == "constant"
+ end
+
+ it "can be accessed from lexical scope" do
+ ConstantVisibility::PrivConstModule::Nested.private_constant_from_scope.should be_true
+ end
+
+ it "is defined? from lexical scope" do
+ ConstantVisibility::PrivConstModule::Nested.defined_from_scope.should == "constant"
+ end
+
+ it "can be accessed from classes that include the module" do
+ 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::ClassIncludingPrivConstModule.new.defined_from_include.should == "constant"
+ end
+ end
+
+ describe "in a class" do
+ it "cannot be accessed from outside the class" do
+ -> do
+ ConstantVisibility::PrivConstClass::PRIVATE_CONSTANT_CLASS
+ end.should raise_error(NameError)
+ end
+
+ it "cannot be reopened as a module" do
+ -> do
+ module ConstantVisibility::ClassContainer::PrivateModule; end
+ end.should raise_error(NameError)
+ end
+
+ it "cannot be reopened as a class" do
+ -> do
+ class ConstantVisibility::ClassContainer::PrivateClass; end
+ end.should raise_error(NameError)
+ end
+
+ it "can be reopened as a module where constant is not private" do
+ class ::ConstantVisibility::ClassContainer
+ module PrivateModule
+ X = 1
+ end
+
+ 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
+ class ::ConstantVisibility::ClassContainer
+ class PrivateClass
+ X = 1
+ end
+
+ 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
+ defined?(ConstantVisibility::PrivConstClass::PRIVATE_CONSTANT_CLASS).should == nil
+ end
+
+ it "can be accessed from the class itself" do
+ ConstantVisibility::PrivConstClass.private_constant_from_self.should be_true
+ end
+
+ it "is defined? from the class itself" do
+ ConstantVisibility::PrivConstClass.defined_from_self.should == "constant"
+ end
+
+ it "can be accessed from lexical scope" do
+ ConstantVisibility::PrivConstClass::Nested.private_constant_from_scope.should be_true
+ end
+
+ it "is defined? from lexical scope" do
+ ConstantVisibility::PrivConstClass::Nested.defined_from_scope.should == "constant"
+ end
+
+ it "can be accessed from subclasses" do
+ ConstantVisibility::PrivConstClassChild.new.private_constant_from_subclass.should be_true
+ end
+
+ it "is defined? from subclasses" do
+ ConstantVisibility::PrivConstClassChild.new.defined_from_subclass.should == "constant"
+ end
+ end
+
+ describe "in Object" do
+ it "cannot be accessed using ::Const form" do
+ -> do
+ ::PRIVATE_CONSTANT_IN_OBJECT
+ end.should raise_error(NameError)
+ end
+
+ it "is not defined? using ::Const form" do
+ defined?(::PRIVATE_CONSTANT_IN_OBJECT).should == nil
+ end
+
+ it "can be accessed through the normal search" do
+ PRIVATE_CONSTANT_IN_OBJECT.should == true
+ end
+
+ it "is defined? through the normal search" 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
+ before :each do
+ @module = ConstantVisibility::PrivConstModule.dup
+ end
+
+ describe "in a module" do
+ it "can be accessed from outside the module" do
+ @module.send :public_constant, :PRIVATE_CONSTANT_MODULE
+ @module::PRIVATE_CONSTANT_MODULE.should == true
+ end
+
+ it "is defined? with A::B form" do
+ @module.send :public_constant, :PRIVATE_CONSTANT_MODULE
+ defined?(@module::PRIVATE_CONSTANT_MODULE).should == "constant"
+ end
+ end
+
+ describe "in a class" do
+ before :each do
+ @class = ConstantVisibility::PrivConstClass.dup
+ end
+
+ it "can be accessed from outside the class" do
+ @class.send :public_constant, :PRIVATE_CONSTANT_CLASS
+ @class::PRIVATE_CONSTANT_CLASS.should == true
+ end
+
+ it "is defined? with A::B form" do
+ @class.send :public_constant, :PRIVATE_CONSTANT_CLASS
+ defined?(@class::PRIVATE_CONSTANT_CLASS).should == "constant"
+ end
+ end
+
+ describe "in Object" do
+ after :each do
+ ConstantVisibility.reset_private_constants
+ end
+
+ it "can be accessed using ::Const form" do
+ Object.send :public_constant, :PRIVATE_CONSTANT_IN_OBJECT
+ ::PRIVATE_CONSTANT_IN_OBJECT.should == true
+ end
+
+ it "is defined? using ::Const form" do
+ Object.send :public_constant, :PRIVATE_CONSTANT_IN_OBJECT
+ defined?(::PRIVATE_CONSTANT_IN_OBJECT).should == "constant"
+ 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
new file mode 100644
index 0000000000..0cf1790791
--- /dev/null
+++ b/spec/ruby/language/def_spec.rb
@@ -0,0 +1,815 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/def'
+
+# Language-level method behaviour
+describe "Redefining a method" do
+ it "replaces the original method" do
+ def barfoo; 100; end
+ barfoo.should == 100
+
+ def barfoo; 200; end
+ barfoo.should == 200
+ end
+end
+
+describe "Defining a method at the top-level" do
+ it "defines it on Object with private visibility by default" do
+ Object.should have_private_instance_method(:some_toplevel_method, false)
+ end
+
+ it "defines it on Object with public visibility after calling public" do
+ Object.should have_public_instance_method(:public_toplevel_method, false)
+ end
+end
+
+describe "Defining an 'initialize' method" do
+ it "sets the method's visibility to private" do
+ class DefInitializeSpec
+ def initialize
+ end
+ end
+ DefInitializeSpec.should have_private_instance_method(:initialize, false)
+ end
+end
+
+describe "Defining an 'initialize_copy' method" do
+ it "sets the method's visibility to private" do
+ class DefInitializeCopySpec
+ def initialize_copy
+ end
+ end
+ DefInitializeCopySpec.should have_private_instance_method(:initialize_copy, false)
+ end
+end
+
+describe "Defining an 'initialize_dup' method" do
+ it "sets the method's visibility to private" do
+ class DefInitializeDupSpec
+ def initialize_dup
+ end
+ end
+ DefInitializeDupSpec.should have_private_instance_method(:initialize_dup, false)
+ end
+end
+
+describe "Defining an 'initialize_clone' method" do
+ it "sets the method's visibility to private" do
+ class DefInitializeCloneSpec
+ def initialize_clone
+ end
+ end
+ DefInitializeCloneSpec.should have_private_instance_method(:initialize_clone, false)
+ end
+end
+
+describe "Defining a 'respond_to_missing?' method" do
+ it "sets the method's visibility to private" do
+ class DefRespondToMissingPSpec
+ def respond_to_missing?
+ end
+ end
+ DefRespondToMissingPSpec.should have_private_instance_method(:respond_to_missing?, false)
+ end
+end
+
+describe "Defining a method" do
+ it "returns a symbol of the method name" do
+ method_name = def some_method; end
+ method_name.should == :some_method
+ 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;
+
+ foo.should == nil
+ foo(1, 2).should == nil
+ foo(1, 2, 3, 4, :a, :b, 'c', 'd').should == nil
+ end
+
+ it "accepts a named * argument" do
+ def foo(*a); a; end;
+ foo.should == []
+ foo(1, 2).should == [1, 2]
+ foo([:a]).should == [[:a]]
+ end
+
+ it "accepts non-* arguments before the * argument" do
+ def foo(a, b, c, d, e, *f); [a, b, c, d, e, f]; end
+ foo(1, 2, 3, 4, 5, 6, 7, 8).should == [1, 2, 3, 4, 5, [6, 7, 8]]
+ end
+
+ it "allows only a single * argument" do
+ -> { 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
+ -> { foo 1 }.should raise_error(ArgumentError, 'wrong number of arguments (given 1, expected 2+)')
+ end
+end
+
+describe "An instance method with a default argument" do
+ it "evaluates the default when no arguments are passed" do
+ def foo(a = 1)
+ a
+ end
+ foo.should == 1
+ foo(2).should == 2
+ end
+
+ it "evaluates the default empty expression when no arguments are passed" do
+ def foo(a = ())
+ a
+ end
+ foo.should == nil
+ foo(2).should == 2
+ end
+
+ it "assigns an empty Array to an unused splat argument" do
+ def foo(a = 1, *b)
+ [a,b]
+ end
+ foo.should == [1, []]
+ foo(2).should == [2, []]
+ end
+
+ it "evaluates the default when required arguments precede it" do
+ def foo(a, b = 2)
+ [a,b]
+ end
+ -> { foo }.should raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1..2)')
+ foo(1).should == [1, 2]
+ end
+
+ it "prefers to assign to a default argument before a splat argument" do
+ def foo(a, b = 2, *c)
+ [a,b,c]
+ end
+ -> { foo }.should raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1+)')
+ foo(1).should == [1,2,[]]
+ end
+
+ it "prefers to assign to a default argument when there are no required arguments" do
+ def foo(a = 1, *args)
+ [a,args]
+ end
+ foo(2,2).should == [2,[2]]
+ end
+
+ it "does not evaluate the default when passed a value and a * argument" do
+ def foo(a, b = 2, *args)
+ [a,b,args]
+ end
+ foo(2,3,3).should == [2,3,[3]]
+ end
+
+ ruby_version_is ""..."3.4" do
+ it "raises a SyntaxError if using the argument in its default value" do
+ -> {
+ eval "def foo(bar = bar)
+ bar
+ end"
+ }.should 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
+ end
+
+ it "calls a method with the same name as the local when explicitly using ()" do
+ def bar
+ 1
+ end
+ def foo(bar = bar())
+ bar
+ end
+ foo.should == 1
+ foo(2).should == 2
+ end
+end
+
+describe "A singleton method definition" do
+ it "can be declared for a local variable" do
+ a = Object.new
+ def a.foo
+ 5
+ end
+ a.foo.should == 5
+ end
+
+ it "can be declared for an instance variable" do
+ @a = Object.new
+ def @a.foo
+ 6
+ end
+ @a.foo.should == 6
+ end
+
+ it "can be declared for a global variable" do
+ $__a__ = +"hi"
+ def $__a__.foo
+ 7
+ end
+ $__a__.foo.should == 7
+ end
+
+ it "can be declared with an empty method body" do
+ class DefSpec
+ def self.foo;end
+ end
+ DefSpec.foo.should == nil
+ end
+
+ it "can be redefined" do
+ obj = Object.new
+ def obj.==(other)
+ 1
+ end
+ (obj==1).should == 1
+ def obj.==(other)
+ 2
+ end
+ (obj==2).should == 2
+ end
+
+ 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
+ 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
+
+describe "Redefining a singleton method" do
+ it "does not inherit a previously set visibility" do
+ o = Object.new
+
+ class << o; private; def foo; end; end;
+
+ class << o; should have_private_instance_method(:foo); end
+
+ class << o; def foo; end; end;
+
+ class << o; should_not have_private_instance_method(:foo); end
+ class << o; should have_instance_method(:foo); end
+
+ end
+end
+
+describe "Redefining a singleton method" do
+ it "does not inherit a previously set visibility" do
+ o = Object.new
+
+ class << o; private; def foo; end; end;
+
+ class << o; should have_private_instance_method(:foo); end
+
+ class << o; def foo; end; end;
+
+ class << o; should_not have_private_instance_method(:foo); end
+ class << o; should have_instance_method(:foo); end
+
+ end
+end
+
+describe "A method defined with extreme default arguments" do
+ it "can redefine itself when the default is evaluated" do
+ class DefSpecs
+ def foo(x = (def foo; "hello"; end;1));x;end
+ end
+
+ d = DefSpecs.new
+ d.foo(42).should == 42
+ d.foo.should == 1
+ d.foo.should == 'hello'
+ end
+
+ it "may use an fcall as a default" do
+ def bar
+ 1
+ end
+ def foo(x = bar())
+ x
+ end
+ foo.should == 1
+ foo(2).should == 2
+ end
+
+ it "evaluates the defaults in the method's scope" do
+ def foo(x = ($foo_self = self; nil)); end
+ foo
+ $foo_self.should == self
+ end
+
+ it "may use preceding arguments as defaults" do
+ def foo(obj, width=obj.length)
+ width
+ end
+ foo('abcde').should == 5
+ end
+
+ it "may use a lambda as a default" do
+ def foo(output = 'a', prc = -> n { output * n })
+ prc.call(5)
+ end
+ foo.should == 'aaaaa'
+ end
+end
+
+describe "A singleton method defined with extreme default arguments" do
+ it "may use a method definition as a default" do
+ $__a = Object.new
+ def $__a.foo(x = (def $__a.foo; "hello"; end;1));x;end
+
+ $__a.foo(42).should == 42
+ $__a.foo.should == 1
+ $__a.foo.should == 'hello'
+ end
+
+ it "may use an fcall as a default" do
+ a = Object.new
+ def a.bar
+ 1
+ end
+ def a.foo(x = bar())
+ x
+ end
+ a.foo.should == 1
+ a.foo(2).should == 2
+ end
+
+ it "evaluates the defaults in the singleton scope" do
+ a = Object.new
+ def a.foo(x = ($foo_self = self; nil)); 5 ;end
+ a.foo
+ $foo_self.should == a
+ end
+
+ it "may use preceding arguments as defaults" do
+ a = Object.new
+ def a.foo(obj, width=obj.length)
+ width
+ end
+ a.foo('abcde').should == 5
+ end
+
+ it "may use a lambda as a default" do
+ a = Object.new
+ def a.foo(output = 'a', prc = -> n { output * n })
+ prc.call(5)
+ end
+ a.foo.should == 'aaaaa'
+ end
+end
+
+describe "A method definition inside a metaclass scope" do
+ it "can create a class method" do
+ class DefSpecSingleton
+ class << self
+ def a_class_method;self;end
+ end
+ end
+
+ DefSpecSingleton.a_class_method.should == DefSpecSingleton
+ -> { Object.a_class_method }.should raise_error(NoMethodError)
+ end
+
+ it "can create a singleton method" do
+ obj = Object.new
+ class << obj
+ def a_singleton_method;self;end
+ end
+
+ obj.a_singleton_method.should == obj
+ -> { Object.new.a_singleton_method }.should raise_error(NoMethodError)
+ end
+
+ it "raises FrozenError if frozen" do
+ obj = Object.new
+ obj.freeze
+
+ class << obj
+ -> { def foo; end }.should raise_error(FrozenError)
+ end
+ end
+end
+
+describe "A nested method definition" do
+ it "creates an instance method when evaluated in an instance method" do
+ class DefSpecNested
+ def create_instance_method
+ def an_instance_method;self;end
+ an_instance_method
+ end
+ end
+
+ obj = DefSpecNested.new
+ obj.create_instance_method.should == obj
+ obj.an_instance_method.should == obj
+
+ other = DefSpecNested.new
+ other.an_instance_method.should == other
+
+ DefSpecNested.should have_instance_method(:an_instance_method)
+ end
+
+ it "creates a class method when evaluated in a class method" do
+ class DefSpecNested
+ class << self
+ # cleanup
+ remove_method :a_class_method if method_defined? :a_class_method
+ def create_class_method
+ def a_class_method;self;end
+ a_class_method
+ end
+ end
+ end
+
+ -> { DefSpecNested.a_class_method }.should raise_error(NoMethodError)
+ DefSpecNested.create_class_method.should == DefSpecNested
+ DefSpecNested.a_class_method.should == DefSpecNested
+ -> { Object.a_class_method }.should raise_error(NoMethodError)
+ -> { DefSpecNested.new.a_class_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a singleton method when evaluated in the metaclass of an instance" do
+ class DefSpecNested
+ def create_singleton_method
+ class << self
+ def a_singleton_method;self;end
+ end
+ a_singleton_method
+ end
+ end
+
+ obj = DefSpecNested.new
+ obj.create_singleton_method.should == obj
+ obj.a_singleton_method.should == obj
+
+ other = DefSpecNested.new
+ -> { 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
+ class DefSpecNested
+ TARGET = Object.new
+ def TARGET.defs_method
+ def inherited_method;self;end
+ end
+ end
+
+ DefSpecNested::TARGET.defs_method
+ DefSpecNested.should have_instance_method :inherited_method
+ DefSpecNested::TARGET.should_not have_method :inherited_method
+
+ obj = DefSpecNested.new
+ obj.inherited_method.should == obj
+ ensure
+ DefSpecNested.send(:remove_const, :TARGET)
+ end
+
+ # See http://yugui.jp/articles/846#label-3
+ it "inside an instance_eval creates a singleton method" do
+ class DefSpecNested
+ OBJ = Object.new
+ OBJ.instance_eval do
+ def create_method_in_instance_eval(a = (def arg_method; end))
+ def body_method; end
+ end
+ end
+ end
+
+ obj = DefSpecNested::OBJ
+ obj.create_method_in_instance_eval
+
+ obj.should have_method :arg_method
+ obj.should have_method :body_method
+
+ DefSpecNested.should_not have_instance_method :arg_method
+ DefSpecNested.should_not have_instance_method :body_method
+ ensure
+ DefSpecNested.send(:remove_const, :OBJ)
+ end
+
+ it "creates an instance method inside Class.new" do
+ cls = Class.new do
+ def do_def
+ def new_def
+ 1
+ end
+ end
+ end
+
+ 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
+
+describe "A method definition inside an instance_eval" do
+ it "creates a singleton method" do
+ obj = Object.new
+ obj.instance_eval do
+ def an_instance_eval_method;self;end
+ end
+ obj.an_instance_eval_method.should == obj
+
+ other = Object.new
+ -> { other.an_instance_eval_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a singleton method when evaluated inside a metaclass" do
+ obj = Object.new
+ obj.instance_eval do
+ class << self
+ def a_metaclass_eval_method;self;end
+ end
+ end
+ obj.a_metaclass_eval_method.should == obj
+
+ other = Object.new
+ -> { other.a_metaclass_eval_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a class method when the receiver is a class" do
+ DefSpecNested.instance_eval do
+ def an_instance_eval_class_method;self;end
+ end
+
+ DefSpecNested.an_instance_eval_class_method.should == DefSpecNested
+ -> { Object.an_instance_eval_class_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a class method when the receiver is an anonymous class" do
+ m = Class.new
+ m.instance_eval do
+ def klass_method
+ :test
+ end
+ end
+
+ m.klass_method.should == :test
+ -> { Object.klass_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a class method when instance_eval is within class" do
+ m = Class.new do
+ instance_eval do
+ def klass_method
+ :test
+ end
+ end
+ end
+
+ m.klass_method.should == :test
+ -> { Object.klass_method }.should raise_error(NoMethodError)
+ end
+end
+
+describe "A method definition inside an instance_exec" do
+ it "creates a class method when the receiver is a class" do
+ DefSpecNested.instance_exec(1) do |param|
+ @stuff = param
+
+ def an_instance_exec_class_method; @stuff; end
+ end
+
+ DefSpecNested.an_instance_exec_class_method.should == 1
+ -> { Object.an_instance_exec_class_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a class method when the receiver is an anonymous class" do
+ m = Class.new
+ m.instance_exec(1) do |param|
+ @stuff = param
+
+ def klass_method
+ @stuff
+ end
+ end
+
+ m.klass_method.should == 1
+ -> { Object.klass_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a class method when instance_exec is within class" do
+ m = Class.new do
+ instance_exec(2) do |param|
+ @stuff = param
+
+ def klass_method
+ @stuff
+ end
+ end
+ end
+
+ m.klass_method.should == 2
+ -> { Object.klass_method }.should raise_error(NoMethodError)
+ end
+end
+
+describe "A method definition in an eval" do
+ it "creates an instance method" do
+ class DefSpecNested
+ def eval_instance_method
+ eval "def an_eval_instance_method;self;end", binding
+ an_eval_instance_method
+ end
+ end
+
+ obj = DefSpecNested.new
+ obj.eval_instance_method.should == obj
+ obj.an_eval_instance_method.should == obj
+
+ other = DefSpecNested.new
+ other.an_eval_instance_method.should == other
+
+ -> { Object.new.an_eval_instance_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a class method" do
+ class DefSpecNestedB
+ class << self
+ def eval_class_method
+ eval "def an_eval_class_method;self;end" #, binding
+ an_eval_class_method
+ end
+ end
+ end
+
+ DefSpecNestedB.eval_class_method.should == DefSpecNestedB
+ DefSpecNestedB.an_eval_class_method.should == DefSpecNestedB
+
+ -> { Object.an_eval_class_method }.should raise_error(NoMethodError)
+ -> { DefSpecNestedB.new.an_eval_class_method}.should raise_error(NoMethodError)
+ end
+
+ it "creates a singleton method" do
+ class DefSpecNested
+ def eval_singleton_method
+ class << self
+ eval "def an_eval_singleton_method;self;end", binding
+ end
+ an_eval_singleton_method
+ end
+ end
+
+ obj = DefSpecNested.new
+ obj.eval_singleton_method.should == obj
+ obj.an_eval_singleton_method.should == obj
+
+ other = DefSpecNested.new
+ -> { other.an_eval_singleton_method }.should raise_error(NoMethodError)
+ end
+end
+
+describe "a method definition that sets more than one default parameter all to the same value" do
+ def foo(a=b=c={})
+ [a,b,c]
+ end
+ it "assigns them all the same object by default" do
+ foo.should == [{},{},{}]
+ a, b, c = foo
+ a.should eql(b)
+ a.should eql(c)
+ end
+
+ it "allows the first argument to be given, and sets the rest to null" do
+ foo(1).should == [1,nil,nil]
+ end
+
+ it "assigns the parameters different objects across different default calls" do
+ a, _b, _c = foo
+ d, _e, _f = foo
+ a.should_not equal(d)
+ end
+
+ it "only allows overriding the default value of the first such parameter in each set" do
+ -> { foo(1,2) }.should raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 0..1)')
+ end
+
+ def bar(a=b=c=1,d=2)
+ [a,b,c,d]
+ end
+
+ it "treats the argument after the multi-parameter normally" do
+ bar.should == [1,1,1,2]
+ bar(3).should == [3,nil,nil,2]
+ bar(3,4).should == [3,nil,nil,4]
+ -> { bar(3,4,5) }.should raise_error(ArgumentError, 'wrong number of arguments (given 3, expected 0..2)')
+ end
+end
+
+describe "The def keyword" do
+ describe "within a closure" do
+ it "looks outside the closure for the visibility" do
+ module DefSpecsLambdaVisibility
+ private
+
+ -> {
+ def some_method; end
+ }.call
+ end
+
+ DefSpecsLambdaVisibility.should have_private_instance_method("some_method")
+ end
+ end
+end
diff --git a/spec/ruby/language/defined_spec.rb b/spec/ruby/language/defined_spec.rb
new file mode 100644
index 0000000000..80ad1818b1
--- /dev/null
+++ b/spec/ruby/language/defined_spec.rb
@@ -0,0 +1,1312 @@
+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
+
+ 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?([NonExistentConstant, Array])
+ ret.should == nil
+ end
+
+ it "returns nil if all elements are not defined" do
+ ret = defined?([NonExistentConstant, AnotherNonExistentConstant])
+ ret.should == nil
+ end
+
+ end
+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
+ ret = defined?(puts)
+ ret.should == "method"
+ ret.frozen?.should == true
+ end
+
+ it "returns nil if the method is not defined" do
+ defined?(defined_specs_undefined_method).should be_nil
+ end
+
+ it "returns 'method' if the method is defined and private" do
+ obj = DefinedSpecs::Basic.new
+ obj.private_method_defined.should == "method"
+ end
+
+ it "returns 'method' if the predicate method is defined and private" do
+ obj = DefinedSpecs::Basic.new
+ obj.private_predicate_defined.should == "method"
+ end
+ end
+
+ describe "having a module as receiver" do
+ it "returns 'method' if the method is defined" do
+ defined?(Kernel.puts).should == "method"
+ end
+
+ it "returns nil if the method is private" do
+ defined?(Object.print).should be_nil
+ end
+
+ it "returns nil if the method is protected" do
+ defined?(DefinedSpecs::Basic.new.protected_method).should be_nil
+ end
+
+ it "returns nil if the method is not defined" do
+ defined?(Kernel.defined_specs_undefined_method).should be_nil
+ end
+
+ it "returns nil if the class is not defined" do
+ defined?(DefinedSpecsUndefined.puts).should be_nil
+ end
+
+ it "returns nil if the subclass is not defined" do
+ defined?(DefinedSpecs::Undefined.puts).should be_nil
+ end
+ end
+
+ describe "having a local variable as receiver" do
+ it "returns 'method' if the method is defined" do
+ obj = DefinedSpecs::Basic.new
+ 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
+ end
+
+ it "returns nil if the variable does not exist" do
+ defined?(nonexistent_local_variable.some_method).should be_nil
+ end
+
+ it "calls #respond_to_missing?" do
+ obj = mock("respond_to_missing object")
+ obj.should_receive(:respond_to_missing?).and_return(true)
+ defined?(obj.something_undefined).should == "method"
+ end
+ end
+
+ describe "having an instance variable as receiver" do
+ it "returns 'method' if the method is defined" do
+ @defined_specs_obj = DefinedSpecs::Basic.new
+ defined?(@defined_specs_obj.a_defined_method).should == "method"
+ end
+
+ it "returns nil if the method is not defined" do
+ @defined_specs_obj = DefinedSpecs::Basic.new
+ defined?(@defined_specs_obj.an_undefined_method).should be_nil
+ end
+
+ it "returns nil if the variable does not exist" do
+ defined?(@nonexistent_instance_variable.some_method).should be_nil
+ end
+ end
+
+ describe "having a global variable as receiver" do
+ it "returns 'method' if the method is defined" do
+ $defined_specs_obj = DefinedSpecs::Basic.new
+ defined?($defined_specs_obj.a_defined_method).should == "method"
+ end
+
+ it "returns nil if the method is not defined" do
+ $defined_specs_obj = DefinedSpecs::Basic.new
+ defined?($defined_specs_obj.an_undefined_method).should be_nil
+ end
+
+ it "returns nil if the variable does not exist" do
+ defined?($nonexistent_global_variable.some_method).should be_nil
+ end
+ end
+
+ describe "having a method call as a receiver" do
+ it "returns nil if evaluating the receiver raises an exception" do
+ defined?(DefinedSpecs.exception_method / 2).should be_nil
+ ScratchPad.recorded.should == :defined_specs_exception
+ end
+
+ it "returns nil if the method is not defined on the object the receiver returns" do
+ defined?(DefinedSpecs.side_effects / 2).should be_nil
+ ScratchPad.recorded.should == :defined_specs_side_effects
+ end
+
+ it "returns 'method' if the method is defined on the object the receiver returns" do
+ defined?(DefinedSpecs.fixnum_method / 2).should == "method"
+ 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
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "returns 'assignment' for assigning a local variable" do
+ ret = defined?(x = 2)
+ ret.should == "assignment"
+ ret.frozen?.should == true
+ end
+
+ it "returns 'assignment' for assigning an instance variable" do
+ defined?(@defined_specs_x = 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for assigning a global variable" do
+ defined?($defined_specs_x = 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for assigning a class variable" 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
+
+ it "returns 'assignment' for an expression with '%='" do
+ defined?(x %= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '/='" do
+ defined?(x /= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '-='" do
+ defined?(x -= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '+='" do
+ 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
+ defined?(x *= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '|='" do
+ defined?(x |= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '&='" do
+ defined?(x &= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '^='" do
+ defined?(x ^= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '~='" do
+ defined?(x = 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '<<='" do
+ defined?(x <<= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '>>='" do
+ defined?(x >>= 2).should == "assignment"
+ end
+
+ 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
+
+ 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"
+ end
+
+ it "returns nil for an expression with == and an undefined method" do
+ defined?(defined_specs_undefined_method == 2).should be_nil
+ end
+
+ it "returns nil for an expression with != and an undefined method" do
+ defined?(defined_specs_undefined_method != 2).should be_nil
+ end
+
+ it "returns nil for an expression with !~ and an undefined method" do
+ defined?(defined_specs_undefined_method !~ 2).should be_nil
+ end
+
+ it "returns 'method' for an expression with '=='" do
+ x = 42
+ defined?(x == 2).should == "method"
+ end
+
+ it "returns 'method' for an expression with '!='" do
+ x = 42
+ defined?(x != 2).should == "method"
+ end
+
+ it "returns 'method' for an expression with '!~'" do
+ x = 42
+ defined?(x !~ 2).should == "method"
+ end
+
+ describe "with logical connectives" do
+ it "returns nil for an expression with '!' and an undefined method" do
+ defined?(!defined_specs_undefined_method).should be_nil
+ end
+
+ it "returns nil for an expression with '!' and an unset class variable" do
+ @result = eval("class singleton_class::A; defined?(!@@doesnt_exist) end", binding, __FILE__, __LINE__)
+ @result.should be_nil
+ end
+
+ it "returns nil for an expression with 'not' and an undefined method" do
+ defined?(not defined_specs_undefined_method).should be_nil
+ end
+
+ it "returns nil for an expression with 'not' and an unset class variable" do
+ @result = eval("class singleton_class::A; defined?(not @@doesnt_exist) end", binding, __FILE__, __LINE__)
+ @result.should be_nil
+ end
+
+ it "does not propagate an exception raised by a method in a 'not' expression" do
+ defined?(not DefinedSpecs.exception_method).should be_nil
+ ScratchPad.recorded.should == :defined_specs_exception
+ end
+
+ it "returns 'expression' for an expression with '&&/and' and an unset global variable" do
+ defined?($defined_specs_undefined_global_variable && true).should == "expression"
+ defined?(true && $defined_specs_undefined_global_variable).should == "expression"
+ defined?($defined_specs_undefined_global_variable and true).should == "expression"
+ end
+
+ it "returns 'expression' for an expression with '&&/and' and an unset instance variable" do
+ defined?(@defined_specs_undefined_instance_variable && true).should == "expression"
+ defined?(true && @defined_specs_undefined_instance_variable).should == "expression"
+ defined?(@defined_specs_undefined_instance_variable and true).should == "expression"
+ end
+
+ it "returns 'expression' for an expression '&&/and' regardless of its truth value" do
+ defined?(true && false).should == "expression"
+ defined?(true and false).should == "expression"
+ end
+
+ it "returns 'expression' for an expression with '||/or' and an unset global variable" do
+ defined?($defined_specs_undefined_global_variable || true).should == "expression"
+ defined?(true || $defined_specs_undefined_global_variable).should == "expression"
+ defined?($defined_specs_undefined_global_variable or true).should == "expression"
+ end
+
+ it "returns 'expression' for an expression with '||/or' and an unset instance variable" do
+ defined?(@defined_specs_undefined_instance_variable || true).should == "expression"
+ defined?(true || @defined_specs_undefined_instance_variable).should == "expression"
+ defined?(@defined_specs_undefined_instance_variable or true).should == "expression"
+ end
+
+ it "returns 'expression' for an expression '||/or' regardless of its truth value" do
+ defined?(true || false).should == "expression"
+ defined?(true or false).should == "expression"
+ end
+
+ it "returns nil for an expression with '!' and an unset global variable" do
+ defined?(!$defined_specs_undefined_global_variable).should be_nil
+ end
+
+ it "returns nil for an expression with '!' and an unset instance variable" do
+ defined?(!@defined_specs_undefined_instance_variable).should be_nil
+ end
+
+ it "returns 'method' for a 'not' expression with a method" do
+ defined?(not DefinedSpecs.side_effects).should == "method"
+ end
+
+ it "calls a method in a 'not' expression and returns 'method'" do
+ defined?(not DefinedSpecs.side_effects).should == "method"
+ ScratchPad.recorded.should == :defined_specs_side_effects
+ end
+
+ it "returns nil for an expression with 'not' and an unset global variable" do
+ defined?(not $defined_specs_undefined_global_variable).should be_nil
+ end
+
+ it "returns nil for an expression with 'not' and an unset instance variable" do
+ defined?(not @defined_specs_undefined_instance_variable).should be_nil
+ end
+
+ it "returns 'expression' for an expression with '&&/and' and an undefined method" do
+ defined?(defined_specs_undefined_method && true).should == "expression"
+ defined?(defined_specs_undefined_method and true).should == "expression"
+ end
+
+ it "returns 'expression' for an expression with '&&/and' and an unset class variable" do
+ defined?(@@defined_specs_undefined_class_variable && true).should == "expression"
+ defined?(@@defined_specs_undefined_class_variable and true).should == "expression"
+ end
+
+ it "does not call a method in an '&&' expression and returns 'expression'" do
+ defined?(DefinedSpecs.side_effects && true).should == "expression"
+ ScratchPad.recorded.should be_nil
+ end
+
+ it "does not call a method in an 'and' expression and returns 'expression'" do
+ defined?(DefinedSpecs.side_effects and true).should == "expression"
+ ScratchPad.recorded.should be_nil
+ end
+
+ it "returns 'expression' for an expression with '||/or' and an undefined method" do
+ defined?(defined_specs_undefined_method || true).should == "expression"
+ defined?(defined_specs_undefined_method or true).should == "expression"
+ end
+
+ it "returns 'expression' for an expression with '||/or' and an unset class variable" do
+ defined?(@@defined_specs_undefined_class_variable || true).should == "expression"
+ defined?(@@defined_specs_undefined_class_variable or true).should == "expression"
+ end
+
+ it "does not call a method in an '||' expression and returns 'expression'" do
+ defined?(DefinedSpecs.side_effects || true).should == "expression"
+ ScratchPad.recorded.should be_nil
+ end
+
+ it "does not call a method in an 'or' expression and returns 'expression'" do
+ defined?(DefinedSpecs.side_effects or true).should == "expression"
+ ScratchPad.recorded.should be_nil
+ end
+ end
+
+ it "returns 'expression' when passed a String" do
+ defined?("garble gooble gable").should == "expression"
+ end
+
+ describe "with a dynamic String" do
+ it "returns 'expression' when the String contains a literal" do
+ defined?("garble #{42}").should == "expression"
+ end
+
+ it "returns 'expression' when the String contains a call to a defined method" do
+ defined?("garble #{DefinedSpecs.side_effects}").should == "expression"
+ end
+
+ it "returns 'expression' when the String contains a call to an undefined method" do
+ defined?("garble #{DefinedSpecs.undefined_method}").should == "expression"
+ end
+
+ it "does not call the method in the String" do
+ defined?("garble #{DefinedSpecs.dynamic_string}").should == "expression"
+ ScratchPad.recorded.should be_nil
+ end
+ end
+
+ describe "with a dynamic Regexp" do
+ it "returns 'expression' when the Regexp contains a literal" do
+ defined?(/garble #{42}/).should == "expression"
+ end
+
+ it "returns 'expression' when the Regexp contains a call to a defined method" do
+ defined?(/garble #{DefinedSpecs.side_effects}/).should == "expression"
+ end
+
+ it "returns 'expression' when the Regexp contains a call to an undefined method" do
+ defined?(/garble #{DefinedSpecs.undefined_method}/).should == "expression"
+ end
+
+ it "does not call the method in the Regexp" do
+ defined?(/garble #{DefinedSpecs.dynamic_string}/).should == "expression"
+ ScratchPad.recorded.should be_nil
+ end
+ end
+
+ it "returns 'expression' when passed a Fixnum literal" do
+ defined?(42).should == "expression"
+ end
+
+ it "returns 'expression' when passed a Bignum literal" do
+ defined?(0xdead_beef_deed_feed).should == "expression"
+ end
+
+ it "returns 'expression' when passed a Float literal" do
+ defined?(1.5).should == "expression"
+ end
+
+ it "returns 'expression' when passed a Range literal" do
+ defined?(0..2).should == "expression"
+ end
+
+ it "returns 'expression' when passed a Regexp literal" do
+ defined?(/undefined/).should == "expression"
+ end
+
+ it "returns 'expression' when passed an Array literal" do
+ defined?([1, 2]).should == "expression"
+ end
+
+ it "returns 'expression' when passed a Hash literal" do
+ defined?({a: :b}).should == "expression"
+ end
+
+ it "returns 'expression' when passed a Symbol literal" do
+ defined?(:defined_specs).should == "expression"
+ end
+end
+
+describe "The defined? keyword for variables" do
+ it "returns 'local-variable' when called with the name of a local variable" do
+ 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
+ DefinedSpecs::Basic.new.local_variable_defined_nil.should == "local-variable"
+ end
+
+ it "returns nil for an instance variable that has not been read" do
+ DefinedSpecs::Basic.new.instance_variable_undefined.should be_nil
+ end
+
+ it "returns nil for an instance variable that has been read but not assigned to" do
+ DefinedSpecs::Basic.new.instance_variable_read.should be_nil
+ end
+
+ it "returns 'instance-variable' for an instance variable that has been assigned" do
+ 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
+ DefinedSpecs::Basic.new.instance_variable_defined_nil.should == "instance-variable"
+ end
+
+ it "returns nil for a global variable that has not been read" do
+ DefinedSpecs::Basic.new.global_variable_undefined.should be_nil
+ end
+
+ it "returns nil for a global variable that has been read but not assigned to" do
+ DefinedSpecs::Basic.new.global_variable_read.should be_nil
+ 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
+ # 'global-variable' even if no match has been done.
+
+ it "returns 'global-variable' for $!" do
+ defined?($!).should == "global-variable"
+ end
+
+ it "returns 'global-variable for $~" do
+ defined?($~).should == "global-variable"
+ end
+
+ describe "when a String does not match a Regexp" do
+ before :each do
+ "mis-matched" =~ /z(z)z/
+ end
+
+ it "returns 'global-variable' for $~" do
+ defined?($~).should == "global-variable"
+ end
+
+ it "returns nil for $&" do
+ defined?($&).should be_nil
+ end
+
+ it "returns nil for $`" do
+ defined?($`).should be_nil
+ end
+
+ it "returns nil for $'" do
+ defined?($').should be_nil
+ end
+
+ it "returns nil for $+" do
+ defined?($+).should be_nil
+ end
+
+ it "returns nil for any last match global" do
+ defined?($1).should be_nil
+ defined?($4).should be_nil
+ defined?($7).should be_nil
+ defined?($10).should be_nil
+ defined?($200).should be_nil
+ end
+ end
+
+ describe "when a String matches a Regexp" do
+ before :each do
+ "mis-matched" =~ /s(-)m(.)/
+ end
+
+ it "returns 'global-variable' for $~" do
+ defined?($~).should == "global-variable"
+ end
+
+ it "returns 'global-variable' for $&" do
+ defined?($&).should == "global-variable"
+ end
+
+ it "returns 'global-variable' for $`" do
+ defined?($`).should == "global-variable"
+ end
+
+ it "returns 'global-variable' for $'" do
+ defined?($').should == "global-variable"
+ end
+
+ it "returns 'global-variable' for $+" do
+ defined?($+).should == "global-variable"
+ end
+
+ it "returns 'global-variable' for the capture references" do
+ defined?($1).should == "global-variable"
+ defined?($2).should == "global-variable"
+ end
+
+ it "returns nil for non-captures" do
+ defined?($4).should be_nil
+ defined?($7).should be_nil
+ defined?($10).should be_nil
+ defined?($200).should be_nil
+ end
+ end
+
+ describe "when a Regexp does not match a String" do
+ before :each do
+ /z(z)z/ =~ "mis-matched"
+ end
+
+ it "returns 'global-variable' for $~" do
+ defined?($~).should == "global-variable"
+ end
+
+ it "returns nil for $&" do
+ defined?($&).should be_nil
+ end
+
+ it "returns nil for $`" do
+ defined?($`).should be_nil
+ end
+
+ it "returns nil for $'" do
+ defined?($').should be_nil
+ end
+
+ it "returns nil for $+" do
+ defined?($+).should be_nil
+ end
+
+ it "returns nil for any last match global" do
+ defined?($1).should be_nil
+ defined?($4).should be_nil
+ defined?($7).should be_nil
+ defined?($10).should be_nil
+ defined?($200).should be_nil
+ end
+ end
+
+ describe "when a Regexp matches a String" do
+ before :each do
+ /s(-)m(.)/ =~ "mis-matched"
+ end
+
+ it "returns 'global-variable' for $~" do
+ defined?($~).should == "global-variable"
+ end
+
+ it "returns 'global-variable' for $&" do
+ defined?($&).should == "global-variable"
+ end
+
+ it "returns 'global-variable' for $`" do
+ defined?($`).should == "global-variable"
+ end
+
+ it "returns 'global-variable' for $'" do
+ defined?($').should == "global-variable"
+ end
+
+ it "returns 'global-variable' for $+" do
+ defined?($+).should == "global-variable"
+ end
+
+ it "returns 'global-variable' for the capture references" do
+ defined?($1).should == "global-variable"
+ defined?($2).should == "global-variable"
+ end
+
+ it "returns nil for non-captures" do
+ defined?($4).should be_nil
+ defined?($7).should be_nil
+ defined?($10).should be_nil
+ defined?($200).should be_nil
+ end
+ end
+ it "returns 'global-variable' for a global variable that has been assigned" do
+ DefinedSpecs::Basic.new.global_variable_defined.should == "global-variable"
+ end
+
+ it "returns nil for a class variable that has not been read" do
+ DefinedSpecs::Basic.new.class_variable_undefined.should be_nil
+ end
+
+ # There is no spec for a class variable that is read before being assigned
+ # to because setting up the code for this raises a NameError before you
+ # 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
+ 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
+ block = Proc.new { |xxx| defined?(xxx) }
+ block.call(1).should == "local-variable"
+ end
+end
+
+describe "The defined? keyword for a simple constant" do
+ it "returns 'constant' when the constant is defined" do
+ ret = defined?(DefinedSpecs)
+ ret.should == "constant"
+ ret.frozen?.should == true
+ end
+
+ it "returns nil when the constant is not defined" do
+ defined?(DefinedSpecsUndefined).should be_nil
+ end
+
+ it "does not call Object.const_missing if the constant is not defined" do
+ Object.should_not_receive(:const_missing)
+ defined?(DefinedSpecsUndefined).should be_nil
+ end
+
+ it "returns 'constant' for an included module" do
+ DefinedSpecs::Child.module_defined.should == "constant"
+ end
+
+ it "returns 'constant' for a constant defined in an included module" do
+ DefinedSpecs::Child.module_constant_defined.should == "constant"
+ end
+end
+
+describe "The defined? keyword for a top-level constant" do
+ it "returns 'constant' when passed the name of a top-level constant" do
+ defined?(::DefinedSpecs).should == "constant"
+ end
+
+ it "returns nil if the constant is not defined" do
+ defined?(::DefinedSpecsUndefined).should be_nil
+ end
+
+ it "does not call Object.const_missing if the constant is not defined" do
+ Object.should_not_receive(:const_missing)
+ defined?(::DefinedSpecsUndefined).should be_nil
+ end
+end
+
+describe "The defined? keyword for a scoped constant" do
+ it "returns 'constant' when the scoped constant is defined" do
+ defined?(DefinedSpecs::Basic).should == "constant"
+ end
+
+ it "returns nil when the scoped constant is not defined" 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
+ end
+
+ it "returns nil when an undefined constant is scoped to a defined constant" do
+ defined?(DefinedSpecs::Child::Undefined).should be_nil
+ end
+
+ it "returns nil when a constant is scoped to an undefined constant" do
+ Object.should_not_receive(:const_missing)
+ defined?(Undefined::Object).should be_nil
+ end
+
+ it "returns nil when the undefined constant is scoped to an undefined constant" do
+ defined?(DefinedSpecs::Undefined::Undefined).should be_nil
+ end
+
+ it "returns nil when a constant is defined on top-level but not on the module" do
+ defined?(DefinedSpecs::String).should be_nil
+ 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
+ defined?(DefinedSpecs::Child::A).should == "constant"
+ end
+end
+
+describe "The defined? keyword for a top-level scoped constant" do
+ it "returns 'constant' when the scoped constant is defined" do
+ defined?(::DefinedSpecs::Basic).should == "constant"
+ end
+
+ it "returns nil when the scoped constant is not defined" do
+ defined?(::DefinedSpecs::Undefined).should be_nil
+ end
+
+ it "returns nil when an undefined constant is scoped to a defined constant" do
+ defined?(::DefinedSpecs::Child::Undefined).should be_nil
+ end
+
+ it "returns nil when the undefined constant is scoped to an undefined constant" do
+ defined?(::DefinedSpecs::Undefined::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if the scoped-scoped constant is defined" do
+ defined?(::DefinedSpecs::Child::A).should == "constant"
+ end
+end
+
+describe "The defined? keyword for a self-send method call scoped constant" do
+ it "returns nil if the constant is not defined in the scope of the method's value" do
+ defined?(defined_specs_method::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if the constant is defined in the scope of the method's value" do
+ defined?(defined_specs_method::Basic).should == "constant"
+ end
+
+ it "returns nil if the last constant is not defined in the scope chain" do
+ defined?(defined_specs_method::Basic::Undefined).should be_nil
+ end
+
+ it "returns nil if the middle constant is not defined in the scope chain" do
+ defined?(defined_specs_method::Undefined::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if all the constants in the scope chain are defined" do
+ defined?(defined_specs_method::Basic::A).should == "constant"
+ end
+end
+
+describe "The defined? keyword for a receiver method call scoped constant" do
+ it "returns nil if the constant is not defined in the scope of the method's value" do
+ defined?(defined_specs_receiver.defined_method::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if the constant is defined in the scope of the method's value" do
+ defined?(defined_specs_receiver.defined_method::Basic).should == "constant"
+ end
+
+ it "returns nil if the last constant is not defined in the scope chain" do
+ defined?(defined_specs_receiver.defined_method::Basic::Undefined).should be_nil
+ end
+
+ it "returns nil if the middle constant is not defined in the scope chain" do
+ defined?(defined_specs_receiver.defined_method::Undefined::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if all the constants in the scope chain are defined" do
+ defined?(defined_specs_receiver.defined_method::Basic::A).should == "constant"
+ end
+end
+
+describe "The defined? keyword for a module method call scoped constant" do
+ it "returns nil if the constant is not defined in the scope of the method's value" do
+ defined?(DefinedSpecs.defined_method::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if the constant scoped by the method's value is defined" do
+ defined?(DefinedSpecs.defined_method::Basic).should == "constant"
+ end
+
+ it "returns nil if the last constant in the scope chain is not defined" do
+ defined?(DefinedSpecs.defined_method::Basic::Undefined).should be_nil
+ end
+
+ it "returns nil if the middle constant in the scope chain is not defined" do
+ defined?(DefinedSpecs.defined_method::Undefined::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if all the constants in the scope chain are defined" do
+ defined?(DefinedSpecs.defined_method::Basic::A).should == "constant"
+ end
+
+ it "returns nil if the outer scope constant in the receiver is not defined" do
+ defined?(Undefined::DefinedSpecs.defined_method::Basic).should be_nil
+ end
+
+ it "returns nil if the scoped constant in the receiver is not defined" do
+ defined?(DefinedSpecs::Undefined.defined_method::Basic).should be_nil
+ end
+
+ it "returns 'constant' if all the constants in the receiver are defined" do
+ defined?(Object::DefinedSpecs.defined_method::Basic).should == "constant"
+ end
+
+ it "returns 'constant' if all the constants in the receiver and scope chain are defined" do
+ defined?(Object::DefinedSpecs.defined_method::Basic::A).should == "constant"
+ end
+end
+
+describe "The defined? keyword for a variable scoped constant" do
+ after :all do
+ if Object.class_variable_defined? :@@defined_specs_obj
+ Object.__send__(:remove_class_variable, :@@defined_specs_obj)
+ end
+ end
+
+ it "returns nil if the instance scoped constant is not defined" do
+ @defined_specs_obj = DefinedSpecs::Basic
+ defined?(@defined_specs_obj::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if the constant is defined in the scope of the instance variable" do
+ @defined_specs_obj = DefinedSpecs::Basic
+ defined?(@defined_specs_obj::A).should == "constant"
+ end
+
+ it "returns nil if the global scoped constant is not defined" do
+ $defined_specs_obj = DefinedSpecs::Basic
+ defined?($defined_specs_obj::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if the constant is defined in the scope of the global variable" do
+ $defined_specs_obj = DefinedSpecs::Basic
+ defined?($defined_specs_obj::A).should == "constant"
+ end
+
+ it "returns nil if the class scoped constant is not defined" do
+ 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
+ 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
+ defined_specs_obj = DefinedSpecs::Basic
+ defined?(defined_specs_obj::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if the constant is defined in the scope of the local variable" do
+ defined_specs_obj = DefinedSpecs::Basic
+ defined?(defined_specs_obj::A).should == "constant"
+ end
+end
+
+describe "The defined? keyword for a self:: scoped constant" do
+ it "returns 'constant' for a constant explicitly scoped to self:: when set" do
+ defined?(DefinedSpecs::SelfScoped).should == "constant"
+ end
+
+ it "returns 'constant' for a constant explicitly scoped to self:: in subclass's metaclass" do
+ DefinedSpecs::Child.parent_constant_defined.should == "constant"
+ end
+end
+
+describe "The defined? keyword for yield" do
+ it "returns nil if no block is passed to a method not taking a block parameter" do
+ DefinedSpecs::Basic.new.no_yield_block.should be_nil
+ end
+
+ it "returns nil if no block is passed to a method taking a block parameter" do
+ DefinedSpecs::Basic.new.no_yield_block_parameter.should be_nil
+ end
+
+ it "returns 'yield' if a block is passed to a method not taking a block parameter" do
+ ret = DefinedSpecs::Basic.new.yield_block
+ ret.should == "yield"
+ ret.frozen?.should == true
+ end
+
+ it "returns 'yield' if a block is passed to a method taking a block parameter" do
+ DefinedSpecs::Basic.new.yield_block_parameter.should == "yield"
+ end
+
+ it "returns 'yield' when called within a block" do
+ def yielder
+ yield
+ end
+
+ def call_defined
+ yielder { defined?(yield) }
+ end
+
+ call_defined() { }.should == "yield"
+ end
+end
+
+describe "The defined? keyword for super" do
+ it "returns nil when a superclass undef's the method" do
+ DefinedSpecs::ClassWithoutMethod.new.test.should be_nil
+ end
+
+ describe "for a method taking no arguments" do
+ it "returns nil when no superclass method exists" do
+ DefinedSpecs::Super.new.no_super_method_no_args.should be_nil
+ end
+
+ it "returns nil from a block when no superclass method exists" do
+ DefinedSpecs::Super.new.no_super_method_block_no_args.should be_nil
+ end
+
+ it "returns nil from a #define_method when no superclass method exists" do
+ DefinedSpecs::Super.new.no_super_define_method_no_args.should be_nil
+ end
+
+ it "returns nil from a block in a #define_method when no superclass method exists" do
+ DefinedSpecs::Super.new.no_super_define_method_block_no_args.should be_nil
+ end
+
+ it "returns 'super' when a superclass method exists" do
+ 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
+ DefinedSpecs::Super.new.method_block_no_args.should == "super"
+ end
+
+ it "returns 'super' from a #define_method when a superclass method exists" do
+ DefinedSpecs::Super.new.define_method_no_args.should == "super"
+ end
+
+ it "returns 'super' from a block in a #define_method when a superclass method exists" do
+ DefinedSpecs::Super.new.define_method_block_no_args.should == "super"
+ end
+
+ it "returns 'super' when the method exists in a supermodule" do
+ DefinedSpecs::SuperWithIntermediateModules.new.method_no_args.should == "super"
+ end
+ end
+
+ describe "for a method taking arguments" do
+ it "returns nil when no superclass method exists" do
+ DefinedSpecs::Super.new.no_super_method_args.should be_nil
+ end
+
+ it "returns nil from a block when no superclass method exists" do
+ DefinedSpecs::Super.new.no_super_method_block_args.should be_nil
+ end
+
+ it "returns nil from a #define_method when no superclass method exists" do
+ DefinedSpecs::Super.new.no_super_define_method_args.should be_nil
+ end
+
+ it "returns nil from a block in a #define_method when no superclass method exists" do
+ DefinedSpecs::Super.new.no_super_define_method_block_args.should be_nil
+ end
+
+ it "returns 'super' when a superclass method exists" do
+ DefinedSpecs::Super.new.method_args.should == "super"
+ end
+
+ it "returns 'super' from a block when a superclass method exists" do
+ DefinedSpecs::Super.new.method_block_args.should == "super"
+ end
+
+ it "returns 'super' from a #define_method when a superclass method exists" do
+ DefinedSpecs::Super.new.define_method_args.should == "super"
+ end
+
+ it "returns 'super' from a block in a #define_method when a superclass method exists" do
+ DefinedSpecs::Super.new.define_method_block_args.should == "super"
+ end
+ end
+
+ describe "within an included module's method" do
+ it "returns 'super' when a superclass method exists in the including hierarchy" do
+ DefinedSpecs::Child.new.defined_super.should == "super"
+ end
+ end
+end
+
+
+describe "The defined? keyword for instance variables" do
+ it "returns 'instance-variable' if assigned" do
+ @assigned_ivar = "some value"
+ defined?(@assigned_ivar).should == "instance-variable"
+ end
+
+ it "returns nil if not assigned" do
+ defined?(@unassigned_ivar).should be_nil
+ end
+end
+
+describe "The defined? keyword for pseudo-variables" do
+ it "returns 'expression' for __FILE__" do
+ defined?(__FILE__).should == "expression"
+ end
+
+ it "returns 'expression' for __LINE__" do
+ defined?(__LINE__).should == "expression"
+ end
+
+ it "returns 'expression' for __ENCODING__" do
+ defined?(__ENCODING__).should == "expression"
+ end
+end
+
+describe "The defined? keyword for conditional expressions" do
+ it "returns 'expression' for an 'if' conditional" do
+ defined?(if x then 'x' else '' end).should == "expression"
+ end
+
+ it "returns 'expression' for an 'unless' conditional" do
+ defined?(unless x then '' else 'x' end).should == "expression"
+ end
+
+ it "returns 'expression' for ternary expressions" do
+ defined?(x ? 'x' : '').should == "expression"
+ end
+end
+
+describe "The defined? keyword for case expressions" do
+ it "returns 'expression'" do
+ defined?(case x; when 'x'; 'y' end).should == "expression"
+ end
+end
+
+describe "The defined? keyword for loop expressions" do
+ it "returns 'expression' for a 'for' expression" do
+ defined?(for n in 1..3 do true end).should == "expression"
+ end
+
+ it "returns 'expression' for a 'while' expression" do
+ defined?(while x do y end).should == "expression"
+ end
+
+ it "returns 'expression' for an 'until' expression" do
+ defined?(until x do y end).should == "expression"
+ end
+
+ it "returns 'expression' for a 'break' expression" do
+ defined?(break).should == "expression"
+ end
+
+ it "returns 'expression' for a 'next' expression" do
+ defined?(next).should == "expression"
+ end
+
+ it "returns 'expression' for a 'redo' expression" do
+ defined?(redo).should == "expression"
+ end
+
+ it "returns 'expression' for a 'retry' expression" do
+ defined?(retry).should == "expression"
+ end
+end
+
+describe "The defined? keyword for return expressions" do
+ it "returns 'expression'" do
+ defined?(return).should == "expression"
+ end
+end
+
+describe "The defined? keyword for exception expressions" do
+ it "returns 'expression'" do
+ defined?(begin 1 end).should == "expression"
+ end
+end
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
new file mode 100644
index 0000000000..e761a53cb6
--- /dev/null
+++ b/spec/ruby/language/encoding_spec.rb
@@ -0,0 +1,36 @@
+# -*- encoding: us-ascii -*-
+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
+ __ENCODING__.should be_kind_of(Encoding)
+ end
+
+ it "is US-ASCII by default" do
+ __ENCODING__.should == Encoding::US_ASCII
+ end
+
+ it "is the evaluated strings's one inside an eval" do
+ eval("__ENCODING__".dup.force_encoding("US-ASCII")).should == Encoding::US_ASCII
+ eval("__ENCODING__".dup.force_encoding("BINARY")).should == Encoding::BINARY
+ end
+
+ it "is the encoding specified by a magic comment inside an eval" do
+ code = "# encoding: BINARY\n__ENCODING__".dup.force_encoding("US-ASCII")
+ eval(code).should == Encoding::BINARY
+
+ code = "# encoding: us-ascii\n__ENCODING__".dup.force_encoding("BINARY")
+ eval(code).should == Encoding::US_ASCII
+ end
+
+ it "is the encoding specified by a magic comment in the file" do
+ CodingUS_ASCII.encoding.should == Encoding::US_ASCII
+ CodingUTF_8.encoding.should == Encoding::UTF_8
+ end
+
+ it "raises a SyntaxError if assigned to" do
+ -> { 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
new file mode 100644
index 0000000000..b76292c007
--- /dev/null
+++ b/spec/ruby/language/ensure_spec.rb
@@ -0,0 +1,346 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/ensure'
+
+describe "An ensure block inside a begin block" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "is executed when an exception is raised in it's corresponding begin block" do
+ -> {
+ begin
+ ScratchPad << :begin
+ raise EnsureSpec::Error
+ ensure
+ ScratchPad << :ensure
+ end
+ }.should raise_error(EnsureSpec::Error)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "is executed when an exception is raised and rescued in it's corresponding begin block" do
+ begin
+ ScratchPad << :begin
+ raise "An exception occurred!"
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+
+ ScratchPad.recorded.should == [:begin, :rescue, :ensure]
+ end
+
+ it "is executed even when a symbol is thrown in it's corresponding begin block" do
+ catch(:symbol) do
+ begin
+ ScratchPad << :begin
+ throw(:symbol)
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "is executed when nothing is raised or thrown in it's corresponding begin block" do
+ begin
+ ScratchPad << :begin
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "has no return value" do
+ begin
+ :begin
+ ensure
+ :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
+ it "in no-exception scenarios, is the value of the last statement of the protected body" do
+ begin
+ v = 1
+ eval('x=1') # to prevent opts from triggering
+ v
+ ensure
+ v = 2
+ end.should == 1
+ end
+
+ it "when an exception is rescued, is the value of the rescuing block" do
+ begin
+ raise 'foo'
+ rescue
+ v = 3
+ ensure
+ v = 2
+ end.should == 3
+ end
+end
+
+describe "An ensure block inside a method" do
+ before :each do
+ @obj = EnsureSpec::Container.new
+ end
+
+ it "is executed when an exception is raised in the method" do
+ -> { @obj.raise_in_method_with_ensure }.should raise_error(EnsureSpec::Error)
+ @obj.executed.should == [:method, :ensure]
+ end
+
+ it "is executed when an exception is raised and rescued in the method" do
+ @obj.raise_and_rescue_in_method_with_ensure
+ @obj.executed.should == [:method, :rescue, :ensure]
+ end
+
+ it "is executed even when a symbol is thrown in the method" do
+ catch(:symbol) { @obj.throw_in_method_with_ensure }
+ @obj.executed.should == [:method, :ensure]
+ end
+
+ it "has no impact on the method's implicit return value" do
+ @obj.implicit_return_in_method_with_ensure.should == :method
+ end
+
+ 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
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "is executed when an exception is raised" do
+ -> {
+ eval <<-ruby
+ class EnsureInClassExample
+ ScratchPad << :class
+ raise EnsureSpec::Error
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+ }.should raise_error(EnsureSpec::Error)
+
+ ScratchPad.recorded.should == [:class, :ensure]
+ end
+
+ it "is executed when an exception is raised and rescued" do
+ eval <<-ruby
+ class EnsureInClassExample
+ ScratchPad << :class
+ raise
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+
+ ScratchPad.recorded.should == [:class, :rescue, :ensure]
+ end
+
+ it "is executed even when a symbol is thrown" do
+ catch(:symbol) do
+ eval <<-ruby
+ class EnsureInClassExample
+ ScratchPad << :class
+ throw(:symbol)
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+ end
+
+ ScratchPad.recorded.should == [:class, :ensure]
+ end
+
+ it "is executed when nothing is raised or thrown" do
+ eval <<-ruby
+ class EnsureInClassExample
+ ScratchPad << :class
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+
+ ScratchPad.recorded.should == [:class, :ensure]
+ end
+
+ it "has no return value" do
+ result = eval <<-ruby
+ class EnsureInClassExample
+ :class
+ ensure
+ :ensure
+ end
+ ruby
+
+ result.should == :class
+ end
+end
+
+describe "An ensure block inside {} block" do
+ it "is not allowed" do
+ -> {
+ eval <<-ruby
+ lambda {
+ raise
+ ensure
+ }
+ ruby
+ }.should raise_error(SyntaxError)
+ end
+end
+
+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
+ -> {
+ 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
+
+ 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, :rescue, :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
+ end
+
+ 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
+ return caller(0, 2) # rubocop:disable Lint/EnsureReturn
+ end
+ end
+ line = __LINE__
+ foo[0].should =~ /#{__FILE__}:#{line-3}:in 'foo'/
+ foo[1].should =~ /#{__FILE__}:#{line+2}:in 'block/
+ end
+ end
+end
diff --git a/spec/ruby/language/execution_spec.rb b/spec/ruby/language/execution_spec.rb
new file mode 100644
index 0000000000..51bcde62e8
--- /dev/null
+++ b/spec/ruby/language/execution_spec.rb
@@ -0,0 +1,93 @@
+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
+ it "is the same as ``" 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
new file mode 100644
index 0000000000..59563d9642
--- /dev/null
+++ b/spec/ruby/language/file_spec.rb
@@ -0,0 +1,29 @@
+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
+ -> { eval("__FILE__ = 1") }.should raise_error(SyntaxError)
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "equals (eval) inside an eval" do
+ eval("__FILE__").should == "(eval)"
+ end
+ end
+
+ 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 with require" do
+ it_behaves_like :language___FILE__, :require, Kernel
+end
+
+describe "The __FILE__ pseudo-variable with load" do
+ it_behaves_like :language___FILE__, :load, Kernel
+end
diff --git a/spec/ruby/language/fixtures/argv_encoding.rb b/spec/ruby/language/fixtures/argv_encoding.rb
new file mode 100644
index 0000000000..8192b2d9a0
--- /dev/null
+++ b/spec/ruby/language/fixtures/argv_encoding.rb
@@ -0,0 +1 @@
+p ARGV.map { |a| a.encoding.name }
diff --git a/spec/ruby/language/fixtures/array.rb b/spec/ruby/language/fixtures/array.rb
new file mode 100644
index 0000000000..c1036575ff
--- /dev/null
+++ b/spec/ruby/language/fixtures/array.rb
@@ -0,0 +1,32 @@
+module ArraySpec
+ class Splat
+ def unpack_3args(a, b, c)
+ [a, b, c]
+ end
+
+ def unpack_4args(a, b, c, d)
+ [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/begin_file.rb b/spec/ruby/language/fixtures/begin_file.rb
new file mode 100644
index 0000000000..73cae61a08
--- /dev/null
+++ b/spec/ruby/language/fixtures/begin_file.rb
@@ -0,0 +1,3 @@
+BEGIN {
+ puts __FILE__
+}
diff --git a/spec/ruby/language/fixtures/binary_symbol.rb b/spec/ruby/language/fixtures/binary_symbol.rb
new file mode 100644
index 0000000000..2ddf565820
--- /dev/null
+++ b/spec/ruby/language/fixtures/binary_symbol.rb
@@ -0,0 +1,4 @@
+# encoding: binary
+
+p :il_était.to_s.bytes
+puts :il_était.encoding.name
diff --git a/spec/ruby/language/fixtures/block.rb b/spec/ruby/language/fixtures/block.rb
new file mode 100644
index 0000000000..33baac6aeb
--- /dev/null
+++ b/spec/ruby/language/fixtures/block.rb
@@ -0,0 +1,61 @@
+module BlockSpecs
+ class Yielder
+ def z
+ yield
+ end
+
+ def m(*a)
+ yield(*a)
+ end
+
+ def s(a)
+ yield(a)
+ end
+
+ 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
+
+ class Yield
+ def splat(*args)
+ yield(*args)
+ end
+
+ def two_args
+ yield 1, 2
+ end
+
+ def two_arg_array
+ yield [1, 2]
+ end
+
+ def yield_splat_inside_block
+ [1, 2].send(:each_with_index) {|*args| yield(*args)}
+ end
+
+ def yield_this(obj)
+ yield obj
+ end
+ end
+
+ class OverwriteBlockVariable
+ def initialize
+ @y = Yielder.new
+ end
+
+ def method_missing(method, *args, &block)
+ self.class.send :define_method, method do |*a, &b|
+ @y.send method, *a, &b
+ end
+
+ send method, *args, &block
+ end
+ end
+end
diff --git a/spec/ruby/language/fixtures/break.rb b/spec/ruby/language/fixtures/break.rb
new file mode 100644
index 0000000000..217c20a2c0
--- /dev/null
+++ b/spec/ruby/language/fixtures/break.rb
@@ -0,0 +1,291 @@
+module BreakSpecs
+ class Driver
+ def initialize(ensures=false)
+ @ensures = ensures
+ end
+
+ def note(value)
+ ScratchPad << value
+ end
+ end
+
+ class Block < Driver
+ def break_nil
+ note :a
+ note yielding {
+ note :b
+ break
+ note :c
+ }
+ note :d
+ end
+
+ def break_value
+ note :a
+ note yielding {
+ note :b
+ break :break
+ note :c
+ }
+ note :d
+ end
+
+ def yielding
+ note :aa
+ note yield
+ note :bb
+ end
+
+ def create_block
+ note :za
+ b = capture_block do
+ note :zb
+ break :break
+ note :zc
+ end
+ note :zd
+ b
+ end
+
+ def capture_block(&b)
+ note :xa
+ b
+ end
+
+ def break_in_method_captured
+ note :a
+ create_block.call
+ note :b
+ end
+
+ def break_in_yield_captured
+ note :a
+ yielding(&create_block)
+ note :b
+ end
+
+ def break_in_method
+ note :a
+ b = capture_block {
+ note :b
+ break :break
+ note :c
+ }
+ note :d
+ note b.call
+ note :e
+ end
+
+ def call_method(b)
+ note :aa
+ note b.call
+ note :bb
+ end
+
+ def break_in_nested_method
+ note :a
+ b = capture_block {
+ note :b
+ break :break
+ note :c
+ }
+ note :cc
+ note call_method(b)
+ note :d
+ end
+
+ def break_in_yielding_method
+ note :a
+ b = capture_block {
+ note :b
+ break :break
+ note :c
+ }
+ note :cc
+ note yielding(&b)
+ note :d
+ end
+
+ def looped_break_in_captured_block
+ note :begin
+ looped_delegate_block do |i|
+ note :prebreak
+ break if i == 1
+ note :postbreak
+ end
+ note :end
+ end
+
+ def looped_delegate_block(&block)
+ note :preloop
+ 2.times do |i|
+ note :predele
+ yield_value(i, &block)
+ note :postdele
+ end
+ note :postloop
+ end
+ private :looped_delegate_block
+
+ def yield_value(value)
+ note :preyield
+ yield value
+ note :postyield
+ end
+ private :yield_value
+
+ def method(v)
+ yield v
+ end
+
+ def invoke_yield_in_while
+ looping = true
+ while looping
+ note :aa
+ yield
+ note :bb
+ looping = false
+ end
+ note :should_not_reach_here
+ end
+
+ def break_in_block_in_while
+ invoke_yield_in_while do
+ note :break
+ break :value
+ note :c
+ end
+ end
+ end
+
+ class Lambda < Driver
+ # Cases for the invocation of the scope defining the lambda still active
+ # on the call stack when the lambda is invoked.
+ def break_in_defining_scope(value=true)
+ note :a
+ note -> {
+ note :b
+ if value
+ break :break
+ else
+ break
+ end
+ note :c
+ }.call
+ note :d
+ end
+
+ def break_in_nested_scope
+ note :a
+ l = -> do
+ note :b
+ break :break
+ note :c
+ end
+ note :d
+
+ invoke_lambda l
+
+ note :e
+ end
+
+ def invoke_lambda(l)
+ note :aa
+ note l.call
+ note :bb
+ end
+
+ def break_in_nested_scope_yield
+ note :a
+ l = -> do
+ note :b
+ break :break
+ note :c
+ end
+ note :d
+
+ invoke_yield(&l)
+
+ note :e
+ end
+
+ def note_invoke_yield
+ note :aa
+ note yield
+ note :bb
+ end
+
+ def break_in_nested_scope_block
+ note :a
+ l = -> do
+ note :b
+ break :break
+ note :c
+ end
+ note :d
+
+ invoke_lambda_block l
+
+ note :e
+ end
+
+ def invoke_yield
+ note :aaa
+ yield
+ note :bbb
+ end
+
+ def invoke_lambda_block(b)
+ note :aa
+ invoke_yield do
+ note :bb
+
+ note b.call
+
+ note :cc
+ end
+ note :dd
+ end
+
+ # Cases for the invocation of the scope defining the lambda NOT still
+ # active on the call stack when the lambda is invoked.
+ def create_lambda
+ note :la
+ l = -> do
+ note :lb
+ break :break
+ note :lc
+ end
+ note :ld
+ l
+ end
+
+ def break_in_method
+ note :a
+
+ note create_lambda.call
+
+ note :b
+ end
+
+ def break_in_block_in_method
+ note :a
+ invoke_yield do
+ note :b
+
+ note create_lambda.call
+
+ note :c
+ end
+ note :d
+ end
+
+ def break_in_method_yield
+ note :a
+
+ invoke_yield(&create_lambda)
+
+ note :b
+ end
+ end
+end
diff --git a/spec/ruby/language/fixtures/break_lambda_toplevel.rb b/spec/ruby/language/fixtures/break_lambda_toplevel.rb
new file mode 100644
index 0000000000..da5abbaf00
--- /dev/null
+++ b/spec/ruby/language/fixtures/break_lambda_toplevel.rb
@@ -0,0 +1,9 @@
+print "a,"
+
+print -> {
+ print "b,"
+ break "break,"
+ print "c,"
+}.call
+
+puts "d"
diff --git a/spec/ruby/language/fixtures/break_lambda_toplevel_block.rb b/spec/ruby/language/fixtures/break_lambda_toplevel_block.rb
new file mode 100644
index 0000000000..3dcee62424
--- /dev/null
+++ b/spec/ruby/language/fixtures/break_lambda_toplevel_block.rb
@@ -0,0 +1,23 @@
+print "a,"
+
+l = -> {
+ print "b,"
+ break "break,"
+ print "c,"
+}
+
+def a(l)
+ print "d,"
+ b { l.call }
+ print "e,"
+end
+
+def b
+ print "f,"
+ print yield
+ print "g,"
+end
+
+a(l)
+
+puts "h"
diff --git a/spec/ruby/language/fixtures/break_lambda_toplevel_method.rb b/spec/ruby/language/fixtures/break_lambda_toplevel_method.rb
new file mode 100644
index 0000000000..a5936a3d70
--- /dev/null
+++ b/spec/ruby/language/fixtures/break_lambda_toplevel_method.rb
@@ -0,0 +1,17 @@
+print "a,"
+
+l = -> {
+ print "b,"
+ break "break,"
+ print "c,"
+}
+
+def a(l)
+ print "d,"
+ print l.call
+ print "e,"
+end
+
+a(l)
+
+puts "f"
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/classes.rb b/spec/ruby/language/fixtures/classes.rb
new file mode 100644
index 0000000000..eb239e1e29
--- /dev/null
+++ b/spec/ruby/language/fixtures/classes.rb
@@ -0,0 +1,31 @@
+module LanguageSpecs
+ # Regexp support
+
+ def self.paired_delimiters
+ [%w[( )], %w[{ }], %w[< >], ["[", "]"]]
+ end
+
+ def self.non_paired_delimiters
+ %w[~ ! # $ % ^ & * _ + ` - = " ' , . ? / | \\]
+ end
+
+ def self.blanks
+ " \t"
+ end
+
+ def self.white_spaces
+ return blanks + "\f\n\r\v"
+ end
+
+ def self.non_alphanum_non_space
+ '~!@#$%^&*()+-\|{}[]:";\'<>?,./'
+ end
+
+ def self.punctuations
+ ",.?" # TODO - Need to fill in the full list
+ end
+
+ def self.get_regexp_with_substitution o
+ /#{o}/o
+ end
+end
diff --git a/spec/ruby/language/fixtures/coding_us_ascii.rb b/spec/ruby/language/fixtures/coding_us_ascii.rb
new file mode 100644
index 0000000000..7df66109d7
--- /dev/null
+++ b/spec/ruby/language/fixtures/coding_us_ascii.rb
@@ -0,0 +1,11 @@
+# encoding: us-ascii
+
+module CodingUS_ASCII
+ def self.encoding
+ __ENCODING__
+ end
+
+ def self.string_literal
+ "string literal"
+ end
+end
diff --git a/spec/ruby/language/fixtures/coding_utf_8.rb b/spec/ruby/language/fixtures/coding_utf_8.rb
new file mode 100644
index 0000000000..3d8e1d9a34
--- /dev/null
+++ b/spec/ruby/language/fixtures/coding_utf_8.rb
@@ -0,0 +1,11 @@
+# encoding: utf-8
+
+module CodingUTF_8
+ def self.encoding
+ __ENCODING__
+ end
+
+ def self.string_literal
+ "string literal"
+ end
+end
diff --git a/spec/ruby/language/fixtures/constant_visibility.rb b/spec/ruby/language/fixtures/constant_visibility.rb
new file mode 100644
index 0000000000..af38b2d8f2
--- /dev/null
+++ b/spec/ruby/language/fixtures/constant_visibility.rb
@@ -0,0 +1,114 @@
+module ConstantVisibility
+ module ModuleContainer
+ module PrivateModule
+ end
+ private_constant :PrivateModule
+
+ class PrivateClass
+ end
+ private_constant :PrivateClass
+ end
+
+ class ClassContainer
+ module PrivateModule
+ end
+ private_constant :PrivateModule
+
+ class PrivateClass
+ end
+ private_constant :PrivateClass
+ end
+
+ module PrivConstModule
+ PRIVATE_CONSTANT_MODULE = true
+ private_constant :PRIVATE_CONSTANT_MODULE
+
+ def self.private_constant_from_self
+ PRIVATE_CONSTANT_MODULE
+ end
+
+ def self.defined_from_self
+ defined? PRIVATE_CONSTANT_MODULE
+ end
+
+ module Nested
+ def self.private_constant_from_scope
+ PRIVATE_CONSTANT_MODULE
+ end
+
+ def self.defined_from_scope
+ defined? PRIVATE_CONSTANT_MODULE
+ end
+ end
+ end
+
+ class PrivConstClass
+ PRIVATE_CONSTANT_CLASS = true
+ private_constant :PRIVATE_CONSTANT_CLASS
+
+ def self.private_constant_from_self
+ PRIVATE_CONSTANT_CLASS
+ end
+
+ def self.defined_from_self
+ defined? PRIVATE_CONSTANT_CLASS
+ end
+
+ module Nested
+ def self.private_constant_from_scope
+ PRIVATE_CONSTANT_CLASS
+ end
+
+ def self.defined_from_scope
+ defined? PRIVATE_CONSTANT_CLASS
+ end
+ end
+ end
+
+ class ClassIncludingPrivConstModule
+ include PrivConstModule
+
+ def private_constant_from_include
+ PRIVATE_CONSTANT_MODULE
+ end
+
+ def defined_from_include
+ defined? PRIVATE_CONSTANT_MODULE
+ 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
+ end
+
+ def defined_from_subclass
+ defined? PRIVATE_CONSTANT_CLASS
+ end
+ end
+
+ def self.reset_private_constants
+ Object.send :private_constant, :PRIVATE_CONSTANT_IN_OBJECT
+ end
+end
+
+class Object
+ PRIVATE_CONSTANT_IN_OBJECT = true
+ private_constant :PRIVATE_CONSTANT_IN_OBJECT
+end
diff --git a/spec/ruby/language/fixtures/constants_sclass.rb b/spec/ruby/language/fixtures/constants_sclass.rb
new file mode 100644
index 0000000000..21dc4081e2
--- /dev/null
+++ b/spec/ruby/language/fixtures/constants_sclass.rb
@@ -0,0 +1,54 @@
+module ConstantSpecs
+
+ CS_SINGLETON1 = Object.new
+ class << CS_SINGLETON1
+ CONST = 1
+ def foo
+ CONST
+ end
+ end
+
+ CS_SINGLETON2 = [Object.new, Object.new]
+ 2.times do |i|
+ obj = CS_SINGLETON2[i]
+ $spec_i = i
+ class << obj
+ CONST = ($spec_i + 1)
+ def foo
+ CONST
+ end
+ end
+ end
+
+ CS_SINGLETON3 = [Object.new, Object.new]
+ 2.times do |i|
+ obj = CS_SINGLETON3[i]
+ class << obj
+ class X
+ # creates <singleton class::X>
+ end
+
+ def x
+ X
+ end
+ end
+ end
+
+ CS_SINGLETON4 = [Object.new, Object.new]
+ CS_SINGLETON4_CLASSES = []
+ 2.times do |i|
+ obj = CS_SINGLETON4[i]
+ $spec_i = i
+ class << obj
+ class X
+ CS_SINGLETON4_CLASSES << self
+ CONST = ($spec_i + 1)
+
+ def foo
+ CONST
+ end
+ end
+ end
+ end
+
+end
diff --git a/spec/ruby/language/fixtures/def.rb b/spec/ruby/language/fixtures/def.rb
new file mode 100644
index 0000000000..e07060ed74
--- /dev/null
+++ b/spec/ruby/language/fixtures/def.rb
@@ -0,0 +1,14 @@
+def toplevel_define_other_method
+ def nested_method_in_toplevel_method
+ 42
+ end
+end
+
+def some_toplevel_method
+end
+
+public
+def public_toplevel_method
+end
+
+private
diff --git a/spec/ruby/language/fixtures/defined.rb b/spec/ruby/language/fixtures/defined.rb
new file mode 100644
index 0000000000..3761cfa5bd
--- /dev/null
+++ b/spec/ruby/language/fixtures/defined.rb
@@ -0,0 +1,312 @@
+module DefinedSpecs
+ self::SelfScoped = 42
+
+ def self.side_effects
+ ScratchPad.record :defined_specs_side_effects
+ end
+
+ def self.fixnum_method
+ ScratchPad.record :defined_specs_fixnum_method
+ 42
+ end
+
+ def self.exception_method
+ ScratchPad.record :defined_specs_exception
+ raise "defined? specs exception method"
+ end
+
+ def self.defined_method
+ DefinedSpecs
+ end
+
+ def self.any_args(*)
+ end
+
+ class Basic
+ A = 42
+
+ def defined_method
+ DefinedSpecs
+ end
+
+ def a_defined_method
+ end
+
+ def protected_method
+ end
+ protected :protected_method
+
+ def private_method
+ end
+ private :private_method
+
+ def private_method_defined
+ defined? private_method
+ end
+
+ def private_predicate?
+ end
+ private :private_predicate?
+
+ def private_predicate_defined
+ defined? private_predicate?
+ end
+
+ def local_variable_defined
+ x = 2
+ defined? x
+ end
+
+ def local_variable_defined_nil
+ x = nil
+ defined? x
+ end
+
+ def instance_variable_undefined
+ defined? @instance_variable_undefined
+ end
+
+ def instance_variable_read
+ value = @instance_variable_read
+ defined? @instance_variable_read
+ end
+
+ def instance_variable_defined
+ @instance_variable_defined = 1
+ defined? @instance_variable_defined
+ end
+
+ def instance_variable_defined_nil
+ @instance_variable_defined_nil = nil
+ defined? @instance_variable_defined_nil
+ end
+
+ def global_variable_undefined
+ defined? $defined_specs_global_variable_undefined
+ end
+
+ def global_variable_read
+ suppress_warning do
+ value = $defined_specs_global_variable_read
+ end
+ defined? $defined_specs_global_variable_read
+ end
+
+ def global_variable_defined
+ $defined_specs_global_variable_defined = 1
+ 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
+
+ def class_variable_defined
+ @@class_variable_defined = 1
+ defined? @@class_variable_defined
+ end
+
+ def yield_defined_method
+ defined? yield
+ end
+
+ def yield_defined_parameter_method(&block)
+ defined? yield
+ end
+
+ def no_yield_block
+ yield_defined_method
+ end
+
+ def no_yield_block_parameter
+ yield_defined_parameter_method
+ end
+
+ def yield_block
+ yield_defined_method { 42 }
+ end
+
+ def yield_block_parameter
+ yield_defined_parameter_method { 42 }
+ end
+ end
+
+ module Mixin
+ MixinConstant = 42
+
+ def defined_super
+ defined? super()
+ end
+ end
+
+ class Parent
+ ParentConstant = 42
+
+ def defined_super; end
+ end
+
+ class Child < Parent
+ include Mixin
+
+ A = 42
+
+ def self.parent_constant_defined
+ defined? self::ParentConstant
+ end
+
+ def self.module_defined
+ defined? Mixin
+ end
+
+ def self.module_constant_defined
+ defined? MixinConstant
+ end
+
+ def defined_super
+ super
+ end
+ end
+
+ class Superclass
+ def yield_method
+ yield
+ end
+
+ def method_no_args
+ end
+
+ def method_args
+ end
+
+ def method_block_no_args
+ end
+
+ def method_block_args
+ end
+
+ def define_method_no_args
+ end
+
+ def define_method_args
+ end
+
+ def define_method_block_no_args
+ end
+
+ def define_method_block_args
+ end
+ end
+
+ class Super < Superclass
+ def no_super_method_no_args
+ defined? super
+ end
+
+ def no_super_method_args
+ defined? super()
+ end
+
+ def method_no_args
+ defined? super
+ end
+
+ def method_args
+ defined? super()
+ end
+
+ def no_super_method_block_no_args
+ yield_method { defined? super }
+ end
+
+ def no_super_method_block_args
+ yield_method { defined? super() }
+ end
+
+ def method_block_no_args
+ yield_method { defined? super }
+ end
+
+ def method_block_args
+ yield_method { defined? super() }
+ end
+
+ define_method(:no_super_define_method_no_args) { defined? super }
+ define_method(:no_super_define_method_args) { defined? super() }
+ define_method(:define_method_no_args) { defined? super }
+ define_method(:define_method_args) { defined? super() }
+
+ define_method(:no_super_define_method_block_no_args) do
+ yield_method { defined? super }
+ end
+
+ define_method(:no_super_define_method_block_args) do
+ yield_method { defined? super() }
+ end
+
+ define_method(:define_method_block_no_args) do
+ yield_method { defined? super }
+ end
+
+ define_method(:define_method_block_args) do
+ yield_method { defined? super() }
+ end
+ end
+
+ class ClassWithMethod
+ def test
+ end
+ end
+
+ class ClassUndefiningMethod < ClassWithMethod
+ undef :test
+ end
+
+ class ClassWithoutMethod < ClassUndefiningMethod
+ # If an undefined method overridden in descendants
+ # define?(super) should return nil
+ def test
+ defined?(super)
+ end
+ end
+
+ module IntermediateModule1
+ def method_no_args
+ end
+ end
+
+ module IntermediateModule2
+ def method_no_args
+ defined?(super)
+ end
+ end
+
+ module ModuleWithConstMissing
+ def self.const_missing(const)
+ const
+ end
+ end
+
+ class SuperWithIntermediateModules
+ include IntermediateModule1
+ include IntermediateModule2
+
+ def method_no_args
+ super
+ end
+ end
+end
+
+class Object
+ def defined_specs_method
+ DefinedSpecs
+ end
+
+ def defined_specs_receiver
+ DefinedSpecs::Basic.new
+ end
+end
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/dollar_zero.rb b/spec/ruby/language/fixtures/dollar_zero.rb
new file mode 100644
index 0000000000..683bce8d4e
--- /dev/null
+++ b/spec/ruby/language/fixtures/dollar_zero.rb
@@ -0,0 +1,6 @@
+puts $0
+puts __FILE__
+
+if $0 == __FILE__
+ print "OK"
+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
new file mode 100644
index 0000000000..6047ac5bc0
--- /dev/null
+++ b/spec/ruby/language/fixtures/ensure.rb
@@ -0,0 +1,121 @@
+module EnsureSpec
+ class Container
+ attr_reader :executed
+
+ def initialize
+ @executed = []
+ end
+
+ def raise_in_method_with_ensure
+ @executed << :method
+ raise EnsureSpec::Error
+ ensure
+ @executed << :ensure
+ end
+
+ def raise_and_rescue_in_method_with_ensure
+ @executed << :method
+ raise "An Exception"
+ rescue
+ @executed << :rescue
+ ensure
+ @executed << :ensure
+ end
+
+ def throw_in_method_with_ensure
+ @executed << :method
+ throw(:symbol)
+ ensure
+ @executed << :ensure
+ end
+
+ def implicit_return_in_method_with_ensure
+ :method
+ ensure
+ :ensure
+ end
+
+ def explicit_return_in_method_with_ensure
+ return :method
+ 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
+
+module EnsureSpec
+
+ class Test
+
+ def initialize
+ @values = []
+ end
+
+ attr_reader :values
+
+ def call_block
+ begin
+ @values << :start
+ yield
+ ensure
+ @values << :end
+ end
+ end
+
+ def do_test
+ call_block do
+ @values << :in_block
+ return :did_test
+ end
+ end
+ end
+end
+
+module EnsureSpec
+ class Error < RuntimeError
+ end
+end
diff --git a/spec/ruby/language/fixtures/file.rb b/spec/ruby/language/fixtures/file.rb
new file mode 100644
index 0000000000..7b862cfe1a
--- /dev/null
+++ b/spec/ruby/language/fixtures/file.rb
@@ -0,0 +1 @@
+ScratchPad.record __FILE__
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_across_files.rb b/spec/ruby/language/fixtures/freeze_magic_comment_across_files.rb
new file mode 100644
index 0000000000..3aed2f29b6
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_across_files.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+require_relative 'freeze_magic_comment_required'
+
+p "abc".object_id == $second_literal_id
diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb b/spec/ruby/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb
new file mode 100644
index 0000000000..53ef959970
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+require_relative 'freeze_magic_comment_required_diff_enc'
+
+p "abc".object_id != $second_literal_id
diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_across_files_no_comment.rb b/spec/ruby/language/fixtures/freeze_magic_comment_across_files_no_comment.rb
new file mode 100644
index 0000000000..fc6cd5bf82
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_across_files_no_comment.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+require_relative 'freeze_magic_comment_required_no_comment'
+
+p "abc".object_id != $second_literal_id
diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_one_literal.rb b/spec/ruby/language/fixtures/freeze_magic_comment_one_literal.rb
new file mode 100644
index 0000000000..d35905b332
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_one_literal.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+ids = Array.new(2) { "abc".object_id }
+p ids.first == ids.last
diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_required.rb b/spec/ruby/language/fixtures/freeze_magic_comment_required.rb
new file mode 100644
index 0000000000..a4ff4459b1
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_required.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+$second_literal_id = "abc".object_id
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
new file mode 100644
index 0000000000..f72a32e879
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb
@@ -0,0 +1,4 @@
+# encoding: euc-jp # built-in for old regexp option
+# frozen_string_literal: true
+
+$second_literal_id = "abc".object_id
diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_required_no_comment.rb b/spec/ruby/language/fixtures/freeze_magic_comment_required_no_comment.rb
new file mode 100644
index 0000000000..e09232a5f4
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_required_no_comment.rb
@@ -0,0 +1 @@
+$second_literal_id = "abc".object_id
diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb b/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb
new file mode 100644
index 0000000000..cccc5969bd
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+p "abc".equal?("abc")
diff --git a/spec/ruby/language/fixtures/hash_strings_binary.rb b/spec/ruby/language/fixtures/hash_strings_binary.rb
new file mode 100644
index 0000000000..44b99cbf80
--- /dev/null
+++ b/spec/ruby/language/fixtures/hash_strings_binary.rb
@@ -0,0 +1,7 @@
+# encoding: binary
+
+module HashStringsBinary
+ def self.literal_hash
+ {"foo" => "bar"}
+ end
+end
diff --git a/spec/ruby/language/fixtures/hash_strings_usascii.rb b/spec/ruby/language/fixtures/hash_strings_usascii.rb
new file mode 100644
index 0000000000..18cfef7c8c
--- /dev/null
+++ b/spec/ruby/language/fixtures/hash_strings_usascii.rb
@@ -0,0 +1,7 @@
+# encoding: us-ascii
+
+module HashStringsUSASCII
+ def self.literal_hash
+ {"foo" => "bar"}
+ end
+end
diff --git a/spec/ruby/language/fixtures/hash_strings_utf8.rb b/spec/ruby/language/fixtures/hash_strings_utf8.rb
new file mode 100644
index 0000000000..7928090282
--- /dev/null
+++ b/spec/ruby/language/fixtures/hash_strings_utf8.rb
@@ -0,0 +1,7 @@
+# encoding: utf-8
+
+module HashStringsUTF8
+ def self.literal_hash
+ {"foo" => "bar"}
+ end
+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/match_operators.rb b/spec/ruby/language/fixtures/match_operators.rb
new file mode 100644
index 0000000000..f04c54d723
--- /dev/null
+++ b/spec/ruby/language/fixtures/match_operators.rb
@@ -0,0 +1,9 @@
+class OperatorImplementor
+ def =~(val)
+ return val
+ end
+
+ def !~(val)
+ return val
+ end
+end
diff --git a/spec/ruby/language/fixtures/metaclass.rb b/spec/ruby/language/fixtures/metaclass.rb
new file mode 100644
index 0000000000..a8f837e701
--- /dev/null
+++ b/spec/ruby/language/fixtures/metaclass.rb
@@ -0,0 +1,33 @@
+module MetaClassSpecs
+
+ def self.metaclass_of obj
+ class << obj
+ self
+ end
+ end
+
+ class A
+ def self.cheese
+ 'edam'
+ end
+ end
+
+ class B < A
+ def self.cheese
+ 'stilton'
+ end
+ end
+
+ class C
+ class << self
+ class << self
+ def ham
+ 'iberico'
+ end
+ end
+ end
+ end
+
+ class D < C; end
+
+end
diff --git a/spec/ruby/language/fixtures/module.rb b/spec/ruby/language/fixtures/module.rb
new file mode 100644
index 0000000000..75eee77791
--- /dev/null
+++ b/spec/ruby/language/fixtures/module.rb
@@ -0,0 +1,15 @@
+module ModuleSpecs
+ module Modules
+ class Klass
+ end
+
+ A = "Module"
+ B = 1
+ C = nil
+ D = true
+ E = false
+ end
+
+ module Anonymous
+ end
+end
diff --git a/spec/ruby/language/fixtures/next.rb b/spec/ruby/language/fixtures/next.rb
new file mode 100644
index 0000000000..fbca842334
--- /dev/null
+++ b/spec/ruby/language/fixtures/next.rb
@@ -0,0 +1,134 @@
+class NextSpecs
+ def self.yielding_method(expected)
+ yield.should == expected
+ :method_return_value
+ end
+
+ def self.yielding
+ yield
+ end
+
+ # The methods below are defined to spec the behavior of the next statement
+ # while specifically isolating whether the statement is in an Iter block or
+ # not. In a normal spec example, the code is always nested inside a block.
+ # Rather than rely on that implicit context in this case, the context is
+ # made explicit because of the interaction of next in a loop nested inside
+ # an Iter block.
+ def self.while_next(arg)
+ x = true
+ while x
+ begin
+ ScratchPad << :begin
+ x = false
+ if arg
+ next 42
+ else
+ next
+ end
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+ end
+
+ def self.while_within_iter(arg)
+ yielding do
+ x = true
+ while x
+ begin
+ ScratchPad << :begin
+ x = false
+ if arg
+ next 42
+ else
+ next
+ end
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+ end
+ end
+
+ def self.until_next(arg)
+ x = false
+ until x
+ begin
+ ScratchPad << :begin
+ x = true
+ if arg
+ next 42
+ else
+ next
+ end
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+ end
+
+ def self.until_within_iter(arg)
+ yielding do
+ x = false
+ until x
+ begin
+ ScratchPad << :begin
+ x = true
+ if arg
+ next 42
+ else
+ next
+ end
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+ end
+ end
+
+ def self.loop_next(arg)
+ x = 1
+ loop do
+ break if x == 2
+
+ begin
+ ScratchPad << :begin
+ x += 1
+ if arg
+ next 42
+ else
+ next
+ end
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+ end
+
+ def self.loop_within_iter(arg)
+ yielding do
+ x = 1
+ loop do
+ break if x == 2
+
+ begin
+ ScratchPad << :begin
+ x += 1
+ if arg
+ next 42
+ else
+ next
+ end
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+ end
+ end
+
+ class Block
+ def method(v)
+ yield v
+ end
+ end
+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/precedence.rb b/spec/ruby/language/fixtures/precedence.rb
new file mode 100644
index 0000000000..d2295c755b
--- /dev/null
+++ b/spec/ruby/language/fixtures/precedence.rb
@@ -0,0 +1,16 @@
+module PrecedenceSpecs
+ class NonUnaryOpTest
+ def add_num(arg)
+ [1].collect { |i| arg + i +1 }
+ end
+ def sub_num(arg)
+ [1].collect { |i| arg + i -1 }
+ end
+ def add_str
+ %w[1].collect { |i| i +'1' }
+ end
+ def add_var
+ [1].collect { |i| i +i }
+ end
+ end
+end
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
new file mode 100644
index 0000000000..da3e0a97f9
--- /dev/null
+++ b/spec/ruby/language/fixtures/private.rb
@@ -0,0 +1,59 @@
+module Private
+ class A
+ def foo
+ "foo"
+ end
+
+ private
+ def bar
+ "bar"
+ end
+ end
+
+ class B
+ def foo
+ "foo"
+ end
+
+ private
+
+ def self.public_defs_method; 0; end
+
+ class C
+ def baz
+ "baz"
+ end
+ end
+
+ class << self
+ def public_class_method1; 1; end
+ private
+ def private_class_method1; 1; end
+ end
+
+ def bar
+ "bar"
+ end
+ end
+
+ module D
+ private
+ def foo
+ "foo"
+ end
+ 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
new file mode 100644
index 0000000000..b906e17a2f
--- /dev/null
+++ b/spec/ruby/language/fixtures/rescue.rb
@@ -0,0 +1,67 @@
+module RescueSpecs
+ def self.begin_else(raise_exception)
+ begin
+ ScratchPad << :one
+ raise "an error occurred" if raise_exception
+ rescue
+ ScratchPad << :rescue_ran
+ :rescue_val
+ else
+ ScratchPad << :else_ran
+ :val
+ end
+ end
+
+ def self.begin_else_ensure(raise_exception)
+ begin
+ ScratchPad << :one
+ raise "an error occurred" if raise_exception
+ rescue
+ ScratchPad << :rescue_ran
+ :rescue_val
+ else
+ ScratchPad << :else_ran
+ :val
+ ensure
+ ScratchPad << :ensure_ran
+ :ensure_val
+ end
+ end
+
+ def self.begin_else_return(raise_exception)
+ begin
+ ScratchPad << :one
+ raise "an error occurred" if raise_exception
+ rescue
+ ScratchPad << :rescue_ran
+ :rescue_val
+ else
+ ScratchPad << :else_ran
+ :val
+ end
+ ScratchPad << :outside_begin
+ :return_val
+ end
+
+ def self.begin_else_return_ensure(raise_exception)
+ begin
+ ScratchPad << :one
+ raise "an error occurred" if raise_exception
+ rescue
+ ScratchPad << :rescue_ran
+ :rescue_val
+ else
+ ScratchPad << :else_ran
+ :val
+ ensure
+ ScratchPad << :ensure_ran
+ :ensure_val
+ end
+ 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
new file mode 100644
index 0000000000..f6b143f3fa
--- /dev/null
+++ b/spec/ruby/language/fixtures/return.rb
@@ -0,0 +1,135 @@
+module ReturnSpecs
+ class Blocks
+ def yielding_method
+ yield
+ ScratchPad.record :after_yield
+ end
+
+ def enclosing_method
+ yielding_method do
+ ScratchPad.record :before_return
+ return :return_value
+ ScratchPad.record :after_return
+ end
+
+ ScratchPad.record :after_call
+ end
+ end
+
+ class NestedCalls < Blocks
+ def invoking_method(&b)
+ yielding_method(&b)
+ ScratchPad.record :after_invoke
+ end
+
+ def enclosing_method
+ invoking_method do
+ ScratchPad.record :before_return
+ return :return_value
+ ScratchPad.record :after_return
+ end
+ ScratchPad.record :after_invoke
+ end
+ end
+
+ class NestedBlocks < Blocks
+ def enclosing_method
+ yielding_method do
+ yielding_method do
+ ScratchPad.record :before_return
+ return :return_value
+ ScratchPad.record :after_return
+ end
+ ScratchPad.record :after_invoke1
+ end
+ ScratchPad.record :after_invoke2
+ end
+ end
+
+ class SavedInnerBlock
+ def add(&b)
+ @block = b
+ end
+
+ def outer
+ yield
+ @block.call
+ end
+
+ def inner
+ yield
+ end
+
+ def start
+ outer do
+ inner do
+ add do
+ ScratchPad.record :before_return
+ return :return_value
+ end
+ end
+ end
+
+ ScratchPad.record :bottom_of_start
+
+ return false
+ end
+ end
+
+ class ThroughDefineMethod
+ lamb = proc { |x| x.call }
+ define_method :foo, lamb
+
+ def mp(&b); b; end
+
+ def outer
+ pr = mp { return :good }
+
+ foo(pr)
+ return :bad
+ end
+ end
+
+ class DefineMethod
+ lamb = proc { return :good }
+ define_method :foo, lamb
+
+ def outer
+ val = :bad
+
+ # This is tricky, but works. If lamb properly returns, then the
+ # return value will go into val before we run the ensure.
+ #
+ # If lamb's return keeps unwinding incorrectly, val will still
+ # have its old value.
+ #
+ # We can therefore use val to figure out what happened.
+ begin
+ val = foo()
+ ensure
+ return val
+ end
+ end
+ end
+
+ class MethodWithBlock
+ def method1
+ return [2, 3].inject 0 do |a, b|
+ a + b
+ end
+ nil
+ end
+
+ def get_ary(count, &blk)
+ count.times.to_a do |i|
+ blk.call(i) if blk
+ end
+ end
+
+ def method2
+ return get_ary 3 do |i|
+ end
+ nil
+ end
+ 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
new file mode 100644
index 0000000000..4787abee5c
--- /dev/null
+++ b/spec/ruby/language/fixtures/send.rb
@@ -0,0 +1,151 @@
+module LangSendSpecs
+ module_function
+
+ def fooM0; 100 end
+ def fooM1(a); [a]; end
+ def fooM2(a,b); [a,b]; end
+ def fooM3(a,b,c); [a,b,c]; end
+ def fooM4(a,b,c,d); [a,b,c,d]; end
+ def fooM5(a,b,c,d,e); [a,b,c,d,e]; end
+ def fooM0O1(a=1); [a]; end
+ def fooM1O1(a,b=1); [a,b]; end
+ def fooM2O1(a,b,c=1); [a,b,c]; end
+ def fooM3O1(a,b,c,d=1); [a,b,c,d]; end
+ def fooM4O1(a,b,c,d,e=1); [a,b,c,d,e]; end
+ def fooM0O2(a=1,b=2); [a,b]; end
+ def fooM0R(*r); r; end
+ def fooM1R(a, *r); [a, r]; end
+ def fooM0O1R(a=1, *r); [a, r]; end
+ def fooM1O1R(a, b=1, *r); [a, b, r]; end
+
+ def one(a); a; end
+ def oneb(a,&b); [a,yield(b)]; end
+ def twob(a,b,&c); [a,b,yield(c)]; end
+ def makeproc(&b) b end
+
+ def yield_now; yield; end
+
+ def double(x); x * 2 end
+ def weird_parens
+ # means double((5).to_s)
+ # NOT (double(5)).to_s
+ double (5).to_s
+ end
+
+ def rest_len(*a); a.size; end
+
+ def self.twos(a,b,*c)
+ [c.size, c.last]
+ end
+
+ class PrivateSetter
+ attr_reader :foo
+ attr_writer :foo
+ private :foo=
+
+ def call_self_foo_equals(value)
+ self.foo = value
+ end
+
+ def call_self_foo_equals_masgn(value)
+ a, self.foo = 1, value
+ end
+ end
+
+ class PrivateGetter
+ attr_accessor :foo
+ private :foo
+ private :foo=
+
+ def call_self_foo
+ self.foo
+ end
+
+ def call_self_foo_or_equals(value)
+ self.foo ||= 6
+ end
+ end
+
+ class AttrSet
+ attr_reader :result
+ def []=(a, b, c, d); @result = [a,b,c,d]; end
+ end
+
+ class ToProc
+ def initialize(val)
+ @val = val
+ end
+
+ def to_proc
+ Proc.new { @val }
+ 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
+ end
+
+ def to_ary
+ @obj
+ end
+ end
+
+ class MethodMissing
+ def initialize
+ @message = nil
+ @args = nil
+ end
+
+ attr_reader :message, :args
+
+ def method_missing(m, *a)
+ @message = m
+ @args = a
+ end
+ end
+
+ class Attr19Set
+ attr_reader :result
+ def []=(*args); @result = args; end
+ end
+
+ module_function
+
+ def fooR(*r); r; end
+ def fooM0RQ1(*r, q); [r, q]; end
+ def fooM0RQ2(*r, s, q); [r, s, q]; end
+ def fooM1RQ1(a, *r, q); [a, r, q]; end
+ def fooM1O1RQ1(a, b=9, *r, q); [a, b, r, q]; end
+ def fooM1O1RQ2(a, b=9, *r, q, t); [a, b, r, q, t]; end
+
+ def fooO1Q1(a=1, b); [a,b]; end
+ def fooM1O1Q1(a,b=2,c); [a,b,c]; end
+ def fooM2O1Q1(a,b,c=3,d); [a,b,c,d]; end
+ def fooM2O2Q1(a,b,c=3,d=4,e); [a,b,c,d,e]; end
+ def fooO4Q1(a=1,b=2,c=3,d=4,e); [a,b,c,d,e]; end
+ def fooO4Q2(a=1,b=2,c=3,d=4,e,f); [a,b,c,d,e,f]; end
+
+ def destructure2((a,b)); a+b; end
+ def destructure2b((a,b)); [a,b]; end
+ def destructure4r((a,b,*c,d,e)); [a,b,c,d,e]; end
+ def destructure4o(a=1,(b,c),d,&e); [a,b,c,d]; end
+ def destructure5o(a=1, f=2, (b,c),d,&e); [a,f,b,c,d]; end
+ def destructure7o(a=1, f=2, (b,c),(d,e), &g); [a,f,b,c,d,e]; end
+ def destructure7b(a=1, f=2, (b,c),(d,e), &g); g.call([a,f,b,c,d,e]); end
+ def destructure4os(a=1,(b,*c)); [a,b,c]; end
+end
+
+def lang_send_rest_len(*a)
+ a.size
+end
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
new file mode 100644
index 0000000000..984a629e5b
--- /dev/null
+++ b/spec/ruby/language/fixtures/squiggly_heredoc.rb
@@ -0,0 +1,71 @@
+module SquigglyHeredocSpecs
+ def self.message
+ <<~HEREDOC
+ character density, n.:
+ The number of very weird people in the office.
+ HEREDOC
+ end
+
+ def self.blank
+ <<~HERE
+ HERE
+ end
+
+ def self.unquoted
+ <<~HERE
+ unquoted #{"interpolated"}
+ HERE
+ end
+
+ def self.doublequoted
+ <<~"HERE"
+ doublequoted #{"interpolated"}
+ HERE
+ end
+
+ def self.singlequoted
+ <<~'HERE'
+ singlequoted #{"interpolated"}
+ 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
+ b
+ 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
new file mode 100644
index 0000000000..c5bdcf0e40
--- /dev/null
+++ b/spec/ruby/language/fixtures/super.rb
@@ -0,0 +1,804 @@
+module SuperSpecs
+ module S1
+ class A
+ def foo(a)
+ a << "A#foo"
+ bar(a)
+ end
+ def bar(a)
+ a << "A#bar"
+ end
+ end
+ class B < A
+ def foo(a)
+ a << "B#foo"
+ super(a)
+ end
+ def bar(a)
+ a << "B#bar"
+ super(a)
+ end
+ end
+ end
+
+ module S2
+ class A
+ def baz(a)
+ a << "A#baz"
+ end
+ end
+ class B < A
+ def foo(a)
+ a << "B#foo"
+ baz(a)
+ end
+ end
+ class C < B
+ def baz(a)
+ a << "C#baz"
+ super(a)
+ end
+ end
+ end
+
+ module S3
+ class A
+ def foo(a)
+ a << "A#foo"
+ end
+ def self.foo(a)
+ a << "A.foo"
+ end
+ def self.bar(a)
+ a << "A.bar"
+ foo(a)
+ end
+ end
+ class B < A
+ def self.foo(a)
+ a << "B.foo"
+ super(a)
+ end
+ def self.bar(a)
+ a << "B.bar"
+ super(a)
+ end
+ end
+ end
+
+ module S4
+ class A
+ def foo(a)
+ a << "A#foo"
+ end
+ end
+ class B < A
+ def foo(a, b)
+ a << "B#foo(a,#{b})"
+ super(a)
+ end
+ end
+ end
+
+ class S5
+ def here
+ :good
+ end
+ end
+
+ class S6 < S5
+ def under
+ yield
+ end
+
+ def here
+ under {
+ super
+ }
+ end
+ end
+
+ class S7 < S5
+ define_method(:here) { super() }
+ end
+
+ module MS1
+ module ModA
+ def foo(a)
+ a << "ModA#foo"
+ bar(a)
+ end
+ def bar(a)
+ a << "ModA#bar"
+ end
+ end
+ class A
+ include ModA
+ end
+ module ModB
+ def bar(a)
+ a << "ModB#bar"
+ super(a)
+ end
+ end
+ class B < A
+ def foo(a)
+ a << "B#foo"
+ super(a)
+ end
+ include ModB
+ end
+ end
+
+ module MS2
+ class A
+ def baz(a)
+ a << "A#baz"
+ end
+ end
+ module ModB
+ def foo(a)
+ a << "ModB#foo"
+ baz(a)
+ end
+ end
+ class B < A
+ include ModB
+ end
+ class C < B
+ def baz(a)
+ a << "C#baz"
+ super(a)
+ end
+ end
+ end
+
+ module MultiSuperTargets
+ module M
+ def foo
+ super
+ end
+ end
+
+ class BaseA
+ def foo
+ :BaseA
+ end
+ end
+
+ class BaseB
+ def foo
+ :BaseB
+ end
+ end
+
+ class A < BaseA
+ include M
+ end
+
+ class B < BaseB
+ include M
+ end
+ end
+
+ module MS3
+ module ModA
+ def foo(a)
+ a << "ModA#foo"
+ end
+ def bar(a)
+ a << "ModA#bar"
+ foo(a)
+ end
+ end
+ class A
+ def foo(a)
+ a << "A#foo"
+ end
+ class << self
+ include ModA
+ end
+ end
+ class B < A
+ def self.foo(a)
+ a << "B.foo"
+ super(a)
+ end
+ def self.bar(a)
+ a << "B.bar"
+ super(a)
+ end
+ end
+ end
+
+ module MS4
+ module Layer1
+ def example
+ 5
+ end
+ end
+
+ module Layer2
+ include Layer1
+ def example
+ super
+ end
+ end
+
+ class A
+ include Layer2
+ public :example
+ end
+ end
+
+ class MM_A
+ undef_method :is_a?
+ end
+
+ class MM_B < MM_A
+ def is_a?(blah)
+ # should fire the method_missing below
+ super
+ end
+
+ def method_missing(*)
+ false
+ end
+ end
+
+ class Alias1
+ def name
+ [:alias1]
+ end
+ end
+
+ class Alias2 < Alias1
+ def initialize
+ @times = 0
+ end
+
+ def name
+ if @times >= 10
+ raise "runaway super"
+ end
+
+ @times += 1
+
+ # Use this so that we can see collect all supers that we see.
+ # One bug that arises is that we call Alias2#name from Alias2#name
+ # as it's superclass. In that case, either we get a runaway recursion
+ # super OR we get the return value being [:alias2, :alias2, :alias1]
+ # rather than [:alias2, :alias1].
+ #
+ # Which one depends on caches and how super is implemented.
+ [:alias2] + super
+ end
+ end
+
+ class Alias3 < Alias2
+ alias_method :name3, :name
+ # In the method table for Alias3 now should be a special alias entry
+ # that references Alias2 and Alias2#name (probably as an object).
+ #
+ # When name3 is called then, Alias2 (NOT Alias3) is presented as the
+ # current module to Alias2#name, so that when super is called,
+ # 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.
+ end
+
+ module AliasWithSuper
+ module AS1
+ def foo
+ :a
+ end
+ end
+
+ module BS1
+ def foo
+ [:b, super]
+ end
+ end
+
+ class Base
+ extend AS1
+ extend BS1
+ end
+
+ class Trigger < Base
+ class << self
+ def foo_quux
+ foo_baz
+ end
+
+ alias_method :foo_baz, :foo
+ alias_method :foo, :foo_quux
+ end
+ end
+ end
+
+ module RestArgsWithSuper
+ class A
+ def a(*args)
+ args
+ end
+ end
+
+ class B < A
+ def a(*args)
+ args << "foo"
+
+ super
+ end
+ end
+ end
+
+ class AnonymousModuleIncludedTwiceBase
+ def self.whatever
+ mod = Module.new do
+ def a(array)
+ array << "anon"
+ super
+ end
+ end
+
+ include mod
+ end
+
+ def a(array)
+ array << "non-anon"
+ end
+ end
+
+ class AnonymousModuleIncludedTwice < AnonymousModuleIncludedTwiceBase
+ whatever
+ whatever
+ end
+
+ module ZSuperWithBlock
+ class A
+ def a
+ yield
+ end
+
+ def b(&block)
+ block.call
+ end
+
+ def c
+ yield
+ end
+ end
+
+ class B < A
+ def a
+ super { 14 }
+ end
+
+ def b
+ block_ref = -> { 15 }
+ [super { 14 }, super(&block_ref)]
+ end
+
+ def c
+ block_ref = -> { 16 }
+ super(&block_ref)
+ end
+ end
+ end
+
+ module ZSuperWithOptional
+ class A
+ def m(x, y, z)
+ z
+ end
+ end
+
+ class B < A
+ def m(x, y, z = 14)
+ super
+ end
+ end
+
+ class C < A
+ def m(x, y, z = 14)
+ z = 100
+ super
+ end
+ end
+ end
+
+ module ZSuperWithRest
+ class A
+ def m(*args)
+ args
+ end
+
+ def m_modified(*args)
+ args
+ end
+ end
+
+ class B < A
+ def m(*args)
+ super
+ end
+
+ def m_modified(*args)
+ args[1] = 14
+ super
+ end
+ end
+ end
+
+ module ZSuperWithRestAndOthers
+ class A
+ def m(a, b, *args)
+ args
+ end
+
+ def m_modified(a, b, *args)
+ args
+ end
+ end
+
+ class B < A
+ def m(a, b, *args)
+ super
+ end
+
+ def m_modified(a, b, *args)
+ args[1] = 14
+ super
+ end
+ 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
+ end
+
+ class B < A
+ def m(_, _)
+ 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
+ end
+ 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)
+ args
+ end
+ end
+
+ # ----
+
+ class RequiredArguments < Arguments
+ def foo(a:)
+ super
+ end
+ end
+
+ class OptionalArguments < Arguments
+ def foo(b: 'b')
+ super
+ end
+ end
+
+ class PlaceholderArguments < Arguments
+ def foo(**args)
+ super
+ end
+ end
+
+ # ----
+
+ class RequiredAndOptionalArguments < Arguments
+ def foo(a:, b: 'b')
+ super
+ end
+ end
+
+ class RequiredAndPlaceholderArguments < Arguments
+ def foo(a:, **args)
+ super
+ end
+ end
+
+ class OptionalAndPlaceholderArguments < Arguments
+ def foo(b: 'b', **args)
+ super
+ end
+ end
+
+ # ----
+
+ class RequiredAndOptionalAndPlaceholderArguments < Arguments
+ def foo(a:, b: 'b', **args)
+ super
+ end
+ end
+ end
+
+ module RegularAndKeywords
+ class Arguments
+ def foo(a, **options)
+ [a, options]
+ end
+ end
+
+ # -----
+
+ class RequiredArguments < Arguments
+ def foo(a, b:)
+ super
+ end
+ end
+
+ class OptionalArguments < Arguments
+ def foo(a, c: 'c')
+ super
+ end
+ end
+
+ class PlaceholderArguments < Arguments
+ def foo(a, **options)
+ super
+ end
+ end
+
+ # -----
+
+ class RequiredAndOptionalArguments < Arguments
+ def foo(a, b:, c: 'c')
+ super
+ end
+ end
+
+ class RequiredAndPlaceholderArguments < Arguments
+ def foo(a, b:, **options)
+ super
+ end
+ end
+
+ class OptionalAndPlaceholderArguments < Arguments
+ def foo(a, c: 'c', **options)
+ super
+ end
+ end
+
+ # -----
+
+ class RequiredAndOptionalAndPlaceholderArguments < Arguments
+ def foo(a, b:, c: 'c', **options)
+ super
+ end
+ end
+ end
+
+ module SplatAndKeywords
+ class Arguments
+ def foo(*args, **options)
+ [args, options]
+ end
+ end
+
+ class AllArguments < Arguments
+ def foo(*args, **options)
+ super
+ end
+ end
+ end
+
+ module FromBasicObject
+ def __send__(name, *args, &block)
+ super
+ end
+ end
+
+ module IntermediateBasic
+ include FromBasicObject
+ end
+
+ class IncludesFromBasic
+ include FromBasicObject
+
+ def foobar; 43; end
+ end
+
+ class IncludesIntermediate
+ include IntermediateBasic
+
+ def foobar; 42; end
+ end
+
+ module SingletonCase
+ class Base
+ def foobar(array)
+ array << :base
+ end
+ end
+
+ class Foo < Base
+ def foobar(array)
+ array << :foo
+ super
+ end
+ end
+ end
+
+ module SingletonAliasCase
+ class Base
+ def foobar(array)
+ array << :base
+ end
+
+ def alias_on_singleton
+ object = self
+ singleton = (class << object; self; end)
+ singleton.__send__(:alias_method, :new_foobar, :foobar)
+ end
+ end
+
+ class Foo < Base
+ def foobar(array)
+ array << :foo
+ super
+ end
+ end
+ end
+end
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
new file mode 100644
index 0000000000..527caa7a78
--- /dev/null
+++ b/spec/ruby/language/fixtures/variables.rb
@@ -0,0 +1,157 @@
+module VariablesSpecs
+ class ParAsgn
+ attr_accessor :x
+
+ def initialize
+ @x = 0
+ end
+
+ def inc
+ @x += 1
+ end
+
+ def to_ary
+ [1,2,3,4]
+ end
+ end
+
+ class OpAsgn
+ attr_accessor :a, :b, :side_effect
+
+ def do_side_effect
+ self.side_effect = true
+ return @a
+ end
+
+ def do_more_side_effects
+ @a += 5
+ self
+ end
+
+ def do_bool_side_effects
+ @b += 1
+ self
+ end
+ end
+
+ class Hashalike
+ def [](k) k end
+ def []=(k, v) [k, v] end
+ end
+
+ def self.reverse_foo(a, b)
+ return b, a
+ end
+
+ class ArrayLike
+ def initialize(array)
+ @array = array
+ end
+
+ def to_a
+ @array
+ end
+ end
+
+ class ArraySubclass < Array
+ end
+
+ class PrivateMethods
+ private
+
+ def to_ary
+ [1, 2]
+ end
+
+ def to_a
+ [3, 4]
+ end
+ end
+
+ class ToAryNil
+ def to_ary
+ end
+ end
+
+ class Chain
+ def self.without_parenthesis a
+ a
+ end
+ end
+
+ 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
new file mode 100644
index 0000000000..9f7a2ba238
--- /dev/null
+++ b/spec/ruby/language/fixtures/yield.rb
@@ -0,0 +1,41 @@
+module YieldSpecs
+ class Yielder
+ def z
+ yield
+ end
+
+ def ze(&block)
+ block = proc { block }
+ yield
+ end
+
+ def s(a)
+ yield(a)
+ end
+
+ def m(a, b, c)
+ yield(a, b, c)
+ end
+
+ def r(a)
+ yield(*a)
+ end
+
+ def k(a)
+ yield(*a, b: true)
+ end
+
+ def rs(a, b, c)
+ yield(a, b, *c)
+ end
+
+ def self.define_deep(&inned_block)
+ define_method 'deep' do |v|
+ # should yield to inner_block
+ yield v
+ end
+ end
+
+ define_deep { |v| v * 2}
+ end
+end
diff --git a/spec/ruby/language/for_spec.rb b/spec/ruby/language/for_spec.rb
new file mode 100644
index 0000000000..b8ddfe5f0d
--- /dev/null
+++ b/spec/ruby/language/for_spec.rb
@@ -0,0 +1,285 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/for_scope'
+
+# for name[, name]... in expr [do]
+# body
+# end
+describe "The for expression" do
+ it "iterates over an Enumerable passing each element to the block" do
+ j = 0
+ for i in 1..3
+ j += i
+ end
+ j.should == 6
+ end
+
+ it "iterates over a list of arrays and destructures with empty comma" do
+ for i, in [[1,2]]
+ i.should == 1
+ 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
+
+ for i, j in { 1 => 10, 2 => 20 }
+ k += i
+ l += j
+ end
+
+ k.should == 3
+ l.should == 30
+ end
+
+ it "iterates over any object responding to 'each'" do
+ obj = Object.new
+ def obj.each
+ (0..10).each { |i| yield i }
+ end
+
+ j = 0
+ for i in obj
+ j += i
+ end
+ j.should == 55
+ end
+
+ it "allows an instance variable as an iterator name" do
+ m = [1,2,3]
+ n = 0
+ for @var in m
+ n += 1
+ end
+ @var.should == 3
+ n.should == 3
+ end
+
+ it "allows a class variable as an iterator name" do
+ class OFor
+ m = [1,2,3]
+ n = 0
+ for @@var in m
+ n += 1
+ end
+ @@var.should == 3
+ n.should == 3
+ end
+ end
+
+ it "allows a constant as an iterator name" do
+ class OFor
+ m = [1,2,3]
+ n = 0
+ -> {
+ for CONST in m
+ n += 1
+ end
+ }.should complain(/already initialized constant/)
+ CONST.should == 3
+ n.should == 3
+ 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
+ class OFor
+ def each
+ [[1,2,3], [4,5,6]].each do |a|
+ yield(a[0],a[1],a[2])
+ end
+ end
+ end
+ o = OFor.new
+ qs = []
+ for q in o
+ qs << q
+ end
+ qs.should == [1, 4]
+ q.should == 4
+ end
+
+ it "optionally takes a 'do' after the expression" do
+ j = 0
+ for i in 1..3 do
+ j += i
+ end
+ j.should == 6
+ end
+
+ it "allows body begin on the same line if do is used" do
+ j = 0
+ for i in 1..3 do j += i
+ end
+ j.should == 6
+ end
+
+ it "executes code in containing variable scope" do
+ for i in 1..2
+ a = 123
+ end
+
+ a.should == 123
+ end
+
+ it "executes code in containing variable scope with 'do'" do
+ for i in 1..2 do
+ a = 123
+ end
+
+ 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 }
+ end
+
+ it "breaks out of a loop upon 'break', returning nil" do
+ j = 0
+ for i in 1..3
+ j += i
+
+ break if i == 2
+ end.should == nil
+
+ j.should == 3
+ end
+
+ it "allows 'break' to have an argument which becomes the value of the for expression" do
+ for i in 1..3
+ break 10 if i == 2
+ end.should == 10
+ end
+
+ it "starts the next iteration with 'next'" do
+ j = 0
+ for i in 1..5
+ next if i == 2
+
+ j += i
+ end
+
+ j.should == 13
+ end
+
+ it "repeats current iteration with 'redo'" do
+ j = 0
+ for i in 1..3
+ j += i
+
+ redo if i == 2 && j < 4
+ end
+
+ j.should == 8
+ end
+end
diff --git a/spec/ruby/language/hash_spec.rb b/spec/ruby/language/hash_spec.rb
new file mode 100644
index 0000000000..668716e2e3
--- /dev/null
+++ b/spec/ruby/language/hash_spec.rb
@@ -0,0 +1,324 @@
+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
+ {}.size.should == 0
+ {}.should == {}
+ end
+
+ it "{} should return a new hash populated with the given elements" do
+ h = {a: 'a', 'b' => 3, 44 => 2.3}
+ h.size.should == 3
+ h.should == {a: "a", "b" => 3, 44 => 2.3}
+ end
+
+ it "treats empty expressions as nils" do
+ h = {() => ()}
+ h.keys.should == [nil]
+ h.values.should == [nil]
+ h[nil].should == nil
+
+ h = {() => :value}
+ h.keys.should == [nil]
+ h.values.should == [:value]
+ h[nil].should == :value
+
+ h = {key: ()}
+ h.keys.should == [:key]
+ h.values.should == [nil]
+ h[:key].should == nil
+ end
+
+ it "freezes string keys on initialization" do
+ key = +"foo"
+ h = {key => "bar"}
+ key.reverse!
+ h["foo"].should == "bar"
+ h.keys.first.should == "foo"
+ h.keys.first.should.frozen?
+ key.should == "oof"
+ end
+
+ it "checks duplicated keys on initialization" do
+ -> {
+ @h = eval "{foo: :bar, foo: :foo}"
+ }.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
+ h = {a: 1, b: 2,}
+ h.size.should == 2
+ h.should == {a: 1, b: 2}
+ 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}
+ end
+
+ it "with '==>' in the middle raises SyntaxError" 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 "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
+ {foo: 123}.should == {foo: 123}
+ h = {rbx: :cool, specs: 'fail_sometimes'}
+ {rbx: :cool, specs: 'fail_sometimes'}.should == h
+ end
+
+ it "ignores a hanging comma" do
+ {foo: 123,}.should == {foo: 123}
+ h = {rbx: :cool, specs: 'fail_sometimes'}
+ {rbx: :cool, specs: 'fail_sometimes',}.should == h
+ end
+
+ it "accepts mixed 'key: value' and 'key => value' syntax" do
+ h = {:a => 1, :b => 2, "c" => 3}
+ {a: 1, b: 2, "c" => 3}.should == h
+ end
+
+ it "accepts mixed 'key: value', 'key => value' and '\"key\"': value' syntax" do
+ h = {:a => 1, :b => 2, "c" => 3, :d => 4}
+ {a: 1, :b => 2, "c" => 3, "d": 4}.should == h
+ end
+
+ it "expands an '**{}' element into the containing Hash literal initialization" do
+ {a: 1, **{b: 2}, c: 3}.should == {a: 1, b: 2, c: 3}
+ end
+
+ it "expands an '**obj' element into the containing Hash literal initialization" do
+ h = {b: 2, c: 3}
+ {**h, a: 1}.should == {b: 2, c: 3, a: 1}
+ {a: 1, **h}.should == {a: 1, b: 2, c: 3}
+ {a: 1, **h, c: 4}.should == {a: 1, b: 2, c: 4}
+ end
+
+ it "expands a BasicObject using ** into the containing Hash literal initialization" do
+ h = BasicObject.new
+ def h.to_hash; {:b => 2, :c => 3}; end
+ {**h, a: 1}.should == {b: 2, c: 3, a: 1}
+ {a: 1, **h}.should == {a: 1, b: 2, c: 3}
+ {a: 1, **h, c: 4}.should == {a: 1, b: 2, c: 4}
+ end
+
+ ruby_version_is ""..."3.4" do
+ it "does not expand nil using ** into {} and raises TypeError" do
+ h = nil
+ -> { {a: 1, **h} }.should raise_error(TypeError, "no implicit conversion of nil into Hash")
+
+ -> { {a: 1, **nil} }.should raise_error(TypeError, "no implicit conversion of nil into Hash")
+ end
+ end
+
+ ruby_version_is "3.4" do
+ it "expands nil using ** into {}" do
+ h = nil
+ {**h}.should == {}
+ {a: 1, **h}.should == {a: 1}
+
+ {**nil}.should == {}
+ {a: 1, **nil}.should == {a: 1}
+ end
+ end
+
+ it "expands an '**{}' or '**obj' element with the last key/value pair taking precedence" do
+ -> {
+ @h = eval "{a: 1, **{a: 2, b: 3, c: 1}, c: 3}"
+ }.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
+ -> {
+ @h = eval "{a: 1, **{a: 2, **{b: 3, **{c: 4}}, **{d: 5}, }, **{d: 6}}"
+ }.should complain(/key :a is duplicated|duplicated key/)
+ @h.should == {a: 2, b: 3, c: 4, d: 6}
+ end
+
+ it "calls #to_hash to expand an '**obj' element" do
+ obj = mock("hash splat")
+ obj.should_receive(:to_hash).and_return({b: 2, d: 4})
+
+ {a: 1, **obj, c: 3}.should == {a:1, b: 2, c: 3, d: 4}
+ end
+
+ it "allows splatted elements keys that are not symbols" do
+ h = {1 => 2, b: 3}
+ {a: 1, **h}.should == {a: 1, 1 => 2, b: 3}
+ end
+
+ it "raises a TypeError if #to_hash does not return a Hash" do
+ obj = mock("hash splat")
+ obj.should_receive(:to_hash).and_return(obj)
+
+ -> { {**obj} }.should raise_error(TypeError)
+ 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
+ binary_hash = HashStringsBinary.literal_hash
+ utf8_hash = HashStringsUTF8.literal_hash
+ usascii_hash = HashStringsUSASCII.literal_hash
+
+ 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
new file mode 100644
index 0000000000..47ee9c2c51
--- /dev/null
+++ b/spec/ruby/language/heredoc_spec.rb
@@ -0,0 +1,119 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../spec_helper'
+
+describe "Heredoc string" do
+
+ before :each do
+ @ip = 'xxx' # used for interpolation
+ end
+
+ it "allows HEREDOC with <<identifier, interpolated" do
+ s = <<HERE
+foo bar#{@ip}
+HERE
+ s.should == "foo barxxx\n"
+ s.encoding.should == Encoding::US_ASCII
+ end
+
+ it 'allow HEREDOC with <<"identifier", interpolated' do
+ s = <<"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
+ s = <<'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
+ s = <<-HERE
+ foo bar#{@ip}
+ HERE
+
+ s.should == " foo barxxx\n"
+ s.encoding.should == Encoding::US_ASCII
+ end
+
+ it 'allows HEREDOC with <<-"identifier", allowing to indent identifier, interpolated' do
+ s = <<-"HERE"
+ foo bar#{@ip}
+ 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
+ s = <<-'HERE'
+ foo bar#{@ip}
+ HERE
+
+ s.should == ' foo bar#{@ip}' + "\n"
+ s.encoding.should == Encoding::US_ASCII
+ 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
new file mode 100644
index 0000000000..2d1a89f081
--- /dev/null
+++ b/spec/ruby/language/if_spec.rb
@@ -0,0 +1,424 @@
+require_relative '../spec_helper'
+
+describe "The if expression" 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
+
+ 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
+
+ it "evaluates body if expression is true" do
+ a = []
+ if true
+ a << 123
+ end
+ a.should == [123]
+ end
+
+ it "does not evaluate body if expression is false" do
+ a = []
+ if false
+ a << 123
+ end
+ a.should == []
+ end
+
+ it "does not evaluate body if expression is empty" do
+ a = []
+ if ()
+ a << 123
+ end
+ a.should == []
+ end
+
+ it "does not evaluate else-body if expression is true" do
+ a = []
+ if true
+ a << 123
+ else
+ a << 456
+ end
+ a.should == [123]
+ end
+
+ it "evaluates only else-body if expression is false" do
+ a = []
+ if false
+ a << 123
+ else
+ a << 456
+ end
+ a.should == [456]
+ end
+
+ it "returns result of then-body evaluation if expression is true" do
+ if true
+ 123
+ end.should == 123
+ end
+
+ it "returns result of last statement in then-body if expression is true" do
+ if true
+ 'foo'
+ 'bar'
+ 'baz'
+ end.should == 'baz'
+ end
+
+ it "returns result of then-body evaluation if expression is true and else part is present" do
+ if true
+ 123
+ else
+ 456
+ end.should == 123
+ end
+
+ it "returns result of else-body evaluation if expression is false" do
+ if false
+ 123
+ else
+ 456
+ end.should == 456
+ end
+
+ it "returns nil if then-body is empty and expression is true" do
+ if true
+ end.should == nil
+ end
+
+ it "returns nil if then-body is empty, expression is true and else part is present" do
+ if true
+ else
+ 456
+ end.should == nil
+ end
+
+ it "returns nil if then-body is empty, expression is true and else part is empty" do
+ if true
+ else
+ end.should == nil
+ end
+
+ it "returns nil if else-body is empty and expression is false" do
+ if false
+ 123
+ else
+ end.should == nil
+ end
+
+ it "returns nil if else-body is empty, expression is false and then-body is empty" do
+ if false
+ else
+ end.should == nil
+ end
+
+ it "considers an expression with nil result as false" do
+ if nil
+ 123
+ else
+ 456
+ end.should == 456
+ end
+
+ it "considers a non-nil and non-boolean object in expression result as true" do
+ if mock('x')
+ 123
+ else
+ 456
+ end.should == 123
+ end
+
+ it "considers a zero integer in expression result as true" do
+ if 0
+ 123
+ else
+ 456
+ end.should == 123
+ end
+
+ it "allows starting else-body on the same line" do
+ if false
+ 123
+ else 456
+ end.should == 456
+ end
+
+ it "evaluates subsequent elsif statements and execute body of first matching" do
+ if false
+ 123
+ elsif false
+ 234
+ elsif true
+ 345
+ elsif true
+ 456
+ end.should == 345
+ end
+
+ it "evaluates else-body if no if/elsif statements match" do
+ if false
+ 123
+ elsif false
+ 234
+ elsif false
+ 345
+ else
+ 456
+ end.should == 456
+ end
+
+ it "allows 'then' after expression when then-body is on the next line" do
+ if true then
+ 123
+ end.should == 123
+
+ if true then ; 123; end.should == 123
+ end
+
+ it "allows then-body on the same line separated with 'then'" do
+ if true then 123
+ end.should == 123
+
+ if true then 123; end.should == 123
+ end
+
+ it "returns nil when then-body on the same line separated with 'then' and expression is false" do
+ if false then 123
+ end.should == nil
+
+ if false then 123; end.should == nil
+ end
+
+ it "returns nil when then-body separated by 'then' is empty and expression is true" do
+ if true then
+ end.should == nil
+
+ if true then ; end.should == nil
+ end
+
+ it "returns nil when then-body separated by 'then', expression is false and no else part" do
+ if false then
+ end.should == nil
+
+ if false then ; end.should == nil
+ end
+
+ it "evaluates then-body when then-body separated by 'then', expression is true and else part is present" do
+ if true then 123
+ else 456
+ end.should == 123
+
+ if true then 123; else 456; end.should == 123
+ end
+
+ it "evaluates else-body when then-body separated by 'then' and expression is false" do
+ if false then 123
+ else 456
+ end.should == 456
+
+ if false then 123; else 456; end.should == 456
+ end
+
+ describe "with a boolean range ('flip-flop' operator)" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "mimics an awk conditional with a single-element inclusive-end range" do
+ 10.times { |i| ScratchPad << i if (i == 4)..(i == 4) }
+ ScratchPad.recorded.should == [4]
+ end
+
+ it "mimics an awk conditional with a many-element inclusive-end range" do
+ 10.times { |i| ScratchPad << i if (i == 4)..(i == 7) }
+ ScratchPad.recorded.should == [4, 5, 6, 7]
+ end
+
+ it "mimics a sed conditional with a zero-element exclusive-end range" do
+ eval "10.times { |i| ScratchPad << i if (i == 4)...(i == 4) }"
+ ScratchPad.recorded.should == [4, 5, 6, 7, 8, 9]
+ end
+
+ it "mimics a sed conditional with a many-element exclusive-end range" do
+ 10.times { |i| ScratchPad << i if (i == 4)...(i == 5) }
+ ScratchPad.recorded.should == [4, 5]
+ end
+
+ it "allows combining two flip-flops" do
+ 10.times { |i| ScratchPad << i if (i == 4)...(i == 5) or (i == 7)...(i == 8) }
+ ScratchPad.recorded.should == [4, 5, 7, 8]
+ end
+
+ it "evaluates the first conditions lazily with inclusive-end range" do
+ collector = proc { |i| ScratchPad << i }
+ eval "10.times { |i| i if collector[i]...false }"
+ ScratchPad.recorded.should == [0]
+ end
+
+ it "evaluates the first conditions lazily with exclusive-end range" do
+ collector = proc { |i| ScratchPad << i }
+ eval "10.times { |i| i if collector[i]..false }"
+ ScratchPad.recorded.should == [0]
+ end
+
+ it "evaluates the second conditions lazily with inclusive-end range" do
+ collector = proc { |i| ScratchPad << i }
+ 10.times { |i| i if (i == 4)...collector[i] }
+ ScratchPad.recorded.should == [5]
+ end
+
+ it "evaluates the second conditions lazily with exclusive-end range" do
+ collector = proc { |i| ScratchPad << i }
+ 10.times { |i| i if (i == 4)..collector[i] }
+ ScratchPad.recorded.should == [4]
+ end
+
+ it "scopes state by flip-flop" do
+ store_me = proc { |i| ScratchPad << i if (i == 4)..(i == 7) }
+ store_me[1]
+ store_me[4]
+ proc { store_me[1] }.call
+ store_me[7]
+ store_me[5]
+ ScratchPad.recorded.should == [4, 1, 7]
+ end
+
+ it "keeps flip-flops from interfering" do
+ a = eval "proc { |i| ScratchPad << i if (i == 4)..(i == 7) }"
+ b = eval "proc { |i| ScratchPad << i if (i == 4)..(i == 7) }"
+ 6.times(&a)
+ 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
+
+describe "The postfix if form" do
+ it "evaluates statement if expression is true" do
+ a = []
+ a << 123 if true
+ a.should == [123]
+ end
+
+ it "does not evaluate statement if expression is false" do
+ a = []
+ a << 123 if false
+ a.should == []
+ end
+
+ it "returns result of expression if value is true" do
+ (123 if true).should == 123
+ end
+
+ it "returns nil if expression is false" do
+ (123 if false).should == nil
+ end
+
+ it "considers a nil expression as false" do
+ (123 if nil).should == nil
+ end
+
+ it "considers a non-nil object as true" do
+ (123 if mock('x')).should == 123
+ end
+
+ it "evaluates then-body in containing scope" do
+ a = 123
+ if true
+ b = a+1
+ end
+ b.should == 124
+ end
+
+ it "evaluates else-body in containing scope" do
+ a = 123
+ if false
+ b = a+1
+ else
+ b = a+2
+ end
+ b.should == 125
+ end
+
+ it "evaluates elsif-body in containing scope" do
+ a = 123
+ if false
+ b = a+1
+ elsif false
+ b = a+2
+ elsif true
+ b = a+3
+ else
+ b = a+4
+ end
+ b.should == 126
+ 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
new file mode 100644
index 0000000000..ed5a1c69e8
--- /dev/null
+++ b/spec/ruby/language/lambda_spec.rb
@@ -0,0 +1,587 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "A lambda literal -> () { }" do
+ SpecEvaluate.desc = "for definition"
+
+ it "returns a Proc object when used in a BasicObject method" do
+ klass = Class.new(BasicObject) do
+ def create_lambda
+ -> { }
+ end
+ end
+
+ klass.new.create_lambda.should be_an_instance_of(Proc)
+ end
+
+ it "does not execute the block" do
+ -> { fail }.should be_an_instance_of(Proc)
+ end
+
+ it "returns a lambda" do
+ -> { }.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
+ l = -> arg {
+ var = arg
+ # this would override var if it was declared outside the lambda
+ l.call(arg-1) if arg > 0
+ var
+ }
+ l.call(1).should == 1
+ end
+
+ context "assigns no local variables" do
+ evaluate <<-ruby do
+ @a = -> { }
+ @b = ->() { }
+ @c = -> () { }
+ @d = -> do end
+ ruby
+
+ @a.().should be_nil
+ @b.().should be_nil
+ @c.().should be_nil
+ @d.().should be_nil
+ end
+ end
+
+ context "assigns variables from parameters" do
+ evaluate <<-ruby do
+ @a = -> (a) { a }
+ ruby
+
+ @a.(1).should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = -> ((a)) { a }
+ ruby
+
+ @a.(1).should == 1
+ @a.([1, 2, 3]).should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = -> ((*a, b)) { [a, b] }
+ ruby
+
+ @a.(1).should == [[], 1]
+ @a.([1, 2, 3]).should == [[1, 2], 3]
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a={}) { a }
+ ruby
+
+ @a.().should == {}
+ @a.(2).should == 2
+ end
+
+ evaluate <<-ruby do
+ @a = -> (*) { }
+ ruby
+
+ @a.().should be_nil
+ @a.(1).should be_nil
+ @a.(1, 2, 3).should be_nil
+ end
+
+ evaluate <<-ruby do
+ @a = -> (*a) { a }
+ ruby
+
+ @a.().should == []
+ @a.(1).should == [1]
+ @a.(1, 2, 3).should == [1, 2, 3]
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a:) { a }
+ ruby
+
+ -> { @a.() }.should raise_error(ArgumentError)
+ @a.(a: 1).should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a: 1) { a }
+ ruby
+
+ @a.().should == 1
+ @a.(a: 2).should == 2
+ end
+
+ evaluate <<-ruby do
+ @a = -> (**) { }
+ ruby
+
+ @a.().should be_nil
+ @a.(a: 1, b: 2).should be_nil
+ -> { @a.(1) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ @a = -> (**k) { k }
+ ruby
+
+ @a.().should == {}
+ @a.(a: 1, b: 2).should == {a: 1, b: 2}
+ end
+
+ evaluate <<-ruby do
+ @a = -> (&b) { b }
+ ruby
+
+ @a.().should be_nil
+ @a.() { }.should be_an_instance_of(Proc)
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a, b) { [a, b] }
+ ruby
+
+ @a.(1, 2).should == [1, 2]
+ -> { @a.() }.should raise_error(ArgumentError)
+ -> { @a.(1) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ @a = -> ((a, b, *c, d), (*e, f, g), (*h)) do
+ [a, b, c, d, e, f, g, h]
+ end
+ ruby
+
+ @a.(1, 2, 3).should == [1, nil, [], nil, [], 2, nil, [3]]
+ result = @a.([1, 2, 3], [4, 5, 6, 7, 8], [9, 10])
+ result.should == [1, 2, [], 3, [4, 5, 6], 7, 8, [9, 10]]
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a, (b, (c, *d, (e, (*f)), g), (h, (i, j)))) do
+ [a, b, c, d, e, f, g, h, i, j]
+ end
+ ruby
+
+ @a.(1, 2).should == [1, 2, nil, [], nil, [nil], nil, nil, nil, nil]
+ result = @a.(1, [2, [3, 4, 5, [6, [7, 8]], 9], [10, [11, 12]]])
+ result.should == [1, 2, 3, [4, 5], 6, [7, 8], 9, 10, 11, 12]
+ end
+
+ evaluate <<-ruby do
+ @a = -> (*, **k) { k }
+ ruby
+
+ @a.().should == {}
+ @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
+
+ h = mock("keyword splat")
+ h.should_not_receive(:to_hash)
+ @a.(h).should == {}
+ end
+
+ evaluate <<-ruby do
+ @a = -> (*, &b) { b }
+ ruby
+
+ @a.().should be_nil
+ @a.(1, 2, 3, 4).should be_nil
+ @a.(&(l = ->{})).should equal(l)
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a:, b:) { [a, b] }
+ ruby
+
+ @a.(a: 1, b: 2).should == [1, 2]
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a:, b: 1) { [a, b] }
+ ruby
+
+ @a.(a: 1).should == [1, 1]
+ @a.(a: 1, b: 2).should == [1, 2]
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a: 1, b:) { [a, b] }
+ ruby
+
+ @a.(b: 0).should == [1, 0]
+ @a.(b: 2, a: 3).should == [3, 2]
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a: @a = -> (a: 1) { a }, b:) do
+ [a, b]
+ end
+ ruby
+
+ @a.(a: 2, b: 3).should == [2, 3]
+ @a.(b: 1).should == [@a, 1]
+
+ # Note the default value of a: in the original method.
+ @a.().should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a: 1, b: 2) { [a, b] }
+ ruby
+
+ @a.().should == [1, 2]
+ @a.(b: 3, a: 4).should == [4, 3]
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a, b=1, *c, (*d, (e)), f: 2, g:, h:, **k, &l) do
+ [a, b, c, d, e, f, g, h, k, l]
+ end
+ ruby
+
+ result = @a.(9, 8, 7, 6, f: 5, g: 4, h: 3, &(l = ->{}))
+ result.should == [9, 8, [7], [], 6, 5, 4, 3, {}, l]
+ end
+
+ evaluate <<-ruby do
+ @a = -> a, b=1, *c, d, e:, f: 2, g:, **k, &l do
+ [a, b, c, d, e, f, g, k, l]
+ end
+ ruby
+
+ result = @a.(1, 2, e: 3, g: 4, h: 5, i: 6, &(l = ->{}))
+ result.should == [1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l]
+ end
+
+ describe "with circular optional argument reference" do
+ 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
+
+ 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
+ end
+ end
+ end
+end
+
+describe "A lambda expression 'lambda { ... }'" do
+ SpecEvaluate.desc = "for definition"
+
+ it "calls the #lambda method" do
+ obj = mock("lambda definition")
+ obj.should_receive(:lambda).and_return(obj)
+
+ def obj.define
+ lambda { }
+ end
+
+ obj.define.should equal(obj)
+ end
+
+ it "does not execute the block" do
+ lambda { fail }.should be_an_instance_of(Proc)
+ end
+
+ it "returns a lambda" do
+ lambda { }.lambda?.should be_true
+ end
+
+ it "requires a block" do
+ 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
+ before do
+ def meth; lambda; end
+ end
+
+ it "raises ArgumentError" do
+ implicit_lambda = nil
+ suppress_warning do
+ -> {
+ meth { 1 }
+ }.should raise_error(ArgumentError, /tried to create Proc object without a block/)
+ end
+ end
+ end
+
+ context "assigns no local variables" do
+ evaluate <<-ruby do
+ @a = lambda { }
+ @b = lambda { || }
+ ruby
+
+ @a.().should be_nil
+ @b.().should be_nil
+ end
+ end
+
+ context "assigns variables from parameters" do
+ evaluate <<-ruby do
+ @a = lambda { |a| a }
+ ruby
+
+ @a.(1).should == 1
+ end
+
+ evaluate <<-ruby do
+ def m(*a) yield(*a) end
+ @a = lambda { |a| a }
+ ruby
+
+ lambda { m(&@a) }.should raise_error(ArgumentError)
+ lambda { m(1, 2, &@a) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a, | a }
+ ruby
+
+ @a.(1).should == 1
+ @a.([1, 2]).should == [1, 2]
+
+ lambda { @a.() }.should raise_error(ArgumentError)
+ lambda { @a.(1, 2) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(a) yield a end
+ def m2() yield end
+
+ @a = lambda { |a, | a }
+ ruby
+
+ m(1, &@a).should == 1
+ m([1, 2], &@a).should == [1, 2]
+
+ lambda { m2(&@a) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |(a)| a }
+ ruby
+
+ @a.(1).should == 1
+ @a.([1, 2, 3]).should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |(*a, b)| [a, b] }
+ ruby
+
+ @a.(1).should == [[], 1]
+ @a.([1, 2, 3]).should == [[1, 2], 3]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a={}| a }
+ ruby
+
+ @a.().should == {}
+ @a.(2).should == 2
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |*| }
+ ruby
+
+ @a.().should be_nil
+ @a.(1).should be_nil
+ @a.(1, 2, 3).should be_nil
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |*a| a }
+ ruby
+
+ @a.().should == []
+ @a.(1).should == [1]
+ @a.(1, 2, 3).should == [1, 2, 3]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a:| a }
+ ruby
+
+ lambda { @a.() }.should raise_error(ArgumentError)
+ @a.(a: 1).should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a: 1| a }
+ ruby
+
+ @a.().should == 1
+ @a.(a: 2).should == 2
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |**| }
+ ruby
+
+ @a.().should be_nil
+ @a.(a: 1, b: 2).should be_nil
+ lambda { @a.(1) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |**k| k }
+ ruby
+
+ @a.().should == {}
+ @a.(a: 1, b: 2).should == {a: 1, b: 2}
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |&b| b }
+ ruby
+
+ @a.().should be_nil
+ @a.() { }.should be_an_instance_of(Proc)
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a, b| [a, b] }
+ ruby
+
+ @a.(1, 2).should == [1, 2]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda do |(a, b, *c, d), (*e, f, g), (*h)|
+ [a, b, c, d, e, f, g, h]
+ end
+ ruby
+
+ @a.(1, 2, 3).should == [1, nil, [], nil, [], 2, nil, [3]]
+ result = @a.([1, 2, 3], [4, 5, 6, 7, 8], [9, 10])
+ result.should == [1, 2, [], 3, [4, 5, 6], 7, 8, [9, 10]]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda do |a, (b, (c, *d, (e, (*f)), g), (h, (i, j)))|
+ [a, b, c, d, e, f, g, h, i, j]
+ end
+ ruby
+
+ @a.(1, 2).should == [1, 2, nil, [], nil, [nil], nil, nil, nil, nil]
+ result = @a.(1, [2, [3, 4, 5, [6, [7, 8]], 9], [10, [11, 12]]])
+ result.should == [1, 2, 3, [4, 5], 6, [7, 8], 9, 10, 11, 12]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |*, **k| k }
+ ruby
+
+ @a.().should == {}
+ @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
+
+ h = mock("keyword splat")
+ h.should_not_receive(:to_hash)
+ @a.(h).should == {}
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |*, &b| b }
+ ruby
+
+ @a.().should be_nil
+ @a.(1, 2, 3, 4).should be_nil
+ @a.(&(l = ->{})).should equal(l)
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a:, b:| [a, b] }
+ ruby
+
+ @a.(a: 1, b: 2).should == [1, 2]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a:, b: 1| [a, b] }
+ ruby
+
+ @a.(a: 1).should == [1, 1]
+ @a.(a: 1, b: 2).should == [1, 2]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a: 1, b:| [a, b] }
+ ruby
+
+ @a.(b: 0).should == [1, 0]
+ @a.(b: 2, a: 3).should == [3, 2]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda do |a: (@a = -> (a: 1) { a }), b:|
+ [a, b]
+ end
+ ruby
+
+ @a.(a: 2, b: 3).should == [2, 3]
+ @a.(b: 1).should == [@a, 1]
+
+ # Note the default value of a: in the original method.
+ @a.().should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a: 1, b: 2| [a, b] }
+ ruby
+
+ @a.().should == [1, 2]
+ @a.(b: 3, a: 4).should == [4, 3]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda do |a, b=1, *c, (*d, (e)), f: 2, g:, h:, **k, &l|
+ [a, b, c, d, e, f, g, h, k, l]
+ end
+ ruby
+
+ result = @a.(9, 8, 7, 6, f: 5, g: 4, h: 3, &(l = ->{}))
+ result.should == [9, 8, [7], [], 6, 5, 4, 3, {}, l]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda do |a, b=1, *c, d, e:, f: 2, g:, **k, &l|
+ [a, b, c, d, e, f, g, k, l]
+ end
+ ruby
+
+ result = @a.(1, 2, e: 3, g: 4, h: 5, i: 6, &(l = ->{}))
+ result.should == [1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l]
+ end
+ end
+end
diff --git a/spec/ruby/language/line_spec.rb b/spec/ruby/language/line_spec.rb
new file mode 100644
index 0000000000..fcadaa71d7
--- /dev/null
+++ b/spec/ruby/language/line_spec.rb
@@ -0,0 +1,45 @@
+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
+ -> { eval("__LINE__ = 1") }.should raise_error(SyntaxError)
+ end
+
+ before :each do
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "equals the line number of the text inside an eval" do
+ eval <<-EOC
+ScratchPad << __LINE__
+
+# line 3
+
+ScratchPad << __LINE__
+ EOC
+
+ ScratchPad.recorded.should == [1, 5]
+ end
+end
+
+describe "The __LINE__ pseudo-variable" do
+ it_behaves_like :language___LINE__, :require, CodeLoadingSpecs::Method.new
+end
+
+describe "The __LINE__ pseudo-variable" do
+ it_behaves_like :language___LINE__, :require, Kernel
+end
+
+describe "The __LINE__ pseudo-variable" do
+ it_behaves_like :language___LINE__, :load, CodeLoadingSpecs::Method.new
+end
+
+describe "The __LINE__ pseudo-variable" do
+ it_behaves_like :language___LINE__, :load, Kernel
+end
diff --git a/spec/ruby/language/loop_spec.rb b/spec/ruby/language/loop_spec.rb
new file mode 100644
index 0000000000..fd17b53910
--- /dev/null
+++ b/spec/ruby/language/loop_spec.rb
@@ -0,0 +1,67 @@
+require_relative '../spec_helper'
+
+describe "The loop expression" do
+ it "repeats the given block until a break is called" do
+ outer_loop = 0
+ loop do
+ outer_loop += 1
+ break if outer_loop == 10
+ end
+ outer_loop.should == 10
+ end
+
+ it "executes code in its own scope" do
+ loop do
+ inner_loop = 123
+ break
+ end
+ -> { inner_loop }.should raise_error(NameError)
+ end
+
+ it "returns the value passed to break if interrupted by break" do
+ loop do
+ break 123
+ end.should == 123
+ end
+
+ it "returns nil if interrupted by break with no arguments" do
+ loop do
+ break
+ end.should == nil
+ end
+
+ it "skips to end of body with next" do
+ a = []
+ i = 0
+ loop do
+ break if (i+=1) >= 5
+ next if i == 3
+ a << i
+ end
+ a.should == [1, 2, 4]
+ end
+
+ it "restarts the current iteration with redo" do
+ a = []
+ loop do
+ a << 1
+ redo if a.size < 2
+ a << 2
+ break if a.size == 3
+ end
+ a.should == [1, 1, 2]
+ end
+
+ it "uses a spaghetti nightmare of redo, next and break" do
+ a = []
+ loop do
+ a << 1
+ redo if a.size == 1
+ a << 2
+ next if a.size == 3
+ a << 3
+ break if a.size > 6
+ end
+ a.should == [1, 1, 2, 1, 2, 3, 1, 2, 3]
+ end
+end
diff --git a/spec/ruby/language/magic_comment_spec.rb b/spec/ruby/language/magic_comment_spec.rb
new file mode 100644
index 0000000000..af9c9dbfd0
--- /dev/null
+++ b/spec/ruby/language/magic_comment_spec.rb
@@ -0,0 +1,93 @@
+require_relative '../spec_helper'
+
+# 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 "are optional" do
+ @object.call('no_magic_comment.rb').should == @default.name
+ end
+
+ it "are case-insensitive" do
+ @object.call('case_magic_comment.rb').should == Encoding::Big5.name
+ end
+
+ it "must be at the first line" do
+ @object.call('second_line_magic_comment.rb').should == @default.name
+ end
+
+ it "must be the first token of the line" do
+ @object.call('second_token_magic_comment.rb').should == @default.name
+ end
+
+ it "can be after the shebang" do
+ @object.call('shebang_magic_comment.rb').should == Encoding::Big5.name
+ end
+
+ it "can take Emacs style" do
+ @object.call('emacs_magic_comment.rb').should == Encoding::Big5.name
+ end
+
+ it "can take vim style" do
+ @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
new file mode 100644
index 0000000000..ebf677cabc
--- /dev/null
+++ b/spec/ruby/language/match_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/match_operators'
+
+describe "The !~ operator" do
+ before :each do
+ @obj = OperatorImplementor.new
+ end
+
+ it "evaluates as a call to !~" do
+ expected = "hello world"
+
+ opval = (@obj !~ expected)
+ methodval = @obj.send(:"!~", expected)
+
+ opval.should == expected
+ methodval.should == expected
+ end
+end
+
+describe "The =~ operator" do
+ before :each do
+ @impl = OperatorImplementor.new
+ end
+
+ it "calls the =~ method" do
+ expected = "hello world"
+
+ opval = (@obj =~ expected)
+ methodval = @obj.send(:"=~", expected)
+
+ opval.should == expected
+ methodval.should == expected
+ end
+end
+
+describe "The =~ operator with named captures" do
+ before :each do
+ @regexp = /(?<matched>foo)(?<unmatched>bar)?/
+ @string = "foofoo"
+ end
+
+ describe "on syntax of /regexp/ =~ string_variable" do
+ it "sets local variables by the captured pairs" do
+ /(?<matched>foo)(?<unmatched>bar)?/ =~ @string
+ local_variables.should == [:matched, :unmatched]
+ matched.should == "foo"
+ unmatched.should == nil
+ 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)?/
+ local_variables.should == []
+ end
+ end
+
+ describe "on syntax of regexp_variable =~ string_variable" do
+ it "does not set local variables" do
+ @regexp =~ @string
+ local_variables.should == []
+ end
+ end
+
+ describe "on the method calling" do
+ it "does not set local variables" do
+ @regexp.=~(@string)
+ local_variables.should == []
+
+ @regexp.send :=~, @string
+ local_variables.should == []
+ end
+ end
+end
diff --git a/spec/ruby/language/metaclass_spec.rb b/spec/ruby/language/metaclass_spec.rb
new file mode 100644
index 0000000000..fc83067977
--- /dev/null
+++ b/spec/ruby/language/metaclass_spec.rb
@@ -0,0 +1,143 @@
+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
+ class << true; self; end.should == TrueClass
+ end
+
+ it "is FalseClass for false" do
+ class << false; self; end.should == FalseClass
+ end
+
+ it "is NilClass for nil" do
+ class << nil; self; end.should == NilClass
+ end
+
+ it "raises a TypeError for numbers" do
+ -> { class << 1; self; end }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError for symbols" do
+ -> { class << :symbol; self; end }.should raise_error(TypeError)
+ end
+
+ it "is a singleton Class instance" do
+ cls = class << mock('x'); self; end
+ cls.is_a?(Class).should == true
+ cls.should_not equal(Object)
+ end
+end
+
+describe "A constant on a metaclass" do
+ before :each do
+ @object = Object.new
+ class << @object
+ CONST = self
+ end
+ end
+
+ it "can be accessed after the metaclass body is reopened" do
+ class << @object
+ CONST.should == self
+ end
+ end
+
+ it "can be accessed via self::CONST" do
+ class << @object
+ self::CONST.should == self
+ end
+ end
+
+ it "can be accessed via const_get" do
+ class << @object
+ const_get(:CONST).should == self
+ end
+ end
+
+ it "is not defined on the object's class" do
+ @object.class.const_defined?(:CONST).should be_false
+ end
+
+ it "is not defined in the metaclass opener's scope" do
+ class << @object
+ CONST
+ end
+ -> { CONST }.should raise_error(NameError)
+ end
+
+ it "cannot be accessed via object::CONST" do
+ -> do
+ @object::CONST
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a NameError for anonymous_module::CONST" do
+ @object = Class.new
+ class << @object
+ CONST = 100
+ end
+
+ -> do
+ @object::CONST
+ end.should raise_error(NameError)
+ end
+
+ it "appears in the metaclass constant list" do
+ constants = class << @object; constants; end
+ constants.should include(:CONST)
+ end
+
+ it "does not appear in the object's class constant list" do
+ @object.class.constants.should_not include(:CONST)
+ end
+
+ it "is not preserved when the object is duped" do
+ @object = @object.dup
+
+ -> do
+ class << @object; CONST; end
+ end.should raise_error(NameError)
+ end
+
+ it "is preserved when the object is cloned" do
+ @object = @object.clone
+
+ class << @object
+ CONST.should_not be_nil
+ end
+ end
+end
+
+describe "calling methods on the metaclass" do
+
+ it "calls a method on the metaclass" do
+ MetaClassSpecs::A.cheese.should == 'edam'
+ MetaClassSpecs::B.cheese.should == 'stilton'
+ end
+
+ it "calls a method on the instance's metaclass" do
+ b = MetaClassSpecs::B.new
+ b_meta = MetaClassSpecs.metaclass_of b
+ b_meta.send(:define_method, :cheese) {'cheshire'}
+ b.cheese.should == 'cheshire'
+ end
+
+ it "calls a method in deeper chains of metaclasses" do
+ b = MetaClassSpecs::B.new
+ b_meta = MetaClassSpecs.metaclass_of b
+ b_meta_meta = MetaClassSpecs.metaclass_of b_meta
+ b_meta_meta.send(:define_method, :cheese) {'gouda'}
+ b_meta.cheese.should == 'gouda'
+
+ b_meta_meta_meta = MetaClassSpecs.metaclass_of b_meta_meta
+ b_meta_meta_meta.send(:define_method, :cheese) {'wensleydale'}
+ b_meta_meta.cheese.should == 'wensleydale'
+ end
+
+ it "calls a method defined on the metaclass of the metaclass" do
+ d_meta = MetaClassSpecs::D.singleton_class
+ d_meta.ham.should == 'iberico'
+ end
+end
diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb
new file mode 100644
index 0000000000..8f72bd45ed
--- /dev/null
+++ b/spec/ruby/language/method_spec.rb
@@ -0,0 +1,1649 @@
+require_relative '../spec_helper'
+
+describe "A method send" do
+ evaluate <<-ruby do
+ def m(a) a end
+ ruby
+
+ a = b = m 1
+ a.should == 1
+ b.should == 1
+ end
+
+ context "with a single splatted Object argument" do
+ before :all do
+ def m(a) a end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ m(*x).should equal(x)
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([1])
+
+ m(*x).should == 1
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ m(*x).should == x
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { m(*x) }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a leading splatted Object argument" do
+ before :all do
+ def m(a, b, *c, d, e) [a, b, c, d, e] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ m(*x, 1, 2, 3).should == [x, 1, [], 2, 3]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([1])
+
+ m(*x, 2, 3, 4).should == [1, 2, [], 3, 4]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ m(*x, 2, 3, 4).should == [x, 2, [], 3, 4]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { m(*x, 2, 3) }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a middle splatted Object argument" do
+ before :all do
+ def m(a, b, *c, d, e) [a, b, c, d, e] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ m(1, 2, *x, 3, 4).should == [1, 2, [x], 3, 4]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([5, 6, 7])
+
+ m(1, 2, *x, 3).should == [1, 2, [5, 6], 7, 3]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ m(1, 2, *x, 4).should == [1, 2, [], x, 4]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { m(1, *x, 2, 3) }.should raise_error(TypeError)
+ end
+
+ it "copies the splatted array" do
+ args = [3, 4]
+ m(1, 2, *args, 4, 5).should == [1, 2, [3, 4], 4, 5]
+ m(1, 2, *args, 4, 5)[2].should_not equal(args)
+ end
+
+ it "allows an array being splatted to be modified by another argument" do
+ args = [3, 4]
+ m(1, args.shift, *args, 4, 5).should == [1, 3, [4], 4, 5]
+ end
+ end
+
+ context "with a trailing splatted Object argument" do
+ before :all do
+ def m(a, *b, c) [a, b, c] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ m(1, 2, *x).should == [1, [2], x]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([5, 6, 7])
+
+ m(1, 2, *x).should == [1, [2, 5, 6], 7]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ m(1, 2, *x, 4).should == [1, [2, x], 4]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { 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
+
+describe "An element assignment method send" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ context "with a single splatted Object argument" do
+ before :all do
+ @o = mock("element set receiver")
+ def @o.[]=(a, b) ScratchPad.record [a, b] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ (@o[*x] = 1).should == 1
+ ScratchPad.recorded.should == [x, 1]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([1])
+
+ (@o[*x] = 2).should == 2
+ ScratchPad.recorded.should == [1, 2]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ (@o[*x] = 1).should == 1
+ ScratchPad.recorded.should == [x, 1]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { @o[*x] = 1 }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a leading splatted Object argument" do
+ before :all do
+ @o = mock("element set receiver")
+ def @o.[]=(a, b, *c, d, e) ScratchPad.record [a, b, c, d, e] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ (@o[*x, 2, 3, 4] = 1).should == 1
+ ScratchPad.recorded.should == [x, 2, [3], 4, 1]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([1, 2, 3])
+
+ (@o[*x, 4, 5] = 6).should == 6
+ ScratchPad.recorded.should == [1, 2, [3, 4], 5, 6]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ (@o[*x, 2, 3, 4] = 5).should == 5
+ ScratchPad.recorded.should == [x, 2, [3], 4, 5]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { @o[*x, 2, 3] = 4 }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a middle splatted Object argument" do
+ before :all do
+ @o = mock("element set receiver")
+ def @o.[]=(a, b, *c, d, e) ScratchPad.record [a, b, c, d, e] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ (@o[1, *x, 2, 3] = 4).should == 4
+ ScratchPad.recorded.should == [1, x, [2], 3, 4]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([2, 3])
+
+ (@o[1, *x, 4] = 5).should == 5
+ ScratchPad.recorded.should == [1, 2, [3], 4, 5]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ (@o[1, 2, *x, 3] = 4).should == 4
+ ScratchPad.recorded.should == [1, 2, [x], 3, 4]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { @o[1, 2, *x, 3] = 4 }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a trailing splatted Object argument" do
+ before :all do
+ @o = mock("element set receiver")
+ def @o.[]=(a, b, *c, d, e) ScratchPad.record [a, b, c, d, e] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ (@o[1, 2, 3, 4, *x] = 5).should == 5
+ ScratchPad.recorded.should == [1, 2, [3, 4], x, 5]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([4, 5])
+
+ (@o[1, 2, 3, *x] = 6).should == 6
+ ScratchPad.recorded.should == [1, 2, [3, 4], 5, 6]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ (@o[1, 2, 3, *x] = 4).should == 4
+ ScratchPad.recorded.should == [1, 2, [3], x, 4]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { @o[1, 2, 3, *x] = 4 }.should raise_error(TypeError)
+ end
+ end
+end
+
+describe "An attribute assignment method send" do
+ context "with a single splatted Object argument" do
+ before :all do
+ @o = mock("element set receiver")
+ def @o.m=(a, b) [a, b] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ (@o.send :m=, *x, 1).should == [x, 1]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([1])
+
+ (@o.send :m=, *x, 2).should == [1, 2]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ (@o.send :m=, *x, 1).should == [x, 1]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { @o.send :m=, *x, 1 }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a leading splatted Object argument" do
+ before :all do
+ @o = mock("element set receiver")
+ def @o.m=(a, b, *c, d, e) [a, b, c, d, e] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ (@o.send :m=, *x, 2, 3, 4, 1).should == [x, 2, [3], 4, 1]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([1, 2, 3])
+
+ (@o.send :m=, *x, 4, 5, 6).should == [1, 2, [3, 4], 5, 6]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ (@o.send :m=, *x, 2, 3, 4, 5).should == [x, 2, [3], 4, 5]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { @o.send :m=, *x, 2, 3, 4 }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a middle splatted Object argument" do
+ before :all do
+ @o = mock("element set receiver")
+ def @o.m=(a, b, *c, d, e) [a, b, c, d, e] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ (@o.send :m=, 1, *x, 2, 3, 4).should == [1, x, [2], 3, 4]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([2, 3])
+
+ (@o.send :m=, 1, *x, 4, 5).should == [1, 2, [3], 4, 5]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ (@o.send :m=, 1, 2, *x, 3, 4).should == [1, 2, [x], 3, 4]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { @o.send :m=, 1, 2, *x, 3, 4 }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a trailing splatted Object argument" do
+ before :all do
+ @o = mock("element set receiver")
+ def @o.m=(a, b, *c, d, e) [a, b, c, d, e] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ (@o.send :m=, 1, 2, 3, 4, *x, 5).should == [1, 2, [3, 4], x, 5]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([4, 5])
+
+ (@o.send :m=, 1, 2, 3, *x, 6).should == [1, 2, [3, 4], 5, 6]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ (@o.send :m=, 1, 2, 3, *x, 4).should == [1, 2, [3], x, 4]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { @o.send :m=, 1, 2, 3, *x, 4 }.should raise_error(TypeError)
+ end
+ end
+end
+
+describe "A method" do
+ SpecEvaluate.desc = "for definition"
+
+ context "assigns no local variables" do
+ evaluate <<-ruby do
+ def m
+ end
+ ruby
+
+ m.should be_nil
+ end
+
+ evaluate <<-ruby do
+ def m()
+ end
+ ruby
+
+ m.should be_nil
+ end
+ end
+
+ context "assigns local variables from method parameters" do
+ evaluate <<-ruby do
+ def m(a) a end
+ ruby
+
+ m((args = 1, 2, 3)).should equal(args)
+ end
+
+ evaluate <<-ruby do
+ def m((a)) a end
+ ruby
+
+ m(1).should == 1
+ m([1, 2, 3]).should == 1
+ end
+
+ evaluate <<-ruby do
+ def m((*a, b)) [a, b] end
+ ruby
+
+ m(1).should == [[], 1]
+ m([1, 2, 3]).should == [[1, 2], 3]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1) a end
+ ruby
+
+ m().should == 1
+ m(2).should == 2
+ 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
+
+ m().should be_nil
+ m(1).should be_nil
+ m(1, 2, 3).should be_nil
+ end
+
+ evaluate <<-ruby do
+ def m(*a) a end
+ ruby
+
+ 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
+
+ -> { m() }.should raise_error(ArgumentError)
+ m(a: 1).should == 1
+ 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
+ def m(a: 1) a end
+ ruby
+
+ m().should == 1
+ m(a: 2).should == 2
+ end
+
+ evaluate <<-ruby do
+ def m(**) end
+ ruby
+
+ m().should be_nil
+ m(a: 1, b: 2).should be_nil
+ -> { m(1) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(**k) k end
+ ruby
+
+ m().should == {}
+ m(a: 1, b: 2).should == { a: 1, b: 2 }
+ 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
+ def m(&b) b end
+ ruby
+
+ m { }.should be_an_instance_of(Proc)
+ end
+
+ evaluate <<-ruby do
+ def m(a, b) [a, b] end
+ ruby
+
+ m(1, 2).should == [1, 2]
+ end
+
+ evaluate <<-ruby do
+ def m(a, (b, c)) [a, b, c] end
+ ruby
+
+ m(1, 2).should == [1, 2, nil]
+ m(1, [2, 3, 4]).should == [1, 2, 3]
+ end
+
+ evaluate <<-ruby do
+ def m((a), (b)) [a, b] end
+ ruby
+
+ m(1, 2).should == [1, 2]
+ m([1, 2], [3, 4]).should == [1, 3]
+ end
+
+ evaluate <<-ruby do
+ def m((*), (*)) end
+ ruby
+
+ m(2, 3).should be_nil
+ m([2, 3, 4], [5, 6]).should be_nil
+ -> { m a: 1 }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m((*a), (*b)) [a, b] end
+ ruby
+
+ m(1, 2).should == [[1], [2]]
+ m([1, 2], [3, 4]).should == [[1, 2], [3, 4]]
+ end
+
+ evaluate <<-ruby do
+ def m((a, b), (c, d))
+ [a, b, c, d]
+ end
+ ruby
+
+ m(1, 2).should == [1, nil, 2, nil]
+ m([1, 2, 3], [4, 5, 6]).should == [1, 2, 4, 5]
+ end
+
+ evaluate <<-ruby do
+ def m((a, *b), (*c, d))
+ [a, b, c, d]
+ end
+ ruby
+
+ m(1, 2).should == [1, [], [], 2]
+ m([1, 2, 3], [4, 5, 6]).should == [1, [2, 3], [4, 5], 6]
+ end
+
+ evaluate <<-ruby do
+ def m((a, b, *c, d), (*e, f, g), (*h))
+ [a, b, c, d, e, f, g, h]
+ end
+ ruby
+
+ m(1, 2, 3).should == [1, nil, [], nil, [], 2, nil, [3]]
+ result = m([1, 2, 3], [4, 5, 6, 7, 8], [9, 10])
+ result.should == [1, 2, [], 3, [4, 5, 6], 7, 8, [9, 10]]
+ end
+
+ evaluate <<-ruby do
+ def m(a, (b, (c, *d), *e))
+ [a, b, c, d, e]
+ end
+ ruby
+
+ m(1, 2).should == [1, 2, nil, [], []]
+ m(1, [2, [3, 4, 5], 6, 7, 8]).should == [1, 2, 3, [4, 5], [6, 7, 8]]
+ end
+
+ evaluate <<-ruby do
+ def m(a, (b, (c, *d, (e, (*f)), g), (h, (i, j))))
+ [a, b, c, d, e, f, g, h, i, j]
+ end
+ ruby
+
+ m(1, 2).should == [1, 2, nil, [], nil, [nil], nil, nil, nil, nil]
+ result = m(1, [2, [3, 4, 5, [6, [7, 8]], 9], [10, [11, 12]]])
+ result.should == [1, 2, 3, [4, 5], 6, [7, 8], 9, 10, 11, 12]
+ end
+
+ evaluate <<-ruby do
+ def m(a, b=1) [a, b] end
+ ruby
+
+ m(2).should == [2, 1]
+ m(1, 2).should == [1, 2]
+ end
+
+ evaluate <<-ruby do
+ def m(a, *) a end
+ ruby
+
+ m(1).should == 1
+ m(1, 2, 3).should == 1
+ end
+
+ evaluate <<-ruby do
+ def m(a, *b) [a, b] end
+ ruby
+
+ m(1).should == [1, []]
+ m(1, 2, 3).should == [1, [2, 3]]
+ end
+
+ evaluate <<-ruby do
+ def m(a, b:) [a, b] end
+ ruby
+
+ m(1, b: 2).should == [1, 2]
+ suppress_keyword_warning do
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a, b: 1) [a, b] end
+ ruby
+
+ m(2).should == [2, 1]
+ m(1, b: 2).should == [1, 2]
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(a, **) a end
+ ruby
+
+ m(1).should == 1
+ m(1, a: 2, b: 3).should == 1
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(a, **k) [a, k] end
+ ruby
+
+ m(1).should == [1, {}]
+ m(1, a: 2, b: 3).should == [1, {a: 2, b: 3}]
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(a, &b) [a, b] end
+ ruby
+
+ m(1).should == [1, nil]
+ m(1, &(l = -> {})).should == [1, l]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, b) [a, b] end
+ ruby
+
+ m(2).should == [1, 2]
+ m(2, 3).should == [2, 3]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, *) a end
+ ruby
+
+ m().should == 1
+ m(2, 3, 4).should == 2
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, *b) [a, b] end
+ ruby
+
+ m().should == [1, []]
+ m(2, 3, 4).should == [2, [3, 4]]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, (b, c)) [a, b, c] end
+ ruby
+
+ m(2).should == [1, 2, nil]
+ m(2, 3).should == [2, 3, nil]
+ m(2, [3, 4, 5]).should == [2, 3, 4]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, (b, (c, *d))) [a, b, c, d] end
+ ruby
+
+ m(2).should == [1, 2, nil, []]
+ m(2, 3).should == [2, 3, nil, []]
+ m(2, [3, [4, 5, 6], 7]).should == [2, 3, 4, [5, 6]]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, (b, (c, *d), *e)) [a, b, c, d, e] end
+ ruby
+
+ m(2).should == [1, 2, nil, [], []]
+ m(2, [3, 4, 5, 6]).should == [2, 3, 4, [], [5, 6]]
+ m(2, [3, [4, 5, 6], 7]).should == [2, 3, 4, [5, 6], [7]]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, (b), (c)) [a, b, c] end
+ ruby
+
+ m(2, 3).should == [1, 2, 3]
+ m(2, 3, 4).should == [2, 3, 4]
+ m(2, [3, 4], [5, 6, 7]).should == [2, 3, 5]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, (*b), (*c)) [a, b, c] end
+ ruby
+
+ -> { 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
+
+ evaluate <<-ruby do
+ def m(a=1, (b, c), (d, e)) [a, b, c, d, e] end
+ ruby
+
+ m(2, 3).should == [1, 2, nil, 3, nil]
+ m(2, [3, 4, 5], [6, 7, 8]).should == [2, 3, 4, 6, 7]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, (b, *c), (*d, e))
+ [a, b, c, d, e]
+ end
+ ruby
+
+ m(1, 2).should == [1, 1, [], [], 2]
+ m(1, [2, 3], [4, 5, 6]).should == [1, 2, [3], [4, 5], 6]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, (b, *c), (d, (*e, f)))
+ [a, b, c, d, e, f]
+ end
+ ruby
+
+ m(1, 2).should == [1, 1, [], 2, [], nil]
+ m(nil, nil).should == [1, nil, [], nil, [], nil]
+ result = m([1, 2, 3], [4, 5, 6], [7, 8, 9])
+ result.should == [[1, 2, 3], 4, [5, 6], 7, [], 8]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, b:) [a, b] end
+ ruby
+
+ m(b: 2).should == [1, 2]
+ m(2, b: 1).should == [2, 1]
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, b: 2) [a, b] end
+ ruby
+
+ m().should == [1, 2]
+ m(2).should == [2, 2]
+ m(b: 3).should == [1, 3]
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, **) a end
+ ruby
+
+ m().should == 1
+ m(2, a: 1, b: 0).should == 2
+ m("a" => 1, a: 2).should == 1
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, **k) [a, k] end
+ ruby
+
+ m().should == [1, {}]
+ m(2, a: 1, b: 2).should == [2, {a: 1, b: 2}]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, &b) [a, b] end
+ ruby
+
+ m().should == [1, nil]
+ m(&(l = -> {})).should == [1, l]
+
+ p = -> {}
+ l = mock("to_proc")
+ l.should_receive(:to_proc).and_return(p)
+ m(&l).should == [1, p]
+ end
+
+ evaluate <<-ruby do
+ def m(*, a) a end
+ ruby
+
+ m(1).should == 1
+ m(1, 2, 3).should == 3
+ end
+
+ evaluate <<-ruby do
+ def m(*a, b) [a, b] end
+ ruby
+
+ m(1).should == [[], 1]
+ m(1, 2, 3).should == [[1, 2], 3]
+ end
+
+ evaluate <<-ruby do
+ def m(*, &b) b end
+ ruby
+
+ m().should be_nil
+ m(1, 2, 3, 4).should be_nil
+ m(&(l = ->{})).should equal(l)
+ end
+
+ evaluate <<-ruby do
+ def m(*a, &b) [a, b] end
+ ruby
+
+ m().should == [[], nil]
+ m(1).should == [[1], nil]
+ m(1, 2, 3, &(l = -> {})).should == [[1, 2, 3], l]
+ end
+
+ evaluate <<-ruby do
+ def m(a:, b:) [a, b] end
+ ruby
+
+ m(a: 1, b: 2).should == [1, 2]
+ suppress_keyword_warning do
+ -> { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a:, b: 1) [a, b] end
+ ruby
+
+ m(a: 1).should == [1, 1]
+ m(a: 1, b: 2).should == [1, 2]
+ suppress_keyword_warning do
+ -> { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a:, **) a end
+ ruby
+
+ m(a: 1).should == 1
+ m(a: 1, b: 2).should == 1
+ m("a" => 1, a: 1, b: 2).should == 1
+ end
+
+ evaluate <<-ruby do
+ def m(a:, **k) [a, k] end
+ ruby
+
+ m(a: 1).should == [1, {}]
+ m(a: 1, b: 2, c: 3).should == [1, {b: 2, c: 3}]
+ m("a" => 1, a: 1, b: 2).should == [1, {"a" => 1, b: 2}]
+ end
+
+ evaluate <<-ruby do
+ def m(a:, &b) [a, b] end
+ ruby
+
+ m(a: 1).should == [1, nil]
+ m(a: 1, &(l = ->{})).should == [1, l]
+ end
+
+ evaluate <<-ruby do
+ def m(a: 1, b:) [a, b] end
+ ruby
+
+ m(b: 0).should == [1, 0]
+ m(b: 2, a: 3).should == [3, 2]
+ end
+
+ evaluate <<-ruby do
+ def m(a: def m(a: 1) a end, b:)
+ [a, b]
+ end
+ ruby
+
+ m(a: 2, b: 3).should == [2, 3]
+ m(b: 1).should == [:m, 1]
+
+ # Note the default value of a: in the original method.
+ m().should == 1
+ end
+
+ evaluate <<-ruby do
+ def m(a: 1, b: 2) [a, b] end
+ ruby
+
+ m().should == [1, 2]
+ m(b: 3, a: 4).should == [4, 3]
+ end
+
+ evaluate <<-ruby do
+ def m(a: 1, **) a end
+ ruby
+
+ m().should == 1
+ m(a: 2, b: 1).should == 2
+ end
+
+ evaluate <<-ruby do
+ def m(a: 1, **k) [a, k] end
+ ruby
+
+ m(b: 2, c: 3).should == [1, {b: 2, c: 3}]
+ end
+
+ evaluate <<-ruby do
+ def m(a: 1, &b) [a, b] end
+ ruby
+
+ m(&(l = ->{})).should == [1, l]
+ m().should == [1, nil]
+ end
+
+ evaluate <<-ruby do
+ def m(**, &b) b end
+ ruby
+
+ m(a: 1, b: 2, &(l = ->{})).should == l
+ end
+
+ evaluate <<-ruby do
+ def m(**k, &b) [k, b] end
+ ruby
+
+ m(a: 1, b: 2).should == [{ a: 1, b: 2}, nil]
+ end
+
+ evaluate <<-ruby do
+ def m(a, b=1, *c, (*d, (e)), f: 2, g:, h:, **k, &l)
+ [a, b, c, d, e, f, g, h, k, l]
+ end
+ ruby
+
+ result = m(9, 8, 7, 6, f: 5, g: 4, h: 3, &(l = ->{}))
+ result.should == [9, 8, [7], [], 6, 5, 4, 3, {}, l]
+ end
+
+ evaluate <<-ruby do
+ def m(a, b=1, *c, d, e:, f: 2, g:, **k, &l)
+ [a, b, c, d, e, f, g, k, l]
+ end
+ ruby
+
+ result = m(1, 2, e: 3, g: 4, h: 5, i: 6, &(l = ->{}))
+ result.should == [1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l]
+ end
+
+ evaluate <<-ruby do
+ 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
+
+ result = m(1, 2)
+ result.should == [1, nil, nil, 2, nil, {}]
+
+ result = m(1, 2, {foo: :bar})
+ result.should == [1, 2, nil, {foo: :bar}, nil, {}]
+
+ result = m(1, {foo: :bar})
+ result.should == [1, nil, nil, {foo: :bar}, nil, {}]
+ end
+ 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
+ before(:each) do
+ def m(*args)
+ args
+ end
+
+ def n(value, &block)
+ [value, block.call]
+ end
+ end
+
+ context "when no arguments provided" do
+ it "assigns nil" do
+ args = m ()
+ args.should == [nil]
+ end
+ end
+
+ 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 multiple arguments are provided" do
+ it "assigns simple expressions" do
+ args = m (1), (2)
+ args.should == [1, 2]
+ end
+
+ it "assigns expressions consisting of multiple statements" do
+ args = m ((0; 1)), ((2; 3))
+ args.should == [1, 3]
+ end
+ end
+
+ context "when the argument looks like an argument list" do
+ it "raises a syntax error" do
+ -> {
+ eval("m (1, 2)")
+ }.should raise_error(SyntaxError)
+
+ -> {
+ eval("m (1, 2, 3)")
+ }.should raise_error(SyntaxError)
+ end
+ end
+
+ it "allows to pass a block with curly braces" do
+ args = n () { :block_value }
+ args.should == [nil, :block_value]
+
+ args = n (1) { :block_value }
+ args.should == [1, :block_value]
+ end
+
+ it "allows to pass a block with do/end" do
+ args = n () do
+ :block_value
+ end
+ args.should == [nil, :block_value]
+
+ args = n (1) do
+ :block_value
+ end
+ args.should == [1, :block_value]
+ end
+end
+
+describe "An array-dereference method ([])" do
+ SpecEvaluate.desc = "for definition"
+
+ context "received the passed-in block" do
+ evaluate <<-ruby do
+ def [](*, &b)
+ b.call
+ end
+ ruby
+ pr = proc {:ok}
+
+ self[&pr].should == :ok
+ self['foo', &pr].should == :ok
+ self.[](&pr).should == :ok
+ self.[]('foo', &pr).should == :ok
+ end
+
+ evaluate <<-ruby do
+ def [](*)
+ yield
+ end
+ ruby
+ pr = proc {:ok}
+
+ self[&pr].should == :ok
+ self['foo', &pr].should == :ok
+ self.[](&pr).should == :ok
+ self.[]('foo', &pr).should == :ok
+ 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
new file mode 100644
index 0000000000..fba4aa8c6e
--- /dev/null
+++ b/spec/ruby/language/module_spec.rb
@@ -0,0 +1,123 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/module'
+
+describe "The module keyword" do
+ it "creates a new module without semicolon" do
+ module ModuleSpecsKeywordWithoutSemicolon end
+ ModuleSpecsKeywordWithoutSemicolon.should be_an_instance_of(Module)
+ end
+
+ it "creates a new module with a non-qualified constant name" do
+ module ModuleSpecsToplevel; end
+ ModuleSpecsToplevel.should be_an_instance_of(Module)
+ end
+
+ it "creates a new module with a qualified constant name" do
+ module ModuleSpecs::Nested; end
+ ModuleSpecs::Nested.should be_an_instance_of(Module)
+ end
+
+ it "creates a new module with a variable qualified constant name" do
+ m = Module.new
+ module m::N; end
+ m::N.should be_an_instance_of(Module)
+ end
+
+ it "reopens an existing module" do
+ module ModuleSpecs; Reopened = true; end
+ ModuleSpecs::Reopened.should be_true
+ ensure
+ ModuleSpecs.send(:remove_const, :Reopened)
+ end
+
+ it "does not reopen a module included in Object" do
+ ruby_exe(<<~RUBY).should == "false"
+ module IncludedInObject
+ module IncludedModule; end
+ end
+ class Object
+ include IncludedInObject
+ end
+ module IncludedModule; end
+ print IncludedInObject::IncludedModule == Object::IncludedModule
+ RUBY
+ end
+
+ it "does not reopen a module included in non-Object modules" do
+ ruby_exe(<<~RUBY).should == "false/false"
+ module Included
+ module IncludedModule; end
+ end
+ module M
+ include Included
+ module IncludedModule; end
+ end
+ class C
+ include Included
+ module IncludedModule; end
+ end
+ print Included::IncludedModule == M::IncludedModule, "/",
+ Included::IncludedModule == C::IncludedModule
+ RUBY
+ end
+
+ it "raises a TypeError if the constant is a Class" do
+ -> do
+ module ModuleSpecs::Modules::Klass; end
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the constant is a String" do
+ -> { module ModuleSpecs::Modules::A; end }.should raise_error(TypeError)
+ end
+
+ 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
+ -> { module ModuleSpecs::Modules::C; end }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the constant is true" do
+ -> { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the constant is false" do
+ -> { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError)
+ end
+end
+
+describe "Assigning an anonymous module to a constant" do
+ it "sets the name of the module" do
+ mod = Module.new
+ mod.name.should be_nil
+
+ ::ModuleSpecs_CS1 = mod
+ mod.name.should == "ModuleSpecs_CS1"
+ ensure
+ Object.send(:remove_const, :ModuleSpecs_CS1)
+ end
+
+ it "sets the name of a module scoped by an anonymous module" do
+ a, b = Module.new, Module.new
+ a::B = b
+ b.name.should.end_with? '::B'
+ end
+
+ it "sets the name of contained modules when assigning a toplevel anonymous module" do
+ a, b, c, d = Module.new, Module.new, Module.new, Module.new
+ a::B = b
+ a::B::C = c
+ a::B::C::E = c
+ a::D = d
+
+ ::ModuleSpecs_CS2 = a
+ a.name.should == "ModuleSpecs_CS2"
+ 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
new file mode 100644
index 0000000000..6fbfc4a54d
--- /dev/null
+++ b/spec/ruby/language/next_spec.rb
@@ -0,0 +1,410 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/next'
+
+describe "The next statement from within the block" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "ends block execution" do
+ a = []
+ -> {
+ a << 1
+ next
+ a << 2
+ }.call
+ a.should == [1]
+ end
+
+ it "causes block to return nil if invoked without arguments" do
+ -> { 123; next; 456 }.call.should == nil
+ end
+
+ it "causes block to return nil if invoked with an empty expression" do
+ -> { next (); 456 }.call.should be_nil
+ end
+
+ it "returns the argument passed" do
+ -> { 123; next 234; 345 }.call.should == 234
+ end
+
+ it "returns to the invoking method" do
+ NextSpecs.yielding_method(nil) { next }.should == :method_return_value
+ end
+
+ it "returns to the invoking method, with the specified value" do
+ NextSpecs.yielding_method(nil) {
+ next nil;
+ fail("next didn't end the block execution")
+ }.should == :method_return_value
+
+ NextSpecs.yielding_method(1) {
+ next 1
+ fail("next didn't end the block execution")
+ }.should == :method_return_value
+
+ NextSpecs.yielding_method([1, 2, 3]) {
+ next 1, 2, 3
+ fail("next didn't end the block execution")
+ }.should == :method_return_value
+ end
+
+ it "returns to the currently yielding method in case of chained calls" do
+ class ChainedNextTest
+ def self.meth_with_yield(&b)
+ yield.should == :next_return_value
+ :method_return_value
+ end
+ def self.invoking_method(&b)
+ meth_with_yield(&b)
+ end
+ def self.enclosing_method
+ invoking_method do
+ next :next_return_value
+ :wrong_return_value
+ end
+ end
+ end
+
+ ChainedNextTest.enclosing_method.should == :method_return_value
+ end
+
+ it "causes ensure blocks to run" do
+ [1].each do |i|
+ begin
+ ScratchPad << :begin
+ next
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "skips following code outside an exception block" do
+ 3.times do |i|
+ begin
+ ScratchPad << :begin
+ next if i == 0
+ break if i == 2
+ ScratchPad << :begin_end
+ ensure
+ ScratchPad << :ensure
+ end
+
+ ScratchPad << :after
+ end
+
+ ScratchPad.recorded.should == [
+ :begin, :ensure, :begin, :begin_end, :ensure, :after, :begin, :ensure]
+ end
+
+ it "passes the value returned by a method with omitted parenthesis and passed block" do
+ obj = NextSpecs::Block.new
+ -> { 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
+ -> {
+ eval("def m; next; end")
+ }.should raise_error(SyntaxError)
+ end
+ end
+end
+
+describe "The next statement" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ describe "in a while loop" do
+ describe "when not passed an argument" do
+ it "causes ensure blocks to run" do
+ NextSpecs.while_next(false)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "causes ensure blocks to run when nested in an block" do
+ NextSpecs.while_within_iter(false)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+ end
+
+ describe "when passed an argument" do
+ it "causes ensure blocks to run" do
+ NextSpecs.while_next(true)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "causes ensure blocks to run when nested in an block" do
+ NextSpecs.while_within_iter(true)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+ end
+
+ it "causes nested ensure blocks to run" do
+ x = true
+ while x
+ begin
+ ScratchPad << :outer_begin
+ x = false
+ begin
+ ScratchPad << :inner_begin
+ next
+ ensure
+ ScratchPad << :inner_ensure
+ end
+ ensure
+ ScratchPad << :outer_ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:outer_begin, :inner_begin, :inner_ensure, :outer_ensure]
+ end
+
+ it "causes ensure blocks to run when mixed with break" do
+ x = 1
+ while true
+ begin
+ ScratchPad << :begin
+ break if x > 1
+ x += 1
+ next
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure, :begin, :ensure]
+ end
+ end
+
+ describe "in an until loop" do
+ describe "when not passed an argument" do
+ it "causes ensure blocks to run" do
+ NextSpecs.until_next(false)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "causes ensure blocks to run when nested in an block" do
+ NextSpecs.until_within_iter(false)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+ end
+
+ describe "when passed an argument" do
+ it "causes ensure blocks to run" do
+ NextSpecs.until_next(true)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "causes ensure blocks to run when nested in an block" do
+ NextSpecs.until_within_iter(true)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+ end
+
+ it "causes nested ensure blocks to run" do
+ x = false
+ until x
+ begin
+ ScratchPad << :outer_begin
+ x = true
+ begin
+ ScratchPad << :inner_begin
+ next
+ ensure
+ ScratchPad << :inner_ensure
+ end
+ ensure
+ ScratchPad << :outer_ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:outer_begin, :inner_begin, :inner_ensure, :outer_ensure]
+ end
+
+ it "causes ensure blocks to run when mixed with break" do
+ x = 1
+ until false
+ begin
+ ScratchPad << :begin
+ break if x > 1
+ x += 1
+ next
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure, :begin, :ensure]
+ end
+ end
+
+ describe "in a loop" do
+ describe "when not passed an argument" do
+ it "causes ensure blocks to run" do
+ NextSpecs.loop_next(false)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "causes ensure blocks to run when nested in an block" do
+ NextSpecs.loop_within_iter(false)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+ end
+
+ describe "when passed an argument" do
+ it "causes ensure blocks to run" do
+ NextSpecs.loop_next(true)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "causes ensure blocks to run when nested in an block" do
+ NextSpecs.loop_within_iter(true)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+ end
+
+ it "causes nested ensure blocks to run" do
+ x = 1
+ loop do
+ break if x == 2
+
+ begin
+ ScratchPad << :outer_begin
+ begin
+ ScratchPad << :inner_begin
+ x += 1
+ next
+ ensure
+ ScratchPad << :inner_ensure
+ end
+ ensure
+ ScratchPad << :outer_ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:outer_begin, :inner_begin, :inner_ensure, :outer_ensure]
+ end
+
+ it "causes ensure blocks to run when mixed with break" do
+ x = 1
+ loop do
+ begin
+ ScratchPad << :begin
+ break if x > 1
+ x += 1
+ next
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure, :begin, :ensure]
+ end
+ end
+end
+
+describe "Assignment via next" do
+ it "assigns objects" do
+ def r(val); a = yield(); val.should == a; end
+ r(nil){next}
+ r(nil){next nil}
+ r(1){next 1}
+ r([]){next []}
+ r([1]){next [1]}
+ r([nil]){next [nil]}
+ r([[]]){next [[]]}
+ r([]){next [*[]]}
+ r([1]){next [*[1]]}
+ r([1,2]){next [*[1,2]]}
+ end
+
+ it "assigns splatted objects" do
+ def r(val); a = yield(); val.should == a; end
+ r([]){next *nil}
+ r([1]){next *1}
+ r([]){next *[]}
+ r([1]){next *[1]}
+ r([nil]){next *[nil]}
+ r([[]]){next *[[]]}
+ r([]){next *[*[]]}
+ r([1]){next *[*[1]]}
+ r([1,2]){next *[*[1,2]]}
+ end
+
+ it "assigns objects to a splatted reference" do
+ def r(val); *a = yield(); val.should == a; end
+ r([nil]){next}
+ r([nil]){next nil}
+ r([1]){next 1}
+ r([]){next []}
+ r([1]){next [1]}
+ r([nil]){next [nil]}
+ r([[]]){next [[]]}
+ r([1,2]){next [1,2]}
+ r([]){next [*[]]}
+ r([1]){next [*[1]]}
+ r([1,2]){next [*[1,2]]}
+ end
+
+ it "assigns splatted objects to a splatted reference via a splatted yield" do
+ def r(val); *a = *yield(); val.should == a; end
+ r([]){next *nil}
+ r([1]){next *1}
+ r([]){next *[]}
+ r([1]){next *[1]}
+ r([nil]){next *[nil]}
+ r([[]]){next *[[]]}
+ r([1,2]){next *[1,2]}
+ r([]){next *[*[]]}
+ r([1]){next *[*[1]]}
+ r([1,2]){next *[*[1,2]]}
+ end
+
+ it "assigns objects to multiple variables" do
+ def r(val); a,b,*c = yield(); val.should == [a,b,c]; end
+ r([nil,nil,[]]){next}
+ r([nil,nil,[]]){next nil}
+ r([1,nil,[]]){next 1}
+ r([nil,nil,[]]){next []}
+ r([1,nil,[]]){next [1]}
+ r([nil,nil,[]]){next [nil]}
+ r([[],nil,[]]){next [[]]}
+ r([1,2,[]]){next [1,2]}
+ r([nil,nil,[]]){next [*[]]}
+ r([1,nil,[]]){next [*[1]]}
+ r([1,2,[]]){next [*[1,2]]}
+ end
+
+ it "assigns splatted objects to multiple variables" do
+ def r(val); a,b,*c = *yield(); val.should == [a,b,c]; end
+ r([nil,nil,[]]){next *nil}
+ r([1,nil,[]]){next *1}
+ r([nil,nil,[]]){next *[]}
+ r([1,nil,[]]){next *[1]}
+ r([nil,nil,[]]){next *[nil]}
+ r([[],nil,[]]){next *[[]]}
+ r([1,2,[]]){next *[1,2]}
+ r([nil,nil,[]]){next *[*[]]}
+ r([1,nil,[]]){next *[*[1]]}
+ r([1,2,[]]){next *[*[1,2]]}
+ end
+end
diff --git a/spec/ruby/language/not_spec.rb b/spec/ruby/language/not_spec.rb
new file mode 100644
index 0000000000..052af9b256
--- /dev/null
+++ b/spec/ruby/language/not_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../spec_helper'
+
+describe "The not keyword" do
+ it "negates a `true' value" do
+ (not true).should be_false
+ (not 'true').should be_false
+ end
+
+ it "negates a `false' value" do
+ (not false).should be_true
+ (not nil).should be_true
+ end
+
+ it "accepts an argument" do
+ not(true).should be_false
+ end
+
+ it "returns false if the argument is true" do
+ (not(true)).should be_false
+ end
+
+ it "returns true if the argument is false" do
+ (not(false)).should be_true
+ end
+
+ it "returns true if the argument is nil" do
+ (not(nil)).should be_true
+ end
+end
+
+describe "The `!' keyword" do
+ it "negates a `true' value" do
+ (!true).should be_false
+ (!'true').should be_false
+ end
+
+ it "negates a `false' value" do
+ (!false).should be_true
+ (!nil).should be_true
+ end
+
+ it "doubled turns a truthful object into `true'" do
+ (!!true).should be_true
+ (!!'true').should be_true
+ end
+
+ it "doubled turns a not truthful object into `false'" do
+ (!!false).should be_false
+ (!!nil).should be_false
+ end
+end
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
new file mode 100644
index 0000000000..a8e023efb6
--- /dev/null
+++ b/spec/ruby/language/numbers_spec.rb
@@ -0,0 +1,105 @@
+require_relative '../spec_helper'
+
+describe "A number literal" do
+
+ it "can be a sequence of decimal digits" do
+ 435.should == 435
+ end
+
+ it "can have '_' characters between digits" do
+ 4_3_5_7.should == 4357
+ end
+
+ it "cannot have a leading underscore" do
+ -> { eval("_4_2") }.should raise_error(NameError)
+ end
+
+ it "can have a decimal point" do
+ 4.35.should == 4.35
+ end
+
+ it "must have a digit before the decimal point" do
+ 0.75.should == 0.75
+ -> { eval(".75") }.should raise_error(SyntaxError)
+ -> { eval("-.75") }.should raise_error(SyntaxError)
+ end
+
+ it "can have an exponent" do
+ 1.2e-3.should == 0.0012
+ end
+
+ it "can be a sequence of hexadecimal digits with a leading '0x'" do
+ 0xffff.should == 65535
+ end
+
+ it "can be a sequence of binary digits with a leading '0x'" do
+ 0b01011.should == 11
+ end
+
+ it "can be a sequence of octal digits with a leading '0'" do
+ 0377.should == 255
+ end
+
+ it "can be an integer literal with trailing 'r' to represent a Rational" do
+ eval('3r').should == Rational(3, 1)
+ eval('-3r').should == Rational(-3, 1)
+ end
+
+ it "can be an float literal with trailing 'r' to represent a Rational in a canonical form" do
+ eval('1.0r').should == Rational(1, 1)
+ end
+
+ it "can be a float literal with trailing 'r' to represent a Rational" do
+ eval('0.0174532925199432957r').should == Rational(174532925199432957, 10000000000000000000)
+ end
+
+ it "can be a bignum literal with trailing 'r' to represent a Rational" do
+ eval('1111111111111111111111111111111111111111111111r').should == Rational(1111111111111111111111111111111111111111111111, 1)
+ eval('-1111111111111111111111111111111111111111111111r').should == Rational(-1111111111111111111111111111111111111111111111, 1)
+ end
+
+ it "can be a decimal literal with trailing 'r' to represent a Rational" do
+ eval('0.3r').should == Rational(3, 10)
+ eval('-0.3r').should == Rational(-3, 10)
+ end
+
+ it "can be a hexadecimal literal with trailing 'r' to represent a Rational" do
+ eval('0xffr').should == Rational(255, 1)
+ eval('-0xffr').should == Rational(-255, 1)
+ end
+
+ it "can be an octal literal with trailing 'r' to represent a Rational" do
+ eval('042r').should == Rational(34, 1)
+ eval('-042r').should == Rational(-34, 1)
+ end
+
+ it "can be a binary literal with trailing 'r' to represent a Rational" do
+ eval('0b1111r').should == Rational(15, 1)
+ eval('-0b1111r').should == Rational(-15, 1)
+ end
+
+ it "can be an integer literal with trailing 'i' to represent a Complex" do
+ eval('5i').should == Complex(0, 5)
+ eval('-5i').should == Complex(0, -5)
+ end
+
+ it "can be a decimal literal with trailing 'i' to represent a Complex" do
+ eval('0.6i').should == Complex(0, 0.6)
+ eval('-0.6i').should == Complex(0, -0.6)
+ end
+
+ it "can be a hexadecimal literal with trailing 'i' to represent a Complex" do
+ eval('0xffi').should == Complex(0, 255)
+ eval('-0xffi').should == Complex(0, -255)
+ end
+
+ it "can be a octal literal with trailing 'i' to represent a Complex" do
+ eval("042i").should == Complex(0, 34)
+ eval("-042i").should == Complex(0, -34)
+ end
+
+ it "can be a binary literal with trailing 'i' to represent a Complex" do
+ eval('0b1110i').should == Complex(0, 14)
+ eval('-0b1110i').should == Complex(0, -14)
+ end
+end
diff --git a/spec/ruby/language/optional_assignments_spec.rb b/spec/ruby/language/optional_assignments_spec.rb
new file mode 100644
index 0000000000..5fe3e3671b
--- /dev/null
+++ b/spec/ruby/language/optional_assignments_spec.rb
@@ -0,0 +1,742 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/constants'
+
+describe 'Optional variable assignments' do
+ describe 'using ||=' do
+ describe 'using a single variable' do
+ it 'assigns a new variable' do
+ a ||= 10
+
+ a.should == 10
+ end
+
+ it 're-assigns an existing variable set to false' do
+ a = false
+ a ||= 10
+
+ a.should == 10
+ end
+
+ it 're-assigns an existing variable set to nil' do
+ a = nil
+ a ||= 10
+
+ a.should == 10
+ end
+
+ it 'does not re-assign a variable with a truthy value' do
+ a = 10
+ a ||= 20
+
+ a.should == 10
+ end
+
+ it 'does not evaluate the right side when not needed' do
+ a = 10
+ a ||= raise('should not be executed')
+ a.should == 10
+ end
+
+ it 'does not re-assign a variable with a truthy value when using an inline rescue' do
+ a = 10
+ a ||= 20 rescue 30
+
+ 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 an accessor' do
+ before do
+ klass = Class.new { attr_accessor :b }
+ @a = klass.new
+ end
+
+ it 'assigns a new variable' do
+ @a.b ||= 10
+
+ @a.b.should == 10
+ end
+
+ it 're-assigns an existing variable set to false' do
+ @a.b = false
+ @a.b ||= 10
+
+ @a.b.should == 10
+ end
+
+ it 're-assigns an existing variable set to nil' do
+ @a.b = nil
+ @a.b ||= 10
+
+ @a.b.should == 10
+ end
+
+ it 'does not re-assign a variable with a truthy value' do
+ @a.b = 10
+ @a.b ||= 20
+
+ @a.b.should == 10
+ end
+
+ it 'does not evaluate the right side when not needed' do
+ @a.b = 10
+ @a.b ||= raise('should not be executed')
+ @a.b.should == 10
+ end
+
+ it 'does not re-assign a variable with a truthy value when using an inline rescue' do
+ @a.b = 10
+ @a.b ||= 20 rescue 30
+
+ @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
+
+ describe 'using &&=' do
+ describe 'using a single variable' do
+ it 'leaves new variable unassigned' do
+ a &&= 10
+
+ a.should == nil
+ end
+
+ it 'leaves false' do
+ a = false
+ a &&= 10
+
+ a.should == false
+ end
+
+ it 'leaves nil' do
+ a = nil
+ a &&= 10
+
+ a.should == nil
+ end
+
+ it 'does not evaluate the right side when not needed' do
+ a = nil
+ a &&= raise('should not be executed')
+ a.should == nil
+ end
+
+ it 'does re-assign a variable with a truthy value' do
+ a = 10
+ a &&= 20
+
+ a.should == 20
+ end
+
+ it 'does re-assign a variable with a truthy value when using an inline rescue' do
+ a = 10
+ a &&= 20 rescue 30
+
+ a.should == 20
+ end
+ end
+
+ describe 'using an accessor' do
+ before do
+ klass = Class.new { attr_accessor :b }
+ @a = klass.new
+ end
+
+ it 'leaves new variable unassigned' do
+ @a.b &&= 10
+
+ @a.b.should == nil
+ end
+
+ it 'leaves false' do
+ @a.b = false
+ @a.b &&= 10
+
+ @a.b.should == false
+ end
+
+ it 'leaves nil' do
+ @a.b = nil
+ @a.b &&= 10
+
+ @a.b.should == nil
+ end
+
+ it 'does not evaluate the right side when not needed' do
+ @a.b = nil
+ @a.b &&= raise('should not be executed')
+ @a.b.should == nil
+ end
+
+ it 'does re-assign a variable with a truthy value' do
+ @a.b = 10
+ @a.b &&= 20
+
+ @a.b.should == 20
+ end
+
+ it 'does re-assign a variable with a truthy value when using an inline rescue' do
+ @a.b = 10
+ @a.b &&= 20 rescue 30
+
+ @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 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
+
+ it 'with ||= assignments' do
+ Object::A ||= 10
+ Object::A.should == 10
+ end
+
+ it 'with ||= do not reassign' do
+ Object::A = 20
+ Object::A ||= 10
+ Object::A.should == 20
+ end
+
+ it 'with &&= assignments' do
+ Object::A = 20
+ -> {
+ Object::A &&= 10
+ }.should complain(/already initialized constant/)
+ Object::A.should == 10
+ end
+
+ it 'with &&= assignments will fail with non-existent constants' do
+ -> { Object::A &&= 10 }.should raise_error(NameError)
+ end
+
+ it 'with operator assignments' do
+ Object::A = 20
+ -> {
+ Object::A += 10
+ }.should complain(/already initialized constant/)
+ Object::A.should == 30
+ end
+
+ it 'with operator assignments will fail with non-existent constants' do
+ -> { 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
new file mode 100644
index 0000000000..fb75e788f1
--- /dev/null
+++ b/spec/ruby/language/or_spec.rb
@@ -0,0 +1,90 @@
+require_relative '../spec_helper'
+
+describe "The || operator" do
+ it "evaluates to true if any of its operands are true" do
+ if false || true || nil
+ x = true
+ end
+ x.should == true
+ end
+
+ it "evaluated to false if all of its operands are false" do
+ if false || nil
+ x = true
+ end
+ x.should == nil
+ end
+
+ it "is evaluated before assignment operators" do
+ x = nil || true
+ x.should == true
+ end
+
+ it "has a lower precedence than the && operator" do
+ x = 1 || false && x = 2
+ x.should == 1
+ end
+
+ it "treats empty expressions as nil" do
+ (() || true).should be_true
+ (() || false).should be_false
+ (true || ()).should be_true
+ (false || ()).should be_nil
+ (() || ()).should be_nil
+ end
+
+ it "has a higher precedence than 'break' in 'break true || false'" do
+ # see also 'break true or false' below
+ -> { break false || true }.call.should be_true
+ end
+
+ it "has a higher precedence than 'next' in 'next true || false'" do
+ -> { next false || true }.call.should be_true
+ end
+
+ it "has a higher precedence than 'return' in 'return true || false'" do
+ -> { return false || true }.call.should be_true
+ end
+end
+
+describe "The or operator" do
+ it "evaluates to true if any of its operands are true" do
+ x = nil
+ if false or true
+ x = true
+ end
+ x.should == true
+ end
+
+ it "is evaluated after variables are assigned" do
+ x = nil or true
+ x.should == nil
+ end
+
+ it "has a lower precedence than the || operator" do
+ x,y = nil
+ x = true || false or y = 1
+ y.should == nil
+ end
+
+ it "treats empty expressions as nil" do
+ (() or true).should be_true
+ (() or false).should be_false
+ (true or ()).should be_true
+ (false or ()).should be_nil
+ (() or ()).should be_nil
+ end
+
+ it "has a lower precedence than 'break' in 'break true or false'" do
+ # see also 'break true || false' above
+ -> { eval "break true or false" }.should raise_error(SyntaxError, /void value expression/)
+ end
+
+ it "has a lower precedence than 'next' in 'next true or false'" do
+ -> { eval "next true or false" }.should raise_error(SyntaxError, /void value expression/)
+ end
+
+ it "has a lower precedence than 'return' in 'return true or false'" do
+ -> { eval "return true or false" }.should raise_error(SyntaxError, /void value expression/)
+ end
+end
diff --git a/spec/ruby/language/order_spec.rb b/spec/ruby/language/order_spec.rb
new file mode 100644
index 0000000000..d550f6b3f4
--- /dev/null
+++ b/spec/ruby/language/order_spec.rb
@@ -0,0 +1,75 @@
+require_relative '../spec_helper'
+
+describe "A method call" do
+ before :each do
+ @obj = Object.new
+ def @obj.foo0(&a)
+ [a ? a.call : nil]
+ end
+ def @obj.foo1(a, &b)
+ [a, b ? b.call : nil]
+ end
+ def @obj.foo2(a, b, &c)
+ [a, b, c ? c.call : nil]
+ end
+ def @obj.foo3(a, b, c, &d)
+ [a, b, c, d ? d.call : nil]
+ end
+ def @obj.foo4(a, b, c, d, &e)
+ [a, b, c, d, e ? e.call : nil]
+ end
+ end
+
+ it "evaluates the receiver first" do
+ (obj = @obj).foo1(obj = nil).should == [nil, nil]
+ (obj = @obj).foo2(obj = nil, obj = nil).should == [nil, nil, nil]
+ (obj = @obj).foo3(obj = nil, obj = nil, obj = nil).should == [nil, nil, nil, nil]
+ (obj = @obj).foo4(obj = nil, obj = nil, obj = nil, obj = nil).should == [nil, nil, nil, nil, nil]
+ end
+
+ it "evaluates arguments after receiver" do
+ a = 0
+ (a += 1; @obj).foo1(a).should == [1, nil]
+ (a += 1; @obj).foo2(a, a).should == [2, 2, nil]
+ (a += 1; @obj).foo3(a, a, a).should == [3, 3, 3, nil]
+ (a += 1; @obj).foo4(a, a, a, a).should == [4, 4, 4, 4, nil]
+ a.should == 4
+ end
+
+ it "evaluates arguments left-to-right" do
+ a = 0
+ @obj.foo1(a += 1).should == [1, nil]
+ @obj.foo2(a += 1, a += 1).should == [2, 3, nil]
+ @obj.foo3(a += 1, a += 1, a += 1).should == [4, 5, 6, nil]
+ @obj.foo4(a += 1, a += 1, a += 1, a += 1).should == [7, 8, 9, 10, nil]
+ a.should == 10
+ end
+
+ it "evaluates block pass after arguments" do
+ a = 0
+ p = proc {true}
+ @obj.foo1(a += 1, &(a += 1; p)).should == [1, true]
+ @obj.foo2(a += 1, a += 1, &(a += 1; p)).should == [3, 4, true]
+ @obj.foo3(a += 1, a += 1, a += 1, &(a += 1; p)).should == [6, 7, 8, true]
+ @obj.foo4(a += 1, a += 1, a += 1, a += 1, &(a += 1; p)).should == [10, 11, 12, 13, true]
+ a.should == 14
+ end
+
+ it "evaluates block pass after receiver" do
+ p1 = proc {true}
+ p2 = proc {false}
+ p1.should_not == p2
+
+ p = p1
+ (p = p2; @obj).foo0(&p).should == [false]
+ p = p1
+ (p = p2; @obj).foo1(1, &p).should == [1, false]
+ p = p1
+ (p = p2; @obj).foo2(1, 1, &p).should == [1, 1, false]
+ p = p1
+ (p = p2; @obj).foo3(1, 1, 1, &p).should == [1, 1, 1, false]
+ p = p1
+ (p = p2; @obj).foo4(1, 1, 1, 1, &p).should == [1, 1, 1, 1, false]
+ p = p1
+ end
+end
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
new file mode 100644
index 0000000000..5e606c16d8
--- /dev/null
+++ b/spec/ruby/language/precedence_spec.rb
@@ -0,0 +1,445 @@
+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
+# to use a technique from formal proofs that involve a set of
+# equivalent statements. Suppose you have statements A, B, C.
+# If they are claimed to be equivalent, this can be shown by
+# proving that A implies B, B implies C, and C implies A.
+# (Actually any closed circuit of implications.)
+#
+# Here, we can use a similar technique where we show starting
+# at the top that each level of operator has precedence over
+# the level below (as well as showing associativity within
+# the precedence level).
+
+# 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
+# The correct table derived from MRI's parse.y is as follows:
+#
+# Operator Assoc Description
+#---------------------------------------------------------------
+# ! ~ + > Not, complement, unary plus
+# ** > Exponentiation
+# - > Unary minus
+# * / % < Multiply, divide, and modulo
+# + - < Plus and minus
+# >> << < Right and left shift
+# & < “And” (bitwise for integers)
+# ^ | < Exclusive “or” and regular “or” (bitwise for integers)
+# <= < > >= < Comparison operators
+# <=> == === != =~ !~ no Equality and pattern match operators (!=
+# and !~ may not be defined as methods)
+# && < Logical “and”
+# || < Logical “or”
+# .. ... no Range (inclusive and exclusive)
+# ? : > Ternary if-then-else
+# rescue < Rescue modifier
+# = %= /= -= += |= &= > Assignment
+# >>= <<= *= &&= ||= **=
+# defined? no Check if symbol defined
+# not > Logical negation
+# or and < Logical composition
+# if unless while until no Expression modifiers
+# -----------------------------------------------------------------------
+#
+# [] and []= seem to fall out of here, as well as begin/end
+#
+
+# TODO: Resolve these two tables with actual specs. As the comment at the
+# top suggests, these specs need to be reorganized into a single describe
+# block for each operator. The describe block should include an example
+# for associativity (if relevant), an example for any short circuit behavior
+# (e.g. &&, ||, etc.) and an example block for each operator over which the
+# instant operator has immediately higher precedence.
+
+describe "Operators" do
+ it "! ~ + is right-associative" do
+ (!!true).should == true
+ (~~0).should == 0
+ (++2).should == 2
+ end
+
+ it "** is right-associative" do
+ (2**2**3).should == 256
+ end
+
+ it "** has higher precedence than unary minus" do
+ (-2**2).should == -4
+ end
+
+ it "unary minus is right-associative" do
+ (--2).should == 2
+ end
+
+ it "unary minus has higher precedence than * / %" do
+ class UnaryMinusTest; def -@; 50; end; end
+ b = UnaryMinusTest.new
+
+ (-b * 5).should == 250
+ (-b / 5).should == 10
+ (-b % 7).should == 1
+ end
+
+ it "treats +/- as a regular send if the arguments are known locals or block locals" do
+ a = PrecedenceSpecs::NonUnaryOpTest.new
+ a.add_num(1).should == [3]
+ a.sub_num(1).should == [1]
+ a.add_str.should == ['11']
+ a.add_var.should == [2]
+ end
+
+ it "* / % are left-associative" do
+ (2*1/2).should == (2*1)/2
+ # Guard against the Mathn library
+ # TODO: Make these specs not rely on specific behaviour / result values
+ # by using mocks.
+ guard -> { !defined?(Math.rsqrt) } do
+ (2*1/2).should_not == 2*(1/2)
+ end
+
+ (10/7/5).should == (10/7)/5
+ (10/7/5).should_not == 10/(7/5)
+
+ (101 % 55 % 7).should == (101 % 55) % 7
+ (101 % 55 % 7).should_not == 101 % (55 % 7)
+
+ (50*20/7%42).should == ((50*20)/7)%42
+ (50*20/7%42).should_not == 50*(20/(7%42))
+ end
+
+ it "* / % have higher precedence than + -" do
+ (2+2*2).should == 6
+ (1+10/5).should == 3
+ (2+10%5).should == 2
+
+ (2-2*2).should == -2
+ (1-10/5).should == -1
+ (10-10%4).should == 8
+ end
+
+ it "+ - are left-associative" do
+ (2-3-4).should == -5
+ (4-3+2).should == 3
+
+ binary_plus = Class.new(String) do
+ alias_method :plus, :+
+ def +(a)
+ plus(a) + "!"
+ end
+ end
+ s = binary_plus.new("a")
+
+ (s+s+s).should == (s+s)+s
+ (s+s+s).should_not == s+(s+s)
+ end
+
+ it "+ - have higher precedence than >> <<" do
+ (2<<1+2).should == 16
+ (8>>1+2).should == 1
+ (4<<1-3).should == 1
+ (2>>1-3).should == 8
+ end
+
+ it ">> << are left-associative" do
+ (1 << 2 << 3).should == 32
+ (10 >> 1 >> 1).should == 2
+ (10 << 4 >> 1).should == 80
+ end
+
+ it ">> << have higher precedence than &" do
+ (4 & 2 << 1).should == 4
+ (2 & 4 >> 1).should == 2
+ end
+
+ it "& is left-associative" do
+ class BitwiseAndTest; def &(a); a+1; end; end
+ c = BitwiseAndTest.new
+
+ (c & 5 & 2).should == (c & 5) & 2
+ (c & 5 & 2).should_not == c & (5 & 2)
+ end
+
+ it "& has higher precedence than ^ |" do
+ (8 ^ 16 & 16).should == 24
+ (8 | 16 & 16).should == 24
+ end
+
+ it "^ | are left-associative" do
+ class OrAndXorTest; def ^(a); a+10; end; def |(a); a-10; end; end
+ d = OrAndXorTest.new
+
+ (d ^ 13 ^ 16).should == (d ^ 13) ^ 16
+ (d ^ 13 ^ 16).should_not == d ^ (13 ^ 16)
+
+ (d | 13 | 4).should == (d | 13) | 4
+ (d | 13 | 4).should_not == d | (13 | 4)
+ end
+
+ it "^ | have higher precedence than <= < > >=" do
+ (10 <= 7 ^ 7).should == false
+ (10 < 7 ^ 7).should == false
+ (10 > 7 ^ 7).should == true
+ (10 >= 7 ^ 7).should == true
+ (10 <= 7 | 7).should == false
+ (10 < 7 | 7).should == false
+ (10 > 7 | 7).should == true
+ (10 >= 7 | 7).should == true
+ end
+
+ it "<= < > >= are left-associative" do
+ class ComparisonTest
+ def <=(a); 0; end;
+ def <(a); 0; end;
+ def >(a); 0; end;
+ def >=(a); 0; end;
+ end
+
+ e = ComparisonTest.new
+
+ (e <= 0 <= 1).should == (e <= 0) <= 1
+ (e <= 0 <= 1).should_not == e <= (0 <= 1)
+
+ (e < 0 < 1).should == (e < 0) < 1
+ (e < 0 < 1).should_not == e < (0 < 1)
+
+ (e >= 0 >= 1).should == (e >= 0) >= 1
+ (e >= 0 >= 1).should_not == e >= (0 >= 1)
+
+ (e > 0 > 1).should == (e > 0) > 1
+ (e > 0 > 1).should_not == e > (0 > 1)
+ end
+
+ it "<=> == === != =~ !~ are non-associative" do
+ -> { eval("1 <=> 2 <=> 3") }.should raise_error(SyntaxError)
+ -> { eval("1 == 2 == 3") }.should raise_error(SyntaxError)
+ -> { eval("1 === 2 === 3") }.should raise_error(SyntaxError)
+ -> { eval("1 != 2 != 3") }.should raise_error(SyntaxError)
+ -> { eval("1 =~ 2 =~ 3") }.should raise_error(SyntaxError)
+ -> { eval("1 !~ 2 !~ 3") }.should raise_error(SyntaxError)
+ end
+
+ it "<=> == === != =~ !~ have higher precedence than &&" do
+ (false && 2 <=> 3).should == false
+ (false && 3 == false).should == false
+ (false && 3 === false).should == false
+ (false && 3 != true).should == false
+
+ class FalseClass; def =~(o); o == false; end; end
+ (false && true =~ false).should == (false && (true =~ false))
+ (false && true =~ false).should_not == ((false && true) =~ false)
+ class FalseClass; undef_method :=~; end
+
+ (false && true !~ true).should == false
+ end
+
+ # XXX: figure out how to test it
+ # (a && b) && c equals to a && (b && c) for all a,b,c values I can imagine so far
+ it "&& is left-associative"
+
+ it "&& has higher precedence than ||" do
+ (true || false && false).should == true
+ end
+
+ # XXX: figure out how to test it
+ it "|| is left-associative"
+
+ it "|| has higher precedence than .. ..." do
+ (1..false||10).should == (1..10)
+ (1...false||10).should == (1...10)
+ end
+
+ it ".. ... are non-associative" do
+ -> { eval("1..2..3") }.should raise_error(SyntaxError)
+ -> { eval("1...2...3") }.should raise_error(SyntaxError)
+ end
+
+ it ".. ... have higher precedence than ? :" do
+ # Use variables to avoid warnings
+ from = 1
+ to = 2
+ # These are flip-flop, not Range instances
+ (from..to ? 3 : 4).should == 3
+ (from...to ? 3 : 4).should == 3
+ end
+
+ it "? : is right-associative" do
+ (true ? 2 : 3 ? 4 : 5).should == 2
+ end
+
+ def oops; raise end
+
+ it "? : has higher precedence than rescue" do
+ (true ? oops : 0 rescue 10).should == 10
+ end
+
+ # XXX: figure how to test it (problem similar to || associativity)
+ it "rescue is left-associative"
+
+ it "rescue has higher precedence than =" do
+ a = oops rescue 10
+ a.should == 10
+
+ # rescue doesn't have the same sense for %= /= and friends
+ end
+
+ it "= %= /= -= += |= &= >>= <<= *= &&= ||= **= are right-associative" do
+ a = b = 10
+ a.should == 10
+ b.should == 10
+
+ a = b = 10
+ a %= b %= 3
+ a.should == 0
+ b.should == 1
+
+ a = b = 10
+ a /= b /= 2
+ a.should == 2
+ b.should == 5
+
+ a = b = 10
+ a -= b -= 2
+ a.should == 2
+ b.should == 8
+
+ a = b = 10
+ a += b += 2
+ a.should == 22
+ b.should == 12
+
+ a,b = 32,64
+ a |= b |= 2
+ a.should == 98
+ b.should == 66
+
+ a,b = 25,13
+ a &= b &= 7
+ a.should == 1
+ b.should == 5
+
+ a,b=8,2
+ a >>= b >>= 1
+ a.should == 4
+ b.should == 1
+
+ a,b=8,2
+ a <<= b <<= 1
+ a.should == 128
+ b.should == 4
+
+ a,b=8,2
+ a *= b *= 2
+ a.should == 32
+ b.should == 4
+
+ a,b=10,20
+ a &&= b &&= false
+ a.should == false
+ b.should == false
+
+ a,b=nil,nil
+ a ||= b ||= 10
+ a.should == 10
+ b.should == 10
+
+ a,b=2,3
+ a **= b **= 2
+ a.should == 512
+ b.should == 9
+ end
+
+ it "= %= /= -= += |= &= >>= <<= *= &&= ||= **= have higher precedence than defined? operator" do
+ (defined? a = 10).should == "assignment"
+ (defined? a %= 10).should == "assignment"
+ (defined? a /= 10).should == "assignment"
+ (defined? a -= 10).should == "assignment"
+ (defined? a += 10).should == "assignment"
+ (defined? a |= 10).should == "assignment"
+ (defined? a &= 10).should == "assignment"
+ (defined? a >>= 10).should == "assignment"
+ (defined? a <<= 10).should == "assignment"
+ (defined? a *= 10).should == "assignment"
+ (defined? a &&= 10).should == "assignment"
+ (defined? a ||= 10).should == "assignment"
+ (defined? a **= 10).should == "assignment"
+ end
+
+ # XXX: figure out how to test it
+ it "defined? is non-associative"
+
+ it "defined? has higher precedence than not" do
+ # does it have sense?
+ (not defined? qqq).should == true
+ end
+
+ it "not is right-associative" do
+ (not not false).should == false
+ (not not 10).should == true
+ end
+
+ it "not has higher precedence than or/and" do
+ (not false and false).should == false
+ (not false or true).should == true
+ end
+
+ # XXX: figure out how to test it
+ it "or/and are left-associative"
+
+ it "or/and have higher precedence than if unless while until modifiers" do
+ (1 if 2 and 3).should == 1
+ (1 if 2 or 3).should == 1
+
+ (1 unless false and true).should == 1
+ (1 unless false or false).should == 1
+
+ (1 while true and false).should == nil # would hang upon error
+ (1 while false or false).should == nil
+
+ ((raise until true and false) rescue 10).should == 10
+ (1 until false or true).should == nil # would hang upon error
+ end
+
+ # XXX: it seems to me they are right-associative
+ it "if unless while until are non-associative"
+end
diff --git a/spec/ruby/language/predefined/data_spec.rb b/spec/ruby/language/predefined/data_spec.rb
new file mode 100644
index 0000000000..921d236ee9
--- /dev/null
+++ b/spec/ruby/language/predefined/data_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+
+describe "The DATA constant" do
+ it "exists when the main script contains __END__" do
+ ruby_exe(fixture(__FILE__, "data1.rb")).chomp.should == "true"
+ end
+
+ it "does not exist when the main script contains no __END__" do
+ ruby_exe("puts Object.const_defined?(:DATA)").chomp.should == 'false'
+ end
+
+ it "does not exist when an included file has a __END__" do
+ ruby_exe(fixture(__FILE__, "data2.rb")).chomp.should == "false"
+ end
+
+ it "does not change when an included files also has a __END__" do
+ ruby_exe(fixture(__FILE__, "data3.rb")).chomp.should == "data 3"
+ end
+
+ it "is included in an otherwise empty file" do
+ ap = fixture(__FILE__, "print_data.rb")
+ str = ruby_exe(fixture(__FILE__, "data_only.rb"), options: "-r#{ap}")
+ 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
+end
diff --git a/spec/ruby/language/predefined/fixtures/data1.rb b/spec/ruby/language/predefined/fixtures/data1.rb
new file mode 100644
index 0000000000..cb9572255b
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data1.rb
@@ -0,0 +1,4 @@
+puts Object.const_defined?(:DATA)
+
+__END__
+data1
diff --git a/spec/ruby/language/predefined/fixtures/data2.rb b/spec/ruby/language/predefined/fixtures/data2.rb
new file mode 100644
index 0000000000..a764ca56d1
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data2.rb
@@ -0,0 +1,3 @@
+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
new file mode 100644
index 0000000000..e37313c68b
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data3.rb
@@ -0,0 +1,6 @@
+require_relative 'data4'
+
+puts DATA.read
+
+__END__
+data 3
diff --git a/spec/ruby/language/predefined/fixtures/data4.rb b/spec/ruby/language/predefined/fixtures/data4.rb
new file mode 100644
index 0000000000..139ef80d7b
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data4.rb
@@ -0,0 +1,4 @@
+# nothing
+
+__END__
+data 4
diff --git a/spec/ruby/language/predefined/fixtures/data5.rb b/spec/ruby/language/predefined/fixtures/data5.rb
new file mode 100644
index 0000000000..48f060e1a9
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data5.rb
@@ -0,0 +1,5 @@
+DATA.rewind
+puts DATA.gets
+
+__END__
+data 5
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/data_only.rb b/spec/ruby/language/predefined/fixtures/data_only.rb
new file mode 100644
index 0000000000..004ac62737
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data_only.rb
@@ -0,0 +1,2 @@
+__END__
+data only
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/print_data.rb b/spec/ruby/language/predefined/fixtures/print_data.rb
new file mode 100644
index 0000000000..4a5692e6a7
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/print_data.rb
@@ -0,0 +1,3 @@
+at_exit {
+ puts DATA.read
+}
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
new file mode 100644
index 0000000000..fc1667a38f
--- /dev/null
+++ b/spec/ruby/language/predefined_spec.rb
@@ -0,0 +1,1574 @@
+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'
+# Second Edition by Dave Thomas, Chad Fowler, and Andy Hunt, page 319-22.
+#
+# 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.
+
+# Exception Information
+# ---------------------------------------------------------------------------------------------------
+#
+# $! Exception The exception object passed to raise. [thread]
+# $@ Array The stack backtrace generated by the last exception. [thread]
+
+# Pattern Matching Variables
+# ---------------------------------------------------------------------------------------------------
+#
+# These variables are set to nil after an unsuccessful pattern match.
+#
+# $& String The string matched (following a successful pattern match). This variable is
+# local to the current scope. [r/o, thread]
+# $+ String The contents of the highest-numbered group matched following a successful
+# pattern match. Thus, in "cat" =~/(c|a)(t|z)/, $+ will be set to “t”. This
+# variable is local to the current scope. [r/o, thread]
+# $` String The string preceding the match in a successful pattern match. This variable
+# is local to the current scope. [r/o, thread]
+# $' String The string following the match in a successful pattern match. This variable
+# is local to the current scope. [r/o, thread]
+# $1 to $<N> String The contents of successive groups matched in a successful pattern match. In
+# "cat" =~/(c|a)(t|z)/, $1 will be set to “a” and $2 to “t”. This variable
+# is local to the current scope. [r/o, thread]
+# $~ MatchData An object that encapsulates the results of a successful pattern match. The
+# variables $&, $`, $', and $1 to $<N> are all derived from $~. Assigning to $~
+# changes the values of these derived variables. This variable is local to the
+# current scope. [thread]
+
+
+describe "Predefined global $~" do
+ it "is set to contain the MatchData object of the last match if successful" do
+ md = /foo/.match 'foo'
+ $~.should be_kind_of(MatchData)
+ $~.should equal md
+
+ /bar/ =~ 'bar'
+ $~.should be_kind_of(MatchData)
+ $~.should_not equal md
+ end
+
+ it "is set to nil if the last match was unsuccessful" do
+ /foo/ =~ 'foo'
+ $~.should_not.nil?
+
+ /foo/ =~ 'bar'
+ $~.should.nil?
+ end
+
+ it "is set at the method-scoped level rather than block-scoped" do
+ obj = Object.new
+ def obj.foo; yield; end
+ def obj.foo2(&proc); proc.call; end
+
+ match2 = nil
+ match3 = nil
+ match4 = nil
+
+ match1 = /foo/.match "foo"
+
+ obj.foo { match2 = /bar/.match("bar") }
+
+ match2.should_not == nil
+ $~.should == match2
+
+ match3 = /baz/.match("baz")
+
+ match3.should_not == nil
+ $~.should == match3
+
+ obj.foo2 { match4 = /qux/.match("qux") }
+
+ match4.should_not == nil
+ $~.should == match4
+ end
+
+ it "raises an error if assigned an object not nil or instanceof MatchData" do
+ $~ = nil
+ $~.should == nil
+ $~ = /foo/.match("foo")
+ $~.should be_an_instance_of(MatchData)
+
+ -> { $~ = 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
+ "foo" =~ /(f)oo/
+ foo_match = $~
+ "bar" =~ /(b)ar/
+ $~ = foo_match
+ $1.should == "f"
+ end
+
+ it "changes the value of the derived preceding match global" do
+ "foo hello" =~ /hello/
+ foo_match = $~
+ "bar" =~ /(bar)/
+ $~ = foo_match
+ $`.should == "foo "
+ end
+
+ it "changes the value of the derived following match global" do
+ "foo hello" =~ /foo/
+ foo_match = $~
+ "bar" =~ /(bar)/
+ $~ = foo_match
+ $'.should == " hello"
+ end
+
+ it "changes the value of the derived full match global" do
+ "foo hello" =~ /foo/
+ foo_match = $~
+ "bar" =~ /(bar)/
+ $~ = foo_match
+ $&.should == "foo"
+ end
+end
+
+describe "Predefined global $&" do
+ it "is equivalent to MatchData#[0] on the last match $~" do
+ /foo/ =~ 'barfoobaz'
+ $&.should == $~[0]
+ $&.should == 'foo'
+ 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
+
+describe "Predefined global $`" do
+ it "is equivalent to MatchData#pre_match on the last match $~" do
+ /foo/ =~ 'barfoobaz'
+ $`.should == $~.pre_match
+ $`.should == 'bar'
+ 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".dup.force_encoding(Encoding::ISO_8859_1) =~ /a/
+ $`.encoding.should equal(Encoding::ISO_8859_1)
+ end
+
+ it "is read-only" do
+ -> {
+ eval %q{$` = ""}
+ }.should raise_error(SyntaxError, /Can't set variable \$`/)
+ end
+
+ it "is read-only when aliased" do
+ alias $predefined_spec_backquote $`
+ -> {
+ $predefined_spec_backquote = ""
+ }.should raise_error(NameError, '$predefined_spec_backquote is a read-only variable')
+ end
+end
+
+describe "Predefined global $'" do
+ it "is equivalent to MatchData#post_match on the last match $~" do
+ /foo/ =~ 'barfoobaz'
+ $'.should == $~.post_match
+ $'.should == 'baz'
+ 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".dup.force_encoding(Encoding::ISO_8859_1) =~ /c/
+ $'.encoding.should equal(Encoding::ISO_8859_1)
+ end
+
+ it "is read-only" do
+ -> {
+ eval %q{$' = ""}
+ }.should raise_error(SyntaxError, /Can't set variable \$'/)
+ end
+
+ it "is read-only when aliased" do
+ alias $predefined_spec_single_quote $'
+ -> {
+ $predefined_spec_single_quote = ""
+ }.should raise_error(NameError, '$predefined_spec_single_quote is a read-only variable')
+ end
+end
+
+describe "Predefined global $+" do
+ it "is equivalent to $~.captures.last" do
+ /(f(o)o)/ =~ 'barfoobaz'
+ $+.should == $~.captures.last
+ $+.should == 'o'
+ end
+
+ it "captures the last non nil capture" do
+ /(a)|(b)/ =~ 'a'
+ $+.should == 'a'
+ 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
+
+describe "Predefined globals $1..N" do
+ it "are equivalent to $~[N]" do
+ /(f)(o)(o)/ =~ 'foo'
+ $1.should == $~[1]
+ $2.should == $~[2]
+ $3.should == $~[3]
+ $4.should == $~[4]
+
+ [$1, $2, $3, $4].should == ['f', 'o', 'o', nil]
+ end
+
+ it "are nil unless a match group occurs" do
+ def test(arg)
+ case arg
+ when /-(.)?/
+ $1
+ end
+ end
+ test("-").should == nil
+ 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
+
+describe "Predefined global $stdout" do
+ before :each do
+ @old_stdout = $stdout
+ end
+
+ after :each do
+ $stdout = @old_stdout
+ end
+
+ it "raises TypeError error if assigned to nil" do
+ -> { $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')
+ -> { $stdout = obj }.should raise_error(TypeError)
+
+ obj.stub!(:write)
+ $stdout = obj
+ $stdout.should equal(obj)
+ end
+end
+
+describe "Predefined global $!" do
+ it "is Fiber-local" do
+ Fiber.new do
+ raise "hi"
+ rescue
+ Fiber.yield
+ end.resume
+
+ $!.should == nil
+ end
+
+ it "is read-only" do
+ -> {
+ $! = []
+ }.should raise_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
+
+ obj = Class.new do
+ def method_missing(*args)
+ super
+ end
+ end.new
+
+ [obj, 'foo'].join
+
+ $!.should == nil
+ end
+
+ it "should be set to the value of $! before the begin after a successful rescue" do
+ outer = StandardError.new 'outer'
+ inner = StandardError.new 'inner'
+
+ begin
+ raise outer
+ rescue
+ $!.should == outer
+
+ # nested rescue
+ begin
+ $!.should == outer
+ raise inner
+ rescue
+ $!.should == inner
+ ensure
+ $!.should == outer
+ end
+ $!.should == outer
+ end
+ $!.should == nil
+ end
+
+ it "should be set to the value of $! before the begin after a rescue which returns" do
+ def foo
+ outer = StandardError.new 'outer'
+ inner = StandardError.new 'inner'
+
+ begin
+ raise outer
+ rescue
+ $!.should == outer
+
+ # nested rescue
+ begin
+ $!.should == outer
+ raise inner
+ rescue
+ $!.should == inner
+ return
+ ensure
+ $!.should == outer
+ end
+ $!.should == outer
+ end
+ $!.should == nil
+ end
+ foo
+ end
+
+ it "should be set to the value of $! before the begin after a successful rescue within an ensure" do
+ outer = StandardError.new 'outer'
+ inner = StandardError.new 'inner'
+
+ begin
+ begin
+ raise outer
+ ensure
+ $!.should == outer
+
+ # nested rescue
+ begin
+ $!.should == outer
+ raise inner
+ rescue
+ $!.should == inner
+ ensure
+ $!.should == outer
+ end
+ $!.should == outer
+ end
+ flunk "outer should be raised after the ensure"
+ rescue
+ $!.should == outer
+ end
+ $!.should == nil
+ end
+
+ it "should be set to the new exception after a throwing rescue" do
+ outer = StandardError.new 'outer'
+ inner = StandardError.new 'inner'
+
+ begin
+ raise outer
+ rescue
+ $!.should == outer
+
+ begin
+ # nested rescue
+ begin
+ $!.should == outer
+ raise inner
+ rescue # the throwing rescue
+ $!.should == inner
+ raise inner
+ ensure
+ $!.should == inner
+ end
+ rescue # do not make the exception fail the example
+ $!.should == inner
+ end
+ $!.should == outer
+ end
+ $!.should == nil
+ end
+
+ describe "in bodies without ensure" do
+ it "should be cleared when an exception is rescued" do
+ e = StandardError.new 'foo'
+ begin
+ raise e
+ rescue
+ $!.should == e
+ end
+ $!.should == nil
+ end
+
+ it "should be cleared when an exception is rescued even when a non-local return is present" do
+ def foo(e)
+ $!.should == e
+ yield
+ end
+ def bar
+ e = StandardError.new 'foo'
+ begin
+ raise e
+ rescue
+ $!.should == e
+ foo(e) { return }
+ end
+ end
+
+ bar
+ $!.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
+ begin
+ begin
+ raise e
+ rescue TypeError
+ flunk
+ end
+ ensure
+ $!.should == e
+ end
+ rescue
+ $!.should == e
+ end
+ $!.should == nil
+ end
+
+ it "should not be cleared when an exception is rescued and rethrown" do
+ e = StandardError.new 'foo'
+ begin
+ begin
+ begin
+ raise e
+ rescue => e
+ $!.should == e
+ raise e
+ end
+ ensure
+ $!.should == e
+ end
+ rescue
+ $!.should == e
+ end
+ $!.should == nil
+ end
+ end
+
+ describe "in ensure-protected bodies" do
+ it "should be cleared when an exception is rescued" do
+ e = StandardError.new 'foo'
+ begin
+ raise e
+ rescue
+ $!.should == e
+ ensure
+ $!.should == nil
+ end
+ $!.should == nil
+ end
+
+ it "should not be cleared when an exception is not rescued" do
+ e = StandardError.new
+ begin
+ begin
+ begin
+ raise e
+ rescue TypeError
+ flunk
+ ensure
+ $!.should == e
+ end
+ ensure
+ $!.should == e
+ end
+ rescue
+ $!.should == e
+ end
+ end
+
+ it "should not be cleared when an exception is rescued and rethrown" do
+ e = StandardError.new
+ begin
+ begin
+ begin
+ raise e
+ rescue => e
+ $!.should == e
+ raise e
+ ensure
+ $!.should == e
+ end
+ ensure
+ $!.should == e
+ end
+ rescue
+ $!.should == e
+ end
+ end
+ end
+end
+
+describe "Predefined global $@" do
+ it "is Fiber-local" do
+ Fiber.new do
+ raise "hi"
+ rescue
+ Fiber.yield
+ end.resume
+
+ $@.should == nil
+ end
+
+ it "is set to a backtrace of a rescued exception" do
+ begin
+ raise
+ rescue
+ $@.should be_an_instance_of(Array)
+ $@.should == $!.backtrace
+ end
+ end
+
+ it "is cleared when an exception is rescued" do
+ begin
+ raise
+ rescue
+ end
+
+ $@.should == nil
+ end
+
+ it "is not set when there is no current exception" do
+ $@.should == nil
+ end
+
+ it "is set to a backtrace of a rescued exception" do
+ begin
+ raise
+ rescue
+ $@.should be_an_instance_of(Array)
+ $@.should == $!.backtrace
+ end
+ end
+
+ it "is not read-only" do
+ begin
+ raise
+ rescue
+ $@ = []
+ $@.should == []
+ end
+ end
+
+ it_behaves_like :exception_set_backtrace, -> backtrace {
+ exception = nil
+ begin
+ raise
+ rescue
+ $@ = backtrace
+ exception = $!
+ end
+ exception
+ }
+
+ it "cannot be assigned when there is no a rescued exception" do
+ -> {
+ $@ = []
+ }.should raise_error(ArgumentError, '$! not set')
+ end
+end
+
+# Input/Output Variables
+# ---------------------------------------------------------------------------------------------------
+#
+# $/ 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
+
+ after :each do
+ $/ = @dollar_slash
+ $-0 = @dollar_dash_zero
+ $VERBOSE = @verbose
+ end
+
+ 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
+ end
+
+ it "returns the value assigned" do
+ ($/ = "xyz").should == "xyz"
+ end
+
+ it "changes $-0" do
+ $/ = "xyz"
+ $-0.should equal($/)
+ end
+
+ it "does not call #to_str to convert the object to a String" do
+ obj = mock("$/ value")
+ obj.should_not_receive(:to_str)
+
+ -> { $/ = obj }.should raise_error(TypeError, 'value of $/ must be String')
+ end
+
+ 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
+ -> { $/ = 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
+
+ after :each do
+ $/ = @dollar_slash
+ $-0 = @dollar_dash_zero
+ $VERBOSE = @verbose
+ end
+
+ 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
+ $-0 = nil
+ $-0.should be_nil
+ end
+
+ it "returns the value assigned" do
+ ($-0 = "xyz").should == "xyz"
+ end
+
+ it "changes $/" do
+ $-0 = "xyz"
+ $/.should equal($-0)
+ end
+
+ it "does not call #to_str to convert the object to a String" do
+ obj = mock("$-0 value")
+ obj.should_not_receive(:to_str)
+
+ -> { $-0 = obj }.should raise_error(TypeError, 'value of $-0 must be String')
+ end
+
+ 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
+ -> { $-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
+
+describe "Predefined global $," do
+ after :each do
+ $, = nil
+ end
+
+ it "defaults to nil" do
+ $,.should be_nil
+ end
+
+ it "raises TypeError if assigned a non-String" do
+ -> { $, = 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
+
+describe "Predefined global $_" do
+ it "is set to the last line read by e.g. StringIO#gets" do
+ stdin = StringIO.new("foo\nbar\n", "r")
+
+ read = stdin.gets
+ read.should == "foo\n"
+ $_.should == read
+
+ read = stdin.gets
+ read.should == "bar\n"
+ $_.should == read
+
+ read = stdin.gets
+ read.should == nil
+ $_.should == read
+ end
+
+ it "is set at the method-scoped level rather than block-scoped" do
+ obj = Object.new
+ def obj.foo; yield; end
+ def obj.foo2; yield; end
+
+ stdin = StringIO.new("foo\nbar\nbaz\nqux\n", "r")
+ match = stdin.gets
+
+ obj.foo { match = stdin.gets }
+
+ match.should == "bar\n"
+ $_.should == match
+
+ match = stdin.gets
+
+ match.should == "baz\n"
+ $_.should == match
+
+ obj.foo2 { match = stdin.gets }
+
+ match.should == "qux\n"
+ $_.should == match
+ end
+
+ it "is Thread-local" do
+ $_ = nil
+ running = false
+
+ thr = Thread.new do
+ $_ = "last line"
+ running = true
+ end
+
+ Thread.pass until running
+ $_.should be_nil
+
+ thr.join
+ end
+
+ it "can be assigned any value" do
+ $_ = nil
+ $_.should == nil
+ $_ = "foo"
+ $_.should == "foo"
+ o = Object.new
+ $_ = o
+ $_.should == o
+ $_ = 1
+ $_.should == 1
+ end
+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
+ ($:.length > 0).should == true
+ end
+
+ it "does not include the current directory" do
+ $:.should_not include(".")
+ end
+
+ it "is the same object as $LOAD_PATH and $-I" do
+ $:.__id__.should == $LOAD_PATH.__id__
+ $:.__id__.should == $-I.__id__
+ end
+
+ it "can be changed via <<" do
+ $: << "foo"
+ $:.should include("foo")
+ ensure
+ $:.delete("foo")
+ end
+
+ it "is read-only" do
+ -> {
+ $: = []
+ }.should raise_error(NameError, '$: is a read-only variable')
+
+ -> {
+ $LOAD_PATH = []
+ }.should raise_error(NameError, '$LOAD_PATH is a read-only variable')
+
+ -> {
+ $-I = []
+ }.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
+ $".should equal $LOADED_FEATURES
+ end
+
+ it "is read-only" do
+ -> {
+ $" = []
+ }.should raise_error(NameError, '$" is a read-only variable')
+
+ -> {
+ $LOADED_FEATURES = []
+ }.should raise_error(NameError, '$LOADED_FEATURES is a read-only variable')
+ end
+end
+
+describe "Global variable $<" do
+ it "is read-only" do
+ -> {
+ $< = nil
+ }.should raise_error(NameError, '$< is a read-only variable')
+ end
+end
+
+describe "Global variable $FILENAME" do
+ it "is read-only" do
+ -> {
+ $FILENAME = "-"
+ }.should raise_error(NameError, '$FILENAME is a read-only variable')
+ end
+end
+
+describe "Global variable $?" do
+ it "is read-only" do
+ -> {
+ $? = nil
+ }.should raise_error(NameError, '$? is a read-only variable')
+ end
+
+ it "is thread-local" do
+ system(ruby_cmd('exit 0'))
+ Thread.new { $?.should be_nil }.join
+ end
+end
+
+describe "Global variable $-a" do
+ it "is read-only" do
+ -> { $-a = true }.should raise_error(NameError, '$-a is a read-only variable')
+ end
+end
+
+describe "Global variable $-l" do
+ it "is read-only" do
+ -> { $-l = true }.should raise_error(NameError, '$-l is a read-only variable')
+ end
+end
+
+describe "Global variable $-p" do
+ it "is read-only" do
+ -> { $-p = true }.should raise_error(NameError, '$-p is a read-only variable')
+ end
+end
+
+describe "Global variable $-d" do
+ before :each do
+ @debug = $DEBUG
+ end
+
+ after :each do
+ $DEBUG = @debug
+ end
+
+ it "is an alias of $DEBUG" do
+ $DEBUG = true
+ $-d.should be_true
+ $-d = false
+ $DEBUG.should be_false
+ end
+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
+ $VERBOSE.should be_true
+ end
+ end
+
+ it "allows false" do
+ $VERBOSE = false
+ $VERBOSE.should be_false
+ end
+
+ it "allows nil without coercing to false" do
+ $VERBOSE = nil
+ $VERBOSE.should be_nil
+ end
+end
+
+describe :verbose_global_alias, shared: true do
+ before :each do
+ @verbose = $VERBOSE
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it "is an alias of $VERBOSE" do
+ $VERBOSE = true
+ eval(@method).should be_true
+ eval("#{@method} = false")
+ $VERBOSE.should be_false
+ end
+end
+
+describe "Global variable $-v" do
+ it_behaves_like :verbose_global_alias, '$-v'
+end
+
+describe "Global variable $-w" do
+ it_behaves_like :verbose_global_alias, '$-w'
+end
+
+describe "Global variable $0" do
+ before :each do
+ @orig_program_name = $0
+ end
+
+ after :each do
+ $0 = @orig_program_name
+ end
+
+ it "is the path given as the main script and the same as __FILE__" do
+ script = "fixtures/dollar_zero.rb"
+ Dir.chdir(__dir__) do
+ ruby_exe(script).should == "#{script}\n#{script}\nOK"
+ end
+ end
+
+ it "returns the program name" do
+ $0 = "rbx"
+ $0.should == "rbx"
+ end
+
+ platform_is :linux, :darwin do
+ it "actually sets the program name" do
+ title = "rubyspec-dollar0-test"
+ $0 = title
+ `ps -ocommand= -p#{$$}`.should include(title)
+ end
+ end
+
+ it "returns the given value when set" do
+ ($0 = "rbx").should == "rbx"
+ end
+
+ it "raises a TypeError when not given an object that can be coerced to a String" do
+ -> { $0 = nil }.should raise_error(TypeError)
+ end
+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
+ Object.const_defined?(:ARGF).should == true
+ end
+
+ it "includes ARGV" do
+ Object.const_defined?(:ARGV).should == true
+ end
+
+ it "includes a hash-like object ENV" do
+ Object.const_defined?(:ENV).should == true
+ ENV.respond_to?(:[]).should == true
+ end
+end
+
+describe "The predefined standard object nil" do
+ it "is an instance of NilClass" do
+ nil.should be_kind_of(NilClass)
+ end
+
+ it "raises a SyntaxError if assigned to" do
+ -> { eval("nil = true") }.should raise_error(SyntaxError, /Can't assign to nil/)
+ end
+end
+
+describe "The predefined standard object true" do
+ it "is an instance of TrueClass" do
+ true.should be_kind_of(TrueClass)
+ end
+
+ it "raises a SyntaxError if assigned to" do
+ -> { eval("true = false") }.should raise_error(SyntaxError, /Can't assign to true/)
+ end
+end
+
+describe "The predefined standard object false" do
+ it "is an instance of FalseClass" do
+ false.should be_kind_of(FalseClass)
+ end
+
+ it "raises a SyntaxError if assigned to" do
+ -> { eval("false = nil") }.should raise_error(SyntaxError, /Can't assign to false/)
+ end
+end
+
+describe "The self pseudo-variable" do
+ it "raises a SyntaxError if assigned to" do
+ -> { eval("self = 1") }.should raise_error(SyntaxError, /Can't change the value of self/)
+ end
+end
+
+# Global Constants
+# ---------------------------------------------------------------------------------------------------
+#
+# The following constants are defined by the Ruby interpreter.
+#
+# DATA IO If the main program file contains the directive __END__, then
+# the constant DATA will be initialized so that reading from it will
+# return lines following __END__ from the source file.
+# FALSE FalseClass Synonym for false (deprecated, removed in Ruby 3).
+# NIL NilClass Synonym for nil (deprecated, removed in Ruby 3).
+# RUBY_PLATFORM String The identifier of the platform running this program. This string
+# is in the same form as the platform identifier used by the GNU
+# configure utility (which is not a coincidence).
+# RUBY_RELEASE_DATE String The date of this release.
+# RUBY_VERSION String The version number of the interpreter.
+# STDERR IO The actual standard error stream for the program. The initial
+# value of $stderr.
+# STDIN IO The actual standard input stream for the program. The initial
+# value of $stdin.
+# STDOUT IO The actual standard output stream for the program. The initial
+# value of $stdout.
+# SCRIPT_LINES__ Hash If a constant SCRIPT_LINES__ is defined and references a Hash,
+# Ruby will store an entry containing the contents of each file it
+# parses, with the file’s name as the key and an array of strings as
+# the value.
+# TOPLEVEL_BINDING Binding A Binding object representing the binding at Ruby’s top level—
+# the level where programs are initially executed.
+# TRUE TrueClass Synonym for true (deprecated, removed in Ruby 3).
+
+describe "The predefined global constants" do
+ describe "TRUE" do
+ it "is no longer defined" do
+ Object.const_defined?(:TRUE).should == false
+ end
+ end
+
+ describe "FALSE" do
+ it "is no longer defined" do
+ Object.const_defined?(:FALSE).should == false
+ end
+ end
+
+ describe "NIL" do
+ it "is no longer defined" do
+ Object.const_defined?(:NIL).should == false
+ end
+ end
+
+ it "includes STDIN" do
+ Object.const_defined?(:STDIN).should == true
+ end
+
+ it "includes STDOUT" do
+ Object.const_defined?(:STDOUT).should == true
+ end
+
+ it "includes STDERR" do
+ Object.const_defined?(:STDERR).should == true
+ end
+
+ it "includes RUBY_VERSION" do
+ Object.const_defined?(:RUBY_VERSION).should == true
+ end
+
+ it "includes RUBY_RELEASE_DATE" do
+ Object.const_defined?(:RUBY_RELEASE_DATE).should == true
+ end
+
+ it "includes RUBY_PLATFORM" do
+ Object.const_defined?(:RUBY_PLATFORM).should == true
+ end
+
+ it "includes TOPLEVEL_BINDING" do
+ Object.const_defined?(:TOPLEVEL_BINDING).should == true
+ end
+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
+
+ 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
+
+ it "has the same external encoding as Encoding.default_external when that encoding is changed" do
+ Encoding.default_external = Encoding::ISO_8859_16
+ STDIN.external_encoding.should equal(Encoding::ISO_8859_16)
+ end
+
+ it "has nil for the internal encoding" do
+ STDIN.internal_encoding.should be_nil
+ end
+
+ it "has nil for the internal encoding despite Encoding.default_internal being changed" do
+ Encoding.default_internal = Encoding::IBM437
+ STDIN.internal_encoding.should be_nil
+ end
+ end
+
+ 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
+ end
+
+ describe "STDOUT" do
+ it "has nil for the external encoding" do
+ STDOUT.external_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 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
+
+ it "has nil for the internal encoding" do
+ STDOUT.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
+ STDOUT.internal_encoding.should be_nil
+ end
+ end
+
+ describe "STDERR" do
+ it "has nil for the external encoding" do
+ STDERR.external_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 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
+
+ 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
new file mode 100644
index 0000000000..b04aa25c9e
--- /dev/null
+++ b/spec/ruby/language/private_spec.rb
@@ -0,0 +1,67 @@
+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)
+ -> { a.bar }.should raise_error(NoMethodError)
+
+ b = Private::B.new
+ b.methods.should_not include(:bar)
+ -> { b.bar }.should raise_error(NoMethodError)
+ end
+
+ # def expr.meth() methods are always public
+ it "has no effect on def expr.meth() methods" do
+ Private::B.public_defs_method.should == 0
+ end
+
+ it "is overridden when a new class is opened" do
+ c = Private::B::C.new
+ c.methods.should include(:baz)
+ c.baz
+ Private::B.public_class_method1.should == 1
+ -> { Private::B.private_class_method1 }.should raise_error(NoMethodError)
+ end
+
+ it "is no longer in effect when the class is closed" do
+ b = Private::B.new
+ b.methods.should include(:foo)
+ b.foo
+ end
+
+ it "changes visibility of previously called method" do
+ klass = Class.new do
+ def foo
+ "foo"
+ end
+ end
+ f = klass.new
+ f.foo
+ klass.class_eval do
+ private :foo
+ end
+ -> { f.foo }.should raise_error(NoMethodError)
+ end
+
+ it "changes visibility of previously called methods with same send/call site" do
+ g = ::Private::G.new
+ -> {
+ 2.times do
+ g.foo
+ module ::Private
+ class G
+ private :foo
+ end
+ end
+ end
+ }.should raise_error(NoMethodError)
+ end
+
+ it "changes the visibility of the existing method in the subclass" do
+ ::Private::A.new.foo.should == 'foo'
+ -> { ::Private::H.new.foo }.should raise_error(NoMethodError)
+ ::Private::H.new.send(:foo).should == 'foo'
+ end
+end
diff --git a/spec/ruby/language/proc_spec.rb b/spec/ruby/language/proc_spec.rb
new file mode 100644
index 0000000000..ca9a13aa61
--- /dev/null
+++ b/spec/ruby/language/proc_spec.rb
@@ -0,0 +1,249 @@
+require_relative '../spec_helper'
+
+describe "A Proc" do
+ it "captures locals from the surrounding scope" do
+ var = 1
+ lambda { var }.call.should == 1
+ end
+
+ it "does not capture a local when an argument has the same name" do
+ var = 1
+ lambda { |var| var }.call(2).should == 2
+ var.should == 1
+ end
+
+ describe "taking zero arguments" do
+ before :each do
+ @l = lambda { 1 }
+ end
+
+ it "does not raise an exception if no values are passed" do
+ @l.call.should == 1
+ end
+
+ it "raises an ArgumentError if a value is passed" do
+ lambda { @l.call(0) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "taking || arguments" do
+ before :each do
+ @l = lambda { || 1 }
+ end
+
+ it "does not raise an exception when passed no values" do
+ @l.call.should == 1
+ end
+
+ it "raises an ArgumentError if a value is passed" do
+ lambda { @l.call(0) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "taking |a| arguments" do
+ before :each do
+ @l = lambda { |a| a }
+ end
+
+ it "assigns the value passed to the argument" do
+ @l.call(2).should == 2
+ end
+
+ it "does not destructure a single Array value" do
+ @l.call([1, 2]).should == [1, 2]
+ end
+
+ it "does not call #to_ary to convert a single passed object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_not_receive(:to_ary)
+
+ @l.call(obj).should equal(obj)
+ end
+
+ it "raises an ArgumentError if no value is passed" do
+ lambda { @l.call }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "taking |a, b| arguments" do
+ before :each do
+ @l = lambda { |a, b| [a, b] }
+ end
+
+ it "raises an ArgumentError if passed no values" do
+ lambda { @l.call }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if passed one value" do
+ lambda { @l.call(0) }.should raise_error(ArgumentError)
+ end
+
+ it "assigns the values passed to the arguments" do
+ @l.call(1, 2).should == [1, 2]
+ end
+
+ it "does not call #to_ary to convert a single passed object to an Array" do
+ obj = mock("proc call to_ary")
+ obj.should_not_receive(:to_ary)
+
+ lambda { @l.call(obj) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "taking |a, *b| arguments" do
+ before :each do
+ @l = lambda { |a, *b| [a, b] }
+ end
+
+ it "raises an ArgumentError if passed no values" do
+ lambda { @l.call }.should raise_error(ArgumentError)
+ end
+
+ it "does not destructure a single Array value yielded" do
+ @l.call([1, 2, 3]).should == [[1, 2, 3], []]
+ end
+
+ it "assigns all passed values after the first to the rest argument" do
+ @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
+ obj = mock("block yield to_ary")
+ obj.should_not_receive(:to_ary)
+
+ @l.call(obj).should == [obj, []]
+ end
+ end
+
+ describe "taking |*| arguments" do
+ before :each do
+ @l = lambda { |*| 1 }
+ end
+
+ it "does not raise an exception when passed no values" do
+ @l.call.should == 1
+ end
+
+ it "does not raise an exception when passed multiple values" do
+ @l.call(2, 3, 4).should == 1
+ end
+
+ it "does not call #to_ary to convert a single passed object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_not_receive(:to_ary)
+
+ @l.call(obj).should == 1
+ end
+ end
+
+ describe "taking |*a| arguments" do
+ before :each do
+ @l = lambda { |*a| a }
+ end
+
+ it "assigns [] to the argument when passed no values" do
+ @l.call.should == []
+ end
+
+ it "assigns the argument an Array wrapping one passed value" do
+ @l.call(1).should == [1]
+ end
+
+ it "assigns the argument an Array wrapping all values passed" do
+ @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
+ obj = mock("block yield to_ary")
+ obj.should_not_receive(:to_ary)
+
+ @l.call(obj).should == [obj]
+ end
+ end
+
+ describe "taking |*a, b| arguments" do
+ it "assigns [] to the argument when passed no values" do
+ proc { |*a, b| [a, b] }.call.should == [[], nil]
+ end
+ end
+
+ describe "taking |a, *b, c| arguments" do
+ it "assigns [] to the argument when passed no values" do
+ proc { |a, *b, c| [a, b, c] }.call.should == [nil, [], nil]
+ end
+ end
+
+ describe "taking |a, | arguments" do
+ before :each do
+ @l = lambda { |a, | a }
+ end
+
+ it "raises an ArgumentError when passed no values" do
+ lambda { @l.call }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed more than one value" do
+ lambda { @l.call(1, 2) }.should raise_error(ArgumentError)
+ end
+
+ it "assigns the argument the value passed" do
+ @l.call(1).should == 1
+ end
+
+ it "does not destructure when passed a single Array" do
+ @l.call([1,2]).should == [1, 2]
+ end
+
+ it "does not call #to_ary to convert a single passed object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_not_receive(:to_ary)
+
+ @l.call(obj).should == obj
+ end
+ end
+
+ describe "taking |(a, b)| arguments" do
+ before :each do
+ @l = lambda { |(a, b)| [a, b] }
+ end
+
+ it "raises an ArgumentError when passed no values" do
+ lambda { @l.call }.should raise_error(ArgumentError)
+ end
+
+ it "destructures a single Array value yielded" do
+ @l.call([1, 2]).should == [1, 2]
+ end
+
+ it "calls #to_ary to convert a single passed object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ @l.call(obj).should == [1, 2]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ obj = mock("block yield to_ary invalid")
+ obj.should_receive(:to_ary).and_return(1)
+
+ 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
new file mode 100644
index 0000000000..57532553b3
--- /dev/null
+++ b/spec/ruby/language/redo_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../spec_helper'
+
+describe "The redo statement" do
+ it "restarts block execution if used within block" do
+ a = []
+ -> {
+ a << 1
+ redo if a.size < 2
+ a << 2
+ }.call
+ a.should == [1, 1, 2]
+ end
+
+ it "re-executes the closest loop" do
+ exist = [2,3]
+ processed = []
+ order = []
+ [1,2,3,4].each do |x|
+ order << x
+ begin
+ processed << x
+ if exist.include?(x)
+ raise StandardError, "included"
+ end
+ rescue StandardError
+ exist.delete(x)
+ redo
+ end
+ end
+ processed.should == [1,2,2,3,3,4]
+ exist.should == []
+ order.should == [1,2,2,3,3,4]
+ end
+
+ it "re-executes the last step in enumeration" do
+ list = []
+ [1,2,3].each do |x|
+ list << x
+ break if list.size == 6
+ redo if x == 3
+ end
+ list.should == [1,2,3,3,3,3]
+ end
+
+ it "triggers ensure block when re-executing a block" do
+ list = []
+ [1,2,3].each do |x|
+ list << x
+ begin
+ list << 10*x
+ redo if list.count(1) == 1
+ ensure
+ list << 100*x
+ end
+ end
+ list.should == [1,10,100,1,10,100,2,20,200,3,30,300]
+ end
+
+ describe "in a method" do
+ it "is invalid and raises a SyntaxError" do
+ -> {
+ eval("def m; redo; end")
+ }.should raise_error(SyntaxError)
+ end
+ end
+end
diff --git a/spec/ruby/language/regexp/anchors_spec.rb b/spec/ruby/language/regexp/anchors_spec.rb
new file mode 100644
index 0000000000..cdc06c0b4d
--- /dev/null
+++ b/spec/ruby/language/regexp/anchors_spec.rb
@@ -0,0 +1,179 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexps with anchors" do
+ it "supports ^ (line start anchor)" do
+ # Basic matching
+ /^foo/.match("foo").to_a.should == ["foo"]
+ /^bar/.match("foo\nbar").to_a.should == ["bar"]
+ # Basic non-matching
+ /^foo/.match(" foo").should be_nil
+ /foo^/.match("foo\n\n\n").should be_nil
+
+ # A bit advanced
+ /^^^foo/.match("foo").to_a.should == ["foo"]
+ (/^[^f]/ =~ "foo\n\n").should == "foo\n".size and $~.to_a.should == ["\n"]
+ (/($^)($^)/ =~ "foo\n\n").should == "foo\n".size and $~.to_a.should == ["", "", ""]
+
+ # Different start of line chars
+ /^bar/.match("foo\rbar").should be_nil
+ /^bar/.match("foo\0bar").should be_nil
+
+ # Trivial
+ /^/.match("foo").to_a.should == [""]
+
+ # Grouping
+ /(^foo)/.match("foo").to_a.should == ["foo", "foo"]
+ /(^)/.match("foo").to_a.should == ["", ""]
+ /(foo\n^)(^bar)/.match("foo\nbar").to_a.should == ["foo\nbar", "foo\n", "bar"]
+ end
+
+ it "does not match ^ after trailing \\n" do
+ /^(?!\A)/.match("foo\n").should be_nil # There is no (empty) line after a trailing \n
+ end
+
+ it "supports $ (line end anchor)" do
+ # Basic matching
+ /foo$/.match("foo").to_a.should == ["foo"]
+ /foo$/.match("foo\nbar").to_a.should == ["foo"]
+ # Basic non-matching
+ /foo$/.match("foo ").should be_nil
+ /$foo/.match("\n\n\nfoo").should be_nil
+
+ # A bit advanced
+ /foo$$$/.match("foo").to_a.should == ["foo"]
+ (/[^o]$/ =~ "foo\n\n").should == ("foo\n".size - 1) and $~.to_a.should == ["\n"]
+
+ # Different end of line chars
+ /foo$/.match("foo\r\nbar").should be_nil
+ /foo$/.match("foo\0bar").should be_nil
+
+ # Trivial
+ (/$/ =~ "foo").should == "foo".size and $~.to_a.should == [""]
+
+ # Grouping
+ /(foo$)/.match("foo").to_a.should == ["foo", "foo"]
+ (/($)/ =~ "foo").should == "foo".size and $~.to_a.should == ["", ""]
+ /(foo$)($\nbar)/.match("foo\nbar").to_a.should == ["foo\nbar", "foo", "\nbar"]
+ end
+
+ it "supports \\A (string start anchor)" do
+ # Basic matching
+ /\Afoo/.match("foo").to_a.should == ["foo"]
+ # Basic non-matching
+ /\Abar/.match("foo\nbar").should be_nil
+ /\Afoo/.match(" foo").should be_nil
+
+ # A bit advanced
+ /\A\A\Afoo/.match("foo").to_a.should == ["foo"]
+ /(\A\Z)(\A\Z)/.match("").to_a.should == ["", "", ""]
+
+ # Different start of line chars
+ /\Abar/.match("foo\0bar").should be_nil
+
+ # Grouping
+ /(\Afoo)/.match("foo").to_a.should == ["foo", "foo"]
+ /(\A)/.match("foo").to_a.should == ["", ""]
+ end
+
+ it "supports \\Z (string end anchor, including before trailing \\n)" do
+ # Basic matching
+ /foo\Z/.match("foo").to_a.should == ["foo"]
+ /foo\Z/.match("foo\n").to_a.should == ["foo"]
+ # Basic non-matching
+ /foo\Z/.match("foo\nbar").should be_nil
+ /foo\Z/.match("foo ").should be_nil
+
+ # A bit advanced
+ /foo\Z\Z\Z/.match("foo\n").to_a.should == ["foo"]
+ (/($\Z)($\Z)/ =~ "foo\n").should == "foo".size and $~.to_a.should == ["", "", ""]
+ (/(\z\Z)(\z\Z)/ =~ "foo\n").should == "foo\n".size and $~.to_a.should == ["", "", ""]
+
+ # Different end of line chars
+ /foo\Z/.match("foo\0bar").should be_nil
+ /foo\Z/.match("foo\r\n").should be_nil
+
+ # Grouping
+ /(foo\Z)/.match("foo").to_a.should == ["foo", "foo"]
+ (/(\Z)/ =~ "foo").should == "foo".size and $~.to_a.should == ["", ""]
+ end
+
+ it "supports \\z (string end anchor)" do
+ # Basic matching
+ /foo\z/.match("foo").to_a.should == ["foo"]
+ # Basic non-matching
+ /foo\z/.match("foo\nbar").should be_nil
+ /foo\z/.match("foo\n").should be_nil
+ /foo\z/.match("foo ").should be_nil
+
+ # A bit advanced
+ /foo\z\z\z/.match("foo").to_a.should == ["foo"]
+ (/($\z)($\z)/ =~ "foo").should == "foo".size and $~.to_a.should == ["", "", ""]
+
+ # Different end of line chars
+ /foo\z/.match("foo\0bar").should be_nil
+ /foo\z/.match("foo\r\nbar").should be_nil
+
+ # Grouping
+ /(foo\z)/.match("foo").to_a.should == ["foo", "foo"]
+ (/(\z)/ =~ "foo").should == "foo".size and $~.to_a.should == ["", ""]
+ end
+
+ it "supports \\b (word boundary)" do
+ # Basic matching
+ /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"]
+ end
+ LanguageSpecs.non_alphanum_non_space.scan(/./).each do |c|
+ /foo\b/.match("foo" + c).to_a.should == ["foo"]
+ end
+ /foo\b/.match("foo\0").to_a.should == ["foo"]
+ # Basic non-matching
+ /foo\b/.match("foobar").should be_nil
+ /foo\b/.match("foo123").should be_nil
+ /foo\b/.match("foo_").should be_nil
+ end
+
+ it "supports \\B (non-word-boundary)" do
+ # Basic matching
+ /foo\B/.match("foobar").to_a.should == ["foo"]
+ /foo\B/.match("foo123").to_a.should == ["foo"]
+ /foo\B/.match("foo_").to_a.should == ["foo"]
+ # Basic non-matching
+ /foo\B/.match("foo").should be_nil
+ /foo\B/.match("foo\n").should be_nil
+ LanguageSpecs.white_spaces.scan(/./).each do |c|
+ /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
+ end
+ /foo\B/.match("foo\0").should be_nil
+ end
+
+ it "supports (?= ) (positive lookahead)" do
+ /foo.(?=bar)/.match("foo1 foo2bar").to_a.should == ["foo2"]
+ end
+
+ it "supports (?! ) (negative lookahead)" do
+ /foo.(?!bar)/.match("foo1bar foo2").to_a.should == ["foo2"]
+ end
+
+ it "supports (?!<) (negative lookbehind)" do
+ /(?<!foo)bar./.match("foobar1 bar2").to_a.should == ["bar2"]
+ end
+
+ it "supports (?<=) (positive lookbehind)" do
+ /(?<=foo)bar./.match("bar1 foobar2").to_a.should == ["bar2"]
+ end
+
+ it "supports (?<=\\b) (positive lookbehind with word boundary)" do
+ /(?<=\bfoo)bar./.match("1foobar1 foobar2").to_a.should == ["bar2"]
+ end
+
+ it "supports (?!<\\b) (negative lookbehind with word boundary)" do
+ /(?<!\bfoo)bar./.match("foobar1 1foobar2").to_a.should == ["bar2"]
+ end
+end
diff --git a/spec/ruby/language/regexp/back-references_spec.rb b/spec/ruby/language/regexp/back-references_spec.rb
new file mode 100644
index 0000000000..627c8daace
--- /dev/null
+++ b/spec/ruby/language/regexp/back-references_spec.rb
@@ -0,0 +1,149 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexps with back-references" do
+ it "saves match data in the $~ pseudo-global variable" do
+ "hello" =~ /l+/
+ $~.to_a.should == ["ll"]
+ end
+
+ 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"
+ $2.should == "2"
+ $3.should == "3"
+ $4.should == "4"
+ $5.should == "5"
+ $6.should == "6"
+ $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
+ cap1, cap2, cap3 = nil
+ "foo" =~ /(o+)/
+ cap1 = [$~.to_a, $1]
+ Thread.new do
+ cap2 = [$~.to_a, $1]
+ "bar" =~ /(a)/
+ cap3 = [$~.to_a, $1]
+ end.join
+ cap4 = [$~.to_a, $1]
+ cap1.should == [["oo", "oo"], "oo"]
+ cap2.should == [[], nil]
+ cap3.should == [["a", "a"], "a"]
+ cap4.should == [["oo", "oo"], "oo"]
+ end
+
+ it "supports \<n> (backreference to previous group match)" do
+ /(foo.)\1/.match("foo1foo1").to_a.should == ["foo1foo1", "foo1"]
+ /(foo.)\1/.match("foo1foo2").should be_nil
+ end
+
+ 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
new file mode 100644
index 0000000000..018757db41
--- /dev/null
+++ b/spec/ruby/language/regexp/character_classes_spec.rb
@@ -0,0 +1,647 @@
+# coding: utf-8
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexp with character classes" do
+ it "supports \\w (word character)" do
+ /\w/.match("a").to_a.should == ["a"]
+ /\w/.match("1").to_a.should == ["1"]
+ /\w/.match("_").to_a.should == ["_"]
+
+ # Non-matches
+ /\w/.match(LanguageSpecs.white_spaces).should be_nil
+ /\w/.match(LanguageSpecs.non_alphanum_non_space).should be_nil
+ /\w/.match("\0").should be_nil
+ end
+
+ it "supports \\W (non-word character)" do
+ /\W+/.match(LanguageSpecs.white_spaces).to_a.should == [LanguageSpecs.white_spaces]
+ /\W+/.match(LanguageSpecs.non_alphanum_non_space).to_a.should == [LanguageSpecs.non_alphanum_non_space]
+ /\W/.match("\0").to_a.should == ["\0"]
+
+ # Non-matches
+ /\W/.match("a").should be_nil
+ /\W/.match("1").should be_nil
+ /\W/.match("_").should be_nil
+ end
+
+ it "supports \\s (space character)" do
+ /\s+/.match(LanguageSpecs.white_spaces).to_a.should == [LanguageSpecs.white_spaces]
+
+ # Non-matches
+ /\s/.match("a").should be_nil
+ /\s/.match("1").should be_nil
+ /\s/.match(LanguageSpecs.non_alphanum_non_space).should be_nil
+ /\s/.match("\0").should be_nil
+ end
+
+ it "supports \\S (non-space character)" do
+ /\S/.match("a").to_a.should == ["a"]
+ /\S/.match("1").to_a.should == ["1"]
+ /\S+/.match(LanguageSpecs.non_alphanum_non_space).to_a.should == [LanguageSpecs.non_alphanum_non_space]
+ /\S/.match("\0").to_a.should == ["\0"]
+
+ # Non-matches
+ /\S/.match(LanguageSpecs.white_spaces).should be_nil
+ end
+
+ it "supports \\d (numeric digit)" do
+ /\d/.match("1").to_a.should == ["1"]
+
+ # Non-matches
+ /\d/.match("a").should be_nil
+ /\d/.match(LanguageSpecs.white_spaces).should be_nil
+ /\d/.match(LanguageSpecs.non_alphanum_non_space).should be_nil
+ /\d/.match("\0").should be_nil
+ end
+
+ it "supports \\D (non-digit)" do
+ /\D/.match("a").to_a.should == ["a"]
+ /\D+/.match(LanguageSpecs.white_spaces).to_a.should == [LanguageSpecs.white_spaces]
+ /\D+/.match(LanguageSpecs.non_alphanum_non_space).to_a.should == [LanguageSpecs.non_alphanum_non_space]
+ /\D/.match("\0").to_a.should == ["\0"]
+
+ # Non-matches
+ /\D/.match("1").should be_nil
+ end
+
+ it "supports [] (character class)" do
+ /[a-z]+/.match("fooBAR").to_a.should == ["foo"]
+ /[\b]/.match("\b").to_a.should == ["\b"] # \b inside character class is backspace
+ end
+
+ it "supports [[:alpha:][:digit:][:etc:]] (predefined character classes)" do
+ /[[:alnum:]]+/.match("a1").to_a.should == ["a1"]
+ /[[:alpha:]]+/.match("Aa1").to_a.should == ["Aa"]
+ /[[:blank:]]+/.match(LanguageSpecs.white_spaces).to_a.should == [LanguageSpecs.blanks]
+ # /[[:cntrl:]]/.match("").to_a.should == [""] # TODO: what should this match?
+ /[[:digit:]]/.match("1").to_a.should == ["1"]
+ # /[[:graph:]]/.match("").to_a.should == [""] # TODO: what should this match?
+ /[[:lower:]]+/.match("Aa1").to_a.should == ["a"]
+ /[[:print:]]+/.match(LanguageSpecs.white_spaces).to_a.should == [" "] # include all of multibyte encoded characters
+ /[[:punct:]]+/.match(LanguageSpecs.punctuations).to_a.should == [LanguageSpecs.punctuations]
+ /[[:space:]]+/.match(LanguageSpecs.white_spaces).to_a.should == [LanguageSpecs.white_spaces]
+ /[[:upper:]]+/.match("123ABCabc").to_a.should == ["ABC"]
+ /[[:xdigit:]]+/.match("xyz0123456789ABCDEFabcdefXYZ").to_a.should == ["0123456789ABCDEFabcdef"]
+
+ # Parsing
+ /[[:lower:][:digit:]A-C]+/.match("a1ABCDEF").to_a.should == ["a1ABC"] # can be composed with other constructs in the character class
+ /[^[:lower:]A-C]+/.match("abcABCDEF123def").to_a.should == ["DEF123"] # negated character class
+ /[:alnum:]+/.match("a:l:n:u:m").to_a.should == ["a:l:n:u:m"] # should behave like regular character class composed of the individual letters
+ /[\[:alnum:]+/.match("[:a:l:n:u:m").to_a.should == ["[:a:l:n:u:m"] # should behave like regular character class composed of the individual letters
+ -> { eval('/[[:alpha:]-[:digit:]]/') }.should raise_error(SyntaxError) # can't use character class as a start value of range
+ end
+
+ it "matches ASCII characters with [[:ascii:]]" do
+ "\x00".match(/[[:ascii:]]/).to_a.should == ["\x00"]
+ "\x7F".match(/[[:ascii:]]/).to_a.should == ["\x7F"]
+ end
+
+ not_supported_on :opal do
+ it "doesn't match non-ASCII characters with [[:ascii:]]" do
+ /[[:ascii:]]/.match("\u{80}").should be_nil
+ /[[:ascii:]]/.match("\u{9898}").should be_nil
+ end
+ end
+
+ it "matches Unicode letter characters with [[:alnum:]]" do
+ "à".match(/[[:alnum:]]/).to_a.should == ["à"]
+ end
+
+ it "matches Unicode digits with [[:alnum:]]" do
+ "\u{0660}".match(/[[:alnum:]]/).to_a.should == ["\u{0660}"]
+ end
+
+ it "doesn't matches Unicode marks with [[:alnum:]]" do
+ "\u{3099}".match(/[[:alnum:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode control characters with [[:alnum:]]" do
+ "\u{16}".match(/[[:alnum:]]/).to_a.should == []
+ end
+
+ it "doesn't match Unicode punctuation characters with [[:alnum:]]" do
+ "\u{3F}".match(/[[:alnum:]]/).to_a.should == []
+ end
+
+ it "matches Unicode letter characters with [[:alpha:]]" do
+ "à".match(/[[:alpha:]]/).to_a.should == ["à"]
+ end
+
+ it "doesn't match Unicode digits with [[:alpha:]]" do
+ "\u{0660}".match(/[[:alpha:]]/).to_a.should == []
+ end
+
+ it "doesn't matches Unicode marks with [[:alpha:]]" do
+ "\u{3099}".match(/[[:alpha:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode control characters with [[:alpha:]]" do
+ "\u{16}".match(/[[:alpha:]]/).to_a.should == []
+ end
+
+ it "doesn't match Unicode punctuation characters with [[:alpha:]]" do
+ "\u{3F}".match(/[[:alpha:]]/).to_a.should == []
+ end
+
+ it "matches Unicode space characters with [[:blank:]]" do
+ "\u{1680}".match(/[[:blank:]]/).to_a.should == ["\u{1680}"]
+ end
+
+ it "doesn't match Unicode control characters with [[:blank:]]" do
+ "\u{16}".match(/[[:blank:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode punctuation characters with [[:blank:]]" do
+ "\u{3F}".match(/[[:blank:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode letter characters with [[:blank:]]" do
+ "à".match(/[[:blank:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode digits with [[:blank:]]" do
+ "\u{0660}".match(/[[:blank:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode marks with [[:blank:]]" do
+ "\u{36F}".match(/[[:blank:]]/).should be_nil
+ end
+
+ it "doesn't Unicode letter characters with [[:cntrl:]]" do
+ "à".match(/[[:cntrl:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode digits with [[:cntrl:]]" do
+ "\u{0660}".match(/[[:cntrl:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode marks with [[:cntrl:]]" do
+ "\u{36F}".match(/[[:cntrl:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode punctuation characters with [[:cntrl:]]" do
+ "\u{3F}".match(/[[:cntrl:]]/).should be_nil
+ end
+
+ it "matches Unicode control characters with [[:cntrl:]]" do
+ "\u{16}".match(/[[:cntrl:]]/).to_a.should == ["\u{16}"]
+ end
+
+ it "doesn't match Unicode format characters with [[:cntrl:]]" do
+ "\u{2060}".match(/[[:cntrl:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode private-use characters with [[:cntrl:]]" do
+ "\u{E001}".match(/[[:cntrl:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode letter characters with [[:digit:]]" do
+ "à".match(/[[:digit:]]/).should be_nil
+ end
+
+ it "matches Unicode digits with [[:digit:]]" do
+ "\u{0660}".match(/[[:digit:]]/).to_a.should == ["\u{0660}"]
+ "\u{FF12}".match(/[[:digit:]]/).to_a.should == ["\u{FF12}"]
+ end
+
+ it "doesn't match Unicode marks with [[:digit:]]" do
+ "\u{36F}".match(/[[:digit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode punctuation characters with [[:digit:]]" do
+ "\u{3F}".match(/[[:digit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode control characters with [[:digit:]]" do
+ "\u{16}".match(/[[:digit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode format characters with [[:digit:]]" do
+ "\u{2060}".match(/[[:digit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode private-use characters with [[:digit:]]" do
+ "\u{E001}".match(/[[:digit:]]/).should be_nil
+ end
+
+ it "matches Unicode letter characters with [[:graph:]]" do
+ "à".match(/[[:graph:]]/).to_a.should == ["à"]
+ end
+
+ it "matches Unicode digits with [[:graph:]]" do
+ "\u{0660}".match(/[[:graph:]]/).to_a.should == ["\u{0660}"]
+ "\u{FF12}".match(/[[:graph:]]/).to_a.should == ["\u{FF12}"]
+ end
+
+ it "matches Unicode marks with [[:graph:]]" do
+ "\u{36F}".match(/[[:graph:]]/).to_a.should ==["\u{36F}"]
+ end
+
+ it "matches Unicode punctuation characters with [[:graph:]]" do
+ "\u{3F}".match(/[[:graph:]]/).to_a.should == ["\u{3F}"]
+ end
+
+ it "doesn't match Unicode control characters with [[:graph:]]" do
+ "\u{16}".match(/[[:graph:]]/).should be_nil
+ end
+
+ it "match Unicode format characters with [[:graph:]]" do
+ "\u{2060}".match(/[[:graph:]]/).to_a.should == ["\u2060"]
+ end
+
+ it "match Unicode private-use characters with [[:graph:]]" do
+ "\u{E001}".match(/[[:graph:]]/).to_a.should == ["\u{E001}"]
+ end
+
+ it "matches Unicode lowercase letter characters with [[:lower:]]" do
+ "\u{FF41}".match(/[[:lower:]]/).to_a.should == ["\u{FF41}"]
+ "\u{1D484}".match(/[[:lower:]]/).to_a.should == ["\u{1D484}"]
+ "\u{E8}".match(/[[:lower:]]/).to_a.should == ["\u{E8}"]
+ end
+
+ it "doesn't match Unicode uppercase letter characters with [[:lower:]]" do
+ "\u{100}".match(/[[:lower:]]/).should be_nil
+ "\u{130}".match(/[[:lower:]]/).should be_nil
+ "\u{405}".match(/[[:lower:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode title-case characters with [[:lower:]]" do
+ "\u{1F88}".match(/[[:lower:]]/).should be_nil
+ "\u{1FAD}".match(/[[:lower:]]/).should be_nil
+ "\u{01C5}".match(/[[:lower:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode digits with [[:lower:]]" do
+ "\u{0660}".match(/[[:lower:]]/).should be_nil
+ "\u{FF12}".match(/[[:lower:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode marks with [[:lower:]]" do
+ "\u{36F}".match(/[[:lower:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode punctuation characters with [[:lower:]]" do
+ "\u{3F}".match(/[[:lower:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode control characters with [[:lower:]]" do
+ "\u{16}".match(/[[:lower:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode format characters with [[:lower:]]" do
+ "\u{2060}".match(/[[:lower:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode private-use characters with [[:lower:]]" do
+ "\u{E001}".match(/[[:lower:]]/).should be_nil
+ end
+
+ it "matches Unicode lowercase letter characters with [[:print:]]" do
+ "\u{FF41}".match(/[[:print:]]/).to_a.should == ["\u{FF41}"]
+ "\u{1D484}".match(/[[:print:]]/).to_a.should == ["\u{1D484}"]
+ "\u{E8}".match(/[[:print:]]/).to_a.should == ["\u{E8}"]
+ end
+
+ it "matches Unicode uppercase letter characters with [[:print:]]" do
+ "\u{100}".match(/[[:print:]]/).to_a.should == ["\u{100}"]
+ "\u{130}".match(/[[:print:]]/).to_a.should == ["\u{130}"]
+ "\u{405}".match(/[[:print:]]/).to_a.should == ["\u{405}"]
+ end
+
+ it "matches Unicode title-case characters with [[:print:]]" do
+ "\u{1F88}".match(/[[:print:]]/).to_a.should == ["\u{1F88}"]
+ "\u{1FAD}".match(/[[:print:]]/).to_a.should == ["\u{1FAD}"]
+ "\u{01C5}".match(/[[:print:]]/).to_a.should == ["\u{01C5}"]
+ end
+
+ it "matches Unicode digits with [[:print:]]" do
+ "\u{0660}".match(/[[:print:]]/).to_a.should == ["\u{0660}"]
+ "\u{FF12}".match(/[[:print:]]/).to_a.should == ["\u{FF12}"]
+ end
+
+ it "matches Unicode marks with [[:print:]]" do
+ "\u{36F}".match(/[[:print:]]/).to_a.should == ["\u{36F}"]
+ end
+
+ it "matches Unicode punctuation characters with [[:print:]]" do
+ "\u{3F}".match(/[[:print:]]/).to_a.should == ["\u{3F}"]
+ end
+
+ it "doesn't match Unicode control characters with [[:print:]]" do
+ "\u{16}".match(/[[:print:]]/).should be_nil
+ end
+
+ it "match Unicode format characters with [[:print:]]" do
+ "\u{2060}".match(/[[:print:]]/).to_a.should == ["\u{2060}"]
+ end
+
+ it "match Unicode private-use characters with [[:print:]]" do
+ "\u{E001}".match(/[[:print:]]/).to_a.should == ["\u{E001}"]
+ end
+
+
+ it "doesn't match Unicode lowercase letter characters with [[:punct:]]" do
+ "\u{FF41}".match(/[[:punct:]]/).should be_nil
+ "\u{1D484}".match(/[[:punct:]]/).should be_nil
+ "\u{E8}".match(/[[:punct:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode uppercase letter characters with [[:punct:]]" do
+ "\u{100}".match(/[[:punct:]]/).should be_nil
+ "\u{130}".match(/[[:punct:]]/).should be_nil
+ "\u{405}".match(/[[:punct:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode title-case characters with [[:punct:]]" do
+ "\u{1F88}".match(/[[:punct:]]/).should be_nil
+ "\u{1FAD}".match(/[[:punct:]]/).should be_nil
+ "\u{01C5}".match(/[[:punct:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode digits with [[:punct:]]" do
+ "\u{0660}".match(/[[:punct:]]/).should be_nil
+ "\u{FF12}".match(/[[:punct:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode marks with [[:punct:]]" do
+ "\u{36F}".match(/[[:punct:]]/).should be_nil
+ end
+
+ it "matches Unicode Pc characters with [[:punct:]]" do
+ "\u{203F}".match(/[[:punct:]]/).to_a.should == ["\u{203F}"]
+ end
+
+ it "matches Unicode Pd characters with [[:punct:]]" do
+ "\u{2E17}".match(/[[:punct:]]/).to_a.should == ["\u{2E17}"]
+ end
+
+ it "matches Unicode Ps characters with [[:punct:]]" do
+ "\u{0F3A}".match(/[[:punct:]]/).to_a.should == ["\u{0F3A}"]
+ end
+
+ it "matches Unicode Pe characters with [[:punct:]]" do
+ "\u{2046}".match(/[[:punct:]]/).to_a.should == ["\u{2046}"]
+ end
+
+ it "matches Unicode Pi characters with [[:punct:]]" do
+ "\u{00AB}".match(/[[:punct:]]/).to_a.should == ["\u{00AB}"]
+ end
+
+ it "matches Unicode Pf characters with [[:punct:]]" do
+ "\u{201D}".match(/[[:punct:]]/).to_a.should == ["\u{201D}"]
+ "\u{00BB}".match(/[[:punct:]]/).to_a.should == ["\u{00BB}"]
+ end
+
+ it "matches Unicode Po characters with [[:punct:]]" do
+ "\u{00BF}".match(/[[:punct:]]/).to_a.should == ["\u{00BF}"]
+ end
+
+ it "doesn't match Unicode format characters with [[:punct:]]" do
+ "\u{2060}".match(/[[:punct:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode private-use characters with [[:punct:]]" do
+ "\u{E001}".match(/[[:punct:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode lowercase letter characters with [[:space:]]" do
+ "\u{FF41}".match(/[[:space:]]/).should be_nil
+ "\u{1D484}".match(/[[:space:]]/).should be_nil
+ "\u{E8}".match(/[[:space:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode uppercase letter characters with [[:space:]]" do
+ "\u{100}".match(/[[:space:]]/).should be_nil
+ "\u{130}".match(/[[:space:]]/).should be_nil
+ "\u{405}".match(/[[:space:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode title-case characters with [[:space:]]" do
+ "\u{1F88}".match(/[[:space:]]/).should be_nil
+ "\u{1FAD}".match(/[[:space:]]/).should be_nil
+ "\u{01C5}".match(/[[:space:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode digits with [[:space:]]" do
+ "\u{0660}".match(/[[:space:]]/).should be_nil
+ "\u{FF12}".match(/[[:space:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode marks with [[:space:]]" do
+ "\u{36F}".match(/[[:space:]]/).should be_nil
+ end
+
+ it "matches Unicode Zs characters with [[:space:]]" do
+ "\u{205F}".match(/[[:space:]]/).to_a.should == ["\u{205F}"]
+ end
+
+ it "matches Unicode Zl characters with [[:space:]]" do
+ "\u{2028}".match(/[[:space:]]/).to_a.should == ["\u{2028}"]
+ end
+
+ it "matches Unicode Zp characters with [[:space:]]" do
+ "\u{2029}".match(/[[:space:]]/).to_a.should == ["\u{2029}"]
+ end
+
+ it "doesn't match Unicode format characters with [[:space:]]" do
+ "\u{2060}".match(/[[:space:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode private-use characters with [[:space:]]" do
+ "\u{E001}".match(/[[:space:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode lowercase characters with [[:upper:]]" do
+ "\u{FF41}".match(/[[:upper:]]/).should be_nil
+ "\u{1D484}".match(/[[:upper:]]/).should be_nil
+ "\u{E8}".match(/[[:upper:]]/).should be_nil
+ end
+
+ it "matches Unicode uppercase characters with [[:upper:]]" do
+ "\u{100}".match(/[[:upper:]]/).to_a.should == ["\u{100}"]
+ "\u{130}".match(/[[:upper:]]/).to_a.should == ["\u{130}"]
+ "\u{405}".match(/[[:upper:]]/).to_a.should == ["\u{405}"]
+ end
+
+ it "doesn't match Unicode title-case characters with [[:upper:]]" do
+ "\u{1F88}".match(/[[:upper:]]/).should be_nil
+ "\u{1FAD}".match(/[[:upper:]]/).should be_nil
+ "\u{01C5}".match(/[[:upper:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode digits with [[:upper:]]" do
+ "\u{0660}".match(/[[:upper:]]/).should be_nil
+ "\u{FF12}".match(/[[:upper:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode marks with [[:upper:]]" do
+ "\u{36F}".match(/[[:upper:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode punctuation characters with [[:upper:]]" do
+ "\u{3F}".match(/[[:upper:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode control characters with [[:upper:]]" do
+ "\u{16}".match(/[[:upper:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode format characters with [[:upper:]]" do
+ "\u{2060}".match(/[[:upper:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode private-use characters with [[:upper:]]" do
+ "\u{E001}".match(/[[:upper:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode letter characters [^a-fA-F] with [[:xdigit:]]" do
+ "à".match(/[[:xdigit:]]/).should be_nil
+ "g".match(/[[:xdigit:]]/).should be_nil
+ "X".match(/[[:xdigit:]]/).should be_nil
+ end
+
+ it "matches Unicode letter characters [a-fA-F] with [[:xdigit:]]" do
+ "a".match(/[[:xdigit:]]/).to_a.should == ["a"]
+ "F".match(/[[:xdigit:]]/).to_a.should == ["F"]
+ end
+
+ it "doesn't match Unicode digits [^0-9] with [[:xdigit:]]" do
+ "\u{0660}".match(/[[:xdigit:]]/).should be_nil
+ "\u{FF12}".match(/[[:xdigit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode marks with [[:xdigit:]]" do
+ "\u{36F}".match(/[[:xdigit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode punctuation characters with [[:xdigit:]]" do
+ "\u{3F}".match(/[[:xdigit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode control characters with [[:xdigit:]]" do
+ "\u{16}".match(/[[:xdigit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode format characters with [[:xdigit:]]" do
+ "\u{2060}".match(/[[:xdigit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode private-use characters with [[:xdigit:]]" do
+ "\u{E001}".match(/[[:xdigit:]]/).should be_nil
+ end
+
+ it "matches Unicode lowercase characters with [[:word:]]" do
+ "\u{FF41}".match(/[[:word:]]/).to_a.should == ["\u{FF41}"]
+ "\u{1D484}".match(/[[:word:]]/).to_a.should == ["\u{1D484}"]
+ "\u{E8}".match(/[[:word:]]/).to_a.should == ["\u{E8}"]
+ end
+
+ it "matches Unicode uppercase characters with [[:word:]]" do
+ "\u{100}".match(/[[:word:]]/).to_a.should == ["\u{100}"]
+ "\u{130}".match(/[[:word:]]/).to_a.should == ["\u{130}"]
+ "\u{405}".match(/[[:word:]]/).to_a.should == ["\u{405}"]
+ end
+
+ it "matches Unicode title-case characters with [[:word:]]" do
+ "\u{1F88}".match(/[[:word:]]/).to_a.should == ["\u{1F88}"]
+ "\u{1FAD}".match(/[[:word:]]/).to_a.should == ["\u{1FAD}"]
+ "\u{01C5}".match(/[[:word:]]/).to_a.should == ["\u{01C5}"]
+ end
+
+ it "matches Unicode decimal digits with [[:word:]]" do
+ "\u{FF10}".match(/[[:word:]]/).to_a.should == ["\u{FF10}"]
+ "\u{096C}".match(/[[:word:]]/).to_a.should == ["\u{096C}"]
+ end
+
+ it "matches Unicode marks with [[:word:]]" do
+ "\u{36F}".match(/[[:word:]]/).to_a.should == ["\u{36F}"]
+ end
+
+ it "match Unicode Nl characters with [[:word:]]" 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
+ it "doesn't match Unicode punctuation characters with [[:word:]]" do
+ "\u{3F}".match(/[[:word:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode control characters with [[:word:]]" do
+ "\u{16}".match(/[[:word:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode format characters with [[:word:]]" do
+ "\u{2060}".match(/[[:word:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode private-use characters with [[:word:]]" do
+ "\u{E001}".match(/[[:word:]]/).should be_nil
+ end
+
+ it "matches unicode named character properties" do
+ "a1".match(/\p{Alpha}/).to_a.should == ["a"]
+ end
+
+ it "matches unicode abbreviated character properties" do
+ "a1".match(/\p{L}/).to_a.should == ["a"]
+ end
+
+ it "matches unicode script properties" do
+ "a\u06E9b".match(/\p{Arabic}/).to_a.should == ["\u06E9"]
+ end
+
+ it "matches unicode Han properties" do
+ "松本行弘 Ruby".match(/\p{Han}+/u).to_a.should == ["松本行弘"]
+ end
+
+ it "matches unicode Hiragana properties" do
+ "Ruby(ルビー)、まつもとゆきひろ".match(/\p{Hiragana}+/u).to_a.should == ["まつもとゆきひろ"]
+ end
+
+ it "matches unicode Katakana properties" do
+ "Ruby(ルビー)、まつもとゆきひろ".match(/\p{Katakana}+/u).to_a.should == ["ルビ"]
+ end
+
+ it "matches unicode Hangul properties" do
+ "루비(Ruby)".match(/\p{Hangul}+/u).to_a.should == ["루비"]
+ end
+
+ 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 == ["🤘🏽"]
+
+ # 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 == ["👩👩👧👦"]
+
+ # 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
new file mode 100644
index 0000000000..ceb9cf823a
--- /dev/null
+++ b/spec/ruby/language/regexp/encoding_spec.rb
@@ -0,0 +1,152 @@
+# 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".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".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".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
+ /./e.encoding.should == Encoding::EUC_JP
+ end
+
+ it 'preserves EUC-JP as /e encoding through interpolation' do
+ /#{/./}/e.encoding.should == Encoding::EUC_JP
+ end
+
+ it "supports /n (No encoding)" do
+ /./n.match("\303\251").to_a.should == ["\303"]
+ end
+
+ it "supports /n (No encoding) with interpolation" do
+ /#{/./}/n.match("\303\251").to_a.should == ["\303"]
+ end
+
+ it "supports /n (No encoding) with interpolation /o" 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 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 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".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".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".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
+ /./s.encoding.should == Encoding::Windows_31J
+ end
+
+ it 'preserves Windows-31J as /s encoding through interpolation' do
+ /#{/./}/s.encoding.should == Encoding::Windows_31J
+ end
+
+ it "supports /u (UTF8 encoding)" do
+ /./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".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".dup.force_encoding('utf-8')).to_a.should == ["\u{e9}"]
+ end
+
+ it 'uses UTF-8 as /u encoding' do
+ /./u.encoding.should == Encoding::UTF_8
+ end
+
+ it 'preserves UTF-8 as /u encoding through interpolation' do
+ /#{/./}/u.encoding.should == Encoding::UTF_8
+ end
+
+ 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
new file mode 100644
index 0000000000..541998b937
--- /dev/null
+++ b/spec/ruby/language/regexp/escapes_spec.rb
@@ -0,0 +1,169 @@
+# 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 "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
+ /\r/.match("\r").to_a.should == ["\r"] # return
+ /\f/.match("\f").to_a.should == ["\f"] # form feed
+ /\a/.match("\a").to_a.should == ["\a"] # bell
+ /\e/.match("\e").to_a.should == ["\e"] # escape
+
+ # \nnn octal char (encoded byte value)
+ end
+
+ it "supports quoting meta-characters via escape sequence" do
+ # parenthesis, etc
+ /\(/.match("(").to_a.should == ["("]
+ /\)/.match(")").to_a.should == [")"]
+ /\[/.match("[").to_a.should == ["["]
+ /\]/.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
+ /\?/.match("?").to_a.should == ["?"]
+ /\./.match(".").to_a.should == ["."]
+ /\*/.match("*").to_a.should == ["*"]
+ /\+/.match("+").to_a.should == ["+"]
+ # line anchors
+ /\^/.match("^").to_a.should == ["^"]
+ /\$/.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 "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
+ -> { eval('/\xG/') }.should raise_error(SyntaxError)
+
+ # \x{7HHHHHHH} wide hexadecimal char (character code point value)
+ end
+
+ 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"]
+ /\c(\cH\ch/.match("\b\b\b").to_a.should == ["\b\b\b"]
+ /\c)\cI\ci/.match("\t\t\t").to_a.should == ["\t\t\t"]
+ /\c*\cJ\cj/.match("\n\n\n").to_a.should == ["\n\n\n"]
+ /\c+\cK\ck/.match("\v\v\v").to_a.should == ["\v\v\v"]
+ /\c,\cL\cl/.match("\f\f\f").to_a.should == ["\f\f\f"]
+ /\c-\cM\cm/.match("\r\r\r").to_a.should == ["\r\r\r"]
+
+ /\cJ/.match("\r").should be_nil
+
+ # Parsing precedence
+ /\cJ+/.match("\n\n").to_a.should == ["\n\n"] # Quantifiers apply to entire escape sequence
+ /\\cJ/.match("\\cJ").to_a.should == ["\\cJ"]
+ -> { eval('/[abc\x]/') }.should raise_error(SyntaxError) # \x is treated as a escape sequence even inside a character class
+ # Syntax error
+ -> { 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
new file mode 100644
index 0000000000..313858f714
--- /dev/null
+++ b/spec/ruby/language/regexp/grouping_spec.rb
@@ -0,0 +1,63 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexps with grouping" do
+ it "support ()" do
+ /(a)/.match("a").to_a.should == ["a", "a"]
+ end
+
+ it "allows groups to be nested" do
+ md = /(hay(st)a)ck/.match('haystack')
+ md.to_a.should == ['haystack','haysta', 'st']
+ end
+
+ it "raises a SyntaxError when parentheses aren't balanced" do
+ -> { eval "/(hay(st)ack/" }.should raise_error(SyntaxError)
+ end
+
+ it "supports (?: ) (non-capturing group)" do
+ /(?:foo)(bar)/.match("foobar").to_a.should == ["foobar", "bar"]
+ # 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
new file mode 100644
index 0000000000..6951fd38ca
--- /dev/null
+++ b/spec/ruby/language/regexp/interpolation_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexps with interpolation" do
+
+ it "allows interpolation of strings" do
+ str = "foo|bar"
+ /#{str}/.should == /foo|bar/
+ end
+
+ it "allows interpolation of literal regexps" do
+ re = /foo|bar/
+ /#{re}/.should == /(?-mix:foo|bar)/
+ end
+
+ it "allows interpolation of any object that responds to to_s" do
+ o = Object.new
+ def o.to_s
+ "object_with_to_s"
+ end
+ /#{o}/.should == /object_with_to_s/
+ end
+
+ it "allows interpolation which mixes modifiers" do
+ re = /foo/i
+ /#{re} bar/m.should == /(?i-mx:foo) bar/m
+ end
+
+ it "allows interpolation to interact with other Regexp constructs" do
+ str = "foo)|(bar"
+ /(#{str})/.should == /(foo)|(bar)/
+
+ str = "a"
+ /[#{str}-z]/.should == /[a-z]/
+ end
+
+ it "gives precedence to escape sequences over substitution" do
+ str = "J"
+ /\c#{str}/.to_s.should include('{str}')
+ end
+
+ it "throws RegexpError for malformed interpolation" do
+ s = ""
+ -> { /(#{s}/ }.should raise_error(RegexpError)
+ s = "("
+ -> { /#{s}/ }.should raise_error(RegexpError)
+ end
+
+ it "allows interpolation in extended mode" do
+ var = "#comment\n foo #comment\n | bar"
+ (/#{var}/x =~ "foo").should == (/foo|bar/ =~ "foo")
+ end
+
+ it "allows escape sequences in interpolated regexps" do
+ escape_seq = %r{"\x80"}n
+ %r{#{escape_seq}}n.should == /(?-mix:"\x80")/n
+ end
+end
diff --git a/spec/ruby/language/regexp/modifiers_spec.rb b/spec/ruby/language/regexp/modifiers_spec.rb
new file mode 100644
index 0000000000..2f5522bc8a
--- /dev/null
+++ b/spec/ruby/language/regexp/modifiers_spec.rb
@@ -0,0 +1,115 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexps with modifiers" do
+ it "supports /i (case-insensitive)" do
+ /foo/i.match("FOO").to_a.should == ["FOO"]
+ end
+
+ it "supports /m (multiline)" do
+ /foo.bar/m.match("foo\nbar").to_a.should == ["foo\nbar"]
+ /foo.bar/.match("foo\nbar").should be_nil
+ end
+
+ it "supports /x (extended syntax)" do
+ /\d +/x.match("abc123").to_a.should == ["123"] # Quantifiers can be separated from the expression they apply to
+ end
+
+ it "supports /o (once)" do
+ 2.times do |i|
+ /#{i}/o.should == /0/
+ end
+ end
+
+ it "invokes substitutions for /o only once" do
+ ScratchPad.record []
+ o = Object.new
+ def o.to_s
+ ScratchPad << :to_s
+ "class_with_to_s"
+ end
+ eval "2.times { /#{o}/o }"
+ ScratchPad.recorded.should == [:to_s]
+ end
+
+ it "supports modifier combinations" do
+ /foo/imox.match("foo").to_a.should == ["foo"]
+ /foo/imoximox.match("foo").to_a.should == ["foo"]
+
+ -> { eval('/foo/a') }.should raise_error(SyntaxError)
+ 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
+ /(?i)foo/.match("FOO").to_a.should == ["FOO"]
+ /foo(?i)/.match("FOO").should be_nil
+ # Interaction with /i
+ /(?-i)foo/i.match("FOO").should be_nil
+ /foo(?-i)/i.match("FOO").to_a.should == ["FOO"]
+ # Multiple uses
+ /foo (?i)bar (?-i)baz/.match("foo BAR baz").to_a.should == ["foo BAR baz"]
+ /foo (?i)bar (?-i)baz/.match("foo BAR BAZ").should be_nil
+
+ /(?m)./.match("\n").to_a.should == ["\n"]
+ /.(?m)/.match("\n").should be_nil
+ # Interaction with /m
+ /(?-m)./m.match("\n").should be_nil
+ /.(?-m)/m.match("\n").to_a.should == ["\n"]
+ # Multiple uses
+ /. (?m). (?-m)./.match(". \n .").to_a.should == [". \n ."]
+ /. (?m). (?-m)./.match(". \n \n").should be_nil
+
+ /(?x) foo /.match("foo").to_a.should == ["foo"]
+ / foo (?x)/.match("foo").should be_nil
+ # Interaction with /x
+ /(?-x) foo /x.match("foo").should be_nil
+ / foo (?-x)/x.match("foo").to_a.should == ["foo"]
+ # Multiple uses
+ /( foo )(?x)( bar )(?-x)( baz )/.match(" foo bar baz ").to_a.should == [" foo bar baz ", " foo ", "bar", " baz "]
+ /( foo )(?x)( bar )(?-x)( baz )/.match(" foo barbaz").should be_nil
+
+ # Parsing
+ /(?i-i)foo/.match("FOO").should be_nil
+ /(?ii)foo/.match("FOO").to_a.should == ["FOO"]
+ /(?-)foo/.match("foo").to_a.should == ["foo"]
+ -> { eval('/(?o)/') }.should raise_error(SyntaxError)
+ end
+
+ it "supports (?imx-imx:expr) (scoped inline modifiers)" do
+ /foo (?i:bar) baz/.match("foo BAR baz").to_a.should == ["foo BAR baz"]
+ /foo (?i:bar) baz/.match("foo BAR BAZ").should be_nil
+ /foo (?-i:bar) baz/i.match("foo BAR BAZ").should be_nil
+
+ /. (?m:.) ./.match(". \n .").to_a.should == [". \n ."]
+ /. (?m:.) ./.match(". \n \n").should be_nil
+ /. (?-m:.) ./m.match("\n \n \n").should be_nil
+
+ /( foo )(?x: bar )( baz )/.match(" foo bar baz ").to_a.should == [" foo bar baz ", " foo ", " baz "]
+ /( foo )(?x: bar )( baz )/.match(" foo barbaz").should be_nil
+ /( foo )(?-x: bar )( baz )/x.match("foo bar baz").to_a.should == ["foo bar baz", "foo", "baz"]
+
+ # Parsing
+ /(?i-i:foo)/.match("FOO").should be_nil
+ /(?ii:foo)/.match("FOO").to_a.should == ["FOO"]
+ /(?-:)foo/.match("foo").to_a.should == ["foo"]
+ -> { eval('/(?o:)/') }.should raise_error(SyntaxError)
+ end
+
+ it "supports . with /m" do
+ # Basic matching
+ /./m.match("\n").to_a.should == ["\n"]
+ end
+
+ 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"]
+ eval('/(?a)\w+/').match("a\u3042").to_a.should == ["a"]
+ eval('/(?d)\w+/').match("a\u3042").to_a.should == ["a"]
+ eval('/(?u)\w+/').match("a\u3042").to_a.should == ["a\u3042"]
+ end
+end
diff --git a/spec/ruby/language/regexp/repetition_spec.rb b/spec/ruby/language/regexp/repetition_spec.rb
new file mode 100644
index 0000000000..d76619688f
--- /dev/null
+++ b/spec/ruby/language/regexp/repetition_spec.rb
@@ -0,0 +1,138 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexps with repetition" do
+ it "supports * (0 or more of previous subexpression)" do
+ /a*/.match("aaa").to_a.should == ["aaa"]
+ /a*/.match("bbb").to_a.should == [""]
+ /<.*>/.match("<a>foo</a>").to_a.should == ["<a>foo</a>"] # it is greedy
+ end
+
+ it "supports *? (0 or more of previous subexpression - lazy)" do
+ /a*?/.match("aaa").to_a.should == [""]
+ /<.*?>/.match("<a>foo</a>").to_a.should == ["<a>"]
+ end
+
+ it "supports + (1 or more of previous subexpression)" do
+ /a+/.match("aaa").to_a.should == ["aaa"]
+ /a+/.match("bbb").should be_nil
+ /<.+>/.match("<a>foo</a>").to_a.should == ["<a>foo</a>"] # it is greedy
+ end
+
+ it "supports +? (0 or more of previous subexpression - lazy)" do
+ /a+?/.match("aaa").to_a.should == ["a"]
+ /<.+?>/.match("<a>foo</a>").to_a.should == ["<a>"]
+ end
+
+ it "supports {m,n} (m to n of previous subexpression)" do
+ /a{2,4}/.match("aaaaaa").to_a.should == ["aaaa"]
+ /<.{1,}>/.match("<a>foo</a>").to_a.should == ["<a>foo</a>"] # it is greedy
+ end
+
+ it "supports {m,n}? (m to n of previous subexpression) - lazy)" do
+ /<.{1,}?>/.match("<a>foo</a>").to_a.should == ["<a>"]
+ /.([0-9]){3,5}?foo/.match("9876543210foo").to_a.should == ["543210foo", "0"]
+ end
+
+ it "does not treat {m,n}+ as possessive" do
+ -> {
+ @regexp = eval "/foo(A{0,1}+)Abar/"
+ }.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
new file mode 100644
index 0000000000..ce344b5b05
--- /dev/null
+++ b/spec/ruby/language/regexp_spec.rb
@@ -0,0 +1,167 @@
+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
+ -> {
+ eval <<-EOR
+ $_ = nil
+ (true if /foo/).should_not == true
+
+ $_ = "foo"
+ (true if /foo/).should == true
+ EOR
+ }.should complain(/regex literal in condition/)
+ end
+
+ it "yields a Regexp" 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|
+ rs << /foo/
+ end
+ rs[0].should equal(rs[1])
+ end
+
+ it "throws SyntaxError for malformed literals" do
+ -> { eval('/(/') }.should raise_error(SyntaxError)
+ end
+
+ #############################################################################
+ # %r
+ #############################################################################
+
+ it "supports paired delimiters with %r" do
+ LanguageSpecs.paired_delimiters.each do |p0, p1|
+ eval("%r#{p0} foo #{p1}").should == / foo /
+ end
+ end
+
+ it "supports grouping constructs that are also paired delimiters" do
+ LanguageSpecs.paired_delimiters.each do |p0, p1|
+ eval("%r#{p0} () [c]{1} #{p1}").should == / () [c]{1} /
+ end
+ end
+
+ it "allows second part of paired delimiters to be used as non-paired delimiters" do
+ LanguageSpecs.paired_delimiters.each do |p0, p1|
+ eval("%r#{p1} foo #{p1}").should == / foo /
+ end
+ end
+
+ it "disallows first part of paired delimiters to be used as non-paired delimiters" do
+ LanguageSpecs.paired_delimiters.each do |p0, p1|
+ -> { eval("%r#{p0} foo #{p0}") }.should raise_error(SyntaxError)
+ end
+ end
+
+ it "supports non-paired delimiters with %r" do
+ LanguageSpecs.non_paired_delimiters.each do |c|
+ eval("%r#{c} foo #{c}").should == / foo /
+ end
+ end
+
+ it "disallows alphabets as non-paired delimiter with %r" do
+ -> { eval('%ra foo a') }.should raise_error(SyntaxError)
+ end
+
+ it "disallows spaces after %r and delimiter" do
+ -> { eval('%r !foo!') }.should raise_error(SyntaxError)
+ end
+
+ it "allows unescaped / to be used with %r" do
+ %r[/].to_s.should == /\//.to_s
+ end
+
+
+ #############################################################################
+ # Specs for the matching semantics
+ #############################################################################
+
+ it "supports . (any character except line terminator)" do
+ # Basic matching
+ /./.match("foo").to_a.should == ["f"]
+ # Basic non-matching
+ /./.match("").should be_nil
+ /./.match("\n").should be_nil
+ /./.match("\0").to_a.should == ["\0"]
+ end
+
+ it "supports | (alternations)" do
+ /a|b/.match("a").to_a.should == ["a"]
+ end
+
+ it "supports (?> ) (embedded subexpression)" do
+ /(?>foo)(?>bar)/.match("foobar").to_a.should == ["foobar"]
+ /(?>foo*)obar/.match("foooooooobar").should be_nil # it is possessive
+ end
+
+ it "supports (?# )" do
+ /foo(?#comment)bar/.match("foobar").to_a.should == ["foobar"]
+ /foo(?#)bar/.match("foobar").to_a.should == ["foobar"]
+ end
+
+ it "supports (?<= ) (positive lookbehind)" 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
+
+ it "supports \\g (named backreference)" do
+ /(?<foo>foo.)bar\g<foo>/.match("foo1barfoo2").to_a.should == ["foo1barfoo2", "foo2"]
+ end
+
+ it "supports character class composition" do
+ /[a-z&&[^a-c]]+/.match("abcdef").to_a.should == ["def"]
+ /[a-z&&[^d-i&&[^d-f]]]+/.match("abcdefghi").to_a.should == ["abcdef"]
+ end
+
+ it "supports possessive quantifiers" do
+ /fooA++bar/.match("fooAAAbar").to_a.should == ["fooAAAbar"]
+
+ /fooA++Abar/.match("fooAAAbar").should be_nil
+ /fooA?+Abar/.match("fooAAAbar").should be_nil
+ /fooA*+Abar/.match("fooAAAbar").should be_nil
+ end
+
+ it "supports conditional regular expressions with positional capture groups" do
+ pattern = /\A(foo)?(?(1)(T)|(F))\z/
+
+ pattern.should =~ 'fooT'
+ pattern.should =~ 'F'
+ pattern.should_not =~ 'fooF'
+ pattern.should_not =~ 'T'
+ end
+
+ it "supports conditional regular expressions with named capture groups" do
+ pattern = /\A(?<word>foo)?(?(<word>)(T)|(F))\z/
+
+ pattern.should =~ 'fooT'
+ pattern.should =~ 'F'
+ 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
new file mode 100644
index 0000000000..6be3bfd023
--- /dev/null
+++ b/spec/ruby/language/rescue_spec.rb
@@ -0,0 +1,616 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/rescue'
+
+class SpecificExampleException < StandardError
+end
+class OtherCustomException < StandardError
+end
+class ArbitraryException < StandardError
+end
+
+exception_list = [SpecificExampleException, ArbitraryException]
+
+describe "The rescue keyword" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "can be used to handle a specific exception" do
+ begin
+ raise SpecificExampleException, "Raising this to be handled below"
+ rescue SpecificExampleException
+ :caught
+ end.should == :caught
+ end
+
+ 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
+
+ it "returns value from `rescue` if an exception was raised" do
+ begin
+ raise
+ rescue
+ :caught
+ end.should == :caught
+ end
+
+ it "returns value from `else` section if no exceptions were raised" do
+ result = begin
+ :begin
+ rescue
+ :rescue
+ else
+ :else
+ ensure
+ :ensure
+ end
+
+ result.should == :else
+ end
+
+ it "can rescue multiple raised exceptions with a single rescue block" do
+ [->{raise ArbitraryException}, ->{raise SpecificExampleException}].map do |block|
+ begin
+ block.call
+ rescue SpecificExampleException, ArbitraryException
+ :caught
+ end
+ end.should == [:caught, :caught]
+ end
+
+ it "can rescue a splatted list of exceptions" do
+ caught_it = false
+ begin
+ raise SpecificExampleException, "not important"
+ rescue *exception_list
+ caught_it = true
+ end
+ caught_it.should be_true
+ caught = []
+ [->{raise ArbitraryException}, ->{raise SpecificExampleException}].each do |block|
+ begin
+ block.call
+ rescue *exception_list
+ caught << $!
+ end
+ end
+ caught.size.should == 2
+ exception_list.each do |exception_class|
+ caught.map{|e| e.class}.should include(exception_class)
+ end
+ end
+
+ it "converts the splatted list of exceptions using #to_a" do
+ exceptions = mock("to_a")
+ exceptions.should_receive(:to_a).and_return(exception_list)
+ caught_it = false
+ begin
+ raise SpecificExampleException, "not important"
+ rescue *exceptions
+ caught_it = true
+ end
+ caught_it.should be_true
+ end
+
+ it "can combine a splatted list of exceptions with a literal list of exceptions" do
+ caught_it = false
+ begin
+ raise SpecificExampleException, "not important"
+ rescue ArbitraryException, *exception_list
+ caught_it = true
+ end
+ caught_it.should be_true
+ caught = []
+ [->{raise ArbitraryException}, ->{raise SpecificExampleException}].each do |block|
+ begin
+ block.call
+ rescue ArbitraryException, *exception_list
+ caught << $!
+ end
+ end
+ caught.size.should == 2
+ exception_list.each do |exception_class|
+ caught.map{|e| e.class}.should include(exception_class)
+ end
+ end
+
+ it "will only rescue the specified exceptions when doing a splat rescue" do
+ -> do
+ begin
+ raise OtherCustomException, "not rescued!"
+ rescue *exception_list
+ end
+ end.should raise_error(OtherCustomException)
+ end
+
+ it "can rescue different types of exceptions in different ways" do
+ begin
+ raise Exception
+ rescue RuntimeError
+ rescue StandardError
+ rescue Exception
+ ScratchPad << :exception
+ end
+
+ ScratchPad.recorded.should == [:exception]
+ end
+
+ it "rescues exception within the first suitable section in order of declaration" do
+ begin
+ raise StandardError
+ rescue RuntimeError
+ ScratchPad << :runtime_error
+ rescue StandardError
+ ScratchPad << :standard_error
+ rescue Exception
+ ScratchPad << :exception
+ end
+
+ 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
+ rescue
+ ScratchPad << :does_not_run
+ else
+ ScratchPad << :two
+ :val
+ end
+ result.should == :val
+ ScratchPad.recorded.should == [:one, :two]
+ end
+
+ it "will execute an else block with ensure only if no exceptions were raised" do
+ result = begin
+ ScratchPad << :one
+ rescue
+ ScratchPad << :does_not_run
+ else
+ ScratchPad << :two
+ :val
+ ensure
+ ScratchPad << :ensure
+ :ensure_val
+ end
+ result.should == :val
+ ScratchPad.recorded.should == [:one, :two, :ensure]
+ end
+
+ it "will execute an else block only if no exceptions were raised in a method" do
+ result = RescueSpecs.begin_else(false)
+ result.should == :val
+ ScratchPad.recorded.should == [:one, :else_ran]
+ end
+
+ it "will execute an else block with ensure only if no exceptions were raised in a method" do
+ result = RescueSpecs.begin_else_ensure(false)
+ result.should == :val
+ ScratchPad.recorded.should == [:one, :else_ran, :ensure_ran]
+ end
+
+ it "will execute an else block but use the outer scope return value in a method" do
+ result = RescueSpecs.begin_else_return(false)
+ result.should == :return_val
+ ScratchPad.recorded.should == [:one, :else_ran, :outside_begin]
+ end
+
+ it "will execute an else block with ensure but use the outer scope return value in a method" do
+ result = RescueSpecs.begin_else_return_ensure(false)
+ result.should == :return_val
+ ScratchPad.recorded.should == [:one, :else_ran, :ensure_ran, :outside_begin]
+ end
+
+ it "raises SyntaxError when else is used without rescue and ensure" do
+ -> {
+ eval <<-ruby
+ begin
+ ScratchPad << :begin
+ else
+ ScratchPad << :else
+ end
+ ruby
+ }.should raise_error(SyntaxError, /else without rescue is useless/)
+ end
+
+ it "will not execute an else block if an exception was raised" do
+ result = begin
+ ScratchPad << :one
+ raise "an error occurred"
+ rescue
+ ScratchPad << :two
+ :val
+ else
+ ScratchPad << :does_not_run
+ end
+ result.should == :val
+ ScratchPad.recorded.should == [:one, :two]
+ end
+
+ it "will not execute an else block with ensure if an exception was raised" do
+ result = begin
+ ScratchPad << :one
+ raise "an error occurred"
+ rescue
+ ScratchPad << :two
+ :val
+ else
+ ScratchPad << :does_not_run
+ ensure
+ ScratchPad << :ensure
+ :ensure_val
+ end
+ result.should == :val
+ ScratchPad.recorded.should == [:one, :two, :ensure]
+ end
+
+ it "will not execute an else block if an exception was raised in a method" do
+ result = RescueSpecs.begin_else(true)
+ result.should == :rescue_val
+ ScratchPad.recorded.should == [:one, :rescue_ran]
+ end
+
+ it "will not execute an else block with ensure if an exception was raised in a method" do
+ result = RescueSpecs.begin_else_ensure(true)
+ result.should == :rescue_val
+ ScratchPad.recorded.should == [:one, :rescue_ran, :ensure_ran]
+ end
+
+ it "will not execute an else block but use the outer scope return value in a method" do
+ result = RescueSpecs.begin_else_return(true)
+ result.should == :return_val
+ ScratchPad.recorded.should == [:one, :rescue_ran, :outside_begin]
+ end
+
+ it "will not execute an else block with ensure but use the outer scope return value in a method" do
+ result = RescueSpecs.begin_else_return_ensure(true)
+ result.should == :return_val
+ ScratchPad.recorded.should == [:one, :rescue_ran, :ensure_ran, :outside_begin]
+ end
+
+ it "will not rescue errors raised in an else block in the rescue block above it" do
+ -> do
+ begin
+ ScratchPad << :one
+ rescue Exception
+ ScratchPad << :does_not_run
+ else
+ ScratchPad << :two
+ raise SpecificExampleException, "an error from else"
+ end
+ end.should raise_error(SpecificExampleException)
+ ScratchPad.recorded.should == [:one, :two]
+ end
+
+ it "parses 'a += b rescue c' as 'a += (b rescue c)'" do
+ a = 'a'
+ c = 'c'
+ a += b rescue c
+ a.should == 'ac'
+ end
+
+ context "without rescue expression" do
+ it "will rescue only StandardError and its subclasses" do
+ begin
+ raise StandardError
+ rescue
+ ScratchPad << :caught
+ end
+
+ ScratchPad.recorded.should == [:caught]
+ end
+
+ it "will not rescue exceptions except StandardError" do
+ [ Exception.new, NoMemoryError.new, ScriptError.new, SecurityError.new,
+ SignalException.new('INT'), SystemExit.new, SystemStackError.new
+ ].each do |exception|
+ -> {
+ begin
+ raise exception
+ rescue
+ ScratchPad << :caught
+ end
+ }.should raise_error(exception.class)
+ end
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ it "uses === to compare against rescued classes" do
+ rescuer = Class.new
+
+ def rescuer.===(exception)
+ true
+ end
+
+ begin
+ raise Exception
+ rescue rescuer
+ rescued = :success
+ rescue Exception
+ rescued = :failure
+ end
+
+ rescued.should == :success
+ end
+
+ it "only accepts Module or Class in rescue clauses" do
+ rescuer = 42
+ -> {
+ begin
+ raise "error"
+ rescue rescuer
+ end
+ }.should raise_error(TypeError) { |e|
+ e.message.should =~ /class or module required for rescue clause/
+ }
+ end
+
+ it "only accepts Module or Class in splatted rescue clauses" do
+ rescuer = [42]
+ -> {
+ begin
+ raise "error"
+ rescue *rescuer
+ end
+ }.should raise_error(TypeError) { |e|
+ e.message.should =~ /class or module required for rescue clause/
+ }
+ end
+
+ it "evaluates rescue expressions only when needed" do
+ begin
+ 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
+ begin
+ raise "raise"
+ rescue *(RuntimeError) => e
+ :expected
+ end.should == :expected
+ end
+
+ it "allows rescue in class" do
+ eval <<-ruby
+ class RescueInClassExample
+ raise SpecificExampleException
+ rescue SpecificExampleException
+ ScratchPad << :caught
+ end
+ ruby
+
+ ScratchPad.recorded.should == [:caught]
+ end
+
+ it "does not allow rescue in {} block" do
+ -> {
+ eval <<-ruby
+ lambda {
+ raise SpecificExampleException
+ rescue SpecificExampleException
+ :caught
+ }
+ ruby
+ }.should raise_error(SyntaxError)
+ end
+
+ 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
+
+ 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
+
+ ruby_version_is "3.4" do
+ it "does not introduce extra backtrace entries" do
+ def foo
+ begin
+ raise "oops"
+ rescue
+ return caller(0, 2)
+ end
+ end
+ line = __LINE__
+ foo[0].should =~ /#{__FILE__}:#{line-3}:in 'foo'/
+ foo[1].should =~ /#{__FILE__}:#{line+2}:in 'block/
+ end
+ end
+
+ describe "inline form" do
+ it "can be inlined" do
+ a = 1/0 rescue 1
+ a.should == 1
+ end
+
+ it "doesn't except rescue expression" do
+ -> {
+ eval <<-ruby
+ a = 1 rescue RuntimeError 2
+ ruby
+ }.should raise_error(SyntaxError)
+ end
+
+ it "rescues only StandardError and its subclasses" do
+ a = raise(StandardError) rescue 1
+ a.should == 1
+
+ -> {
+ 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
new file mode 100644
index 0000000000..669d5f0ff5
--- /dev/null
+++ b/spec/ruby/language/retry_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../spec_helper'
+
+describe "The retry statement" do
+ it "re-executes the closest block" do
+ retry_first = true
+ retry_second = true
+ results = []
+ begin
+ results << 1
+ raise
+ rescue
+ results << 2
+ if retry_first
+ results << 3
+ retry_first = false
+ retry
+ end
+ begin
+ results << 4
+ raise
+ rescue
+ results << 5
+ if retry_second
+ results << 6
+ retry_second = false
+ retry
+ end
+ end
+ end
+
+ results.should == [1, 2, 3, 1, 2, 4, 5, 6, 4, 5]
+ end
+
+ 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
+
+describe "The retry keyword inside a begin block's rescue block" do
+ it "causes the begin block to be executed again" do
+ counter = 0
+
+ begin
+ counter += 1
+ raise "An exception"
+ rescue
+ retry unless counter == 7
+ end
+
+ counter.should == 7
+ end
+end
diff --git a/spec/ruby/language/return_spec.rb b/spec/ruby/language/return_spec.rb
new file mode 100644
index 0000000000..a62ed1242d
--- /dev/null
+++ b/spec/ruby/language/return_spec.rb
@@ -0,0 +1,490 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/return'
+
+describe "The return keyword" do
+ it "returns any object directly" do
+ def r; return 1; end
+ r().should == 1
+ end
+
+ it "returns an single element array directly" do
+ def r; return [1]; end
+ r().should == [1]
+ end
+
+ it "returns an multi element array directly" do
+ def r; return [1,2]; end
+ r().should == [1,2]
+ end
+
+ it "returns nil by default" do
+ def r; return; end
+ r().should be_nil
+ end
+
+ describe "in a Thread" do
+ it "raises a LocalJumpError if used to exit a thread" do
+ t = Thread.new {
+ begin
+ return
+ rescue LocalJumpError => e
+ e
+ end
+ }
+ t.value.should be_an_instance_of(LocalJumpError)
+ end
+ end
+
+ describe "when passed a splat" do
+ it "returns [] when the ary is empty" do
+ def r; ary = []; return *ary; end
+ r.should == []
+ end
+
+ it "returns the array when the array is size of 1" do
+ def r; ary = [1]; return *ary; end
+ r.should == [1]
+ end
+
+ it "returns the whole array when size is greater than 1" do
+ def r; ary = [1,2]; return *ary; end
+ r.should == [1,2]
+
+ def r; ary = [1,2,3]; return *ary; end
+ r.should == [1,2,3]
+ end
+
+ it "returns an array when used as a splat" do
+ def r; value = 1; return *value; end
+ r.should == [1]
+ end
+
+ it "calls 'to_a' on the splatted value first" do
+ def r
+ obj = Object.new
+ def obj.to_a
+ [1,2]
+ end
+
+ return *obj
+ end
+
+ r().should == [1,2]
+ end
+ end
+
+ describe "within a begin" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "executes ensure before returning" do
+ def f()
+ begin
+ ScratchPad << :begin
+ return :begin
+ ScratchPad << :after_begin
+ ensure
+ ScratchPad << :ensure
+ end
+ ScratchPad << :function
+ end
+ f().should == :begin
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "returns last value returned in ensure" do
+ def f()
+ begin
+ ScratchPad << :begin
+ return :begin
+ ScratchPad << :after_begin
+ ensure
+ ScratchPad << :ensure
+ return :ensure
+ ScratchPad << :after_ensure
+ end
+ ScratchPad << :function
+ end
+ f().should == :ensure
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "executes nested ensures before returning" do
+ def f()
+ begin
+ begin
+ ScratchPad << :inner_begin
+ return :inner_begin
+ ScratchPad << :after_inner_begin
+ ensure
+ ScratchPad << :inner_ensure
+ end
+ ScratchPad << :outer_begin
+ return :outer_begin
+ ScratchPad << :after_outer_begin
+ ensure
+ ScratchPad << :outer_ensure
+ end
+ ScratchPad << :function
+ end
+ f().should == :inner_begin
+ ScratchPad.recorded.should == [:inner_begin, :inner_ensure, :outer_ensure]
+ end
+
+ it "returns last value returned in nested ensures" do
+ def f()
+ begin
+ begin
+ ScratchPad << :inner_begin
+ return :inner_begin
+ ScratchPad << :after_inner_begin
+ ensure
+ ScratchPad << :inner_ensure
+ return :inner_ensure
+ ScratchPad << :after_inner_ensure
+ end
+ ScratchPad << :outer_begin
+ return :outer_begin
+ ScratchPad << :after_outer_begin
+ ensure
+ ScratchPad << :outer_ensure
+ return :outer_ensure
+ ScratchPad << :after_outer_ensure
+ end
+ ScratchPad << :function
+ end
+ f().should == :outer_ensure
+ ScratchPad.recorded.should == [:inner_begin, :inner_ensure, :outer_ensure]
+ end
+
+ it "executes the ensure clause when begin/ensure are inside a lambda" do
+ -> do
+ begin
+ return
+ ensure
+ ScratchPad.recorded << :ensure
+ end
+ end.call
+ ScratchPad.recorded.should == [:ensure]
+ end
+ end
+
+ describe "within a block" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "causes lambda to return nil if invoked without any arguments" do
+ -> { return; 456 }.call.should be_nil
+ end
+
+ it "causes lambda to return nil if invoked with an empty expression" do
+ -> { return (); 456 }.call.should be_nil
+ end
+
+ it "causes lambda to return the value passed to return" do
+ -> { return 123; 456 }.call.should == 123
+ end
+
+ it "causes the method that lexically encloses the block to return" do
+ ReturnSpecs::Blocks.new.enclosing_method.should == :return_value
+ ScratchPad.recorded.should == :before_return
+ end
+
+ it "returns from the lexically enclosing method even in case of chained calls" do
+ ReturnSpecs::NestedCalls.new.enclosing_method.should == :return_value
+ ScratchPad.recorded.should == :before_return
+ end
+
+ it "returns from the lexically enclosing method even in case of chained calls(in yield)" do
+ ReturnSpecs::NestedBlocks.new.enclosing_method.should == :return_value
+ ScratchPad.recorded.should == :before_return
+ end
+
+ it "causes the method to return even when the immediate parent has already returned" do
+ ReturnSpecs::SavedInnerBlock.new.start.should == :return_value
+ ScratchPad.recorded.should == :before_return
+ end
+
+ # jruby/jruby#3143
+ describe "downstream from a lambda" do
+ it "returns to its own return-capturing lexical enclosure" do
+ def a
+ ->{ yield }.call
+ return 2
+ end
+ def b
+ a { return 1 }
+ end
+
+ b.should == 1
+ end
+ end
+
+ end
+
+ describe "within two blocks" do
+ it "causes the method that lexically encloses the block to return" do
+ def f
+ 1.times { 1.times {return true}; false}; false
+ end
+ f.should be_true
+ end
+ end
+
+ describe "within define_method" do
+ it "goes through the method via a closure" do
+ ReturnSpecs::ThroughDefineMethod.new.outer.should == :good
+ end
+
+ it "stops at the method when the return is used directly" do
+ ReturnSpecs::DefineMethod.new.outer.should == :good
+ end
+ end
+
+ describe "invoked with a method call without parentheses with a block" do
+ it "returns the value returned from the method call" do
+ ReturnSpecs::MethodWithBlock.new.method1.should == 5
+ ReturnSpecs::MethodWithBlock.new.method2.should == [0, 1, 2]
+ end
+ end
+
+ describe "at top level" do
+ before :each do
+ @filename = tmp("top_return.rb")
+ ScratchPad.record []
+ 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
+
+ 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
+
+ ScratchPad << "after if"
+ END_OF_CODE
+
+ load @filename
+ ScratchPad.recorded.should == ["before if"]
+ end
+ 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 while"
+ END_OF_CODE
+
+ load @filename
+ ScratchPad.recorded.should == ["before while"]
+ end
+ 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 begin"
+ END_OF_CODE
+
+ 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
+
+ ScratchPad << "after begin"
+ END_OF_CODE
+
+ 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
+
+ ScratchPad << "after begin"
+ END_OF_CODE
+
+ load @filename
+ ScratchPad.recorded.should == ["before begin"]
+ end
+
+ it "fires ensure block before returning" do
+ ruby_exe(<<-END_OF_CODE).should == "within ensure\n"
+ begin
+ return
+ ensure
+ puts "within ensure"
+ end
+
+ puts "after begin"
+ END_OF_CODE
+ 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
+
+ ScratchPad << "after begin"
+ END_OF_CODE
+
+ load @filename
+ ScratchPad.recorded.should == ["before begin", "within ensure"]
+ end
+
+ 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
+ end
+
+ 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
+
+ load @filename
+ ScratchPad.recorded.should == ["before call"]
+ end
+ end
+
+ describe "within a class" do
+ it "raises a SyntaxError" do
+ File.write(@filename, <<-END_OF_CODE)
+ class ReturnSpecs::A
+ ScratchPad << "before return"
+ return
+
+ ScratchPad << "after return"
+ end
+ END_OF_CODE
+
+ -> { load @filename }.should raise_error(SyntaxError)
+ end
+ end
+
+ 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"
+ 1.times { return }
+ ScratchPad << "after return"
+ end
+ END_OF_CODE
+
+ -> { load @filename }.should raise_error(LocalJumpError)
+ end
+ end
+
+ describe "within BEGIN" do
+ it "is allowed" do
+ File.write(@filename, <<-END_OF_CODE)
+ BEGIN {
+ ScratchPad << "before call"
+ return
+ ScratchPad << "after call"
+ }
+ END_OF_CODE
+
+ load @filename
+ ScratchPad.recorded.should == ["before call"]
+ end
+ end
+
+ 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
+
+ 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
+end
diff --git a/spec/ruby/language/safe_navigator_spec.rb b/spec/ruby/language/safe_navigator_spec.rb
new file mode 100644
index 0000000000..b1e28c3963
--- /dev/null
+++ b/spec/ruby/language/safe_navigator_spec.rb
@@ -0,0 +1,147 @@
+require_relative '../spec_helper'
+
+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
+
+ it "can be chained" do
+ 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
+
+ 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 truthy" do
+ it "calls the method" do
+ 1&.to_s.should == "1"
+
+ -> { 1&.unknown }.should raise_error(NoMethodError)
+ end
+ end
+
+ 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
+
+ it "allows assignment methods" do
+ klass = Class.new do
+ attr_reader :foo
+ def foo=(val)
+ @foo = val
+ 42
+ end
+ end
+ obj = klass.new
+
+ (obj&.foo = 3).should == 3
+ obj.foo.should == 3
+
+ obj = nil
+ (obj&.foo = 3).should == nil
+ end
+
+ it "allows assignment operators" do
+ klass = Class.new do
+ attr_reader :m
+
+ def initialize
+ @m = 0
+ end
+
+ def m=(v)
+ @m = v
+ 42
+ end
+ end
+
+ obj = klass.new
+
+ obj&.m += 3
+ obj.m.should == 3
+
+ obj = nil
+ (obj&.m += 3).should == nil
+ end
+
+ it "allows ||= operator" do
+ klass = Class.new do
+ attr_reader :m
+
+ def initialize
+ @m = false
+ end
+
+ def m=(v)
+ @m = v
+ 42
+ end
+ end
+
+ obj = klass.new
+
+ (obj&.m ||= true).should == true
+ obj.m.should == true
+
+ obj = nil
+ (obj&.m ||= true).should == nil
+ obj.should == nil
+ end
+
+ it "allows &&= operator" do
+ klass = Class.new do
+ attr_accessor :m
+
+ def initialize
+ @m = true
+ end
+ end
+
+ obj = klass.new
+
+ (obj&.m &&= false).should == false
+ obj.m.should == false
+
+ obj = nil
+ (obj&.m &&= false).should == nil
+ obj.should == nil
+ end
+
+ it "does not call the operator method lazily with an assignment operator" do
+ 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
new file mode 100644
index 0000000000..5d6340ffc5
--- /dev/null
+++ b/spec/ruby/language/send_spec.rb
@@ -0,0 +1,570 @@
+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 - Mandatory Args
+# O - Optional Arg
+# R - Rest Arg
+# Q - Post Mandatory Args
+
+specs = LangSendSpecs
+
+describe "Invoking a method" do
+ describe "with zero arguments" do
+ it "requires no arguments passed" do
+ specs.fooM0.should == 100
+ end
+
+ it "raises ArgumentError if the method has a positive arity" do
+ -> {
+ specs.fooM1
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "with only mandatory arguments" do
+ it "requires exactly the same number of passed values" do
+ specs.fooM1(1).should == [1]
+ specs.fooM2(1,2).should == [1,2]
+ specs.fooM3(1,2,3).should == [1,2,3]
+ specs.fooM4(1,2,3,4).should == [1,2,3,4]
+ specs.fooM5(1,2,3,4,5).should == [1,2,3,4,5]
+ end
+
+ it "raises ArgumentError if the methods arity doesn't match" do
+ -> {
+ specs.fooM1(1,2)
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "with optional arguments" do
+ it "uses the optional argument if none is passed" do
+ specs.fooM0O1.should == [1]
+ end
+
+ it "uses the passed argument if available" do
+ specs.fooM0O1(2).should == [2]
+ end
+
+ it "raises ArgumentError if extra arguments are passed" do
+ -> {
+ specs.fooM0O1(2,3)
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "with mandatory and optional arguments" do
+ it "uses the passed values in left to right order" do
+ specs.fooM1O1(2).should == [2,1]
+ end
+
+ it "raises an ArgumentError if there are no values for the mandatory args" do
+ -> {
+ specs.fooM1O1
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if too many values are passed" do
+ -> {
+ specs.fooM1O1(1,2,3)
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "with a rest argument" do
+ it "is an empty array if there are no additional arguments" do
+ specs.fooM0R().should == []
+ specs.fooM1R(1).should == [1, []]
+ end
+
+ it "gathers unused arguments" do
+ specs.fooM0R(1).should == [1]
+ specs.fooM1R(1,2).should == [1, [2]]
+ end
+ end
+
+ it "with a block makes it available to yield" do
+ specs.oneb(10) { 200 }.should == [10,200]
+ end
+
+ it "with a block converts the block to a Proc" do
+ prc = specs.makeproc { "hello" }
+ prc.should be_kind_of(Proc)
+ prc.call.should == "hello"
+ end
+
+ it "with an object as a block uses 'to_proc' for coercion" do
+ o = LangSendSpecs::ToProc.new(:from_to_proc)
+
+ specs.makeproc(&o).call.should == :from_to_proc
+
+ specs.yield_now(&o).should == :from_to_proc
+ end
+
+ ruby_version_is "4.0" do
+ it "raises TypeError if 'to_proc' doesn't return a Proc" do
+ o = LangSendSpecs::RawToProc.new(42)
+
+ -> {
+ specs.makeproc(&o)
+ }.should raise_error(TypeError, "can't convert LangSendSpecs::RawToProc to Proc (LangSendSpecs::RawToProc#to_proc gives Integer)")
+ end
+
+ it "raises TypeError if block object isn't a Proc and doesn't respond to `to_proc`" do
+ o = Object.new
+
+ -> {
+ specs.makeproc(&o)
+ }.should raise_error(TypeError, "no implicit conversion of Object into Proc")
+ end
+ end
+
+ it "raises a SyntaxError with both a literal block and an object as block" do
+ -> {
+ eval "specs.oneb(10, &l){ 42 }"
+ }.should raise_error(SyntaxError)
+ end
+
+ it "with same names as existing variables is ok" do
+ foobar = 100
+
+ def foobar; 200; end
+
+ foobar.should == 100
+ foobar().should == 200
+ end
+
+ it "with splat operator makes the object the direct arguments" do
+ a = [1,2,3]
+ specs.fooM3(*a).should == [1,2,3]
+ end
+
+ it "without parentheses works" do
+ (specs.fooM3 1,2,3).should == [1,2,3]
+ end
+
+ it "with a space separating method name and parenthesis treats expression in parenthesis as first argument" do
+ specs.weird_parens().should == "55"
+ end
+
+ describe "allows []=" do
+ before :each do
+ @obj = LangSendSpecs::AttrSet.new
+ end
+
+ it "with *args in the [] expanded to individual arguments" do
+ ary = [2,3]
+ (@obj[1, *ary] = 4).should == 4
+ @obj.result.should == [1,2,3,4]
+ end
+
+ it "with multiple *args" do
+ ary = [2,3]
+ post = [4,5]
+ (@obj[1, *ary] = *post).should == [4,5]
+ @obj.result.should == [1,2,3,[4,5]]
+ end
+
+ it "with multiple *args and does not unwrap the last splat" do
+ ary = [2,3]
+ post = [4]
+ (@obj[1, *ary] = *post).should == [4]
+ @obj.result.should == [1,2,3,[4]]
+ end
+
+ it "with a *args and multiple rhs args" do
+ ary = [2,3]
+ (@obj[1, *ary] = 4, 5).should == [4,5]
+ @obj.result.should == [1,2,3,[4,5]]
+ end
+ end
+
+ it "passes literal hashes without curly braces as the last parameter" do
+ specs.fooM3('abc', 456, 'rbx' => 'cool',
+ 'specs' => 'fail sometimes', 'oh' => 'weh').should == \
+ ['abc', 456, {'rbx' => 'cool', 'specs' => 'fail sometimes', 'oh' => 'weh'}]
+ end
+
+ it "passes a literal hash without curly braces or parens" do
+ (specs.fooM3 'abc', 456, 'rbx' => 'cool',
+ 'specs' => 'fail sometimes', 'oh' => 'weh').should == \
+ ['abc', 456, { 'rbx' => 'cool', 'specs' => 'fail sometimes', 'oh' => 'weh'}]
+ end
+
+ it "allows to literal hashes without curly braces as the only parameter" do
+ specs.fooM1(rbx: :cool, specs: :fail_sometimes).should ==
+ [{ rbx: :cool, specs: :fail_sometimes }]
+
+ (specs.fooM1 rbx: :cool, specs: :fail_sometimes).should ==
+ [{ rbx: :cool, specs: :fail_sometimes }]
+ end
+
+ describe "when the method is not available" do
+ it "invokes method_missing if it is defined" do
+ o = LangSendSpecs::MethodMissing.new
+ o.not_there(1,2)
+ o.message.should == :not_there
+ o.args.should == [1,2]
+ end
+
+ it "raises NameError if invoked as a vcall" do
+ -> { 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
+ -> { 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
+
+end
+
+describe "Invoking a public setter method" do
+ it 'returns the set value' do
+ klass = Class.new do
+ def foobar=(*)
+ 1
+ end
+ end
+
+ (klass.new.foobar = 'bar').should == 'bar'
+ (klass.new.foobar = 'bar', 'baz').should == ["bar", "baz"]
+ end
+end
+
+describe "Invoking []= methods" do
+ it 'returns the set value' do
+ klass = Class.new do
+ def []=(*)
+ 1
+ end
+ end
+
+ (klass.new[33] = 'bar').should == 'bar'
+ (klass.new[33] = 'bar', 'baz').should == ['bar', 'baz']
+ (klass.new[33, 34] = 'bar', 'baz').should == ['bar', 'baz']
+ end
+end
+
+describe "Invoking a private setter method" do
+ describe "permits self as a receiver" do
+ it "for normal assignment" do
+ receiver = LangSendSpecs::PrivateSetter.new
+ receiver.call_self_foo_equals(42)
+ receiver.foo.should == 42
+ end
+
+ it "for multiple assignment" do
+ receiver = LangSendSpecs::PrivateSetter.new
+ receiver.call_self_foo_equals_masgn(42)
+ receiver.foo.should == 42
+ end
+ end
+end
+
+describe "Invoking a private getter method" do
+ it "permits self as a receiver" do
+ receiver = LangSendSpecs::PrivateGetter.new
+ receiver.call_self_foo_or_equals(6)
+ receiver.call_self_foo.should == 6
+ end
+end
+
+describe "Invoking a method" do
+ describe "with required args after the rest arguments" do
+ it "binds the required arguments first" do
+ specs.fooM0RQ1(1).should == [[], 1]
+ specs.fooM0RQ1(1,2).should == [[1], 2]
+ specs.fooM0RQ1(1,2,3).should == [[1,2], 3]
+
+ specs.fooM1RQ1(1,2).should == [1, [], 2]
+ specs.fooM1RQ1(1,2,3).should == [1, [2], 3]
+ specs.fooM1RQ1(1,2,3,4).should == [1, [2, 3], 4]
+
+ specs.fooM1O1RQ1(1,2).should == [1, 9, [], 2]
+ specs.fooM1O1RQ1(1,2,3).should == [1, 2, [], 3]
+ specs.fooM1O1RQ1(1,2,3,4).should == [1, 2, [3], 4]
+
+ specs.fooM1O1RQ2(1,2,3).should == [1, 9, [], 2, 3]
+ specs.fooM1O1RQ2(1,2,3,4).should == [1, 2, [], 3, 4]
+ specs.fooM1O1RQ2(1,2,3,4,5).should == [1, 2, [3], 4, 5]
+ end
+ end
+
+ describe "with mandatory arguments after optional arguments" do
+ it "binds the required arguments first" do
+ specs.fooO1Q1(0,1).should == [0,1]
+ specs.fooO1Q1(2).should == [1,2]
+
+ specs.fooM1O1Q1(2,3,4).should == [2,3,4]
+ specs.fooM1O1Q1(1,3).should == [1,2,3]
+
+ specs.fooM2O1Q1(1,2,4).should == [1,2,3,4]
+
+ specs.fooM2O2Q1(1,2,3,4,5).should == [1,2,3,4,5]
+ specs.fooM2O2Q1(1,2,3,5).should == [1,2,3,4,5]
+ specs.fooM2O2Q1(1,2,5).should == [1,2,3,4,5]
+
+ specs.fooO4Q1(1,2,3,4,5).should == [1,2,3,4,5]
+ specs.fooO4Q1(1,2,3,5).should == [1,2,3,4,5]
+ specs.fooO4Q1(1,2,5).should == [1,2,3,4,5]
+ specs.fooO4Q1(1,5).should == [1,2,3,4,5]
+ specs.fooO4Q1(5).should == [1,2,3,4,5]
+
+ specs.fooO4Q2(1,2,3,4,5,6).should == [1,2,3,4,5,6]
+ specs.fooO4Q2(1,2,3,5,6).should == [1,2,3,4,5,6]
+ specs.fooO4Q2(1,2,5,6).should == [1,2,3,4,5,6]
+ specs.fooO4Q2(1,5,6).should == [1,2,3,4,5,6]
+ specs.fooO4Q2(5,6).should == [1,2,3,4,5,6]
+ end
+ end
+
+ it "with .() invokes #call" do
+ q = proc { |z| z }
+ q.(1).should == 1
+
+ obj = mock("paren call")
+ obj.should_receive(:call).and_return(:called)
+ obj.().should == :called
+ end
+
+ it "allows a vestigial trailing ',' in the arguments" do
+ specs.fooM1(1,).should == [1]
+ end
+
+ it "with splat operator attempts to coerce it to an Array if the object respond_to?(:to_a)" do
+ ary = [2,3,4]
+ obj = mock("to_a")
+ obj.should_receive(:to_a).and_return(ary).twice
+ specs.fooM0R(*obj).should == ary
+ specs.fooM1R(1,*obj).should == [1, ary]
+ end
+
+ it "with splat operator * and non-Array value uses value unchanged if it does not respond_to?(:to_ary)" do
+ obj = Object.new
+ obj.should_not respond_to(:to_a)
+
+ specs.fooM0R(*obj).should == [obj]
+ specs.fooM1R(1,*obj).should == [1, [obj]]
+ end
+
+ it "accepts additional arguments after splat expansion" do
+ a = [1,2]
+ specs.fooM4(*a,3,4).should == [1,2,3,4]
+ specs.fooM4(0,*a,3).should == [0,1,2,3]
+ end
+
+ it "does not expand final array arguments after a splat expansion" do
+ a = [1, 2]
+ specs.fooM3(*a, [3, 4]).should == [1, 2, [3, 4]]
+ end
+
+ it "accepts final explicit literal Hash arguments after the splat" do
+ a = [1, 2]
+ specs.fooM0RQ1(*a, { a: 1 }).should == [[1, 2], { a: 1 }]
+ end
+
+ it "accepts final implicit literal Hash arguments after the splat" do
+ a = [1, 2]
+ specs.fooM0RQ1(*a, a: 1).should == [[1, 2], { a: 1 }]
+ end
+
+ it "accepts final Hash arguments after the splat" do
+ a = [1, 2]
+ b = { a: 1 }
+ specs.fooM0RQ1(*a, b).should == [[1, 2], { a: 1 }]
+ end
+
+ it "accepts mandatory and explicit literal Hash arguments after the splat" do
+ a = [1, 2]
+ specs.fooM0RQ2(*a, 3, { a: 1 }).should == [[1, 2], 3, { a: 1 }]
+ end
+
+ it "accepts mandatory and implicit literal Hash arguments after the splat" do
+ a = [1, 2]
+ specs.fooM0RQ2(*a, 3, a: 1).should == [[1, 2], 3, { a: 1 }]
+ end
+
+ it "accepts mandatory and Hash arguments after the splat" do
+ a = [1, 2]
+ b = { a: 1 }
+ specs.fooM0RQ2(*a, 3, b).should == [[1, 2], 3, { a: 1 }]
+ end
+
+ it "converts a final splatted explicit Hash to an Array" do
+ a = [1, 2]
+ specs.fooR(*a, 3, *{ a: 1 }).should == [1, 2, 3, [:a, 1]]
+ end
+
+ it "calls #to_a to convert a final splatted Hash object to an Array" do
+ a = [1, 2]
+ b = { a: 1 }
+ b.should_receive(:to_a).and_return([:a, 1])
+
+ specs.fooR(*a, 3, *b).should == [1, 2, 3, :a, 1]
+ end
+
+ it "accepts multiple splat expansions in the same argument list" do
+ a = [1,2,3]
+ b = 7
+ c = mock("pseudo-array")
+ c.should_receive(:to_a).and_return([0,0])
+
+ d = [4,5]
+ specs.rest_len(*a,*d,6,*b).should == 7
+ specs.rest_len(*a,*a,*a).should == 9
+ 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
+
+ it "expands an array to arguments grouped in parentheses and ignores any rest arguments in the array" do
+ specs.destructure2([40,2,84]).should == 42
+ end
+
+ it "expands an array to arguments grouped in parentheses and sets not specified arguments to nil" do
+ specs.destructure2b([42]).should == [42, nil]
+ end
+
+ it "expands an array to arguments grouped in parentheses which in turn takes rest arguments" do
+ specs.destructure4r([1, 2, 3]).should == [1, 2, [], 3, nil]
+ specs.destructure4r([1, 2, 3, 4]).should == [1, 2, [], 3, 4]
+ specs.destructure4r([1, 2, 3, 4, 5]).should == [1, 2, [3], 4, 5]
+ end
+
+ it "with optional argument(s), expands an array to arguments grouped in parentheses" do
+ specs.destructure4o(1, [2, 3]).should == [1, 1, nil, [2, 3]]
+ specs.destructure4o(1, [], 2).should == [1, nil, nil, 2]
+ specs.destructure4os(1, [2, 3]).should == [1, 2, [3]]
+ specs.destructure5o(1, [2, 3]).should == [1, 2, 1, nil, [2, 3]]
+ specs.destructure7o(1, [2, 3]).should == [1, 2, 1, nil, 2, 3]
+ specs.destructure7b(1, [2, 3]) do |(a,*b,c)|
+ [a, c]
+ end.should == [1, 3]
+ end
+
+ describe "new-style hash arguments" do
+ describe "as the only parameter" do
+ it "passes without curly braces" do
+ specs.fooM1(rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
+ [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+
+ it "passes without curly braces or parens" do
+ (specs.fooM1 rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
+ [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+
+ it "handles a hanging comma without curly braces" do
+ specs.fooM1(abc: 123,).should == [{abc: 123}]
+ specs.fooM1(rbx: 'cool', specs: :fail_sometimes, non_sym: 1234,).should ==
+ [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+ end
+
+ describe "as the last parameter" do
+ it "passes without curly braces" do
+ specs.fooM3('abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
+ ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+
+ it "passes without curly braces or parens" do
+ (specs.fooM3 'abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
+ ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+
+ it "handles a hanging comma without curly braces" do
+ specs.fooM3('abc', 123, abc: 123,).should == ['abc', 123, {abc: 123}]
+ specs.fooM3('abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234,).should ==
+ ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+ end
+ end
+
+ describe "mixed new- and old-style hash arguments" do
+ describe "as the only parameter" do
+ it "passes without curly braces" do
+ specs.fooM1(rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
+ [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+
+ it "passes without curly braces or parens" do
+ (specs.fooM1 rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
+ [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+
+ it "handles a hanging comma without curly braces" do
+ specs.fooM1(rbx: 'cool', specs: :fail_sometimes, non_sym: 1234,).should ==
+ [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+ end
+
+ describe "as the last parameter" do
+ it "passes without curly braces" do
+ specs.fooM3('abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
+ ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+
+ it "passes without curly braces or parens" do
+ (specs.fooM3 'abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
+ ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+
+ it "handles a hanging comma without curly braces" do
+ specs.fooM3('abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234,).should ==
+ ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+ end
+ end
+
+end
+
+describe "allows []= with arguments after splat" do
+ before :each do
+ @obj = LangSendSpecs::Attr19Set.new
+ @ary = ["a"]
+ end
+
+ it "with *args in the [] and post args" do
+ @obj[1,*@ary,123] = 2
+ @obj.result.should == [1, "a", 123, 2]
+ end
+end
diff --git a/spec/ruby/language/shared/__FILE__.rb b/spec/ruby/language/shared/__FILE__.rb
new file mode 100644
index 0000000000..3e4f5c958d
--- /dev/null
+++ b/spec/ruby/language/shared/__FILE__.rb
@@ -0,0 +1,23 @@
+describe :language___FILE__, shared: true do
+ before :each do
+ CodeLoadingSpecs.spec_setup
+ @path = File.join(CODE_LOADING_DIR, "file_fixture.rb")
+ end
+
+ after :each do
+ CodeLoadingSpecs.spec_cleanup
+ end
+
+ it "equals the absolute path of a file loaded by an absolute path" do
+ @object.send(@method, @path).should be_true
+ ScratchPad.recorded.should == [@path]
+ end
+
+ it "equals the absolute path of a file loaded by a relative path" do
+ $LOAD_PATH << "."
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, "file_fixture.rb").should be_true
+ end
+ ScratchPad.recorded.should == [@path]
+ end
+end
diff --git a/spec/ruby/language/shared/__LINE__.rb b/spec/ruby/language/shared/__LINE__.rb
new file mode 100644
index 0000000000..076b74b3ba
--- /dev/null
+++ b/spec/ruby/language/shared/__LINE__.rb
@@ -0,0 +1,15 @@
+describe :language___LINE__, shared: true do
+ before :each do
+ CodeLoadingSpecs.spec_setup
+ @path = File.expand_path("line_fixture.rb", CODE_LOADING_DIR)
+ end
+
+ after :each do
+ CodeLoadingSpecs.spec_cleanup
+ end
+
+ it "equals the line number of the text in a loaded file" do
+ @object.send(@method, @path).should be_true
+ ScratchPad.recorded.should == [1, 5]
+ end
+end
diff --git a/spec/ruby/language/singleton_class_spec.rb b/spec/ruby/language/singleton_class_spec.rb
new file mode 100644
index 0000000000..45e1f7f3ad
--- /dev/null
+++ b/spec/ruby/language/singleton_class_spec.rb
@@ -0,0 +1,317 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/class'
+
+describe "A singleton class" do
+ it "is TrueClass for true" do
+ true.singleton_class.should == TrueClass
+ end
+
+ it "is FalseClass for false" do
+ false.singleton_class.should == FalseClass
+ end
+
+ it "is NilClass for nil" do
+ nil.singleton_class.should == NilClass
+ end
+
+ it "raises a TypeError for Integer's" do
+ -> { 1.singleton_class }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError for symbols" do
+ -> { :symbol.singleton_class }.should raise_error(TypeError)
+ end
+
+ it "is a singleton Class instance" do
+ o = mock('x')
+ o.singleton_class.should be_kind_of(Class)
+ o.singleton_class.should_not equal(Object)
+ o.should be_kind_of(o.singleton_class)
+ end
+
+ it "is a Class for classes" do
+ ClassSpecs::A.singleton_class.should be_kind_of(Class)
+ end
+
+ it "inherits from Class for classes" do
+ Class.should be_ancestor_of(Object.singleton_class)
+ end
+
+ it "is a subclass of Class's singleton class" do
+ ec = ClassSpecs::A.singleton_class
+ ec.should be_kind_of(Class.singleton_class)
+ end
+
+ it "is a subclass of the same level of Class's singleton class" do
+ ecec = ClassSpecs::A.singleton_class.singleton_class
+ class_ec = Class.singleton_class
+
+ ecec.should be_kind_of(class_ec.singleton_class)
+ ecec.should be_kind_of(class_ec)
+ end
+
+ it "is a subclass of a superclass's singleton class" do
+ ClassSpecs::K.singleton_class.superclass.should ==
+ ClassSpecs::H.singleton_class
+ end
+
+ it "is a subclass of the same level of superclass's singleton class" do
+ ClassSpecs::K.singleton_class.singleton_class.superclass.should ==
+ ClassSpecs::H.singleton_class.singleton_class
+ end
+
+ it "for BasicObject has Class as it's superclass" do
+ BasicObject.singleton_class.superclass.should == Class
+ end
+
+ it "for BasicObject has the proper level of superclass for Class" do
+ BasicObject.singleton_class.singleton_class.superclass.should ==
+ Class.singleton_class
+ end
+
+ it "has class String as the superclass of a String instance" do
+ "blah".dup.singleton_class.superclass.should == String
+ end
+
+ it "doesn't have singleton class" do
+ -> { bignum_value.singleton_class }.should raise_error(TypeError)
+ end
+end
+
+describe "A constant on a singleton class" do
+ before :each do
+ @object = Object.new
+ class << @object
+ CONST = self
+ end
+ end
+
+ it "can be accessed after the singleton class body is reopened" do
+ class << @object
+ CONST.should == self
+ end
+ end
+
+ it "can be accessed via self::CONST" do
+ class << @object
+ self::CONST.should == self
+ end
+ end
+
+ it "can be accessed via const_get" do
+ class << @object
+ const_get(:CONST).should == self
+ end
+ end
+
+ it "is not defined on the object's class" do
+ @object.class.const_defined?(:CONST).should be_false
+ end
+
+ it "is not defined in the singleton class opener's scope" do
+ class << @object
+ CONST
+ end
+ -> { CONST }.should raise_error(NameError)
+ end
+
+ it "cannot be accessed via object::CONST" do
+ -> do
+ @object::CONST
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a NameError for anonymous_module::CONST" do
+ @object = Class.new
+ class << @object
+ CONST = 100
+ end
+
+ -> do
+ @object::CONST
+ end.should raise_error(NameError)
+ end
+
+ it "appears in the singleton class constant list" do
+ @object.singleton_class.should have_constant(:CONST)
+ end
+
+ it "does not appear in the object's class constant list" do
+ @object.class.should_not have_constant(:CONST)
+ end
+
+ it "is not preserved when the object is duped" do
+ @object = @object.dup
+
+ -> do
+ class << @object; CONST; end
+ end.should raise_error(NameError)
+ end
+
+ it "is preserved when the object is cloned" do
+ @object = @object.clone
+
+ class << @object
+ CONST.should_not be_nil
+ end
+ end
+end
+
+describe "Defining instance methods on a singleton class" do
+ before :each do
+ @k = ClassSpecs::K.new
+ class << @k
+ def singleton_method; 1 end
+ end
+
+ @k_sc = @k.singleton_class
+ end
+
+ it "defines public methods" do
+ @k_sc.should have_public_instance_method(:singleton_method)
+ end
+end
+
+describe "Instance methods of a singleton class" do
+ before :each do
+ k = ClassSpecs::K.new
+ @k_sc = k.singleton_class
+ @a_sc = ClassSpecs::A.new.singleton_class
+ @a_c_sc = ClassSpecs::A.singleton_class
+ end
+
+ it "include ones of the object's class" do
+ @k_sc.should have_instance_method(:example_instance_method)
+ end
+
+ it "does not include class methods of the object's class" do
+ @k_sc.should_not have_instance_method(:example_class_method)
+ end
+
+ it "include instance methods of Object" do
+ @a_sc.should have_instance_method(:example_instance_method_of_object)
+ end
+
+ it "does not include class methods of Object" do
+ @a_sc.should_not have_instance_method(:example_class_method_of_object)
+ end
+
+ describe "for a class" do
+ it "include instance methods of Class" do
+ @a_c_sc.should have_instance_method(:example_instance_method_of_class)
+ end
+
+ it "does not include class methods of Class" do
+ @a_c_sc.should_not have_instance_method(:example_class_method_of_class)
+ end
+
+ it "does not include instance methods of the singleton class of Class" do
+ @a_c_sc.should_not have_instance_method(:example_instance_method_of_singleton_class)
+ end
+
+ it "does not include class methods of the singleton class of Class" do
+ @a_c_sc.should_not have_instance_method(:example_class_method_of_singleton_class)
+ end
+ end
+
+ describe "for a singleton class" do
+ it "includes instance methods of the singleton class of Class" do
+ @a_c_sc.singleton_class.should have_instance_method(:example_instance_method_of_singleton_class)
+ end
+
+ it "does not include class methods of the singleton class of Class" do
+ @a_c_sc.singleton_class.should_not have_instance_method(:example_class_method_of_singleton_class)
+ end
+ end
+end
+
+describe "Class methods of a singleton class" do
+ before :each do
+ k = ClassSpecs::K.new
+ @k_sc = k.singleton_class
+ @a_sc = ClassSpecs::A.new.singleton_class
+ @a_c_sc = ClassSpecs::A.singleton_class
+ end
+
+ it "include ones of the object's class" do
+ @k_sc.should have_method(:example_class_method)
+ end
+
+ it "does not include instance methods of the object's class" do
+ @k_sc.should_not have_method(:example_instance_method)
+ end
+
+ it "include instance methods of Class" do
+ @a_sc.should have_method(:example_instance_method_of_class)
+ end
+
+ it "does not include class methods of Class" do
+ @a_sc.should_not have_method(:example_class_method_of_class)
+ end
+
+ describe "for a class" do
+ it "include instance methods of Class" do
+ @a_c_sc.should have_method(:example_instance_method_of_class)
+ end
+
+ it "include class methods of Class" do
+ @a_c_sc.should have_method(:example_class_method_of_class)
+ end
+
+ it "include instance methods of the singleton class of Class" do
+ @a_c_sc.should have_method(:example_instance_method_of_singleton_class)
+ end
+
+ it "does not include class methods of the singleton class of Class" do
+ @a_c_sc.should_not have_method(:example_class_method_of_singleton_class)
+ end
+ end
+
+ describe "for a singleton class" do
+ it "include instance methods of the singleton class of Class" do
+ @a_c_sc.singleton_class.should have_method(:example_instance_method_of_singleton_class)
+ end
+
+ it "include class methods of the singleton class of Class" do
+ @a_c_sc.singleton_class.should have_method(:example_class_method_of_singleton_class)
+ end
+ end
+end
+
+describe "Instantiating a singleton class" do
+ it "raises a TypeError when new is called" do
+ -> {
+ Object.new.singleton_class.new
+ }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when allocate is called" do
+ -> {
+ 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
new file mode 100644
index 0000000000..f287731bed
--- /dev/null
+++ b/spec/ruby/language/string_spec.rb
@@ -0,0 +1,301 @@
+# encoding: binary
+
+require_relative '../spec_helper'
+require_relative 'fixtures/class_with_class_variable'
+
+# TODO: rewrite these horrid specs. it "are..." seriously?!
+
+describe "Ruby character strings" do
+
+ before :each do
+ @ip = 'xxx' # used for interpolation
+ $ip = 'xxx'
+ end
+
+ it "don't get interpolated when put in single quotes" do
+ '#{@ip}'.should == '#{@ip}'
+ end
+
+ it 'get interpolated with #{} when put in double quotes' do
+ "#{@ip}".should == 'xxx'
+ end
+
+ it "interpolate instance variables just with the # character" do
+ "#@ip".should == 'xxx'
+ end
+
+ it "interpolate global variables just with the # character" 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.'
+ "#@ip(".should == 'xxx('
+ "#@ip=".should == 'xxx='
+ "#@ip?".should == 'xxx?'
+ "#@ip!".should == 'xxx!'
+ "#@ip#@ip".should == 'xxxxxx'
+ end
+
+ 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
+ %(hey #{@ip}).should == "hey xxx"
+ %[hey #{@ip}].should == "hey xxx"
+ %{hey #{@ip}}.should == "hey xxx"
+ %<hey #{@ip}>.should == "hey xxx"
+ %!hey #{@ip}!.should == "hey xxx"
+ %@hey #{@ip}@.should == "hey xxx"
+ %#hey hey#.should == "hey hey"
+ %%hey #{@ip}%.should == "hey xxx"
+ %^hey #{@ip}^.should == "hey xxx"
+ %&hey #{@ip}&.should == "hey xxx"
+ %*hey #{@ip}*.should == "hey xxx"
+ %-hey #{@ip}-.should == "hey xxx"
+ %_hey #{@ip}_.should == "hey xxx"
+ %=hey #{@ip}=.should == "hey xxx"
+ %+hey #{@ip}+.should == "hey xxx"
+ %~hey #{@ip}~.should == "hey xxx"
+ %:hey #{@ip}:.should == "hey xxx"
+ %;hey #{@ip};.should == "hey xxx"
+ %"hey #{@ip}".should == "hey xxx"
+ %|hey #{@ip}|.should == "hey xxx"
+ %?hey #{@ip}?.should == "hey xxx"
+ %/hey #{@ip}/.should == "hey xxx"
+ %,hey #{@ip},.should == "hey xxx"
+ %.hey #{@ip}..should == "hey xxx"
+
+ # surprised? huh
+ %'hey #{@ip}'.should == "hey xxx"
+ %\hey #{@ip}\.should == "hey xxx"
+ %`hey #{@ip}`.should == "hey xxx"
+ %$hey #{@ip}$.should == "hey xxx"
+ end
+
+ it "using percent with 'q', stopping interpolation" do
+ %q(#{@ip}).should == '#{@ip}'
+ end
+
+ it "using percent with 'Q' to interpolate" do
+ %Q(#{@ip}).should == 'xxx'
+ end
+
+ # The backslashes :
+ #
+ # \t (tab), \n (newline), \r (carriage return), \f (form feed), \b
+ # (backspace), \a (bell), \e (escape), \s (whitespace), \nnn (octal),
+ # \xnn (hexadecimal), \cx (control x), \C-x (control x), \M-x (meta x),
+ # \M-\C-x (meta control x)
+
+ it "backslashes follow the same rules as interpolation" do
+ "\t\n\r\f\b\a\e\s\075\x62\cx".should == "\t\n\r\f\b\a\e =b\030"
+ '\t\n\r\f\b\a\e =b\030'.should == "\\t\\n\\r\\f\\b\\a\\e =b\\030"
+ end
+
+ it "calls #to_s when the object is not a String" do
+ obj = mock('to_s')
+ obj.stub!(:to_s).and_return('42')
+
+ "#{obj}".should == '42'
+ end
+
+ it "calls #to_s as a private method" do
+ obj = mock('to_s')
+ obj.stub!(:to_s).and_return('42')
+
+ class << obj
+ private :to_s
+ end
+
+ "#{obj}".should == '42'
+ end
+
+ it "uses an internal representation when #to_s doesn't return a String" do
+ obj = mock('to_s')
+ obj.stub!(:to_s).and_return(42)
+
+ # See rubyspec commit 787c132d by yugui. There is value in
+ # ensuring that this behavior works. So rather than removing
+ # this spec completely, the only thing that can be asserted
+ # is that if you interpolate an object that fails to return
+ # a String, you will still get a String and not raise an
+ # exception.
+ "#{obj}".should be_an_instance_of(String)
+ end
+
+ it "allows a dynamic string to parse a nested do...end block as an argument to a call without parens, interpolated" do
+ s = eval 'eval "#{proc do; 1; end.call}"'
+ s.should == 1
+ end
+
+ it "are produced from character shortcuts" do
+ ?z.should == 'z'
+ end
+
+ it "are produced from control character shortcuts" do
+ # Control-Z
+ ?\C-z.should == "\x1A"
+
+ # Meta-Z
+ ?\M-z.should == "\xFA"
+
+ # Meta-Control-Z
+ ?\M-\C-z.should == "\x9A"
+ end
+
+ describe "Unicode escaping" do
+ it "can be done with \\u and four hex digits" do
+ [ ["\u0000", 0x0000],
+ ["\u2020", 0x2020]
+ ].should be_computed_by(:ord)
+ end
+
+ it "can be done with \\u{} and one to six hex digits" do
+ [ ["\u{a}", 0xa],
+ ["\u{ab}", 0xab],
+ ["\u{abc}", 0xabc],
+ ["\u{1abc}", 0x1abc],
+ ["\u{12abc}", 0x12abc],
+ ["\u{100000}", 0x100000]
+ ].should be_computed_by(:ord)
+ end
+
+ # 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::BINARY
+ end
+
+ it "produces an ASCII string when escaping ASCII characters via \\u{}" do
+ "\u{0000}".encoding.should == Encoding::BINARY
+ end
+
+ it "produces a UTF-8-encoded string when escaping non-ASCII characters via \\u" do
+ "\u1234".encoding.should == Encoding::UTF_8
+ end
+
+ it "produces a UTF-8-encoded string when escaping non-ASCII characters via \\u{}" do
+ "\u{1234}".encoding.should == Encoding::UTF_8
+ end
+ end
+ end
+end
+
+# TODO: rewrite all specs above this
+
+describe "Ruby String literals" do
+ def str_concat
+ "foo" "bar" "baz"
+ end
+
+ def long_string_literals
+ "Beautiful is better than ugly." \
+ "Explicit is better than implicit."
+ end
+
+ it "on a single line with spaces in between are concatenated together" do
+ str_concat.should == "foobarbaz"
+ end
+
+ it "on multiple lines with newlines and backslash in between are concatenated together" do
+ long_string_literals.should == "Beautiful is better than ugly.Explicit is better than implicit."
+ 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 in different files" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files.rb")).chomp.should == "true"
+ end
+
+ guard -> { !(eval("'test'").frozen? && "test".equal?("test")) } do
+ it "produces different objects for literals with the same content in different files if the other file doesn't have the comment and String literals aren't frozen by default" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_no_comment.rb")).chomp.should == "true"
+ end
+ end
+
+ guard -> { eval("'test'").frozen? && "test".equal?("test") } do
+ it "produces the same objects for literals with the same content in different files if the other file doesn't have the comment and String literals are frozen by default" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_no_comment.rb")).chomp.should == "false"
+ end
+ end
+
+ it "produce different objects for literals with the same content in different files if they have different encodings" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_diff_enc.rb")).chomp.should == "true"
+ end
+ end
+
+end
+
+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
+
+ 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
+
+ b = "abc".encode("utf-8")
+ "#{b}".encoding.should == Encoding::BINARY
+ end
+
+ it "raises an Encoding::CompatibilityError if the Encodings are not compatible" do
+ a = "\u3042"
+ b = "\xff".dup.force_encoding "binary"
+
+ -> { "#{a} #{b}" }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "creates a non-frozen String" do
+ code = <<~'RUBY'
+ "a#{6*7}c"
+ RUBY
+ eval(code).should_not.frozen?
+ end
+
+ it "creates a non-frozen String when # frozen-string-literal: true is used" do
+ code = <<~'RUBY'
+ # frozen-string-literal: true
+ "a#{6*7}c"
+ RUBY
+ eval(code).should_not.frozen?
+ end
+end
diff --git a/spec/ruby/language/super_spec.rb b/spec/ruby/language/super_spec.rb
new file mode 100644
index 0000000000..7d9e896d8b
--- /dev/null
+++ b/spec/ruby/language/super_spec.rb
@@ -0,0 +1,464 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/super'
+
+describe "The super keyword" do
+ it "calls the method on the calling class" do
+ 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
+ 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
+ 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
+ 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
+ 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
+ SuperSpecs::MultiSuperTargets::A.new.foo.should == :BaseA
+ SuperSpecs::MultiSuperTargets::B.new.foo.should == :BaseB
+ end
+
+ it "searches class methods including modules" do
+ 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
+ SuperSpecs::IncludesFromBasic.new.__send__(:foobar).should == 43
+ end
+
+ it "searches BasicObject through another module for methods defined there" do
+ SuperSpecs::IncludesIntermediate.new.__send__(:foobar).should == 42
+ end
+
+ it "calls the correct method when the method visibility is modified" do
+ SuperSpecs::MS4::A.new.example.should == 5
+ end
+
+ it "calls the correct method when the superclass argument list is different from the subclass" do
+ 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 when super method does not exist" do
+ sup = Class.new
+ sub_normal = Class.new(sup) do
+ def foo
+ super()
+ end
+ end
+ sub_zsuper = Class.new(sup) do
+ def foo
+ super
+ end
+ end
+
+ -> {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
+ c1 = Class.new do
+ def m
+ yield
+ end
+ end
+ c2 = Class.new(c1) do
+ def m(v)
+ super()
+ end
+ end
+
+ 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
+ SuperSpecs::S6.new.here.should == :good
+ end
+
+ it "calls the superclass method when initial method is defined_method'd" do
+ SuperSpecs::S7.new.here.should == :good
+ end
+
+ it "can call through a define_method multiple times (caching check)" do
+ obj = SuperSpecs::S7.new
+
+ 2.times do
+ obj.here.should == :good
+ end
+ end
+
+ it "supers up appropriate name even if used for multiple method names" do
+ sup = Class.new do
+ def a; "a"; end
+ def b; "b"; end
+ end
+
+ sub = Class.new(sup) do
+ [:a, :b].each do |name|
+ define_method name do
+ super()
+ end
+ end
+ end
+
+ sub.new.a.should == "a"
+ sub.new.b.should == "b"
+ sub.new.a.should == "a"
+ end
+
+ it "raises a RuntimeError when called with implicit arguments from a method defined with define_method" do
+ super_class = Class.new do
+ def a(arg)
+ arg
+ end
+ end
+
+ klass = Class.new super_class do
+ define_method :a do |arg|
+ super
+ end
+ end
+
+ -> { 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
+ 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
+ SuperSpecs::Alias3.new.name3.should == [:alias2, :alias1]
+ end
+
+ it "sees the included version of a module a method is alias from" do
+ SuperSpecs::AliasWithSuper::Trigger.foo.should == [:b, :a]
+ end
+
+ it "find super from a singleton class" do
+ obj = SuperSpecs::SingletonCase::Foo.new
+ def obj.foobar(array)
+ array << :singleton
+ super
+ end
+ obj.foobar([]).should == [:singleton, :foo, :base]
+ end
+
+ it "finds super on other objects if a singleton class aliased the method" do
+ orig_obj = SuperSpecs::SingletonAliasCase::Foo.new
+ orig_obj.alias_on_singleton
+ orig_obj.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
+ SuperSpecs::RestArgsWithSuper::B.new.a("bar").should == ["bar", "foo"]
+ end
+
+ it "passes along modified rest args when they were originally empty" do
+ 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
+ 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
+ SuperSpecs::ZSuperWithBlock::B.new.a.should == 14
+ end
+
+ it "passes along block via reference to method expecting a reference" do
+ SuperSpecs::ZSuperWithBlock::B.new.b.should == [14, 15]
+ end
+
+ it "passes along a block via reference to a method that yields" do
+ SuperSpecs::ZSuperWithBlock::B.new.c.should == 16
+ end
+
+ it "without explicit arguments passes optional arguments that have a default value" do
+ SuperSpecs::ZSuperWithOptional::B.new.m(1, 2).should == 14
+ end
+
+ it "without explicit arguments passes optional arguments that have a non-default value" do
+ 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
+ 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
+ SuperSpecs::ZSuperWithOptional::C.new.m(1, 2, 3).should == 100
+ end
+
+ it "without explicit arguments passes rest arguments" do
+ SuperSpecs::ZSuperWithRest::B.new.m(1, 2, 3).should == [1, 2, 3]
+ end
+
+ it "without explicit arguments passes rest arguments including any modifications" do
+ SuperSpecs::ZSuperWithRest::B.new.m_modified(1, 2, 3).should == [1, 14, 3]
+ end
+
+ it "without explicit arguments passes arguments and rest arguments" do
+ 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
+ SuperSpecs::ZSuperWithRestAndOthers::B.new.m_modified(1, 2, 3, 4, 5).should == [3, 14, 5]
+ end
+
+ it "without explicit arguments that are '_'" do
+ SuperSpecs::ZSuperWithUnderscores::B.new.m(1, 2).should == [1, 2]
+ SuperSpecs::ZSuperWithUnderscores::B.new.m3(1, 2, 3).should == [1, 2, 3]
+ SuperSpecs::ZSuperWithUnderscores::B.new.m4(1, 2, 3, 4).should == [1, 2, 3, 4]
+ SuperSpecs::ZSuperWithUnderscores::B.new.m_default(1).should == [1]
+ SuperSpecs::ZSuperWithUnderscores::B.new.m_default.should == [0]
+ SuperSpecs::ZSuperWithUnderscores::B.new.m_pre_default_rest_post(1, 2, 3, 4, 5, 6, 7).should == [1, 2, 3, 4, 5, 6, 7]
+ SuperSpecs::ZSuperWithUnderscores::B.new.m_rest(1, 2).should == [1, 2]
+ SuperSpecs::ZSuperWithUnderscores::B.new.m_kwrest(a: 1).should == {a: 1}
+ end
+
+ it "without explicit arguments that are '_' including any modifications" do
+ SuperSpecs::ZSuperWithUnderscores::B.new.m_modified(1, 2).should == [14, 2]
+ end
+
+ it "should pass method arguments when called within a closure" do
+ SuperSpecs::ZSuperInBlock::B.new.m(arg: 1).should == 1
+ end
+
+ describe 'when using keyword arguments' do
+ before :each do
+ @req = SuperSpecs::Keywords::RequiredArguments.new
+ @opts = SuperSpecs::Keywords::OptionalArguments.new
+ @etc = SuperSpecs::Keywords::PlaceholderArguments.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 = SuperSpecs::Keywords::RequiredAndOptionalAndPlaceholderArguments.new
+ end
+
+ it 'does not pass any arguments to the parent when none are given' do
+ @etc.foo.should == {}
+ end
+
+ it 'passes only required arguments to the parent when no optional arguments are given' do
+ [@req, @req_and_etc].each do |obj|
+ obj.foo(a: 'a').should == {a: 'a'}
+ end
+ end
+
+ it 'passes default argument values to the parent' do
+ [@opts, @opts_and_etc].each do |obj|
+ obj.foo.should == {b: 'b'}
+ end
+
+ [@req_and_opts, @opts_and_etc, @req_and_opts_and_etc].each do |obj|
+ obj.foo(a: 'a').should == {a: 'a', b: 'b'}
+ end
+ end
+
+ it 'passes any given arguments including optional keyword arguments to the parent' do
+ [@etc, @req_and_opts, @req_and_etc, @opts_and_etc, @req_and_opts_and_etc].each do |obj|
+ obj.foo(a: 'a', b: 'b').should == {a: 'a', b: 'b'}
+ end
+
+ [@etc, @req_and_etc, @opts_and_etc, @req_and_opts_and_etc].each do |obj|
+ obj.foo(a: 'a', b: 'b', c: 'c').should == {a: 'a', b: 'b', c: 'c'}
+ end
+ end
+ end
+
+ describe 'when using regular and keyword arguments' do
+ before :each do
+ @req = SuperSpecs::RegularAndKeywords::RequiredArguments.new
+ @opts = SuperSpecs::RegularAndKeywords::OptionalArguments.new
+ @etc = SuperSpecs::RegularAndKeywords::PlaceholderArguments.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 = SuperSpecs::RegularAndKeywords::RequiredAndOptionalAndPlaceholderArguments.new
+ end
+
+ it 'passes only required regular arguments to the parent when no optional keyword arguments are given' do
+ @etc.foo('a').should == ['a', {}]
+ end
+
+ it 'passes only required regular and keyword arguments to the parent when no optional keyword arguments are given' do
+ [@req, @req_and_etc].each do |obj|
+ obj.foo('a', b: 'b').should == ['a', {b: 'b'}]
+ end
+ end
+
+ it 'passes default argument values to the parent' do
+ [@opts, @opts_and_etc].each do |obj|
+ obj.foo('a').should == ['a', {c: 'c'}]
+ end
+
+ [@req_and_opts, @opts_and_etc, @req_and_opts_and_etc].each do |obj|
+ obj.foo('a', b: 'b').should == ['a', {b: 'b', c: 'c'}]
+ end
+ end
+
+ it 'passes any given regular and keyword arguments including optional keyword arguments to the parent' do
+ [@etc, @req_and_opts, @req_and_etc, @opts_and_etc, @req_and_opts_and_etc].each do |obj|
+ obj.foo('a', b: 'b', c: 'c').should == ['a', {b: 'b', c: 'c'}]
+ end
+
+ [@etc, @req_and_etc, @opts_and_etc, @req_and_opts_and_etc].each do |obj|
+ obj.foo('a', b: 'b', c: 'c', d: 'd').should == ['a', {b: 'b', c: 'c', d: 'd'}]
+ end
+ end
+ end
+
+ describe 'when using splat and keyword arguments' do
+ before :each do
+ @all = SuperSpecs::SplatAndKeywords::AllArguments.new
+ end
+
+ it 'does not pass any arguments to the parent when none are given' do
+ @all.foo.should == [[], {}]
+ end
+
+ it 'passes only splat arguments to the parent when no keyword arguments are given' do
+ @all.foo('a').should == [['a'], {}]
+ end
+
+ it 'passes only keyword arguments to the parent when no splat arguments are given' do
+ @all.foo(b: 'b').should == [[], {b: 'b'}]
+ end
+
+ it 'passes any given splat and keyword arguments to the parent' do
+ @all.foo('a', b: 'b').should == [['a'], {b: 'b'}]
+ end
+ end
+end
diff --git a/spec/ruby/language/symbol_spec.rb b/spec/ruby/language/symbol_spec.rb
new file mode 100644
index 0000000000..0801d3223e
--- /dev/null
+++ b/spec/ruby/language/symbol_spec.rb
@@ -0,0 +1,108 @@
+require_relative '../spec_helper'
+
+describe "A Symbol literal" do
+ it "is a ':' followed by any number of valid characters" do
+ a = :foo
+ a.should be_kind_of(Symbol)
+ a.inspect.should == ':foo'
+ end
+
+ it "is a ':' followed by any valid variable, method, or constant name" do
+ # Add more of these?
+ [ :Foo,
+ :foo,
+ :@foo,
+ :@@foo,
+ :$foo,
+ :_,
+ :~,
+ :- ,
+ :FOO,
+ :_Foo,
+ :&,
+ :_9
+ ].each { |s| s.should be_kind_of(Symbol) }
+ end
+
+ it "is a ':' followed by a single- or double-quoted string that may contain otherwise invalid characters" do
+ [ [:'foo bar', ':"foo bar"'],
+ [:'++', ':"++"'],
+ [:'9', ':"9"'],
+ [:"foo #{1 + 1}", ':"foo 2"'],
+ [:"foo\nbar", ':"foo\nbar"'],
+ ].each { |sym, str|
+ sym.should be_kind_of(Symbol)
+ sym.inspect.should == str
+ }
+ end
+
+ 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]\n#{Encoding::BINARY.name}\n"
+ end
+
+ it "may contain '::' in the string" do
+ :'Some::Class'.should be_kind_of(Symbol)
+ end
+
+ it "is converted to a literal, unquoted representation if the symbol contains only valid characters" do
+ a, b, c = :'foo', :'+', :'Foo__9'
+ a.should be_kind_of(Symbol)
+ a.inspect.should == ':foo'
+ b.should be_kind_of(Symbol)
+ b.inspect.should == ':+'
+ c.should be_kind_of(Symbol)
+ c.inspect.should == ':Foo__9'
+ end
+
+ it "can be created by the %s-delimited expression" do
+ a, b = :'foo bar', %s{foo bar}
+ b.should be_kind_of(Symbol)
+ b.inspect.should == ':"foo bar"'
+ b.should == a
+ end
+
+ it "is the same object when created from identical strings" do
+ var = "@@var"
+ [ [:symbol, :symbol],
+ [:'a string', :'a string'],
+ [:"#{var}", :"#{var}"]
+ ].each { |a, b|
+ a.should equal(b)
+ }
+ end
+
+ it "can contain null in the string" do
+ eval(':"\0" ').inspect.should == ':"\\x00"'
+ end
+
+ it "can be an empty string" do
+ c = :''
+ c.should be_kind_of(Symbol)
+ c.inspect.should == ':""'
+ end
+
+ it "can be :!, :!=, or :!~" do
+ %w{'!', '!=', '!~'}.each do |sym|
+ sym.to_sym.to_s.should == sym
+ end
+ end
+
+ it "can be created from list syntax %i{a b c} without interpolation" do
+ %i{a b #{c}}.should == [:a, :b, :"\#{c}"]
+ end
+
+ it "can be created from list syntax %I{a b c} with interpolation" do
+ %I{a b #{"c"}}.should == [:a, :b, :c]
+ end
+
+ 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
new file mode 100644
index 0000000000..d723843688
--- /dev/null
+++ b/spec/ruby/language/throw_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../spec_helper'
+
+describe "The throw keyword" do
+ it "abandons processing" do
+ i = 0
+ catch(:done) do
+ loop do
+ i += 1
+ throw :done if i > 4
+ end
+ i += 1
+ end
+ i.should == 5
+ end
+
+ it "supports a second parameter" do
+ msg = catch(:exit) do
+ throw :exit,:msg
+ end
+ msg.should == :msg
+ end
+
+ it "uses nil as a default second parameter" do
+ msg = catch(:exit) do
+ throw :exit
+ end
+ msg.should == nil
+ end
+
+ it "clears the current exception" do
+ catch :exit do
+ begin
+ raise "exception"
+ rescue
+ throw :exit
+ end
+ end
+ $!.should be_nil
+ end
+
+ it "allows any object as its argument" do
+ catch(1) { throw 1, 2 }.should == 2
+ o = Object.new
+ catch(o) { throw o, o }.should == o
+ end
+
+ it "does not convert strings to a symbol" do
+ -> { catch(:exit) { throw "exit" } }.should raise_error(ArgumentError)
+ end
+
+ it "unwinds stack from within a method" do
+ def throw_method(handler, val)
+ throw handler, val
+ end
+
+ catch(:exit) do
+ throw_method(:exit, 5)
+ end.should == 5
+ end
+
+ it "unwinds stack from within a lambda" do
+ c = -> { throw :foo, :msg }
+ catch(:foo) { c.call }.should == :msg
+ end
+
+ it "raises an ArgumentError if outside of scope of a matching catch" do
+ -> { throw :test, 5 }.should raise_error(ArgumentError)
+ -> { catch(:different) { throw :test, 5 } }.should raise_error(ArgumentError)
+ end
+
+ it "raises an UncaughtThrowError if used to exit a thread" do
+ catch(:what) do
+ t = Thread.new {
+ -> {
+ throw :what
+ }.should raise_error(UncaughtThrowError)
+ }
+ t.join
+ end
+ end
+end
diff --git a/spec/ruby/language/undef_spec.rb b/spec/ruby/language/undef_spec.rb
new file mode 100644
index 0000000000..268c0b84c3
--- /dev/null
+++ b/spec/ruby/language/undef_spec.rb
@@ -0,0 +1,79 @@
+require_relative '../spec_helper'
+
+describe "The undef keyword" do
+ describe "undefines a method" do
+ before :each do
+ @undef_class = Class.new do
+ def meth(o); o; end
+ end
+ @obj = @undef_class.new
+ @obj.meth(5).should == 5
+ end
+
+ it "with an identifier" do
+ @undef_class.class_eval do
+ undef meth
+ end
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
+ end
+
+ it "with a simple symbol" do
+ @undef_class.class_eval do
+ undef :meth
+ end
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
+ end
+
+ it "with a single quoted symbol" do
+ @undef_class.class_eval do
+ undef :'meth'
+ end
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
+ end
+
+ it "with a double quoted symbol" do
+ @undef_class.class_eval do
+ undef :"meth"
+ end
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
+ end
+
+ it "with an interpolated symbol" do
+ @undef_class.class_eval do
+ undef :"#{'meth'}"
+ end
+ -> { @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
+
+ it "allows undefining multiple methods at a time" do
+ undef_multiple = Class.new do
+ def method1; end
+ def method2; :nope; end
+
+ undef :method1, :method2
+ end
+
+ obj = undef_multiple.new
+ obj.respond_to?(:method1).should == false
+ obj.respond_to?(:method2).should == false
+ end
+
+ it "raises a NameError when passed a missing name" do
+ Class.new do
+ -> {
+ undef not_exist
+ }.should raise_error(NameError) { |e|
+ # a NameError and not a NoMethodError
+ e.class.should == NameError
+ }
+ end
+ end
+end
diff --git a/spec/ruby/language/unless_spec.rb b/spec/ruby/language/unless_spec.rb
new file mode 100644
index 0000000000..98acdc083b
--- /dev/null
+++ b/spec/ruby/language/unless_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../spec_helper'
+
+describe "The unless expression" do
+ it "evaluates the unless body when the expression is false" do
+ unless false
+ a = true
+ else
+ a = false
+ end
+
+ a.should == true
+ end
+
+ it "returns the last statement in the body" do
+ unless false
+ 'foo'
+ 'bar'
+ 'baz'
+ end.should == 'baz'
+ end
+
+ it "evaluates the else body when the expression is true" do
+ unless true
+ 'foo'
+ else
+ 'bar'
+ end.should == 'bar'
+ end
+
+ it "takes an optional then after the expression" do
+ unless false then
+ 'baz'
+ end.should == 'baz'
+ end
+
+ it "does not return a value when the expression is true" do
+ unless true; end.should == nil
+ end
+
+ it "allows expression and body to be on one line (using 'then')" do
+ unless false then 'foo'; else 'bar'; end.should == 'foo'
+ end
+end
diff --git a/spec/ruby/language/until_spec.rb b/spec/ruby/language/until_spec.rb
new file mode 100644
index 0000000000..78c289ff56
--- /dev/null
+++ b/spec/ruby/language/until_spec.rb
@@ -0,0 +1,234 @@
+require_relative '../spec_helper'
+
+# until bool-expr [do]
+# body
+# end
+#
+# begin
+# body
+# end until bool-expr
+#
+# expr until bool-expr
+describe "The until expression" do
+ it "runs while the expression is false" do
+ i = 0
+ until i > 9
+ i += 1
+ end
+
+ i.should == 10
+ end
+
+ it "optionally takes a 'do' after the expression" do
+ i = 0
+ until i > 9 do
+ i += 1
+ end
+
+ i.should == 10
+ end
+
+ it "allows body begin on the same line if do is used" do
+ i = 0
+ until i > 9 do i += 1
+ end
+
+ i.should == 10
+ end
+
+ it "executes code in containing variable scope" do
+ i = 0
+ until i == 1
+ a = 123
+ i = 1
+ end
+
+ a.should == 123
+ end
+
+ it "executes code in containing variable scope with 'do'" do
+ i = 0
+ until i == 1 do
+ a = 123
+ i = 1
+ end
+
+ a.should == 123
+ end
+
+ it "returns nil if ended when condition became true" do
+ i = 0
+ until i > 9
+ i += 1
+ end.should == nil
+ end
+
+ it "evaluates the body if expression is empty" do
+ a = []
+ until ()
+ a << :body_evaluated
+ break
+ end
+ a.should == [:body_evaluated]
+ end
+
+ it "stops running body if interrupted by break" do
+ i = 0
+ until i > 9
+ i += 1
+ break if i > 5
+ end
+ i.should == 6
+ end
+
+ it "returns value passed to break if interrupted by break" do
+ until false
+ break 123
+ end.should == 123
+ end
+
+ it "returns nil if interrupted by break with no arguments" do
+ until false
+ break
+ end.should == nil
+ end
+
+ it "skips to end of body with next" do
+ a = []
+ i = 0
+ until (i+=1)>=5
+ next if i==3
+ a << i
+ end
+ a.should == [1, 2, 4]
+ end
+
+ it "restarts the current iteration without reevaluating condition with redo" do
+ a = []
+ i = 0
+ j = 0
+ until (i+=1)>=3
+ a << i
+ j+=1
+ redo if j<3
+ end
+ a.should == [1, 1, 1, 2]
+ end
+end
+
+describe "The until modifier" do
+ it "runs preceding statement while the condition is false" do
+ i = 0
+ i += 1 until i > 9
+ i.should == 10
+ end
+
+ it "evaluates condition before statement execution" do
+ a = []
+ i = 0
+ a << i until (i+=1) >= 3
+ a.should == [1, 2]
+ end
+
+ it "does not run preceding statement if the condition is true" do
+ i = 0
+ i += 1 until true
+ i.should == 0
+ end
+
+ it "returns nil if ended when condition became true" do
+ i = 0
+ (i += 1 until i>9).should == nil
+ end
+
+ it "returns value passed to break if interrupted by break" do
+ (break 123 until false).should == 123
+ end
+
+ it "returns nil if interrupted by break with no arguments" do
+ (break until false).should == nil
+ end
+
+ it "skips to end of body with next" do
+ i = 0
+ j = 0
+ ((i+=1) == 3 ? next : j+=i) until i > 10
+ j.should == 63
+ end
+
+ it "restarts the current iteration without reevaluating condition with redo" do
+ i = 0
+ j = 0
+ (i+=1) == 4 ? redo : j+=i until (i+=1) > 10
+ j.should == 34
+ end
+end
+
+describe "The until modifier with begin .. end block" do
+ it "runs block while the expression is false" do
+ i = 0
+ begin
+ i += 1
+ end until i > 9
+
+ i.should == 10
+ end
+
+ it "stops running block if interrupted by break" do
+ i = 0
+ begin
+ i += 1
+ break if i > 5
+ end until i > 9
+
+ i.should == 6
+ end
+
+ it "returns value passed to break if interrupted by break" do
+ (begin; break 123; end until false).should == 123
+ end
+
+ it "returns nil if interrupted by break with no arguments" do
+ (begin; break; end until false).should == nil
+ end
+
+ it "runs block at least once (even if the expression is true)" do
+ i = 0
+ begin
+ i += 1
+ end until true
+
+ i.should == 1
+ end
+
+ it "evaluates condition after block execution" do
+ a = []
+ i = 0
+ begin
+ a << i
+ end until (i+=1)>=5
+ a.should == [0, 1, 2, 3, 4]
+ end
+
+ it "skips to end of body with next" do
+ a = []
+ i = 0
+ begin
+ next if i==3
+ a << i
+ end until (i+=1)>=5
+ a.should == [0, 1, 2, 4]
+ end
+
+ it "restart the current iteration without reevaluating condition with redo" do
+ a = []
+ i = 0
+ j = 0
+ begin
+ a << i
+ j+=1
+ redo if j<3
+ end until (i+=1)>=3
+ a.should == [0, 0, 0, 1, 2]
+ end
+end
diff --git a/spec/ruby/language/variables_spec.rb b/spec/ruby/language/variables_spec.rb
new file mode 100644
index 0000000000..e134271939
--- /dev/null
+++ b/spec/ruby/language/variables_spec.rb
@@ -0,0 +1,930 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/variables'
+
+describe "Evaluation order during assignment" do
+ context "with single assignment" do
+ it "evaluates from left to right" do
+ obj = VariablesSpecs::EvalOrder.new
+ obj.instance_eval do
+ foo[0] = a
+ end
+
+ obj.order.should == ["foo", "a", "foo[]="]
+ end
+ end
+
+ context "with multiple assignment" do
+ it "evaluates from left to right, receivers first then methods" do
+ obj = VariablesSpecs::EvalOrder.new
+ obj.instance_eval do
+ foo[0], bar.baz = a, b
+ end
+
+ obj.order.should == ["foo", "bar", "a", "b", "foo[]=", "bar.baz="]
+ end
+
+ it "can be used to swap variables with nested method calls" do
+ node = VariablesSpecs::EvalOrder.new.node
+
+ original_node = node
+ original_node_left = node.left
+ original_node_left_right = node.left.right
+
+ node.left, node.left.right, node = node.left.right, node, node.left
+ # Should evaluate in the order of:
+ # LHS: node, node.left(original_node_left)
+ # RHS: original_node_left_right, original_node, original_node_left
+ # Ops:
+ # * node(original_node), original_node.left = original_node_left_right
+ # * original_node_left.right = original_node
+ # * node = original_node_left
+
+ node.should == original_node_left
+ node.right.should == original_node
+ node.right.left.should == original_node_left_right
+ end
+ end
+end
+
+describe "Multiple assignment" do
+ context "with a single RHS value" do
+ it "assigns a simple MLHS" do
+ (a, b, c = 1).should == 1
+ [a, b, c].should == [1, nil, nil]
+ end
+
+ it "calls #to_ary to convert an Object RHS when assigning a simple MLHS" do
+ x = mock("multi-assign single RHS")
+ x.should_receive(:to_ary).and_return([1, 2])
+
+ (a, b, c = x).should == x
+ [a, b, c].should == [1, 2, nil]
+ end
+
+ it "calls #to_ary if it is private" do
+ x = mock("multi-assign single RHS")
+ x.should_receive(:to_ary).and_return([1, 2])
+ class << x; private :to_ary; end
+
+ (a, b, c = x).should == x
+ [a, b, c].should == [1, 2, nil]
+ end
+
+ it "does not call #to_ary if #respond_to? returns false" do
+ x = mock("multi-assign single RHS")
+ x.should_receive(:respond_to?).with(:to_ary, true).and_return(false)
+ x.should_not_receive(:to_ary)
+
+ (a, b, c = x).should == x
+ [a, b, c].should == [x, nil, nil]
+ end
+
+ it "wraps the Object in an Array if #to_ary returns nil" do
+ x = mock("multi-assign single RHS")
+ x.should_receive(:to_ary).and_return(nil)
+
+ (a, b, c = x).should == x
+ [a, b, c].should == [x, nil, nil]
+ end
+
+ it "raises a TypeError of #to_ary does not return an Array" do
+ x = mock("multi-assign single RHS")
+ x.should_receive(:to_ary).and_return(1)
+
+ -> { 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
+ x = mock("multi-assign single RHS")
+ x.should_not_receive(:to_a)
+
+ (a, b, c = x).should == x
+ [a, b, c].should == [x, nil, nil]
+ end
+
+ it "does not call #to_ary on an Array instance" do
+ x = [1, 2]
+ x.should_not_receive(:to_ary)
+
+ (a, b = x).should == x
+ [a, b].should == [1, 2]
+ end
+
+ it "does not call #to_a on an Array instance" do
+ x = [1, 2]
+ x.should_not_receive(:to_a)
+
+ (a, b = x).should == x
+ [a, b].should == [1, 2]
+ end
+
+ it "returns the RHS when it is an Array" do
+ ary = [1, 2]
+
+ x = (a, b = ary)
+ x.should equal(ary)
+ end
+
+ it "returns the RHS when it is an Array subclass" do
+ cls = Class.new(Array)
+ ary = cls.new [1, 2]
+
+ x = (a, b = ary)
+ x.should equal(ary)
+ end
+
+ it "does not call #to_ary on an Array subclass instance" do
+ x = Class.new(Array).new [1, 2]
+ x.should_not_receive(:to_ary)
+
+ (a, b = x).should == x
+ [a, b].should == [1, 2]
+ end
+
+ it "does not call #to_a on an Array subclass instance" do
+ x = Class.new(Array).new [1, 2]
+ x.should_not_receive(:to_a)
+
+ (a, b = x).should == x
+ [a, b].should == [1, 2]
+ end
+
+ it "assigns a MLHS with a trailing comma" do
+ a, = 1
+ b, c, = []
+ [a, b, c].should == [1, nil, nil]
+ end
+
+ it "assigns a single LHS splat" do
+ (*a = 1).should == 1
+ a.should == [1]
+ end
+
+ it "calls #to_ary to convert an Object RHS" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_ary).and_return([1, 2])
+
+ (*a = x).should == x
+ a.should == [1, 2]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_ary).and_return(1)
+
+ -> { *a = x }.should raise_error(TypeError)
+ end
+
+ it "does not call #to_ary on an Array subclass" do
+ cls = Class.new(Array)
+ ary = cls.new [1, 2]
+ ary.should_not_receive(:to_ary)
+
+ (*a = ary).should == [1, 2]
+ a.should == [1, 2]
+ end
+
+ it "assigns an Array when the RHS is an Array subclass" do
+ cls = Class.new(Array)
+ ary = cls.new [1, 2]
+
+ x = (*a = ary)
+ x.should equal(ary)
+ a.should be_an_instance_of(Array)
+ end
+
+ it "calls #to_ary to convert an Object RHS with MLHS" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_ary).and_return([1, 2])
+
+ (a, *b, c = x).should == x
+ [a, b, c].should == [1, [], 2]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array with MLHS" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_ary).and_return(1)
+
+ -> { a, *b, c = x }.should raise_error(TypeError)
+ end
+
+ it "does not call #to_a to convert an Object RHS with a MLHS" do
+ x = mock("multi-assign splat")
+ x.should_not_receive(:to_a)
+
+ (a, *b = x).should == x
+ [a, b].should == [x, []]
+ end
+
+ it "assigns a MLHS with leading splat" do
+ (*a, b, c = 1).should == 1
+ [a, b, c].should == [[], 1, nil]
+ end
+
+ it "assigns a MLHS with a middle splat" do
+ a, b, *c, d, e = 1
+ [a, b, c, d, e].should == [1, nil, [], nil, nil]
+ end
+
+ it "assigns a MLHS with a trailing splat" do
+ a, b, *c = 1
+ [a, b, c].should == [1, nil, []]
+ end
+
+ it "assigns a grouped LHS without splat" do
+ ((a, b), c), (d, (e,), (f, (g, h))) = 1
+ [a, b, c, d, e, f, g, h].should == [1, nil, nil, nil, nil, nil, nil, nil]
+ end
+
+ it "assigns a single grouped LHS splat" do
+ (*a) = nil
+ a.should == [nil]
+ end
+
+ it "assigns a grouped LHS with splats" do
+ (a, *b), c, (*d, (e, *f, g)) = 1
+ [a, b, c, d, e, f, g].should == [1, [], nil, [], nil, [], nil]
+ end
+
+ it "consumes values for an anonymous splat" do
+ (* = 1).should == 1
+ end
+
+ it "consumes values for a grouped anonymous splat" do
+ ((*) = 1).should == 1
+ end
+
+ it "does not mutate a RHS Array" do
+ x = [1, 2, 3, 4]
+ a, *b, c, d = x
+ [a, b, c, d].should == [1, [2], 3, 4]
+ x.should == [1, 2, 3, 4]
+ end
+
+ it "assigns values from a RHS method call" do
+ def x() 1 end
+
+ (a, b = x).should == 1
+ [a, b].should == [1, nil]
+ end
+
+ it "assigns values from a RHS method call with arguments" do
+ def x(a) a end
+
+ (a, b = x []).should == []
+ [a, b].should == [nil, nil]
+ end
+
+ it "assigns values from a RHS method call with receiver" do
+ x = mock("multi-assign attributes")
+ x.should_receive(:m).and_return([1, 2, 3])
+
+ a, b = x.m
+ [a, b].should == [1, 2]
+ end
+
+ it "calls #to_ary on the value returned by the method call" do
+ y = mock("multi-assign method return value")
+ y.should_receive(:to_ary).and_return([1, 2])
+
+ x = mock("multi-assign attributes")
+ x.should_receive(:m).and_return(y)
+
+ (a, b = x.m).should == y
+ [a, b].should == [1, 2]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array on a single RHS" do
+ y = mock("multi-assign method return value")
+ y.should_receive(:to_ary).and_return(1)
+
+ x = mock("multi-assign attributes")
+ x.should_receive(:m).and_return(y)
+
+ -> { a, b = x.m }.should raise_error(TypeError)
+ end
+
+ it "assigns values from a RHS method call with receiver and arguments" do
+ x = mock("multi-assign attributes")
+ x.should_receive(:m).with(1, 2).and_return([1, 2, 3])
+
+ a, b = x.m 1, 2
+ [a, b].should == [1, 2]
+ end
+
+ it "assigns global variables" do
+ $spec_a, $spec_b = 1
+ [$spec_a, $spec_b].should == [1, nil]
+ end
+
+ it "assigns instance variables" do
+ @a, @b = 1
+ [@a, @b].should == [1, nil]
+ end
+
+ it "assigns attributes" do
+ a = mock("multi-assign attributes")
+ a.should_receive(:x=).with(1)
+ a.should_receive(:y=).with(nil)
+
+ a.x, a.y = 1
+ end
+
+ it "assigns indexed elements" do
+ a = []
+ 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
+ module VariableSpecs
+ 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
+
+ context "with a single splatted RHS value" do
+ it "assigns a single grouped LHS splat" do
+ (*a) = *1
+ a.should == [1]
+ end
+
+ it "assigns an empty Array to a single LHS value when passed nil" do
+ (a = *nil).should == []
+ a.should == []
+ end
+
+ 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 == []
+ 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
+ ary = [1, 2]
+ ary.should_not_receive(:to_a)
+
+ (a = *ary).should == [1, 2]
+ a.should == [1, 2]
+ end
+
+ it "returns a copy of a splatted Array" do
+ ary = [1, 2]
+
+ (a = *ary).should == [1, 2]
+ a.should_not equal(ary)
+ end
+
+ it "does not call #to_a on an Array subclass" do
+ cls = Class.new(Array)
+ ary = cls.new [1, 2]
+ ary.should_not_receive(:to_a)
+
+ (a = *ary).should == [1, 2]
+ a.should == [1, 2]
+ end
+
+ it "returns an Array when the splatted object is an Array subclass" do
+ cls = Class.new(Array)
+ ary = cls.new [1, 2]
+
+ x = (a = *ary)
+
+ x.should == [1, 2]
+ x.should be_an_instance_of(Array)
+
+ a.should == [1, 2]
+ 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]
+ end
+
+ it "consumes values for a grouped anonymous splat" do
+ ((*) = *1).should == [1]
+ end
+
+ it "assigns a single LHS splat" do
+ x = 1
+ (*a = *x).should == [1]
+ a.should == [1]
+ end
+
+ it "calls #to_a to convert an Object RHS with a single splat LHS" do
+ x = mock("multi-assign RHS splat")
+ x.should_receive(:to_a).and_return([1, 2])
+
+ (*a = *x).should == [1, 2]
+ a.should == [1, 2]
+ end
+
+ it "calls #to_a if it is private" do
+ x = mock("multi-assign RHS splat")
+ x.should_receive(:to_a).and_return([1, 2])
+ class << x; private :to_a; end
+
+ (*a = *x).should == [1, 2]
+ a.should == [1, 2]
+ end
+
+ it "does not call #to_a if #respond_to? returns false" do
+ x = mock("multi-assign RHS splat")
+ x.should_receive(:respond_to?).with(:to_a, true).and_return(false)
+ x.should_not_receive(:to_a)
+
+ (*a = *x).should == [x]
+ a.should == [x]
+ end
+
+ it "wraps the Object in an Array if #to_a returns nil" do
+ x = mock("multi-assign RHS splat")
+ x.should_receive(:to_a).and_return(nil)
+
+ (*a = *x).should == [x]
+ a.should == [x]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("multi-assign RHS splat")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { *a = *x }.should raise_error(TypeError)
+ end
+
+ it "does not call #to_ary to convert an Object RHS with a single splat LHS" do
+ x = mock("multi-assign RHS splat")
+ x.should_not_receive(:to_ary)
+
+ (*a = *x).should == [x]
+ a.should == [x]
+ end
+
+ it "assigns a MLHS with leading splat" do
+ (*a, b, c = *1).should == [1]
+ [a, b, c].should == [[], 1, nil]
+ end
+
+ it "assigns a MLHS with a middle splat" do
+ a, b, *c, d, e = *1
+ [a, b, c, d, e].should == [1, nil, [], nil, nil]
+ end
+
+ it "assigns a MLHS with a trailing splat" do
+ a, b, *c = *nil
+ [a, b, c].should == [nil, nil, []]
+ end
+
+ it "calls #to_a to convert an Object RHS with a single LHS" do
+ x = mock("multi-assign RHS splat")
+ x.should_receive(:to_a).and_return([1, 2])
+
+ (a = *x).should == [1, 2]
+ a.should == [1, 2]
+ end
+
+ it "does not call #to_ary to convert an Object RHS with a single LHS" do
+ x = mock("multi-assign RHS splat")
+ x.should_not_receive(:to_ary)
+
+ (a = *x).should == [x]
+ a.should == [x]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array with a single LHS" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { a = *x }.should raise_error(TypeError)
+ end
+
+ it "calls #to_a to convert an Object splat RHS when assigned to a simple MLHS" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_a).and_return([1, 2])
+
+ (a, b, c = *x).should == [1, 2]
+ [a, b, c].should == [1, 2, nil]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array with a simple MLHS" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { 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
+ x = mock("multi-assign splat")
+ x.should_not_receive(:to_ary)
+
+ (a, b, c = *x).should == [x]
+ [a, b, c].should == [x, nil, nil]
+ end
+
+ it "calls #to_a to convert an Object RHS with MLHS" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_a).and_return([1, 2])
+
+ a, *b, c = *x
+ [a, b, c].should == [1, [], 2]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array with MLHS" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { a, *b, c = *x }.should raise_error(TypeError)
+ end
+
+ it "does not call #to_ary to convert an Object RHS with a MLHS" do
+ x = mock("multi-assign splat")
+ x.should_not_receive(:to_ary)
+
+ a, *b = *x
+ [a, b].should == [x, []]
+ end
+
+ it "assigns a grouped LHS without splats" do
+ ((a, b), c), (d, (e,), (f, (g, h))) = *1
+ [a, b, c, d, e, f, g, h].should == [1, nil, nil, nil, nil, nil, nil, nil]
+ end
+
+ it "assigns a grouped LHS with splats" do
+ (a, *b), c, (*d, (e, *f, g)) = *1
+ [a, b, c, d, e, f, g].should == [1, [], nil, [], nil, [], nil]
+ end
+
+ it "does not mutate a RHS Array" do
+ x = [1, 2, 3, 4]
+ a, *b, c, d = *x
+ [a, b, c, d].should == [1, [2], 3, 4]
+ x.should == [1, 2, 3, 4]
+ end
+
+ it "assigns constants" do
+ module VariableSpecs
+ (*SINGLE_SPLATTED_RHS) = *1
+ SINGLE_SPLATTED_RHS.should == [1]
+ end
+ ensure
+ VariableSpecs.send(:remove_const, :SINGLE_SPLATTED_RHS)
+ end
+ end
+
+ context "with a MRHS value" do
+ it "consumes values for an anonymous splat" do
+ (* = 1, 2, 3).should == [1, 2, 3]
+ end
+
+ it "consumes values for a grouped anonymous splat" do
+ ((*) = 1, 2, 3).should == [1, 2, 3]
+ end
+
+ it "consumes values for multiple '_' variables" do
+ a, _, b, _, c = 1, 2, 3, 4, 5
+ [a, b, c].should == [1, 3, 5]
+ end
+
+ it "does not call #to_a to convert an Object in a MRHS" do
+ x = mock("multi-assign MRHS")
+ x.should_not_receive(:to_a)
+
+ (a, b = 1, x).should == [1, x]
+ [a, b].should == [1, x]
+ end
+
+ it "does not call #to_ary to convert an Object in a MRHS" do
+ x = mock("multi-assign MRHS")
+ x.should_not_receive(:to_ary)
+
+ (a, b = 1, x).should == [1, x]
+ [a, b].should == [1, x]
+ end
+
+ it "calls #to_a to convert a splatted Object as part of a MRHS with a splat MLHS" do
+ x = mock("multi-assign splat MRHS")
+ x.should_receive(:to_a).and_return([3, 4])
+
+ (a, *b = 1, *x).should == [1, 3, 4]
+ [a, b].should == [1, [3, 4]]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array with a splat MLHS" do
+ x = mock("multi-assign splat MRHS")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { 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
+ x = mock("multi-assign splat MRHS")
+ x.should_not_receive(:to_ary)
+
+ (a, *b = 1, *x).should == [1, x]
+ [a, b].should == [1, [x]]
+ end
+
+ it "calls #to_a to convert a splatted Object as part of a MRHS" do
+ x = mock("multi-assign splat MRHS")
+ x.should_receive(:to_a).and_return([3, 4])
+
+ (a, *b = *x, 1).should == [3, 4, 1]
+ [a, b].should == [3, [4, 1]]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array with a splat MRHS" do
+ x = mock("multi-assign splat MRHS")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { 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
+ x = mock("multi-assign splat MRHS")
+ x.should_not_receive(:to_ary)
+
+ (a, *b = *x, 1).should == [x, 1]
+ [a, b].should == [x, [1]]
+ end
+
+ it "assigns a grouped LHS without splat from a simple Array" do
+ ((a, b), c), (d, (e,), (f, (g, h))) = 1, 2, 3, 4, 5
+ [a, b, c, d, e, f, g, h].should == [1, nil, nil, 2, nil, nil, nil, nil]
+ end
+
+ it "assigns a grouped LHS without splat from nested Arrays" do
+ ary = [[1, 2, 3], 4], [[5], [6, 7], [8, [9, 10]]]
+ ((a, b), c), (d, (e,), (f, (g, h))) = ary
+ [a, b, c, d, e, f, g, h].should == [1, 2, 4, [5], 6, 8, 9, 10]
+ end
+
+ it "assigns a single grouped LHS splat" do
+ (*a) = 1, 2, 3
+ a.should == [1, 2, 3]
+ end
+
+ it "assigns a grouped LHS with splats from nested Arrays for simple values" do
+ (a, *b), c, (*d, (e, *f, g)) = 1, 2, 3, 4
+ [a, b, c, d, e, f, g].should == [1, [], 2, [], 3, [], nil]
+ end
+
+ it "assigns a grouped LHS with splats from nested Arrays for nested arrays" do
+ (a, *b), c, (*d, (e, *f, g)) = [1, [2, 3]], [4, 5], [6, 7, 8]
+ [a, b, c, d, e, f, g].should == [1, [[2, 3]], [4, 5], [6, 7], 8, [], nil]
+ end
+
+ it "calls #to_ary to convert an Object when the position receiving the value is a multiple assignment" do
+ x = mock("multi-assign mixed RHS")
+ x.should_receive(:to_ary).and_return([1, 2])
+
+ (a, (b, c), d, e = 1, x, 3, 4).should == [1, x, 3, 4]
+ [a, b, c, d, e].should == [1, 1, 2, 3, 4]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ x = mock("multi-assign mixed RHS")
+ x.should_receive(:to_ary).and_return(x)
+
+ -> { a, (b, c), d = 1, x, 3, 4 }.should raise_error(TypeError)
+ end
+
+ it "calls #to_a to convert a splatted Object value in a MRHS" do
+ x = mock("multi-assign mixed splatted RHS")
+ x.should_receive(:to_a).and_return([4, 5])
+
+ (a, *b, (c, d) = 1, 2, 3, *x).should == [1, 2, 3, 4, 5]
+ [a, b, c, d].should == [1, [2, 3, 4], 5, nil]
+
+ end
+
+ it "calls #to_ary to convert a splatted Object when the position receiving the value is a multiple assignment" do
+ x = mock("multi-assign mixed splatted RHS")
+ x.should_receive(:to_ary).and_return([4, 5])
+
+ (a, *b, (c, d) = 1, 2, 3, *x).should == [1, 2, 3, x]
+ [a, b, c, d].should == [1, [2, 3], 4, 5]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array in a MRHS" do
+ x = mock("multi-assign mixed splatted RHS")
+ x.should_receive(:to_ary).and_return(x)
+
+ -> { a, *b, (c, d) = 1, 2, 3, *x }.should raise_error(TypeError)
+ end
+
+ it "does not call #to_ary to convert an Object when the position receiving the value is a simple variable" do
+ x = mock("multi-assign mixed RHS")
+ x.should_not_receive(:to_ary)
+
+ a, b, c, d = 1, x, 3, 4
+ [a, b, c, d].should == [1, x, 3, 4]
+ end
+
+ it "does not call #to_ary to convert an Object when the position receiving the value is a rest variable" do
+ x = mock("multi-assign mixed RHS")
+ x.should_not_receive(:to_ary)
+
+ a, *b, c, d = 1, x, 3, 4
+ [a, b, c, d].should == [1, [x], 3, 4]
+ end
+
+ it "does not call #to_ary to convert a splatted Object when the position receiving the value is a simple variable" do
+ x = mock("multi-assign mixed splatted RHS")
+ x.should_not_receive(:to_ary)
+
+ a, *b, c = 1, 2, *x
+ [a, b, c].should == [1, [2], x]
+ end
+
+ it "does not call #to_ary to convert a splatted Object when the position receiving the value is a rest variable" do
+ x = mock("multi-assign mixed splatted RHS")
+ x.should_not_receive(:to_ary)
+
+ a, b, *c = 1, 2, *x
+ [a, b, c].should == [1, 2, [x]]
+ end
+
+ it "does not mutate the assigned Array" do
+ x = ((a, *b, c, d) = 1, 2, 3, 4, 5)
+ 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
+ module VariableSpecs
+ MRHS_VALUES = 1, 2, 3
+ MRHS_VALUES.should == [1, 2, 3]
+ end
+ ensure
+ VariableSpecs.send(:remove_const, :MRHS_VALUES)
+ end
+ end
+
+ context "with a RHS assignment value" do
+ it "consumes values for an anonymous splat" do
+ (* = (a = 1)).should == 1
+ a.should == 1
+ end
+
+ it "does not mutate a RHS Array" do
+ a, *b, c, d = (e = [1, 2, 3, 4])
+ [a, b, c, d].should == [1, [2], 3, 4]
+ e.should == [1, 2, 3, 4]
+ end
+ end
+end
+
+describe "A local variable assigned only within a conditional block" do
+ context "accessed from a later closure" do
+ it "is defined?" do
+ if VariablesSpecs.false
+ a = 1
+ end
+
+ 1.times do
+ defined?(a).should == "local-variable"
+ end
+ end
+
+ it "is nil" do
+ if VariablesSpecs.false
+ a = 1
+ end
+
+ 1.times do
+ a.inspect.should == "nil"
+ end
+ 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
new file mode 100644
index 0000000000..e172453ca6
--- /dev/null
+++ b/spec/ruby/language/while_spec.rb
@@ -0,0 +1,344 @@
+require_relative '../spec_helper'
+
+# while bool-expr [do]
+# body
+# end
+#
+# begin
+# body
+# end while bool-expr
+#
+# expr while bool-expr
+describe "The while expression" do
+ it "runs while the expression is true" do
+ i = 0
+ while i < 3
+ i += 1
+ end
+ i.should == 3
+ end
+
+ it "optionally takes a 'do' after the expression" do
+ i = 0
+ while i < 3 do
+ i += 1
+ end
+
+ i.should == 3
+ end
+
+ it "allows body begin on the same line if do is used" do
+ i = 0
+ while i < 3 do i += 1
+ end
+
+ i.should == 3
+ end
+
+ it "executes code in containing variable scope" do
+ i = 0
+ while i != 1
+ a = 123
+ i = 1
+ end
+
+ a.should == 123
+ end
+
+ it "executes code in containing variable scope with 'do'" do
+ i = 0
+ while i != 1 do
+ a = 123
+ i = 1
+ end
+
+ a.should == 123
+ end
+
+ it "returns nil if ended when condition became false" do
+ i = 0
+ while i < 3
+ i += 1
+ end.should == nil
+ end
+
+ it "does not evaluate the body if expression is empty" do
+ a = []
+ while ()
+ a << :body_evaluated
+ end
+ a.should == []
+ end
+
+ it "stops running body if interrupted by break" do
+ i = 0
+ while i < 10
+ i += 1
+ break if i > 5
+ end
+ i.should == 6
+ end
+
+ it "stops running body if interrupted by break in a parenthesized element op-assign-or value" do
+ c = true
+ a = []
+ while c
+ a[1] ||=
+ (
+ break if c
+ c = false
+ )
+ end.should be_nil
+ end
+
+ it "stops running body if interrupted by break in a begin ... end element op-assign-or value" do
+ c = true
+ a = []
+ while c
+ a[1] ||= begin
+ break if c
+ c = false
+ end
+ end.should be_nil
+ end
+
+ it "stops running body if interrupted by break in a parenthesized element op-assign value" do
+ c = true
+ a = [1, 2]
+ while c
+ a[1] +=
+ (
+ break if c
+ c = false
+ )
+ end.should be_nil
+ a.should == [1, 2]
+ end
+
+ it "stops running body if interrupted by break in a begin ... end element op-assign value" do
+ c = true
+ a = [1, 2]
+ while c
+ a[1] += begin
+ break if c
+ c = false
+ end
+ end.should be_nil
+ a.should == [1, 2]
+ end
+
+ it "stops running body if interrupted by break with unless in a parenthesized attribute op-assign-or value" do
+ a = mock("attribute assignment break")
+ a.should_receive(:m).twice.and_return(nil)
+ a.should_receive(:m=)
+
+ c = d = true
+ while c
+ a.m ||=
+ (
+ break unless d
+ d = false
+ )
+ end.should be_nil
+ end
+
+ it "stops running body if interrupted by break with unless in a begin ... end attribute op-assign-or value" do
+ a = mock("attribute assignment break")
+ a.should_receive(:m).twice.and_return(nil)
+ a.should_receive(:m=)
+
+ c = d = true
+ while c
+ a.m ||= begin
+ break unless d
+ d = false
+ end
+ end.should be_nil
+ end
+
+ it "stops running body if interrupted by break in a parenthesized attribute op-assign-or value" do
+ a = mock("attribute assignment break")
+ a.should_receive(:m).and_return(nil)
+ a.should_not_receive(:m=)
+
+ c = true
+ while c
+ a.m +=
+ (
+ break if c
+ c = false
+ )
+ end.should be_nil
+ end
+
+ it "stops running body if interrupted by break in a begin ... end attribute op-assign-or value" do
+ a = mock("attribute assignment break")
+ a.should_receive(:m).and_return(nil)
+ a.should_not_receive(:m=)
+
+ c = true
+ while c
+ a.m += begin
+ break if c
+ c = false
+ end
+ end.should be_nil
+ end
+
+ it "returns value passed to break if interrupted by break" do
+ while true
+ break 123
+ end.should == 123
+ end
+
+ it "returns nil if interrupted by break with no arguments" do
+ while true
+ break
+ end.should == nil
+ end
+
+ it "skips to end of body with next" do
+ a = []
+ i = 0
+ while (i+=1)<5
+ next if i==3
+ a << i
+ end
+ a.should == [1, 2, 4]
+ end
+
+ it "restarts the current iteration without reevaluating condition with redo" do
+ a = []
+ i = 0
+ j = 0
+ while (i+=1)<3
+ a << i
+ j+=1
+ redo if j<3
+ end
+ a.should == [1, 1, 1, 2]
+ end
+end
+
+describe "The while modifier" do
+ it "runs preceding statement while the condition is true" do
+ i = 0
+ i += 1 while i < 3
+ i.should == 3
+ end
+
+ it "evaluates condition before statement execution" do
+ a = []
+ i = 0
+ a << i while (i+=1) < 3
+ a.should == [1, 2]
+ end
+
+ it "does not run preceding statement if the condition is false" do
+ i = 0
+ i += 1 while false
+ i.should == 0
+ end
+
+ it "does not run preceding statement if the condition is empty" do
+ i = 0
+ i += 1 while ()
+ i.should == 0
+ end
+
+ it "returns nil if ended when condition became false" do
+ i = 0
+ (i += 1 while i<10).should == nil
+ end
+
+ it "returns value passed to break if interrupted by break" do
+ (break 123 while true).should == 123
+ end
+
+ it "returns nil if interrupted by break with no arguments" do
+ (break while true).should == nil
+ end
+
+ it "skips to end of body with next" do
+ i = 0
+ j = 0
+ ((i+=1) == 3 ? next : j+=i) while i <= 10
+ j.should == 63
+ end
+
+ it "restarts the current iteration without reevaluating condition with redo" do
+ i = 0
+ j = 0
+ (i+=1) == 4 ? redo : j+=i while (i+=1) <= 10
+ j.should == 34
+ end
+end
+
+describe "The while modifier with begin .. end block" do
+ it "runs block while the expression is true" do
+ i = 0
+ begin
+ i += 1
+ end while i < 3
+
+ i.should == 3
+ end
+
+ it "stops running block if interrupted by break" do
+ i = 0
+ begin
+ i += 1
+ break if i > 5
+ end while i < 10
+
+ i.should == 6
+ end
+
+ it "returns value passed to break if interrupted by break" do
+ (begin; break 123; end while true).should == 123
+ end
+
+ it "returns nil if interrupted by break with no arguments" do
+ (begin; break; end while true).should == nil
+ end
+
+ it "runs block at least once (even if the expression is false)" do
+ i = 0
+ begin
+ i += 1
+ end while false
+
+ i.should == 1
+ end
+
+ it "evaluates condition after block execution" do
+ a = []
+ i = 0
+ begin
+ a << i
+ end while (i+=1)<5
+ a.should == [0, 1, 2, 3, 4]
+ end
+
+ it "skips to end of body with next" do
+ a = []
+ i = 0
+ begin
+ next if i==3
+ a << i
+ end while (i+=1)<5
+ a.should == [0, 1, 2, 4]
+ end
+
+ it "restarts the current iteration without reevaluating condition with redo" do
+ a = []
+ i = 0
+ j = 0
+ begin
+ a << i
+ j+=1
+ redo if j<3
+ end while (i+=1)<3
+ a.should == [0, 0, 0, 1, 2]
+ end
+end
diff --git a/spec/ruby/language/yield_spec.rb b/spec/ruby/language/yield_spec.rb
new file mode 100644
index 0000000000..e125cf8e73
--- /dev/null
+++ b/spec/ruby/language/yield_spec.rb
@@ -0,0 +1,220 @@
+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
+# because the outer Array is a consequence of |*a| but it is necessary to
+# clearly distinguish some behaviors.
+
+describe "The yield call" do
+ before :each do
+ @y = YieldSpecs::Yielder.new
+ end
+
+ describe "taking no arguments" do
+ it "raises a LocalJumpError when the method is not passed a block" do
+ -> { @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
+ -> { @y.s(1) }.should raise_error(LocalJumpError)
+ end
+ end
+
+ describe "yielding to a literal block" do
+ it "passes an empty Array when the argument is an empty Array" do
+ @y.s([]) { |*a| a }.should == [[]]
+ end
+
+ it "passes nil as a value" do
+ @y.s(nil) { |*a| a }.should == [nil]
+ end
+
+ it "passes a single value" do
+ @y.s(1) { |*a| a }.should == [1]
+ end
+
+ it "passes a single, multi-value Array" do
+ @y.s([1, 2, 3]) { |*a| a }.should == [[1, 2, 3]]
+ end
+ end
+
+ describe "yielding to a lambda" do
+ it "passes an empty Array when the argument is an empty Array" do
+ @y.s([], &-> *a { a }).should == [[]]
+ end
+
+ it "passes nil as a value" do
+ @y.s(nil, &-> *a { a }).should == [nil]
+ end
+
+ it "passes a single value" do
+ @y.s(1, &-> *a { a }).should == [1]
+ end
+
+ it "passes a single, multi-value Array" do
+ @y.s([1, 2, 3], &-> *a { a }).should == [[1, 2, 3]]
+ end
+
+ it "raises an ArgumentError if too few arguments are passed" do
+ -> {
+ @y.s(1, &-> 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
+ -> { @y.m(1, 2, 3) }.should raise_error(LocalJumpError)
+ end
+
+ it "passes the arguments to the block" do
+ @y.m(1, 2, 3) { |*a| a }.should == [1, 2, 3]
+ end
+
+ it "passes only the first argument if the block takes one parameter" do
+ @y.m(1, 2, 3) { |a| a }.should == 1
+ end
+
+ it "raises an ArgumentError if too many arguments are passed to a lambda" do
+ -> {
+ @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
+ -> {
+ @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
+ -> { @y.r(0) }.should raise_error(LocalJumpError)
+ end
+
+ it "passes a single value" do
+ @y.r(1) { |*a| a }.should == [1]
+ end
+
+ it "passes no arguments when the argument is an empty Array" do
+ @y.r([]) { |*a| a }.should == []
+ end
+
+ it "passes the value when the argument is an Array containing a single value" do
+ @y.r([1]) { |*a| a }.should == [1]
+ end
+
+ it "passes the values of the Array as individual arguments" do
+ @y.r([1, 2, 3]) { |*a| a }.should == [1, 2, 3]
+ end
+
+ it "passes the element of a single element Array" do
+ @y.r([[1, 2]]) { |*a| a }.should == [[1, 2]]
+ @y.r([nil]) { |*a| a }.should == [nil]
+ @y.r([[]]) { |*a| a }.should == [[]]
+ end
+
+ it "passes no values when give nil as an argument" do
+ @y.r(nil) { |*a| a }.should == []
+ end
+ end
+
+ describe "taking multiple arguments with a splat" do
+ it "raises a LocalJumpError when the method is not passed a block" do
+ -> { @y.rs(1, 2, [3, 4]) }.should raise_error(LocalJumpError)
+ end
+
+ it "passes the arguments to the block" do
+ @y.rs(1, 2, 3) { |*a| a }.should == [1, 2, 3]
+ end
+
+ it "does not pass an argument value if the splatted argument is an empty Array" do
+ @y.rs(1, 2, []) { |*a| a }.should == [1, 2]
+ end
+
+ it "passes the Array elements as arguments if the splatted argument is a non-empty Array" do
+ @y.rs(1, 2, [3]) { |*a| a }.should == [1, 2, 3]
+ @y.rs(1, 2, [nil]) { |*a| a }.should == [1, 2, nil]
+ @y.rs(1, 2, [[]]) { |*a| a }.should == [1, 2, []]
+ @y.rs(1, 2, [3, 4, 5]) { |*a| a }.should == [1, 2, 3, 4, 5]
+ end
+
+ it "does not pass an argument value if the splatted argument is nil" do
+ @y.rs(1, 2, nil) { |*a| a }.should == [1, 2]
+ end
+ end
+
+ describe "taking matching arguments with splats and post args" do
+ it "raises a LocalJumpError when the method is not passed a block" do
+ -> { @y.rs(1, 2, [3, 4]) }.should raise_error(LocalJumpError)
+ end
+
+ it "passes the arguments to the block" do
+ @y.rs([1, 2], 3, 4) { |(*a, b), c, d| [a, b, c, d] }.should == [[1], 2, 3, 4]
+ 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