summaryrefslogtreecommitdiff
path: root/spec/ruby/language
diff options
context:
space:
mode:
authoreregon <eregon@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2017-09-20 20:18:52 +0000
committereregon <eregon@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2017-09-20 20:18:52 +0000
commit1d15d5f08032acf1b7bceacbb450d617ff6e0931 (patch)
treea3785a79899302bc149e4a6e72f624ac27dc1f10 /spec/ruby/language
parent75bfc6440d595bf339007f4fb280fd4d743e89c1 (diff)
Move spec/rubyspec to spec/ruby for consistency
* Other ruby implementations use the spec/ruby directory. [Misc #13792] [ruby-core:82287] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@59979 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'spec/ruby/language')
-rw-r--r--spec/ruby/language/BEGIN_spec.rb32
-rw-r--r--spec/ruby/language/README30
-rw-r--r--spec/ruby/language/alias_spec.rb246
-rw-r--r--spec/ruby/language/and_spec.rb80
-rw-r--r--spec/ruby/language/array_spec.rb155
-rw-r--r--spec/ruby/language/block_spec.rb865
-rw-r--r--spec/ruby/language/break_spec.rb348
-rw-r--r--spec/ruby/language/case_spec.rb382
-rw-r--r--spec/ruby/language/class_spec.rb332
-rw-r--r--spec/ruby/language/class_variable_spec.rb84
-rw-r--r--spec/ruby/language/constants_spec.rb613
-rw-r--r--spec/ruby/language/def_spec.rb714
-rw-r--r--spec/ruby/language/defined_spec.rb1132
-rw-r--r--spec/ruby/language/encoding_spec.rb36
-rw-r--r--spec/ruby/language/ensure_spec.rb126
-rw-r--r--spec/ruby/language/execution_spec.rb15
-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.rb11
-rw-r--r--spec/ruby/language/fixtures/block.rb57
-rw-r--r--spec/ruby/language/fixtures/break.rb263
-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/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.rb98
-rw-r--r--spec/ruby/language/fixtures/constants_sclass.rb54
-rw-r--r--spec/ruby/language/fixtures/def.rb8
-rw-r--r--spec/ruby/language/fixtures/defined.rb298
-rw-r--r--spec/ruby/language/fixtures/dollar_zero.rb6
-rw-r--r--spec/ruby/language/fixtures/ensure.rb72
-rw-r--r--spec/ruby/language/fixtures/file.rb1
-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.rbbin0 -> 181 bytes
-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_ascii8bit.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/match_operators.rb9
-rw-r--r--spec/ruby/language/fixtures/metaclass.rb34
-rw-r--r--spec/ruby/language/fixtures/module.rb24
-rw-r--r--spec/ruby/language/fixtures/next.rb134
-rw-r--r--spec/ruby/language/fixtures/precedence.rb16
-rw-r--r--spec/ruby/language/fixtures/private.rb59
-rw-r--r--spec/ruby/language/fixtures/rescue.rb63
-rw-r--r--spec/ruby/language/fixtures/return.rb139
-rw-r--r--spec/ruby/language/fixtures/send.rb140
-rw-r--r--spec/ruby/language/fixtures/squiggly_heredoc.rb39
-rw-r--r--spec/ruby/language/fixtures/super.rb569
-rw-r--r--spec/ruby/language/fixtures/variables.rb85
-rw-r--r--spec/ruby/language/fixtures/yield.rb37
-rw-r--r--spec/ruby/language/for_spec.rb177
-rw-r--r--spec/ruby/language/hash_spec.rb154
-rw-r--r--spec/ruby/language/heredoc_spec.rb87
-rw-r--r--spec/ruby/language/if_spec.rb354
-rw-r--r--spec/ruby/language/lambda_spec.rb573
-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.rb62
-rw-r--r--spec/ruby/language/match_spec.rb74
-rw-r--r--spec/ruby/language/metaclass_spec.rb143
-rw-r--r--spec/ruby/language/method_spec.rb1290
-rw-r--r--spec/ruby/language/module_spec.rb91
-rw-r--r--spec/ruby/language/next_spec.rb410
-rw-r--r--spec/ruby/language/not_spec.rb51
-rw-r--r--spec/ruby/language/numbers_spec.rb97
-rw-r--r--spec/ruby/language/optional_assignments_spec.rb226
-rw-r--r--spec/ruby/language/or_spec.rb90
-rw-r--r--spec/ruby/language/order_spec.rb75
-rw-r--r--spec/ruby/language/precedence_spec.rb448
-rw-r--r--spec/ruby/language/predefined/data_spec.rb29
-rw-r--r--spec/ruby/language/predefined/fixtures/data1.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/data2.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/data3.rb7
-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_only.rb2
-rw-r--r--spec/ruby/language/predefined/fixtures/print_data.rb3
-rw-r--r--spec/ruby/language/predefined_spec.rb1221
-rw-r--r--spec/ruby/language/private_spec.rb67
-rw-r--r--spec/ruby/language/proc_spec.rb220
-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.rb48
-rw-r--r--spec/ruby/language/regexp/character_classes_spec.rb610
-rw-r--r--spec/ruby/language/regexp/encoding_spec.rb103
-rw-r--r--spec/ruby/language/regexp/escapes_spec.rb81
-rw-r--r--spec/ruby/language/regexp/grouping_spec.rb23
-rw-r--r--spec/ruby/language/regexp/interpolation_spec.rb58
-rw-r--r--spec/ruby/language/regexp/modifiers_spec.rb110
-rw-r--r--spec/ruby/language/regexp/repetition_spec.rb57
-rw-r--r--spec/ruby/language/regexp_spec.rb150
-rw-r--r--spec/ruby/language/rescue_spec.rb293
-rw-r--r--spec/ruby/language/retry_spec.rb52
-rw-r--r--spec/ruby/language/return_spec.rb245
-rw-r--r--spec/ruby/language/safe_navigator_spec.rb101
-rw-r--r--spec/ruby/language/send_spec.rb521
-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.rb293
-rw-r--r--spec/ruby/language/string_spec.rb282
-rw-r--r--spec/ruby/language/super_spec.rb282
-rw-r--r--spec/ruby/language/symbol_spec.rb93
-rw-r--r--spec/ruby/language/throw_spec.rb80
-rw-r--r--spec/ruby/language/undef_spec.rb72
-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.rb760
-rw-r--r--spec/ruby/language/while_spec.rb344
-rw-r--r--spec/ruby/language/yield_spec.rb179
117 files changed, 19062 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..826d5f0c89
--- /dev/null
+++ b/spec/ruby/language/BEGIN_spec.rb
@@ -0,0 +1,32 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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
+ lambda { eval "1.times { BEGIN { 1 } }" }.should raise_error(SyntaxError)
+ 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
+end
diff --git a/spec/ruby/language/README b/spec/ruby/language/README
new file mode 100644
index 0000000000..b9d969ba1e
--- /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 Fixnum.
+
+Behavorial 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..e9f0050e17
--- /dev/null
+++ b/spec/ruby/language/alias_spec.rb
@@ -0,0 +1,246 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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 doubule quoted symbol on the left-hand side" do
+ @meta.class_eval do
+ alias :"a" value
+ end
+ @obj.a.should == 5
+ end
+
+ it "works with an interoplated symbol on the left-hand side" do
+ @meta.class_eval do
+ alias :"#{'a'}" 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 doubule quoted symbol on the right-hand side" do
+ @meta.class_eval do
+ alias a :"value"
+ end
+ @obj.a.should == 5
+ end
+
+ it "works with an interoplated symbol on the right-hand side" do
+ @meta.class_eval do
+ alias a :"#{'value'}"
+ 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
+ lambda { 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
+ lambda { 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
+ lambda { 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
+ class Sub < AliasObject;end
+ 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
+ Sub.new.test("testing").should == 4
+ end
+
+ it "is not allowed against Fixnum or String instances" do
+ lambda do
+ 1.instance_eval do
+ alias :foo :to_s
+ end
+ end.should raise_error(TypeError)
+
+ lambda 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", escape: true).should == "Object"
+ end
+
+ it "raises a NameError when passed a missing name" do
+ lambda { @meta.class_eval { alias undef_method not_exist } }.should raise_error(NameError) { |e|
+ # a NameError and not a NoMethodError
+ e.class.should == NameError
+ }
+ end
+end
diff --git a/spec/ruby/language/and_spec.rb b/spec/ruby/language/and_spec.rb
new file mode 100644
index 0000000000..e084fd3cef
--- /dev/null
+++ b/spec/ruby/language/and_spec.rb
@@ -0,0 +1,80 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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..c3ed8c14c5
--- /dev/null
+++ b/spec/ruby/language/array_spec.rb
@@ -0,0 +1,155 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/array', __FILE__)
+
+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
+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/block_spec.rb b/spec/ruby/language/block_spec.rb
new file mode 100644
index 0000000000..219d61e2bf
--- /dev/null
+++ b/spec/ruby/language/block_spec.rb
@@ -0,0 +1,865 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/block', __FILE__)
+
+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 "assgins elements to post arguments" do
+ m([1, 2]) { |a=5, b, c, d| [a, b, c, d] }.should == [5, 1, 2, nil]
+ end
+
+ it "assigns elements to required arguments when a keyword rest argument is present" do
+ m([1, 2]) { |a, **k| [a, k] }.should == [1, {}]
+ end
+
+ it "assigns elements to mixed argument types" do
+ result = m([1, 2, 3, {x: 9}]) { |a, b=5, *c, d, e: 2, **k| [a, b, c, d, e, k] }
+ result.should == [1, 2, [], 3, 2, {x: 9}]
+ end
+
+ it "assigns symbol keys from a Hash to keyword arguments" do
+ result = m(["a" => 1, a: 10]) { |a=nil, **b| [a, b] }
+ result.should == [{"a" => 1}, a: 10]
+ end
+
+ it "assigns symbol keys from a Hash returned by #to_hash to keyword arguments" do
+ obj = mock("coerce block keyword arguments")
+ obj.should_receive(:to_hash).and_return({"a" => 1, b: 2})
+
+ result = m([obj]) { |a=nil, **b| [a, b] }
+ result.should == [{"a" => 1}, b: 2]
+ end
+
+ ruby_version_is "2.2.1" do # SEGV on MRI 2.2.0
+ it "calls #to_hash on the argument but does not use the result when no keywords are present" do
+ obj = mock("coerce block keyword arguments")
+ obj.should_receive(:to_hash).and_return({"a" => 1, "b" => 2})
+
+ result = m([obj]) { |a=nil, **b| [a, b] }
+ result.should == [{"a" => 1, "b" => 2}, {}]
+ end
+ end
+
+ it "assigns non-symbol keys to non-keyword arguments" do
+ result = m(["a" => 10, b: 2]) { |a=nil, **b| [a, b] }
+ result.should == [{"a" => 10}, {b: 2}]
+ end
+
+ it "does not treat hashes with string keys as keyword arguments" do
+ result = m(["a" => 10]) { |a = nil, **b| [a, b] }
+ result.should == [{"a" => 10}, {}]
+ end
+
+ it "calls #to_hash on the last element if keyword arguments are present" do
+ obj = mock("destructure block keyword arguments")
+ obj.should_receive(:to_hash).and_return({x: 9})
+
+ result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] }
+ result.should == [1, [2], 3, {x: 9}]
+ end
+
+ it "assigns the last element to a non-keyword argument if #to_hash returns nil" do
+ obj = mock("destructure block keyword arguments")
+ obj.should_receive(:to_hash).and_return(nil)
+
+ result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] }
+ result.should == [1, [2, 3], obj, {}]
+ end
+
+ it "calls #to_hash on the last element when there are more arguments than parameters" do
+ x = mock("destructure matching block keyword argument")
+ x.should_receive(:to_hash).and_return({x: 9})
+
+ result = m([1, 2, 3, {y: 9}, 4, 5, x]) { |a, b=5, c, **k| [a, b, c, k] }
+ result.should == [1, 2, 3, {x: 9}]
+ end
+
+ it "raises a TypeError if #to_hash does not return a Hash" do
+ obj = mock("destructure block keyword arguments")
+ obj.should_receive(:to_hash).and_return(1)
+
+ lambda { m([1, 2, 3, obj]) { |a, *b, c, **k| } }.should raise_error(TypeError)
+ end
+
+ it "raises the error raised inside #to_hash" do
+ obj = mock("destructure block keyword arguments")
+ error = RuntimeError.new("error while converting to a hash")
+ obj.should_receive(:to_hash).and_raise(error)
+
+ lambda { m([1, 2, 3, obj]) { |a, *b, c, **k| } }.should raise_error(error)
+ 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 "raises a TypeError if #to_ary does not return an Array" do
+ obj = mock("destructure block arguments")
+ obj.should_receive(:to_ary).and_return(1)
+
+ lambda { m(obj) { |a, b| } }.should raise_error(TypeError)
+ 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
+ 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
+ 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
+ end
+
+ describe "taking |a, b| arguments" do
+ it "assgins 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)
+
+ lambda { @y.s(obj) { |a, b| } }.should raise_error(TypeError)
+ end
+
+ it "raises the original exception if #to_ary raises an exception" do
+ obj = mock("block yield to_ary raising an exception")
+ obj.should_receive(:to_ary).and_raise(ZeroDivisionError)
+
+ lambda { @y.s(obj) { |a, b| } }.should raise_error(ZeroDivisionError)
+ 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)
+
+ lambda { @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 "assgins 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)
+
+ lambda { @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)
+
+ lambda { @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)
+
+ lambda { @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 "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
+ lambda { eval "lambda { |x,x| }" }.should raise_error(SyntaxError)
+ lambda { eval "->(x,x) {}" }.should raise_error(SyntaxError)
+ lambda { eval "Proc.new { |x,x| }" }.should raise_error(SyntaxError)
+ end
+
+ it "accepts unnamed arguments" do
+ eval("lambda { |_,_| }").should be_an_instance_of(Proc)
+ eval("->(_,_) {}").should be_an_instance_of(Proc)
+ eval("Proc.new { |_,_| }").should be_an_instance_of(Proc)
+ 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
+ lambda { eval "[1].each {|foo; foo| }" }.should raise_error(SyntaxError)
+ lambda { eval "[1].each {|foo, bar; glark, bar| }" }.should raise_error(SyntaxError)
+ end
+
+ it "can not be prefixed with an asterisk" do
+ lambda { eval "[1].each {|foo; *bar| }" }.should raise_error(SyntaxError)
+ lambda do
+ eval "[1].each {|foo, bar; glark, *fnord| }"
+ end.should raise_error(SyntaxError)
+ end
+
+ it "can not be prefixed with an ampersand" do
+ lambda { eval "[1].each {|foo; &bar| }" }.should raise_error(SyntaxError)
+ lambda do
+ eval "[1].each {|foo, bar; glark, &fnord| }"
+ end.should raise_error(SyntaxError)
+ end
+
+ it "can not be assigned default values" do
+ lambda { eval "[1].each {|foo; bar=1| }" }.should raise_error(SyntaxError)
+ lambda do
+ eval "[1].each {|foo, bar; glark, fnord=:fnord| }"
+ end.should raise_error(SyntaxError)
+ end
+
+ it "need not be preceeded 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
+ lambda { eval "[1].each {|foo; bar; glark| }" }.should raise_error(SyntaxError)
+ lambda { 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" do
+ lambda {
+ lambda do |*a, b|
+ [a, b]
+ end.call
+ }.should raise_error(ArgumentError)
+ 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
+ it "shadows an existing local with the same name as the argument" do
+ a = 1
+ -> {
+ @proc = eval "proc { |a=a| a }"
+ }.should complain(/circular argument reference/)
+ @proc.call.should == nil
+ end
+
+ it "shadows an existing method with the same name as the argument" do
+ def a; 1; end
+ -> {
+ @proc = eval "proc { |a=a| a }"
+ }.should complain(/circular argument reference/)
+ @proc.call.should == nil
+ 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
+ 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
diff --git a/spec/ruby/language/break_spec.rb b/spec/ruby/language/break_spec.rb
new file mode 100644
index 0000000000..794eed2dd9
--- /dev/null
+++ b/spec/ruby/language/break_spec.rb
@@ -0,0 +1,348 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/break', __FILE__)
+
+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
+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
+ lambda { @program.break_in_method }.should raise_error(LocalJumpError)
+ ScratchPad.recorded.should == [:a, :xa, :d, :b]
+ end
+
+ it "raises a LocalJumpError when invoking the block from a method" do
+ lambda { @program.break_in_nested_method }.should raise_error(LocalJumpError)
+ ScratchPad.recorded.should == [:a, :xa, :cc, :aa, :b]
+ end
+
+ it "raises a LocalJumpError when yielding to the block" do
+ lambda { @program.break_in_yielding_method }.should raise_error(LocalJumpError)
+ ScratchPad.recorded.should == [:a, :xa, :cc, :aa, :b]
+ end
+ end
+
+ describe "from a scope that has returned" do
+ it "raises a LocalJumpError when calling the block from a method" do
+ lambda { @program.break_in_method_captured }.should raise_error(LocalJumpError)
+ ScratchPad.recorded.should == [:a, :za, :xa, :zd, :zb]
+ end
+
+ it "raises a LocalJumpError when yielding to the block" do
+ lambda { @program.break_in_yield_captured }.should raise_error(LocalJumpError)
+ 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
+ ScratchPad << :a
+ thread_with_break = Thread.new do
+ ScratchPad << :b
+ break :break
+ ScratchPad << :c
+ end
+
+ lambda { thread_with_break.value }.should raise_error(LocalJumpError)
+ ScratchPad.recorded.should == [:a, :b]
+ 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 = lambda {
+ 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)
+ lambda {
+ 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
+ lambda { 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
+
+
+# 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
+end
diff --git a/spec/ruby/language/case_spec.rb b/spec/ruby/language/case_spec.rb
new file mode 100644
index 0000000000..cb0e8150bf
--- /dev/null
+++ b/spec/ruby/language/case_spec.rb
@@ -0,0 +1,382 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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 "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 "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
+ lambda {
+ 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
+ lambda {
+ 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
+end
+
+describe "The 'case'-construct with no target expression" do
+ it "evaluates the body of the first clause when at least one of its condition expressions is true" do
+ case
+ when true, false; 'foo'
+ end.should == 'foo'
+ end
+
+ it "evaluates the body of the first when clause that is not false/nil" do
+ case
+ when false; 'foo'
+ when 2; 'bar'
+ when 1 == 1; 'baz'
+ end.should == 'bar'
+
+ case
+ when false; 'foo'
+ when nil; 'foo'
+ when 1 == 1; 'bar'
+ end.should == 'bar'
+ end
+
+ it "evaluates the body of the else clause if all when clauses are false/nil" do
+ case
+ when false; 'foo'
+ when nil; 'foo'
+ when 1 == 2; 'bar'
+ else 'baz'
+ end.should == 'baz'
+ end
+
+ it "evaluates multiple conditional expressions as a boolean disjunction" do
+ case
+ when true, false; 'foo'
+ else 'bar'
+ end.should == 'foo'
+
+ case
+ when false, true; 'foo'
+ else 'bar'
+ end.should == 'foo'
+ end
+
+ it "evaluates true as only 'true' when true is the first clause" do
+ case 1
+ when true; "bad"
+ when Integer; "good"
+ 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
+end
diff --git a/spec/ruby/language/class_spec.rb b/spec/ruby/language/class_spec.rb
new file mode 100644
index 0000000000..354570e5e9
--- /dev/null
+++ b/spec/ruby/language/class_spec.rb
@@ -0,0 +1,332 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../../fixtures/class', __FILE__)
+
+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
+
+ ruby_version_is "2.3" do
+ it "does not raise a SyntaxError when opening a class without a semicolon" do
+ eval "class ClassSpecsKeywordWithoutSemicolon end"
+ ClassSpecsKeywordWithoutSemicolon.should be_an_instance_of(Class)
+ end
+ 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
+ lambda {
+ class ClassSpecsNumber
+ end
+ }.should raise_error(TypeError)
+ end
+
+ # test case known to be detecting bugs (JRuby, MRI)
+ it "raises TypeError if the constant qualifying the class is nil" do
+ lambda {
+ class nil::Foo
+ end
+ }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError if any constant qualifying the class is not a Module" do
+ lambda {
+ class ClassSpecs::Number::MyClass
+ end
+ }.should raise_error(TypeError)
+
+ lambda {
+ class ClassSpecsNumber::MyClass
+ end
+ }.should raise_error(TypeError)
+ 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
+ lambda {
+ 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
+
+ lambda {
+ class SuperclassReopenedBasicObject < BasicObject
+ end
+ }.should raise_error(TypeError, /superclass mismatch/)
+ SuperclassReopenedBasicObject.superclass.should == A
+ end
+ end
+
+ # [Bug #12367] [ruby-core:75446]
+ ruby_version_is "2.4" do # Until backported
+ it "raises an error when reopening a class with Object as superclass" do
+ module ClassSpecs
+ class SuperclassReopenedObject < A
+ end
+ SuperclassReopenedObject.superclass.should == A
+
+ lambda {
+ class SuperclassReopenedObject < Object
+ end
+ }.should raise_error(TypeError, /superclass mismatch/)
+ SuperclassReopenedObject.superclass.should == A
+ end
+ 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
+
+ lambda {
+ 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
+
+ lambda {
+ 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
+ lambda { 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 Howdy
+ end
+
+ def self.get_class_name
+ Howdy.name
+ end
+ end
+
+ Howdy.name.should == 'Howdy'
+ klass.get_class_name.should == 'Howdy'
+ 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'
+ 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
+ lambda {
+ eval <<-CODE
+ class << 1
+ def xyz
+ self
+ end
+ end
+ CODE
+ }.should raise_error(TypeError)
+ end
+
+ it "allows accessing the block of the original scope" do
+ ClassSpecs.sclass_with_block { 123 }.should == 123
+ 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
+ lambda { class ClassSpecs::A < Array; end }.should raise_error(TypeError)
+ end
+
+ it "adds new methods to subclasses" do
+ lambda { ClassSpecs::M.m }.should raise_error(NoMethodError)
+ class ClassSpecs::L
+ def self.m
+ 1
+ end
+ end
+ ClassSpecs::M.m.should == 1
+ ClassSpecs::L.singleton_class.send(:remove_method, :m)
+ 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..463f731a93
--- /dev/null
+++ b/spec/ruby/language/class_variable_spec.rb
@@ -0,0 +1,84 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../../fixtures/class_variables', __FILE__)
+
+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)
+
+ lambda { a.class_variable_get(:@@cv) }.should raise_error(NameError)
+ b.class_variable_get(:@@cv).should == :value
+ c.class_variable_get(:@@cv).should == :value
+
+ # updates the same variable
+ c.class_variable_set(:@@cv, :next)
+
+ lambda { a.class_variable_get(:@@cv) }.should raise_error(NameError)
+ b.class_variable_get(:@@cv).should == :next
+ c.class_variable_get(:@@cv).should == :next
+ end
+end
diff --git a/spec/ruby/language/constants_spec.rb b/spec/ruby/language/constants_spec.rb
new file mode 100644
index 0000000000..e68ba495be
--- /dev/null
+++ b/spec/ruby/language/constants_spec.rb
@@ -0,0 +1,613 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../../fixtures/constants', __FILE__)
+require File.expand_path('../fixtures/constants_sclass', __FILE__)
+require File.expand_path('../fixtures/constant_visibility', __FILE__)
+
+# 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
+ lambda do
+ ConstantSpecs::ContainerA::ChildA::CS_CONST14
+ end.should raise_error(NameError)
+ lambda { 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
+ 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
+ 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
+ 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
+ end
+
+ it "searches the superclass chain" do
+ ConstantSpecs::ModuleA::CS_CONST105 = :const105
+ ConstantSpecs::ContainerB::ChildB::CS_CONST105.should == :const105
+ end
+
+ it "searches Object if no class or module qualifier is given" do
+ CS_CONST106 = :const106
+ CS_CONST106.should == :const106
+ end
+
+ it "searches Object if a toplevel qualifier (::X) is given" do
+ ::CS_CONST107 = :const107
+ ::CS_CONST107.should == :const107
+ end
+
+ it "does not search the singleton class of the class or module" do
+ class << ConstantSpecs::ContainerB::ChildB
+ CS_CONST108 = :const108_1
+ end
+
+ lambda do
+ ConstantSpecs::ContainerB::ChildB::CS_CONST108
+ end.should raise_error(NameError)
+
+ module ConstantSpecs
+ class << self
+ CS_CONST108 = :const108_2
+ end
+ end
+
+ lambda { ConstantSpecs::CS_CONST108 }.should raise_error(NameError)
+ 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
+ end
+
+ it "evaluates the right hand side before evaluating a constant path" do
+ mod = Module.new
+
+ mod.module_eval <<-EOC
+ ConstantSpecsRHS::B = begin
+ module ConstantSpecsRHS; end
+
+ "hello"
+ end
+ EOC
+
+ mod::ConstantSpecsRHS::B.should == 'hello'
+ end
+ end
+
+ it "raises a NameError if no constant is defined in the search path" do
+ lambda { ConstantSpecs::ParentA::CS_CONSTX }.should raise_error(NameError)
+ 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
+ lambda { CS_CONST1::CS_CONST }.should raise_error(TypeError)
+ lambda { 1::CS_CONST }.should raise_error(TypeError)
+ lambda { "mod"::CS_CONST }.should raise_error(TypeError)
+ lambda { false::CS_CONST }.should raise_error(TypeError)
+ 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
+ lambda { 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
+ lambda 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ end
+
+ it "does not search the lexical scope of the caller" do
+ ConstantSpecs::ClassB::CS_CONST209 = :const209
+
+ lambda { ConstantSpecs::ClassB.const209 }.should raise_error(NameError)
+ 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
+ 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
+ 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
+ end
+
+ it "does not search the lexical scope of qualifying modules" do
+ ConstantSpecs::ContainerB::CS_CONST214 = :const214
+
+ lambda do
+ ConstantSpecs::ContainerB::ChildB.const214
+ end.should raise_error(NameError)
+ end
+ end
+
+ it "raises a NameError if no constant is defined in the search path" do
+ lambda { ConstantSpecs::ParentA.constx }.should raise_error(NameError)
+ end
+
+ it "sends #const_missing to the original class or module scope" do
+ ConstantSpecs::ClassA.constx.should == :CS_CONSTX
+ ConstantSpecs::ClassA.new.constx.should == :CS_CONSTX
+ end
+
+ describe "with ||=" do
+ it "assignes constant if previously undefined" do
+ ConstantSpecs.should_not have_constant(:OpAssignUndefined)
+ # Literally opening the module is required to avoid content
+ # re-assignment error
+ module ConstantSpecs
+ OpAssignUndefined ||= 42
+ end
+ ConstantSpecs::OpAssignUndefined.should == 42
+ ConstantSpecs.send(:remove_const, :OpAssignUndefined)
+ end
+ 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
+
+ ruby_version_is "2.3" do
+ it "uses its own namespace for each object" do
+ a = ConstantSpecs::CS_SINGLETON2[0].foo
+ b = ConstantSpecs::CS_SINGLETON2[1].foo
+ [a, b].should == [1, 2]
+ end
+
+ it "uses its own namespace for 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
+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/)
+
+ lambda {mod::Foo}.should raise_error(NameError)
+ end
+
+ describe "in a module" do
+ it "cannot be accessed from outside the module" do
+ lambda do
+ ConstantVisibility::PrivConstModule::PRIVATE_CONSTANT_MODULE
+ end.should raise_error(NameError)
+ end
+
+ it "cannot be reopened as a module from scope where constant would be private" do
+ lambda do
+ module ConstantVisibility::ModuleContainer::PrivateModule; end
+ end.should raise_error(NameError)
+ end
+
+ it "cannot be reopened as a class from scope where constant would be private" do
+ lambda do
+ 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
+ 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
+ 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::PrivConstModuleChild.new.private_constant_from_include.should be_true
+ end
+
+ it "is defined? from classes that include the module" do
+ ConstantVisibility::PrivConstModuleChild.new.defined_from_include.should == "constant"
+ end
+ end
+
+ describe "in a class" do
+ it "cannot be accessed from outside the class" do
+ lambda do
+ ConstantVisibility::PrivConstClass::PRIVATE_CONSTANT_CLASS
+ end.should raise_error(NameError)
+ end
+
+ it "cannot be reopened as a module" do
+ lambda do
+ module ConstantVisibility::ClassContainer::PrivateModule; end
+ end.should raise_error(NameError)
+ end
+
+ it "cannot be reopened as a class" do
+ lambda 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
+ 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
+ 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
+ lambda 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
+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
diff --git a/spec/ruby/language/def_spec.rb b/spec/ruby/language/def_spec.rb
new file mode 100644
index 0000000000..55ee283b90
--- /dev/null
+++ b/spec/ruby/language/def_spec.rb
@@ -0,0 +1,714 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/def', __FILE__)
+
+# 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 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
+ lambda { eval 'def foo(a, *b, *c); end' }.should raise_error(SyntaxError)
+ end
+
+ it "requires the presence of any arguments that precede the *" do
+ def foo(a, b, *c); end
+ lambda { foo 1 }.should raise_error(ArgumentError)
+ 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
+ lambda { foo }.should raise_error(ArgumentError)
+ 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
+ lambda { foo }.should raise_error(ArgumentError)
+ 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
+
+ it "shadows an existing method with the same name as the local" do
+ def bar
+ 1
+ end
+ -> {
+ eval "def foo(bar = bar)
+ bar
+ end"
+ }.should complain(/circular argument reference/)
+ foo.should == nil
+ foo(2).should == 2
+ end
+
+ it "calls a method with the same name as the local when explicitly using ()" do
+ 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 RuntimeError if frozen" do
+ obj = Object.new
+ obj.freeze
+ lambda { def obj.foo; end }.should raise_error(RuntimeError)
+ 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 = lambda {|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 = lambda {|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
+ lambda { 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
+ lambda { Object.new.a_singleton_method }.should raise_error(NoMethodError)
+ end
+
+ it "raises RuntimeError if frozen" do
+ obj = Object.new
+ obj.freeze
+
+ class << obj
+ lambda { def foo; end }.should raise_error(RuntimeError)
+ 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
+
+ lambda { DefSpecNested.a_class_method }.should raise_error(NoMethodError)
+ DefSpecNested.create_class_method.should == DefSpecNested
+ DefSpecNested.a_class_method.should == DefSpecNested
+ lambda { Object.a_class_method }.should raise_error(NoMethodError)
+ lambda { DefSpecNested.new.a_class_method }.should raise_error(NoMethodError)
+ 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
+ lambda { 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
+ 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
+ end
+
+ it "defines methods as public by default" 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
+ 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
+ lambda { 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
+ lambda { 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
+ lambda { 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
+ lambda { 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
+ lambda { 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
+ lambda { 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
+ lambda { 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
+ lambda { 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
+
+ lambda { 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
+
+ lambda { Object.an_eval_class_method }.should raise_error(NoMethodError)
+ lambda { 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
+ lambda { 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
+ lambda { foo(1,2) }.should raise_error(ArgumentError)
+ 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]
+ lambda { bar(3,4,5) }.should raise_error(ArgumentError)
+ end
+end
+
+describe "The def keyword" do
+ describe "within a closure" do
+ it "looks outside the closure for the visibility" do
+ module DefSpecsLambdaVisibility
+ private
+
+ lambda {
+ 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..458c74258f
--- /dev/null
+++ b/spec/ruby/language/defined_spec.rb
@@ -0,0 +1,1132 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/defined', __FILE__)
+
+describe "The defined? keyword for literals" do
+ it "returns 'self' for self" do
+ ret = defined?(self)
+ ret.should == "self"
+ end
+
+ it "returns 'nil' for nil" do
+ ret = defined?(nil)
+ ret.should == "nil"
+ end
+
+ it "returns 'true' for true" do
+ ret = defined?(true)
+ ret.should == "true"
+ end
+
+ it "returns 'false' for false" do
+ ret = defined?(false)
+ ret.should == "false"
+ end
+
+ describe "for a literal Array" do
+
+ it "returns 'expression' if each element is defined" do
+ ret = defined?([Object, Array])
+ ret.should == "expression"
+ end
+
+ it "returns nil if one element is not defined" do
+ ret = defined?([NonExistantConstant, Array])
+ ret.should == nil
+ end
+
+ it "returns nil if all elements are not defined" do
+ ret = defined?([NonExistantConstant, AnotherNonExistantConstant])
+ ret.should == nil
+ end
+
+ end
+end
+
+describe "The defined? keyword when called with a method name" do
+ describe "without a receiver" do
+ it "returns 'method' if the method is defined" do
+ defined?(puts).should == "method"
+ 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 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
+end
+
+describe "The defined? keyword for an expression" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "returns 'assignment' for assigning a local variable" do
+ defined?(x = 2).should == "assignment"
+ 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 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?(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
+
+ 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 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 = defined?(!@@defined_specs_undefined_class_variable)
+ }.should complain(/class variable access from toplevel/)
+ @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 = defined?(not @@defined_specs_undefined_class_variable)
+ }.should complain(/class variable access from toplevel/)
+ @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
+ DefinedSpecs::Basic.new.local_variable_defined.should == "local-variable"
+ 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
+ DefinedSpecs::Basic.new.instance_variable_defined.should == "instance-variable"
+ 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
+
+ # 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 $1-$9" do
+ defined?($1).should be_nil
+ defined?($2).should be_nil
+ defined?($3).should be_nil
+ defined?($4).should be_nil
+ defined?($5).should be_nil
+ defined?($6).should be_nil
+ defined?($7).should be_nil
+ defined?($8).should be_nil
+ defined?($9).should be_nil
+ 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?($3).should be_nil
+ defined?($4).should be_nil
+ defined?($5).should be_nil
+ defined?($6).should be_nil
+ defined?($7).should be_nil
+ defined?($8).should be_nil
+ defined?($9).should be_nil
+ 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 $1-$9" do
+ defined?($1).should be_nil
+ defined?($2).should be_nil
+ defined?($3).should be_nil
+ defined?($4).should be_nil
+ defined?($5).should be_nil
+ defined?($6).should be_nil
+ defined?($7).should be_nil
+ defined?($8).should be_nil
+ defined?($9).should be_nil
+ 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?($3).should be_nil
+ defined?($4).should be_nil
+ defined?($5).should be_nil
+ defined?($6).should be_nil
+ defined?($7).should be_nil
+ defined?($8).should be_nil
+ defined?($9).should be_nil
+ 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
+ DefinedSpecs::Basic.new.class_variable_defined.should == "class variable"
+ 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
+ defined?(DefinedSpecs).should == "constant"
+ 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 "retuns 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 "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::B).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 'constant' when a constant is defined on top-level but not on the class" do
+ defined?(DefinedSpecs::Basic::String).should == "constant"
+ 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::B).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
+ -> {
+ @@defined_specs_obj = DefinedSpecs::Basic
+ defined?(@@defined_specs_obj::Undefined).should be_nil
+ }.should complain(/class variable access from toplevel/)
+ end
+
+ it "returns 'constant' if the constant is defined in the scope of the class variable" do
+ -> {
+ @@defined_specs_obj = DefinedSpecs::Basic
+ defined?(@@defined_specs_obj::A).should == "constant"
+ }.should complain(/class variable access from toplevel/)
+ 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
+ DefinedSpecs::Basic.new.yield_block.should == "yield"
+ 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
+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
+ DefinedSpecs::Super.new.method_no_args.should == "super"
+ 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/encoding_spec.rb b/spec/ruby/language/encoding_spec.rb
new file mode 100644
index 0000000000..070fa52bba
--- /dev/null
+++ b/spec/ruby/language/encoding_spec.rb
@@ -0,0 +1,36 @@
+# -*- encoding: us-ascii -*-
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/coding_us_ascii', __FILE__)
+require File.expand_path('../fixtures/coding_utf_8', __FILE__)
+
+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__".force_encoding("US-ASCII")).should == Encoding::US_ASCII
+ eval("__ENCODING__".force_encoding("ASCII-8BIT")).should == Encoding::ASCII_8BIT
+ end
+
+ it "is the encoding specified by a magic comment inside an eval" do
+ code = "# encoding: ASCII-8BIT\n__ENCODING__".force_encoding("US-ASCII")
+ eval(code).should == Encoding::ASCII_8BIT
+
+ code = "# encoding: us-ascii\n__ENCODING__".force_encoding("ASCII-8BIT")
+ 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
+ lambda { 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..13575fccc5
--- /dev/null
+++ b/spec/ruby/language/ensure_spec.rb
@@ -0,0 +1,126 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/ensure', __FILE__)
+
+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
+ lambda {
+ begin
+ ScratchPad << :begin
+ raise "An exception occured!"
+ ensure
+ ScratchPad << :ensure
+ end
+ }.should raise_error(RuntimeError)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+ end
+
+ it "is executed when an exception is raised and rescued in it's corresponding begin block" do
+ begin
+ begin
+ ScratchPad << :begin
+ raise "An exception occured!"
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+
+ ScratchPad.recorded.should == [:begin, :rescue, :ensure]
+ end
+ end
+
+ it "is executed even when a symbol is thrown in it's corresponding begin block" do
+ begin
+ catch(:symbol) do
+ begin
+ ScratchPad << :begin
+ throw(:symbol)
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+ 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
+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
+ lambda { @obj.raise_in_method_with_ensure }.should raise_error(RuntimeError)
+ @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
+end
diff --git a/spec/ruby/language/execution_spec.rb b/spec/ruby/language/execution_spec.rb
new file mode 100644
index 0000000000..3e6e7ff48c
--- /dev/null
+++ b/spec/ruby/language/execution_spec.rb
@@ -0,0 +1,15 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+describe "``" do
+ it "returns the output of the executed sub-process" do
+ ip = 'world'
+ `echo disc #{ip}`.should == "disc world\n"
+ end
+end
+
+describe "%x" do
+ it "is the same as ``" do
+ ip = 'world'
+ %x(echo disc #{ip}).should == "disc world\n"
+ end
+end
diff --git a/spec/ruby/language/file_spec.rb b/spec/ruby/language/file_spec.rb
new file mode 100644
index 0000000000..409400ca83
--- /dev/null
+++ b/spec/ruby/language/file_spec.rb
@@ -0,0 +1,29 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../../fixtures/code_loading', __FILE__)
+require File.expand_path('../shared/__FILE__', __FILE__)
+
+describe "The __FILE__ pseudo-variable" do
+ it "raises a SyntaxError if assigned to" do
+ lambda { eval("__FILE__ = 1") }.should raise_error(SyntaxError)
+ end
+
+ it "equals (eval) inside an eval" do
+ eval("__FILE__").should == "(eval)"
+ end
+end
+
+describe "The __FILE__ pseudo-variable" do
+ it_behaves_like :language___FILE__, :require, CodeLoadingSpecs::Method.new
+end
+
+describe "The __FILE__ pseudo-variable" do
+ it_behaves_like :language___FILE__, :require, Kernel
+end
+
+describe "The __FILE__ pseudo-variable" do
+ it_behaves_like :language___FILE__, :load, CodeLoadingSpecs::Method.new
+end
+
+describe "The __FILE__ pseudo-variable" do
+ 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..4d8ce74ed6
--- /dev/null
+++ b/spec/ruby/language/fixtures/array.rb
@@ -0,0 +1,11 @@
+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
+end
diff --git a/spec/ruby/language/fixtures/block.rb b/spec/ruby/language/fixtures/block.rb
new file mode 100644
index 0000000000..9848d18776
--- /dev/null
+++ b/spec/ruby/language/fixtures/block.rb
@@ -0,0 +1,57 @@
+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
+ 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..50e7fcf5d9
--- /dev/null
+++ b/spec/ruby/language/fixtures/break.rb
@@ -0,0 +1,263 @@
+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 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 lambda {
+ note :b
+ if value
+ break :break
+ else
+ break
+ end
+ note :c
+ }.call
+ note :d
+ end
+
+ def break_in_nested_scope
+ note :a
+ l = lambda 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 = lambda 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 = lambda 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 = lambda 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..05af1d3fdc
--- /dev/null
+++ b/spec/ruby/language/fixtures/break_lambda_toplevel.rb
@@ -0,0 +1,9 @@
+print "a,"
+
+print lambda {
+ 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..a35cb8a8a1
--- /dev/null
+++ b/spec/ruby/language/fixtures/break_lambda_toplevel_block.rb
@@ -0,0 +1,23 @@
+print "a,"
+
+l = lambda {
+ 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..200040d614
--- /dev/null
+++ b/spec/ruby/language/fixtures/break_lambda_toplevel_method.rb
@@ -0,0 +1,17 @@
+print "a,"
+
+l = lambda {
+ 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/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..022554430e
--- /dev/null
+++ b/spec/ruby/language/fixtures/constant_visibility.rb
@@ -0,0 +1,98 @@
+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 PrivConstModuleChild
+ include PrivConstModule
+
+ def private_constant_from_include
+ PRIVATE_CONSTANT_MODULE
+ end
+
+ def defined_from_include
+ defined? 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..81bfce73d0
--- /dev/null
+++ b/spec/ruby/language/fixtures/def.rb
@@ -0,0 +1,8 @@
+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..d26e553c4b
--- /dev/null
+++ b/spec/ruby/language/fixtures/defined.rb
@@ -0,0 +1,298 @@
+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
+
+ 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 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
+
+ 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/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/ensure.rb b/spec/ruby/language/fixtures/ensure.rb
new file mode 100644
index 0000000000..0dad7d8401
--- /dev/null
+++ b/spec/ruby/language/fixtures/ensure.rb
@@ -0,0 +1,72 @@
+module EnsureSpec
+ class Container
+ attr_reader :executed
+
+ def initialize
+ @executed = []
+ end
+
+ def raise_in_method_with_ensure
+ @executed << :method
+ raise "An Exception"
+ 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
+ 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
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/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..d0558a2251
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb
Binary files differ
diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_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..a4d655ad02
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+p "abc".object_id == "abc".object_id
diff --git a/spec/ruby/language/fixtures/hash_strings_ascii8bit.rb b/spec/ruby/language/fixtures/hash_strings_ascii8bit.rb
new file mode 100644
index 0000000000..4ac11b9930
--- /dev/null
+++ b/spec/ruby/language/fixtures/hash_strings_ascii8bit.rb
@@ -0,0 +1,7 @@
+# encoding: ascii-8bit
+
+module HashStringsASCII8BIT
+ 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/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..a1990b9225
--- /dev/null
+++ b/spec/ruby/language/fixtures/metaclass.rb
@@ -0,0 +1,34 @@
+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..33d323846e
--- /dev/null
+++ b/spec/ruby/language/fixtures/module.rb
@@ -0,0 +1,24 @@
+module ModuleSpecs
+ module Modules
+ class Klass
+ end
+
+ A = "Module"
+ B = 1
+ C = nil
+ D = true
+ E = false
+ end
+
+ module Anonymous
+ end
+
+ module IncludedInObject
+ module IncludedModuleSpecs
+ end
+ end
+end
+
+class Object
+ include ModuleSpecs::IncludedInObject
+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/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/private.rb b/spec/ruby/language/fixtures/private.rb
new file mode 100644
index 0000000000..96f73cea3f
--- /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..3fa5df1eb5
--- /dev/null
+++ b/spec/ruby/language/fixtures/rescue.rb
@@ -0,0 +1,63 @@
+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
+end
diff --git a/spec/ruby/language/fixtures/return.rb b/spec/ruby/language/fixtures/return.rb
new file mode 100644
index 0000000000..0414c356e8
--- /dev/null
+++ b/spec/ruby/language/fixtures/return.rb
@@ -0,0 +1,139 @@
+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 it's old value.
+ #
+ # We can therefore use val to figure out what happened.
+ begin
+ val = foo()
+ ensure
+ if val != :good
+ return :bad
+ end
+ end
+
+ return val
+ 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/send.rb b/spec/ruby/language/fixtures/send.rb
new file mode 100644
index 0000000000..c3013616b2
--- /dev/null
+++ b/spec/ruby/language/fixtures/send.rb
@@ -0,0 +1,140 @@
+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_reader :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 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/squiggly_heredoc.rb b/spec/ruby/language/fixtures/squiggly_heredoc.rb
new file mode 100644
index 0000000000..afc87514c7
--- /dev/null
+++ b/spec/ruby/language/fixtures/squiggly_heredoc.rb
@@ -0,0 +1,39 @@
+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.least_indented_on_the_last_line
+ <<~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..4e89d59bed
--- /dev/null
+++ b/spec/ruby/language/fixtures/super.rb
@@ -0,0 +1,569 @@
+module Super
+ 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->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 = lambda { 15 }
+ [super { 14 }, super(&block_ref)]
+ end
+
+ def c
+ block_ref = lambda { 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 ZSuperWithUnderscores
+ class A
+ def m(*args)
+ args
+ end
+
+ def m_modified(*args)
+ args
+ end
+ end
+
+ class B < A
+ def m(_, _)
+ super
+ end
+
+ def m_modified(_, _)
+ _ = 14
+ super
+ end
+ end
+ end
+
+ module KeywordArguments
+ class A
+ def foo(**args)
+ args
+ end
+ end
+
+ class B < A
+ def foo(**)
+ super
+ end
+ end
+
+ class C < A
+ 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
+
+ module SplatAndKeyword
+ class A
+ def foo(*args, **options)
+ [args, options]
+ end
+ end
+
+ class B < A
+ def foo(*args, **options)
+ super
+ end
+ end
+ end
+end
diff --git a/spec/ruby/language/fixtures/variables.rb b/spec/ruby/language/fixtures/variables.rb
new file mode 100644
index 0000000000..07265dbb2b
--- /dev/null
+++ b/spec/ruby/language/fixtures/variables.rb
@@ -0,0 +1,85 @@
+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
+end
diff --git a/spec/ruby/language/fixtures/yield.rb b/spec/ruby/language/fixtures/yield.rb
new file mode 100644
index 0000000000..a195616640
--- /dev/null
+++ b/spec/ruby/language/fixtures/yield.rb
@@ -0,0 +1,37 @@
+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 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..c9d043fa25
--- /dev/null
+++ b/spec/ruby/language/for_spec.rb
@@ -0,0 +1,177 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+# 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 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
+ class XYZ
+ def each
+ (0..10).each { |i| yield i }
+ end
+ end
+
+ j = 0
+ for i in XYZ.new
+ 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
+
+ # 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 "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..edd9d4fbb2
--- /dev/null
+++ b/spec/ruby/language/hash_spec.rb
@@ -0,0 +1,154 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/hash_strings_ascii8bit', __FILE__)
+require File.expand_path('../fixtures/hash_strings_utf8', __FILE__)
+require File.expand_path('../fixtures/hash_strings_usascii', __FILE__)
+
+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.frozen?.should == true
+ 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}
+ 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
+ eval("{:a==>1}").should == {:"a=" => 1}
+ eval("{:a= =>1}").should == {:"a=" => 1}
+ eval("{:a= => 1}").should == {:"a=" => 1}
+ end
+
+ it "with '==>' in the middle raises SyntaxError" do
+ lambda { 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}
+ eval('{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
+
+ it "expands an '**{}' 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}
+ 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 "raises a TypeError if any splatted elements keys are not symbols" do
+ h = {1 => 2, b: 3}
+ lambda { {a: 1, **h} }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if #to_hash does not return a Hash" do
+ obj = mock("hash splat")
+ obj.should_receive(:to_hash).and_return(obj)
+
+ lambda { {**obj} }.should raise_error(TypeError)
+ end
+
+ it "does not change encoding of literal string keys during creation" do
+ ascii8bit_hash = HashStringsASCII8BIT.literal_hash
+ utf8_hash = HashStringsUTF8.literal_hash
+ usascii_hash = HashStringsUSASCII.literal_hash
+
+ ascii8bit_hash.keys.first.encoding.should == Encoding::ASCII_8BIT
+ ascii8bit_hash.keys.first.should == utf8_hash.keys.first
+ 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
+end
diff --git a/spec/ruby/language/heredoc_spec.rb b/spec/ruby/language/heredoc_spec.rb
new file mode 100644
index 0000000000..a57a7b0bb9
--- /dev/null
+++ b/spec/ruby/language/heredoc_spec.rb
@@ -0,0 +1,87 @@
+# -*- encoding: us-ascii -*-
+
+require File.expand_path('../../spec_helper', __FILE__)
+
+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"
+ end
+
+ it 'allow HEREDOC with <<"identifier", interpolated' do
+ s = <<"HERE"
+foo bar#{@ip}
+HERE
+ s.should == "foo barxxx\n"
+ end
+
+ it "allows HEREDOC with <<'identifier', no interpolation" do
+ s = <<'HERE'
+foo bar#{@ip}
+HERE
+ s.should == 'foo bar#{@ip}' + "\n"
+ end
+
+ it "allows HEREDOC with <<-identifier, allowing to indent identifier, interpolated" do
+ s = <<-HERE
+ foo bar#{@ip}
+ HERE
+
+ s.should == " foo barxxx\n"
+ end
+
+ it 'allows HEREDOC with <<-"identifier", allowing to indent identifier, interpolated' do
+ s = <<-"HERE"
+ foo bar#{@ip}
+ HERE
+
+ s.should == " foo barxxx\n"
+ 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"
+ end
+
+ ruby_version_is "2.3" do
+ it "allows HEREDOC with <<~'identifier', allowing to indent identifier and content" do
+ require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
+ SquigglyHeredocSpecs.message.should == "character density, n.:\n The number of very weird people in the office.\n"
+ end
+
+ it "trims trailing newline character for blank HEREDOC with <<~'identifier'" do
+ require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
+ SquigglyHeredocSpecs.blank.should == ""
+ end
+
+ it 'allows HEREDOC with <<~identifier, interpolated' do
+ require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
+ SquigglyHeredocSpecs.unquoted.should == "unquoted interpolated\n"
+ end
+
+ it 'allows HEREDOC with <<~"identifier", interpolated' do
+ require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
+ SquigglyHeredocSpecs.doublequoted.should == "doublequoted interpolated\n"
+ end
+
+ it "allows HEREDOC with <<~'identifier', no interpolation" do
+ require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
+ SquigglyHeredocSpecs.singlequoted.should == "singlequoted \#{\"interpolated\"}\n"
+ end
+
+ it "selects the least-indented line and removes its indentation from all the lines" do
+ require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
+ SquigglyHeredocSpecs.least_indented_on_the_last_line.should == " a\n b\nc\n"
+ end
+ end
+end
diff --git a/spec/ruby/language/if_spec.rb b/spec/ruby/language/if_spec.rb
new file mode 100644
index 0000000000..bd4fdcae81
--- /dev/null
+++ b/spec/ruby/language/if_spec.rb
@@ -0,0 +1,354 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+describe "The if expression" do
+ 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
+ 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/lambda_spec.rb b/spec/ruby/language/lambda_spec.rb
new file mode 100644
index 0000000000..43e2d60ae3
--- /dev/null
+++ b/spec/ruby/language/lambda_spec.rb
@@ -0,0 +1,573 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+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 "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
+
+ lambda { @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
+ lambda { @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]
+ lambda { @a.() }.should raise_error(ArgumentError)
+ lambda { @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_receive(:to_hash).and_return({a: 1})
+ @a.(h).should == {a: 1}
+ 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
+ it "shadows an existing local with the same name as the argument" do
+ a = 1
+ -> {
+ @proc = eval "-> (a=a) { a }"
+ }.should complain(/circular argument reference/)
+ @proc.call.should == nil
+ end
+
+ it "shadows an existing method with the same name as the argument" do
+ def a; 1; end
+ -> {
+ @proc = eval "-> (a=a) { a }"
+ }.should complain(/circular argument reference/)
+ @proc.call.should == nil
+ 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
+ lambda { lambda }.should raise_error(ArgumentError)
+ end
+
+ context "with an implicit block" do
+ before do
+ def meth; lambda; end
+ end
+
+ it "can be created" do
+ implicit_lambda = nil
+ -> {
+ implicit_lambda = meth { 1 }
+ }.should complain(/tried to create Proc object without a block/)
+
+ implicit_lambda.lambda?.should be_true
+ implicit_lambda.call.should == 1
+ 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_receive(:to_hash).and_return({a: 1})
+ @a.(h).should == {a: 1}
+ 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..d9fd307dab
--- /dev/null
+++ b/spec/ruby/language/line_spec.rb
@@ -0,0 +1,45 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../../fixtures/code_loading', __FILE__)
+require File.expand_path('../shared/__LINE__', __FILE__)
+
+describe "The __LINE__ pseudo-variable" do
+ it "raises a SyntaxError if assigned to" do
+ lambda { 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..4e60e0d8e6
--- /dev/null
+++ b/spec/ruby/language/loop_spec.rb
@@ -0,0 +1,67 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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
+ lambda { 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..2f6e3b5c3a
--- /dev/null
+++ b/spec/ruby/language/magic_comment_spec.rb
@@ -0,0 +1,62 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+describe "Magic comment" do
+ it "is optional" do
+ eval("__ENCODING__").should be_an_instance_of(Encoding)
+ end
+
+ it "determines __ENCODING__" do
+ eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
+# encoding: ASCII-8BIT
+__ENCODING__
+EOS
+ end
+
+ it "is case-insensitive" do
+ eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
+# CoDiNg: aScIi-8bIt
+__ENCODING__
+EOS
+ end
+
+ it "must be at the first line" do
+ eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::US_ASCII
+
+# encoding: ASCII-8BIT
+__ENCODING__
+EOS
+ end
+
+ it "must be the first token of the line" do
+ eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::US_ASCII
+1+1 # encoding: ASCII-8BIT
+__ENCODING__
+EOS
+ eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
+ # encoding: ASCII-8BIT
+__ENCODING__
+EOS
+ end
+
+ it "can be after the shebang" do
+ eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
+#!/usr/bin/ruby -Ku
+# encoding: ASCII-8BIT
+__ENCODING__
+EOS
+ end
+
+ it "can take Emacs style" do
+ eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
+# -*- encoding: ascii-8bit -*-
+__ENCODING__
+EOS
+ end
+
+ it "can take vim style" do
+ eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
+# vim: filetype=ruby, fileencoding=ascii-8bit, tabsize=3, shiftwidth=3
+__ENCODING__
+EOS
+ end
+end
diff --git a/spec/ruby/language/match_spec.rb b/spec/ruby/language/match_spec.rb
new file mode 100644
index 0000000000..81604e94b2
--- /dev/null
+++ b/spec/ruby/language/match_spec.rb
@@ -0,0 +1,74 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/match_operators', __FILE__)
+
+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_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..b3bcd9ef18
--- /dev/null
+++ b/spec/ruby/language/metaclass_spec.rb
@@ -0,0 +1,143 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../../fixtures/class', __FILE__)
+require File.expand_path('../fixtures/metaclass', __FILE__)
+
+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
+ lambda { class << 1; self; end }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError for symbols" do
+ lambda { class << :symbol; self; end }.should raise_error(TypeError)
+ 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
+ lambda { CONST }.should raise_error(NameError)
+ end
+
+ it "cannot be accessed via object::CONST" do
+ lambda 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
+
+ lambda 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
+
+ lambda 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..c92a8ee51b
--- /dev/null
+++ b/spec/ruby/language/method_spec.rb
@@ -0,0 +1,1290 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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)
+
+ lambda { 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)
+
+ lambda { 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)
+
+ lambda { 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)
+
+ lambda { m(1, 2, *x) }.should raise_error(TypeError)
+ 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)
+
+ lambda { @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)
+
+ lambda { @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)
+
+ lambda { @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)
+
+ lambda { @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)
+
+ lambda { @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)
+
+ lambda { @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)
+
+ lambda { @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)
+
+ lambda { @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(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]
+ end
+
+ evaluate <<-ruby do
+ def m(a:) a end
+ ruby
+
+ lambda { m() }.should raise_error(ArgumentError)
+ m(a: 1).should == 1
+ lambda { m("a" => 1, a: 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
+ lambda { 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 }
+ lambda { m(2) }.should raise_error(ArgumentError)
+ 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
+ lambda { 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]
+ lambda { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ 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 == [{"a" => 1, b: 2}, 1]
+ 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 == {"a" => 1, b: 2}
+ 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 == [{"a" => 1, b: 2}, {}]
+ 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
+
+ lambda { m() }.should raise_error(ArgumentError)
+ lambda { 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 == [{"a" => 1}, 2]
+ 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 == [{"a" => 1}, 2]
+ 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 == {"a" => 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(*, a:) a end
+ ruby
+
+ m(a: 1).should == 1
+ m(1, 2, a: 3).should == 3
+ m("a" => 1, a: 2).should == 2
+ end
+
+ evaluate <<-ruby do
+ def m(*a, b:) [a, b] end
+ ruby
+
+ m(b: 1).should == [[], 1]
+ m(1, 2, b: 3).should == [[1, 2], 3]
+ m("a" => 1, b: 2).should == [[{"a" => 1}], 2]
+ end
+
+ evaluate <<-ruby do
+ def m(*, a: 1) a end
+ ruby
+
+ m().should == 1
+ m(1, 2).should == 1
+ m(a: 2).should == 2
+ m(1, a: 2).should == 2
+ m("a" => 1, a: 2).should == 2
+ end
+
+ evaluate <<-ruby do
+ def m(*a, b: 1) [a, b] end
+ ruby
+
+ m().should == [[], 1]
+ m(1, 2, 3, b: 4).should == [[1, 2, 3], 4]
+ m("a" => 1, b: 2).should == [[{"a" => 1}], 2]
+
+ a = mock("splat")
+ a.should_not_receive(:to_ary)
+ m(*a).should == [[a], 1]
+ end
+
+ evaluate <<-ruby do
+ def m(*, **) end
+ ruby
+
+ m().should be_nil
+ m(a: 1, b: 2).should be_nil
+ m(1, 2, 3, a: 4, b: 5).should be_nil
+
+ h = mock("keyword splat")
+ h.should_receive(:to_hash).and_return({a: 1})
+ m(h).should be_nil
+
+ h = mock("keyword splat")
+ error = RuntimeError.new("error while converting to a hash")
+ h.should_receive(:to_hash).and_raise(error)
+ lambda { m(h) }.should raise_error(error)
+ end
+
+ evaluate <<-ruby do
+ def m(*a, **) a end
+ ruby
+
+ m().should == []
+ m(1, 2, 3, a: 4, b: 5).should == [1, 2, 3]
+ m("a" => 1, a: 1).should == [{"a" => 1}]
+ m(1, **{a: 2}).should == [1]
+
+ h = mock("keyword splat")
+ h.should_receive(:to_hash)
+ lambda { m(**h) }.should raise_error(TypeError)
+ end
+
+ evaluate <<-ruby do
+ def m(*, **k) k end
+ ruby
+
+ m().should == {}
+ m(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
+ m("a" => 1, a: 1).should == {a: 1}
+
+ h = mock("keyword splat")
+ h.should_receive(:to_hash).and_return({a: 1})
+ m(h).should == {a: 1}
+ end
+
+ evaluate <<-ruby do
+ def m(a = nil, **k) [a, k] end
+ ruby
+
+ m().should == [nil, {}]
+ m("a" => 1).should == [{"a" => 1}, {}]
+ m(a: 1).should == [nil, {a: 1}]
+ m("a" => 1, a: 1).should == [{"a" => 1}, {a: 1}]
+ m({ "a" => 1 }, a: 1).should == [{"a" => 1}, {a: 1}]
+ m({a: 1}, {}).should == [{a: 1}, {}]
+
+ h = {"a" => 1, b: 2}
+ m(h).should == [{"a" => 1}, {b: 2}]
+ h.should == {"a" => 1, b: 2}
+
+ h = {"a" => 1}
+ m(h).first.should == h
+
+ h = {}
+ r = m(h)
+ r.first.should be_nil
+ r.last.should == {}
+
+ hh = {}
+ h = mock("keyword splat empty hash")
+ h.should_receive(:to_hash).and_return(hh)
+ r = m(h)
+ r.first.should be_nil
+ r.last.should == {}
+
+ h = mock("keyword splat")
+ h.should_receive(:to_hash).and_return({"a" => 1, a: 2})
+ m(h).should == [{"a" => 1}, {a: 2}]
+ end
+
+ evaluate <<-ruby do
+ def m(*a, **k) [a, k] end
+ ruby
+
+ m().should == [[], {}]
+ m(1).should == [[1], {}]
+ m(a: 1, b: 2).should == [[], {a: 1, b: 2}]
+ m(1, 2, 3, a: 2).should == [[1, 2, 3], {a: 2}]
+
+ m("a" => 1).should == [[{"a" => 1}], {}]
+ m(a: 1).should == [[], {a: 1}]
+ m("a" => 1, a: 1).should == [[{"a" => 1}], {a: 1}]
+ m({ "a" => 1 }, a: 1).should == [[{"a" => 1}], {a: 1}]
+ m({a: 1}, {}).should == [[{a: 1}], {}]
+ m({a: 1}, {"a" => 1}).should == [[{a: 1}, {"a" => 1}], {}]
+
+ bo = BasicObject.new
+ def bo.to_a; [1, 2, 3]; end
+ def bo.to_hash; {:b => 2, :c => 3}; end
+
+ m(*bo, **bo).should == [[1, 2, 3], {:b => 2, :c => 3}]
+ end
+
+ evaluate <<-ruby do
+ def m(*, &b) b end
+ ruby
+
+ 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]
+ lambda { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ 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]
+ lambda { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(a:, **) a end
+ ruby
+
+ m(a: 1).should == 1
+ m(a: 1, b: 2).should == 1
+ lambda { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ 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}]
+ lambda { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ 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, 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, {}]
+ 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 provided" do
+ it "assigns it" do
+ args = m (1 == 1 ? true : false)
+ args.should == [true]
+ end
+ end
+
+ context "when 2+ arguments provided" do
+ it "raises a syntax error" do
+ lambda {
+ eval("m (1, 2)")
+ }.should raise_error(SyntaxError)
+
+ lambda {
+ eval("m (1, 2, 3)")
+ }.should raise_error(SyntaxError)
+ end
+ 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
diff --git a/spec/ruby/language/module_spec.rb b/spec/ruby/language/module_spec.rb
new file mode 100644
index 0000000000..d5ca71b3b4
--- /dev/null
+++ b/spec/ruby/language/module_spec.rb
@@ -0,0 +1,91 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/module', __FILE__)
+
+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
+ end
+
+ it "reopens a module included in Object" do
+ module IncludedModuleSpecs; Reopened = true; end
+ ModuleSpecs::IncludedInObject::IncludedModuleSpecs::Reopened.should be_true
+ end
+
+ it "raises a TypeError if the constant is a Class" do
+ lambda do
+ module ModuleSpecs::Modules::Klass; end
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the constant is a String" do
+ lambda { module ModuleSpecs::Modules::A; end }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the constant is a Fixnum" do
+ lambda { module ModuleSpecs::Modules::B; end }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the constant is nil" do
+ lambda { module ModuleSpecs::Modules::C; end }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the constant is true" do
+ lambda { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the constant is false" do
+ lambda { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError)
+ 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"
+ end
+
+ it "does not set the name of a module scoped by an anonymous module" do
+ a, b = Module.new, Module.new
+ a::B = b
+ b.name.should be_nil
+ end
+
+ 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"
+ end
+end
diff --git a/spec/ruby/language/next_spec.rb b/spec/ruby/language/next_spec.rb
new file mode 100644
index 0000000000..67da5224dc
--- /dev/null
+++ b/spec/ruby/language/next_spec.rb
@@ -0,0 +1,410 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/next', __FILE__)
+
+describe "The next statement from within the block" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "ends block execution" do
+ a = []
+ lambda {
+ a << 1
+ next
+ a << 2
+ }.call
+ a.should == [1]
+ end
+
+ it "causes block to return nil if invoked without arguments" do
+ lambda { 123; next; 456 }.call.should == nil
+ end
+
+ it "causes block to return nil if invoked with an empty expression" do
+ lambda { next (); 456 }.call.should be_nil
+ end
+
+ it "returns the argument passed" do
+ lambda { 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
+ lambda { next obj.method :value do |x| x end }.call.should == :value
+ end
+end
+
+describe "The next statement" do
+ describe "in a method" do
+ it "is invalid and raises a SyntaxError" do
+ lambda {
+ eval("def m; next; end")
+ }.should raise_error(SyntaxError)
+ end
+ 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..a0cf6b15a6
--- /dev/null
+++ b/spec/ruby/language/not_spec.rb
@@ -0,0 +1,51 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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/numbers_spec.rb b/spec/ruby/language/numbers_spec.rb
new file mode 100644
index 0000000000..e8c82f09a7
--- /dev/null
+++ b/spec/ruby/language/numbers_spec.rb
@@ -0,0 +1,97 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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
+ lambda { 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
+ lambda { eval(".75") }.should raise_error(SyntaxError)
+ lambda { 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 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..c104d08253
--- /dev/null
+++ b/spec/ruby/language/optional_assignments_spec.rb
@@ -0,0 +1,226 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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
+ end
+
+ describe 'using a 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
+ 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 a single variable' 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
+ end
+ end
+
+ describe 'using compunded constants' do
+ before 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-existant constants' do
+ lambda { 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-existant constants' do
+ lambda { Object::A += 10 }.should raise_error(NameError)
+ 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..150d693996
--- /dev/null
+++ b/spec/ruby/language/or_spec.rb
@@ -0,0 +1,90 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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
+ lambda { break false || true }.call.should be_true
+ end
+
+ it "has a higher precedence than 'next' in 'next true || false'" do
+ lambda { next false || true }.call.should be_true
+ end
+
+ it "has a higher precedence than 'return' in 'return true || false'" do
+ lambda { 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
+ lambda { eval "break true or false" }.should raise_error(SyntaxError, /void value expression/)
+ end
+
+ it "has a lower precedence than 'next' in 'next true or false'" do
+ lambda { eval "next true or false" }.should raise_error(SyntaxError, /void value expression/)
+ end
+
+ it "has a lower precedence than 'return' in 'return true or false'" do
+ lambda { eval "return true or false" }.should raise_error(SyntaxError, /void value expression/)
+ end
+end
diff --git a/spec/ruby/language/order_spec.rb b/spec/ruby/language/order_spec.rb
new file mode 100644
index 0000000000..d02bf04077
--- /dev/null
+++ b/spec/ruby/language/order_spec.rb
@@ -0,0 +1,75 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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/precedence_spec.rb b/spec/ruby/language/precedence_spec.rb
new file mode 100644
index 0000000000..90734022ff
--- /dev/null
+++ b/spec/ruby/language/precedence_spec.rb
@@ -0,0 +1,448 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/precedence', __FILE__)
+
+# 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).
+
+=begin
+Excerpted from 'Programming Ruby: The Pragmatic Programmer's Guide'
+Second Edition by Dave Thomas, Chad Fowler, and Andy Hunt, page 324
+
+Table 22.4. Ruby operators (high to low precedence)
+Method Operator Description
+-----------------------------------------------------------------------
+ :: .
+ x* [ ] [ ]= Element reference, element set
+ x ** Exponentiation
+ x ! ~ + - Not, complement, unary plus and minus
+ (method names for the last two are +@ and -@)
+ x * / % Multiply, divide, and modulo
+ x + - Plus and minus
+ x >> << Right and left shift
+ x & “And” (bitwise for integers)
+ x ^ | Exclusive “or” and regular “or” (bitwise for integers)
+ x <= < > >= Comparison operators
+ x <=> == === != =~ !~ Equality and pattern match operators (!=
+ and !~ may not be defined as methods)
+ && Logical “and”
+ || Logical “or”
+ .. ... Range (inclusive and exclusive)
+ ? : Ternary if-then-else
+ = %= /= -= += |= &= Assignment
+ >>= <<= *= &&= ||= **=
+ defined? Check if symbol defined
+ not Logical negation
+ or and Logical composition
+ if unless while until Expression modifiers
+ begin/end Block expression
+-----------------------------------------------------------------------
+
+* Operators marked with 'x' in the Method column are implemented as methods
+and can be overridden (except != and !~ as noted). (But see the specs
+below for implementations that define != and !~ as methods.)
+
+** These are not included in the excerpted table but are shown here for
+completeness.
+=end
+
+# -----------------------------------------------------------------------
+# 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.
+ conflicts_with :Prime 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
+ lambda { eval("1 <=> 2 <=> 3") }.should raise_error(SyntaxError)
+ lambda { eval("1 == 2 == 3") }.should raise_error(SyntaxError)
+ lambda { eval("1 === 2 === 3") }.should raise_error(SyntaxError)
+ lambda { eval("1 != 2 != 3") }.should raise_error(SyntaxError)
+ lambda { eval("1 =~ 2 =~ 3") }.should raise_error(SyntaxError)
+ lambda { eval("1 !~ 2 !~ 3") }.should raise_error(SyntaxError)
+ 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
+ lambda { eval("1..2..3") }.should raise_error(SyntaxError)
+ lambda { eval("1...2...3") }.should raise_error(SyntaxError)
+ end
+
+# XXX: this is commented now due to a bug in compiler, which cannot
+# distinguish between range and flip-flop operator so far. zenspider is
+# currently working on a new lexer, which will be able to do that.
+# As soon as it's done, these piece should be reenabled.
+#
+# it ".. ... have higher precedence than ? :" do
+# (1..2 ? 3 : 4).should == 3
+# (1...2 ? 3 : 4).should == 3
+# end
+
+ it "? : 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..f616879527
--- /dev/null
+++ b/spec/ruby/language/predefined/data_spec.rb
@@ -0,0 +1,29 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+
+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 "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..0f714b06d4
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data2.rb
@@ -0,0 +1,4 @@
+
+require File.expand_path("../data4.rb", __FILE__)
+
+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..6cbf63dae6
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data3.rb
@@ -0,0 +1,7 @@
+
+require File.expand_path("../data4.rb", __FILE__)
+
+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_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/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_spec.rb b/spec/ruby/language/predefined_spec.rb
new file mode 100644
index 0000000000..a0930eb214
--- /dev/null
+++ b/spec/ruby/language/predefined_spec.rb
@@ -0,0 +1,1221 @@
+require File.expand_path('../../spec_helper', __FILE__)
+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.
+
+=begin
+Exception Information
+---------------------------------------------------------------------------------------------------
+
+$! Exception The exception object passed to raise. [thread]
+$@ Array The stack backtrace generated by the last exception. [thread]
+=end
+
+=begin
+Pattern Matching Variables
+---------------------------------------------------------------------------------------------------
+
+These variables are set to nil after an unsuccessful pattern match.
+
+$& String The string matched (following a successful pattern match). This variable is
+ local to the current scope. [r/o, thread]
+$+ String The contents of the highest-numbered group matched following a successful
+ pattern match. Thus, in "cat" =~/(c|a)(t|z)/, $+ will be set to “t”. This
+ variable is local to the current scope. [r/o, thread]
+$` String The string preceding the match in a successful pattern match. This variable
+ is local to the current scope. [r/o, thread]
+$' String The string following the match in a successful pattern match. This variable
+ is local to the current scope. [r/o, thread]
+$1 to $9 String The contents of successive groups matched in a successful pattern match. In
+ "cat" =~/(c|a)(t|z)/, $1 will be set to “a” and $2 to “t”. This variable
+ is local to the current scope. [r/o, thread]
+$~ MatchData An object that encapsulates the results of a successful pattern match. The
+ variables $&, $`, $', and $1 to $9 are all derived from $~. Assigning to $~
+ changes the values of these derived variables. This variable is local to the
+ current scope. [thread]
+=end
+
+
+describe "Predefined global $~" do
+ it "is set to contain the MatchData object of the last match if successful" do
+ md = /foo/.match 'foo'
+ $~.should be_kind_of(MatchData)
+ $~.object_id.should == md.object_id
+
+ /bar/ =~ 'bar'
+ $~.should be_kind_of(MatchData)
+ $~.object_id.should_not == md.object_id
+ end
+
+ it "is set to nil if the last match was unsuccessful" do
+ /foo/ =~ 'foo'
+ $~.nil?.should == false
+
+ /foo/ =~ 'bar'
+ $~.nil?.should == true
+ 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
+
+ eval '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)
+
+ lambda { $~ = Object.new }.should raise_error(TypeError)
+ lambda { $~ = 1 }.should raise_error(TypeError)
+ 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
+
+ with_feature :encoding do
+ it "sets the encoding to the encoding of the source String" do
+ "abc".force_encoding(Encoding::EUC_JP) =~ /b/
+ $&.encoding.should equal(Encoding::EUC_JP)
+ end
+ 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
+
+ with_feature :encoding do
+ it "sets the encoding to the encoding of the source String" do
+ "abc".force_encoding(Encoding::EUC_JP) =~ /b/
+ $`.encoding.should equal(Encoding::EUC_JP)
+ end
+
+ it "sets an empty result to the encoding of the source String" do
+ "abc".force_encoding(Encoding::ISO_8859_1) =~ /a/
+ $`.encoding.should equal(Encoding::ISO_8859_1)
+ end
+ 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
+
+ with_feature :encoding do
+ it "sets the encoding to the encoding of the source String" do
+ "abc".force_encoding(Encoding::EUC_JP) =~ /b/
+ $'.encoding.should equal(Encoding::EUC_JP)
+ end
+
+ it "sets an empty result to the encoding of the source String" do
+ "abc".force_encoding(Encoding::ISO_8859_1) =~ /c/
+ $'.encoding.should equal(Encoding::ISO_8859_1)
+ end
+ 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
+
+ with_feature :encoding do
+ it "sets the encoding to the encoding of the source String" do
+ "abc".force_encoding(Encoding::EUC_JP) =~ /(b)/
+ $+.encoding.should equal(Encoding::EUC_JP)
+ end
+ 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
+
+ with_feature :encoding do
+ it "sets the encoding to the encoding of the source String" do
+ "abc".force_encoding(Encoding::EUC_JP) =~ /(b)/
+ $1.encoding.should equal(Encoding::EUC_JP)
+ end
+ 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
+ lambda { $stdout = nil }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError error if assigned to object that doesn't respond to #write" do
+ obj = mock('object')
+ lambda { $stdout = obj }.should raise_error(TypeError)
+
+ obj.stub!(:write)
+ $stdout = obj
+ $stdout.should equal(obj)
+ end
+end
+
+describe "Predefined global $!" do
+ # 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 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
+
+=begin
+Input/Output Variables
+---------------------------------------------------------------------------------------------------
+
+$/ String The input record separator (newline by default). This is the value that rou-
+ tines such as Kernel#gets use to determine record boundaries. If set to
+ nil, gets will read the entire file.
+$-0 String Synonym for $/.
+$\ String The string appended to the output of every call to methods such as
+ Kernel#print and IO#write. The default value is nil.
+$, String The separator string output between the parameters to methods such as
+ Kernel#print and Array#join. Defaults to nil, which adds no text.
+$. Fixnum The number of the last line read from the current input file.
+$; String The default separator pattern used by String#split. May be set from the
+ command line using the -F flag.
+$< Object An object that provides access to the concatenation of the contents of all
+ the files given as command-line arguments or $stdin (in the case where
+ there are no arguments). $< supports methods similar to a File object:
+ binmode, close, closed?, each, each_byte, each_line, eof, eof?,
+ file, filename, fileno, getc, gets, lineno, lineno=, path, pos, pos=,
+ read, readchar, readline, readlines, rewind, seek, skip, tell, to_a,
+ to_i, to_io, to_s, along with the methods in Enumerable. The method
+ file returns a File object for the file currently being read. This may change
+ as $< reads through the files on the command line. [r/o]
+$> IO The destination of output for Kernel#print and Kernel#printf. The
+ default value is $stdout.
+$_ String The last line read by Kernel#gets or Kernel#readline. Many string-
+ related functions in the Kernel module operate on $_ by default. The vari-
+ able is local to the current scope. [thread]
+$-F String Synonym for $;.
+$stderr IO The current standard error output.
+$stdin IO The current standard input.
+$stdout IO The current standard output. Assignment to $stdout is deprecated: use
+ $stdout.reopen instead.
+=end
+
+describe "Predefined global $/" do
+ before :each do
+ @dollar_slash = $/
+ @dollar_dash_zero = $-0
+ end
+
+ after :each do
+ $/ = @dollar_slash
+ $-0 = @dollar_dash_zero
+ 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 "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)
+
+ lambda { $/ = obj }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if assigned a Fixnum" do
+ lambda { $/ = 1 }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if assigned a boolean" do
+ lambda { $/ = true }.should raise_error(TypeError)
+ end
+end
+
+describe "Predefined global $-0" do
+ before :each do
+ @dollar_slash = $/
+ @dollar_dash_zero = $-0
+ end
+
+ after :each do
+ $/ = @dollar_slash
+ $-0 = @dollar_dash_zero
+ end
+
+ it "can be assigned a String" do
+ str = "abc"
+ $-0 = str
+ $-0.should equal(str)
+ 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)
+
+ lambda { $-0 = obj }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if assigned a Fixnum" do
+ lambda { $-0 = 1 }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if assigned a boolean" do
+ lambda { $-0 = true }.should raise_error(TypeError)
+ 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
+ lambda { $, = Object.new }.should raise_error(TypeError)
+ 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
+
+ eval '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
+
+=begin
+Execution Environment Variables
+---------------------------------------------------------------------------------------------------
+
+$0 String The name of the top-level Ruby program being executed. Typically this will
+ be the program’s filename. On some operating systems, assigning to this
+ variable will change the name of the process reported (for example) by the
+ ps(1) command.
+$* Array An array of strings containing the command-line options from the invoca-
+ tion of the program. Options used by the Ruby interpreter will have been
+ removed. [r/o]
+$" Array An array containing the filenames of modules loaded by require. [r/o]
+$$ Fixnum The process number of the program being executed. [r/o]
+$? Process::Status The exit status of the last child process to terminate. [r/o, thread]
+$: Array An array of strings, where each string specifies a directory to be searched for
+ Ruby scripts and binary extensions used by the load and require methods.
+ The initial value is the value of the arguments passed via the -I command-
+ line option, followed by an installation-defined standard library location, fol-
+ lowed by the current directory (“.”). This variable may be set from within a
+ program to alter the default search path; typically, programs use $: << dir
+ to append dir to the path. [r/o]
+$-a Object True if the -a option is specified on the command line. [r/o]
+$-d Object Synonym for $DEBUG.
+$DEBUG Object Set to true if the -d command-line option is specified.
+__FILE__ String The name of the current source file. [r/o]
+$F Array The array that receives the split input line if the -a command-line option is
+ used.
+$FILENAME String The name of the current input file. Equivalent to $<.filename. [r/o]
+$-i String If in-place edit mode is enabled (perhaps using the -i command-line
+ option), $-i holds the extension used when creating the backup file. If you
+ set a value into $-i, enables in-place edit mode.
+$-I Array Synonym for $:. [r/o]
+$-K String Sets the multibyte coding system for strings and regular expressions. Equiv-
+ alent to the -K command-line option.
+$-l Object Set to true if the -l option (which enables line-end processing) is present
+ on the command line. [r/o]
+__LINE__ String The current line number in the source file. [r/o]
+$LOAD_PATH Array A synonym for $:. [r/o]
+$-p Object Set to true if the -p option (which puts an implicit while gets . . . end
+ loop around your program) is present on the command line. [r/o]
+$SAFE Fixnum The current safe level. This variable’s value may never be
+ reduced by assignment. [thread] (Not implemented in Rubinius)
+$VERBOSE Object Set to true if the -v, --version, -W, or -w option is specified on the com-
+ mand line. Set to false if no option, or -W1 is given. Set to nil if -W0
+ was specified. Setting this option to true causes the interpreter and some
+ library routines to report additional information. Setting to nil suppresses
+ all warnings (including the output of Kernel.warn).
+$-v Object Synonym for $VERBOSE.
+$-w Object Synonym for $VERBOSE.
+=end
+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")
+ end
+
+ it "is read-only" do
+ lambda {
+ $: = []
+ }.should raise_error(NameError)
+
+ lambda {
+ $LOAD_PATH = []
+ }.should raise_error(NameError)
+
+ lambda {
+ $-I = []
+ }.should raise_error(NameError)
+ end
+end
+
+describe "Global variable $\"" do
+ it "is an alias for $LOADED_FEATURES" do
+ $".object_id.should == $LOADED_FEATURES.object_id
+ end
+
+ it "is read-only" do
+ lambda {
+ $" = []
+ }.should raise_error(NameError)
+
+ lambda {
+ $LOADED_FEATURES = []
+ }.should raise_error(NameError)
+ end
+end
+
+describe "Global variable $<" do
+ it "is read-only" do
+ lambda {
+ $< = nil
+ }.should raise_error(NameError)
+ end
+end
+
+describe "Global variable $FILENAME" do
+ it "is read-only" do
+ lambda {
+ $FILENAME = "-"
+ }.should raise_error(NameError)
+ end
+end
+
+describe "Global variable $?" do
+ it "is read-only" do
+ lambda {
+ $? = nil
+ }.should raise_error(NameError)
+ 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
+ lambda { $-a = true }.should raise_error(NameError)
+ end
+end
+
+describe "Global variable $-l" do
+ it "is read-only" do
+ lambda { $-l = true }.should raise_error(NameError)
+ end
+end
+
+describe "Global variable $-p" do
+ it "is read-only" do
+ lambda { $-p = true }.should raise_error(NameError)
+ 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 :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(File.dirname(__FILE__)) 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
+ lambda { $0 = nil }.should raise_error(TypeError)
+ end
+end
+
+=begin
+Standard Objects
+---------------------------------------------------------------------------------------------------
+
+ARGF Object A synonym for $<.
+ARGV Array A synonym for $*.
+ENV Object A hash-like object containing the program’s environment variables. An
+ instance of class Object, ENV implements the full set of Hash methods. Used
+ to query and set the value of an environment variable, as in ENV["PATH"]
+ and ENV["term"]="ansi".
+false FalseClass Singleton instance of class FalseClass. [r/o]
+nil NilClass The singleton instance of class NilClass. The value of uninitialized
+ instance and global variables. [r/o]
+self Object The receiver (object) of the current method. [r/o]
+true TrueClass Singleton instance of class TrueClass. [r/o]
+=end
+
+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
+ lambda { eval("nil = true") }.should raise_error(SyntaxError)
+ 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
+ lambda { eval("true = false") }.should raise_error(SyntaxError)
+ 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
+ lambda { eval("false = nil") }.should raise_error(SyntaxError)
+ end
+end
+
+describe "The self pseudo-variable" do
+ it "raises a SyntaxError if assigned to" do
+ lambda { eval("self = 1") }.should raise_error(SyntaxError)
+ end
+end
+
+=begin
+Global Constants
+---------------------------------------------------------------------------------------------------
+
+The following constants are defined by the Ruby interpreter.
+
+DATA IO If the main program file contains the directive __END__, then
+ the constant DATA will be initialized so that reading from it will
+ return lines following __END__ from the source file.
+FALSE FalseClass Synonym for false.
+NIL NilClass Synonym for nil.
+RUBY_PLATFORM String The identifier of the platform running this program. This string
+ is in the same form as the platform identifier used by the GNU
+ configure utility (which is not a coincidence).
+RUBY_RELEASE_DATE String The date of this release.
+RUBY_VERSION String The version number of the interpreter.
+STDERR IO The actual standard error stream for the program. The initial
+ value of $stderr.
+STDIN IO The actual standard input stream for the program. The initial
+ value of $stdin.
+STDOUT IO The actual standard output stream for the program. The initial
+ value of $stdout.
+SCRIPT_LINES__ Hash If a constant SCRIPT_LINES__ is defined and references a Hash,
+ Ruby will store an entry containing the contents of each file it
+ parses, with the file’s name as the key and an array of strings as
+ the value.
+TOPLEVEL_BINDING Binding A Binding object representing the binding at Ruby’s top level—
+ the level where programs are initially executed.
+TRUE TrueClass Synonym for true.
+=end
+
+describe "The predefined global constants" do
+ ruby_version_is ""..."2.4" do
+ it "includes TRUE" do
+ Object.const_defined?(:TRUE).should == true
+ TRUE.should equal(true)
+ end
+
+ it "includes FALSE" do
+ Object.const_defined?(:FALSE).should == true
+ FALSE.should equal(false)
+ end
+
+ it "includes NIL" do
+ Object.const_defined?(:NIL).should == true
+ NIL.should equal(nil)
+ end
+ end
+
+ ruby_version_is "2.4" do
+ it "includes TRUE" do
+ Object.const_defined?(:TRUE).should == true
+ -> {
+ TRUE.should equal(true)
+ }.should complain(/constant ::TRUE is deprecated/)
+ end
+
+ it "includes FALSE" do
+ Object.const_defined?(:FALSE).should == true
+ -> {
+ FALSE.should equal(false)
+ }.should complain(/constant ::FALSE is deprecated/)
+ end
+
+ it "includes NIL" do
+ Object.const_defined?(:NIL).should == true
+ -> {
+ NIL.should equal(nil)
+ }.should complain(/constant ::NIL is deprecated/)
+ 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
+
+with_feature :encoding do
+ 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
+ it "has the same external encoding as Encoding.default_external" do
+ STDIN.external_encoding.should equal(Encoding.default_external)
+ end
+
+ it "has the same external encoding as Encoding.default_external when that encoding is changed" do
+ Encoding.default_external = Encoding::ISO_8859_16
+ STDIN.external_encoding.should equal(Encoding::ISO_8859_16)
+ end
+
+ it "has the encodings set by #set_encoding" do
+ code = "STDIN.set_encoding Encoding::IBM775, Encoding::IBM866; " \
+ "p [STDIN.external_encoding.name, STDIN.internal_encoding.name]"
+ ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
+ end
+
+ it "retains the encoding set by #set_encoding when Encoding.default_external is changed" do
+ code = "STDIN.set_encoding Encoding::IBM775, Encoding::IBM866; " \
+ "Encoding.default_external = Encoding::ISO_8859_16;" \
+ "p [STDIN.external_encoding.name, STDIN.internal_encoding.name]"
+ ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
+ end
+
+ it "has nil for the internal encoding" do
+ STDIN.internal_encoding.should be_nil
+ end
+
+ it "has nil for the internal encoding despite Encoding.default_internal being changed" do
+ Encoding.default_internal = Encoding::IBM437
+ STDIN.internal_encoding.should be_nil
+ end
+ end
+
+ describe "STDOUT" do
+ it "has nil for the external encoding" do
+ STDOUT.external_encoding.should be_nil
+ 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
+end
diff --git a/spec/ruby/language/private_spec.rb b/spec/ruby/language/private_spec.rb
new file mode 100644
index 0000000000..796c0c1711
--- /dev/null
+++ b/spec/ruby/language/private_spec.rb
@@ -0,0 +1,67 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/private', __FILE__)
+
+describe "The private keyword" do
+ it "marks following methods as being private" do
+ a = Private::A.new
+ a.methods.should_not include(:bar)
+ lambda { a.bar }.should raise_error(NoMethodError)
+
+ b = Private::B.new
+ b.methods.should_not include(:bar)
+ lambda { 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
+ lambda { 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
+ lambda { f.foo }.should raise_error(NoMethodError)
+ end
+
+ it "changes visiblity of previously called methods with same send/call site" do
+ g = ::Private::G.new
+ lambda {
+ 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'
+ lambda {::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..bbef318826
--- /dev/null
+++ b/spec/ruby/language/proc_spec.rb
@@ -0,0 +1,220 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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 ArgumentErro 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, | 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
+end
diff --git a/spec/ruby/language/redo_spec.rb b/spec/ruby/language/redo_spec.rb
new file mode 100644
index 0000000000..53fd30b4f2
--- /dev/null
+++ b/spec/ruby/language/redo_spec.rb
@@ -0,0 +1,66 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+describe "The redo statement" do
+ it "restarts block execution if used within block" do
+ a = []
+ lambda {
+ a << 1
+ redo if a.size < 2
+ a << 2
+ }.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
+ lambda {
+ 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..c6a620a221
--- /dev/null
+++ b/spec/ruby/language/regexp/anchors_spec.rb
@@ -0,0 +1,179 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../../fixtures/classes', __FILE__)
+
+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..607f4463fd
--- /dev/null
+++ b/spec/ruby/language/regexp/back-references_spec.rb
@@ -0,0 +1,48 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../../fixtures/classes', __FILE__)
+
+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-9] 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"
+ 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
+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..74568a0beb
--- /dev/null
+++ b/spec/ruby/language/regexp/character_classes_spec.rb
@@ -0,0 +1,610 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../../fixtures/classes', __FILE__)
+
+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
+ lambda { 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{36F}".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{36F}".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
+
+ 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
+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..1f62244a28
--- /dev/null
+++ b/spec/ruby/language/regexp/encoding_spec.rb
@@ -0,0 +1,103 @@
+# -*- encoding: binary -*-
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../../fixtures/classes', __FILE__)
+
+describe "Regexps with encoding modifiers" do
+ it "supports /e (EUC encoding)" do
+ match = /./e.match("\303\251".force_encoding(Encoding::EUC_JP))
+ match.to_a.should == ["\303\251".force_encoding(Encoding::EUC_JP)]
+ end
+
+ it "supports /e (EUC encoding) with interpolation" do
+ match = /#{/./}/e.match("\303\251".force_encoding(Encoding::EUC_JP))
+ match.to_a.should == ["\303\251".force_encoding(Encoding::EUC_JP)]
+ end
+
+ it "supports /e (EUC encoding) with interpolation /o" do
+ match = /#{/./}/e.match("\303\251".force_encoding(Encoding::EUC_JP))
+ match.to_a.should == ["\303\251".force_encoding(Encoding::EUC_JP)]
+ 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 'uses US-ASCII as /n encoding if all chars are 7-bit' do
+ /./n.encoding.should == Encoding::US_ASCII
+ end
+
+ it 'uses ASCII-8BIT as /n encoding if not all chars are 7-bit' do
+ /\xFF/n.encoding.should == Encoding::ASCII_8BIT
+ end
+
+ it 'preserves US-ASCII as /n encoding through interpolation if all chars are 7-bit' do
+ /.#{/./}/n.encoding.should == Encoding::US_ASCII
+ end
+
+ it 'preserves ASCII-8BIT as /n encoding through interpolation if all chars are 7-bit' do
+ /\xFF#{/./}/n.encoding.should == Encoding::ASCII_8BIT
+ end
+
+ it "supports /s (Windows_31J encoding)" do
+ match = /./s.match("\303\251".force_encoding(Encoding::Windows_31J))
+ match.to_a.should == ["\303".force_encoding(Encoding::Windows_31J)]
+ end
+
+ it "supports /s (Windows_31J encoding) with interpolation" do
+ match = /#{/./}/s.match("\303\251".force_encoding(Encoding::Windows_31J))
+ match.to_a.should == ["\303".force_encoding(Encoding::Windows_31J)]
+ end
+
+ it "supports /s (Windows_31J encoding) with interpolation and /o" do
+ match = /#{/./}/s.match("\303\251".force_encoding(Encoding::Windows_31J))
+ match.to_a.should == ["\303".force_encoding(Encoding::Windows_31J)]
+ 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".force_encoding('utf-8')).to_a.should == ["\u{e9}"]
+ end
+
+ it "supports /u (UTF8 encoding) with interpolation" do
+ /#{/./}/u.match("\303\251".force_encoding('utf-8')).to_a.should == ["\u{e9}"]
+ end
+
+ it "supports /u (UTF8 encoding) with interpolation and /o" do
+ /#{/./}/u.match("\303\251".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
+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..50ac22e51e
--- /dev/null
+++ b/spec/ruby/language/regexp/escapes_spec.rb
@@ -0,0 +1,81 @@
+# -*- encoding: binary -*-
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../../fixtures/classes', __FILE__)
+
+describe "Regexps with escape characters" do
+ it "they're supported" 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 "support quoting meta-characters via escape sequence" do
+ /\\/.match("\\").to_a.should == ["\\"]
+ /\//.match("/").to_a.should == ["/"]
+ # 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 == ["}"]
+ # 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 "allows any character to be escaped" do
+ /\y/.match("y").to_a.should == ["y"]
+ end
+
+ it "support \\x (hex characters)" do
+ /\xA/.match("\nxyz").to_a.should == ["\n"]
+ /\x0A/.match("\n").to_a.should == ["\n"]
+ /\xAA/.match("\nA").should be_nil
+ /\x0AA/.match("\nA").to_a.should == ["\nA"]
+ /\xAG/.match("\nG").to_a.should == ["\nG"]
+ # Non-matches
+ lambda { eval('/\xG/') }.should raise_error(SyntaxError)
+
+ # \x{7HHHHHHH} wide hexadecimal char (character code point value)
+ end
+
+ it "support \\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"] # Quantifers apply to entire escape sequence
+ /\\cJ/.match("\\cJ").to_a.should == ["\\cJ"]
+ lambda { eval('/[abc\x]/') }.should raise_error(SyntaxError) # \x is treated as a escape sequence even inside a character class
+ # Syntax error
+ lambda { 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
+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..443cab7ee0
--- /dev/null
+++ b/spec/ruby/language/regexp/grouping_spec.rb
@@ -0,0 +1,23 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../../fixtures/classes', __FILE__)
+
+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
+ lambda { 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
+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..5536c718f1
--- /dev/null
+++ b/spec/ruby/language/regexp/interpolation_spec.rb
@@ -0,0 +1,58 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../../fixtures/classes', __FILE__)
+
+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 == '(?-mix:\c#' + '{str})'
+ end
+
+ it "throws RegexpError for malformed interpolation" do
+ s = ""
+ lambda { /(#{s}/ }.should raise_error(RegexpError)
+ s = "("
+ lambda { /#{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..03dec26f3f
--- /dev/null
+++ b/spec/ruby/language/regexp/modifiers_spec.rb
@@ -0,0 +1,110 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../../fixtures/classes', __FILE__)
+
+describe "Regexps with modifers" 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"]
+
+ lambda { eval('/foo/a') }.should raise_error(SyntaxError)
+ 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"]
+ lambda { 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"]
+ lambda { eval('/(?o:)/') }.should raise_error(SyntaxError)
+ end
+
+ it "supports . with /m" do
+ # Basic matching
+ /./m.match("\n").to_a.should == ["\n"]
+ end
+
+ it "supports ASII/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..2fc8a74a47
--- /dev/null
+++ b/spec/ruby/language/regexp/repetition_spec.rb
@@ -0,0 +1,57 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../../fixtures/classes', __FILE__)
+
+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
+
+ ruby_version_is ""..."2.4" do
+ it "does not treat {m,n}+ as possessive" do
+ @regexp = eval "/foo(A{0,1}+)Abar/"
+ @regexp.match("fooAAAbar").to_a.should == ["fooAAAbar", "AA"]
+ end
+ end
+
+ ruby_version_is "2.4" do
+ it "does not treat {m,n}+ as possessive" do
+ -> {
+ @regexp = eval "/foo(A{0,1}+)Abar/"
+ }.should complain(/nested repeat operato/)
+ @regexp.match("fooAAAbar").to_a.should == ["fooAAAbar", "AA"]
+ end
+ end
+
+ it "supports ? (0 or 1 of previous subexpression)" do
+ /a?/.match("aaa").to_a.should == ["a"]
+ /a?/.match("bbb").to_a.should == [""]
+ end
+end
diff --git a/spec/ruby/language/regexp_spec.rb b/spec/ruby/language/regexp_spec.rb
new file mode 100644
index 0000000000..d4b0c81128
--- /dev/null
+++ b/spec/ruby/language/regexp_spec.rb
@@ -0,0 +1,150 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+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 "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
+ lambda { 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|
+ lambda { eval("%r#{p0} foo #{p0}") }.should raise_error(SyntaxError)
+ end
+ end
+
+ it "supports non-paired delimiters delimiters with %r" do
+ LanguageSpecs.non_paired_delimiters.each do |c|
+ eval("%r#{c} foo #{c}").should == / foo /
+ end
+ end
+
+ it "disallows alphabets as non-paired delimiter with %r" do
+ lambda { eval('%ra foo a') }.should raise_error(SyntaxError)
+ end
+
+ it "disallows spaces after %r and delimiter" do
+ lambda { 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 possesive
+ 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
+
+ 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
+end
diff --git a/spec/ruby/language/rescue_spec.rb b/spec/ruby/language/rescue_spec.rb
new file mode 100644
index 0000000000..66366cd1a0
--- /dev/null
+++ b/spec/ruby/language/rescue_spec.rb
@@ -0,0 +1,293 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/rescue', __FILE__)
+
+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
+
+ it "can capture the raised exception in a local variable" do
+ begin
+ raise SpecificExampleException, "some text"
+ rescue SpecificExampleException => e
+ e.message.should == "some text"
+ end
+ end
+
+ it "can rescue multiple raised exceptions with a single rescue block" do
+ [lambda{raise ArbitraryException}, lambda{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 = []
+ [lambda{raise ArbitraryException}, lambda{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 "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 = []
+ [lambda{raise ArbitraryException}, lambda{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
+ lambda do
+ begin
+ raise OtherCustomException, "not rescued!"
+ rescue *exception_list
+ end
+ end.should raise_error(OtherCustomException)
+ 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 "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
+ lambda 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
+
+ it "without classes will not rescue Exception" do
+ lambda do
+ begin
+ raise Exception
+ rescue
+ 'Exception wrongly rescued'
+ end
+ end.should raise_error(Exception)
+ 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
+ lambda {
+ 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]
+ lambda {
+ 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
+ invalid_rescuer = Object.new
+ begin
+ :foo
+ rescue rescuer
+ end.should == :foo
+ end
+
+ it "should splat the handling Error classes" do
+ begin
+ raise "raise"
+ rescue *(RuntimeError) => e
+ :expected
+ end.should == :expected
+ end
+end
diff --git a/spec/ruby/language/retry_spec.rb b/spec/ruby/language/retry_spec.rb
new file mode 100644
index 0000000000..96e69b763a
--- /dev/null
+++ b/spec/ruby/language/retry_spec.rb
@@ -0,0 +1,52 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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 begin statement" do
+ lambda { eval 'retry' }.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..323f74aad3
--- /dev/null
+++ b/spec/ruby/language/return_spec.rb
@@ -0,0 +1,245 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/return', __FILE__)
+
+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
+ lambda { Thread.new { return }.join }.should raise_error(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
+ lambda 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
+ lambda { return; 456 }.call.should be_nil
+ end
+
+ it "causes lambda to return nil if invoked with an empty expression" do
+ lambda { return (); 456 }.call.should be_nil
+ end
+
+ it "causes lambda to return the value passed to return" do
+ lambda { 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
+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..a8b29dc5a3
--- /dev/null
+++ b/spec/ruby/language/safe_navigator_spec.rb
@@ -0,0 +1,101 @@
+require File.expand_path("../../spec_helper", __FILE__)
+
+ruby_version_is "2.3" do
+ describe "Safe navigator" do
+ it "requires a method name to be provided" do
+ lambda { eval("obj&. {}") }.should raise_error(SyntaxError)
+ end
+
+ context "when context is nil" do
+ it "always returns nil" do
+ eval("nil&.unknown").should == nil
+ eval("[][10]&.unknown").should == nil
+ end
+
+ it "can be chained" do
+ eval("nil&.one&.two&.three").should == nil
+ end
+
+ it "doesn't evaluate arguments" do
+ obj = Object.new
+ obj.should_not_receive(:m)
+ eval("nil&.unknown(obj.m) { obj.m }")
+ end
+ end
+
+ context "when context is false" do
+ it "calls the method" do
+ eval("false&.to_s").should == "false"
+
+ lambda { eval("false&.unknown") }.should raise_error(NoMethodError)
+ end
+ end
+
+ context "when context is truthy" do
+ it "calls the method" do
+ eval("1&.to_s").should == "1"
+
+ lambda { eval("1&.unknown") }.should raise_error(NoMethodError)
+ end
+ end
+
+ it "takes a list of arguments" do
+ eval("[1,2,3]&.first(2)").should == [1,2]
+ end
+
+ it "takes a block" do
+ eval("[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
+
+ eval("obj&.foo = 3").should == 3
+ obj.foo.should == 3
+
+ obj = nil
+ eval("obj&.foo = 3").should == nil
+ end
+
+ it "allows assignment operators" do
+ klass = Class.new do
+ attr_accessor :m
+
+ def initialize
+ @m = 0
+ end
+ end
+
+ obj = klass.new
+
+ eval("obj&.m += 3")
+ obj.m.should == 3
+
+ obj = nil
+ eval("obj&.m += 3").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
+
+ lambda {
+ eval("obj&.foo += 3")
+ }.should raise_error(NoMethodError) { |e|
+ e.name.should == :+
+ }
+ end
+ end
+end
diff --git a/spec/ruby/language/send_spec.rb b/spec/ruby/language/send_spec.rb
new file mode 100644
index 0000000000..646a700785
--- /dev/null
+++ b/spec/ruby/language/send_spec.rb
@@ -0,0 +1,521 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/send', __FILE__)
+
+# Why so many fixed arg tests? JRuby and I assume other Ruby impls have
+# separate call paths for simple fixed arity methods. Testing up to five
+# will verify special and generic arity code paths for all impls.
+#
+# Method naming conventions:
+# M - Manditory Args
+# O - Optional Arg
+# R - Rest Arg
+# Q - Post Manditory 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
+ lambda {
+ 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
+ lambda {
+ specs.fooM1(1,2)
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "with optional arguments" do
+ it "uses the optional argument if none is is passed" do
+ 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
+ lambda {
+ 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
+ lambda {
+ specs.fooM1O1
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if too many values are passed" do
+ lambda {
+ specs.fooM1O1(1,2,3)
+ }.should raise_error(ArgumentError)
+ end
+ 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
+
+ it "raises a SyntaxError with both a literal block and an object as block" do
+ lambda {
+ eval "specs.oneb(10, &l){ 42 }"
+ }.should raise_error(SyntaxError)
+ end
+
+ 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
+ lambda { no_such_method }.should raise_error NameError
+ end
+
+ it "raises NoMethodError if invoked as an unambiguous method call" do
+ lambda { no_such_method() }.should raise_error NoMethodError
+ lambda { no_such_method(1,2,3) }.should raise_error NoMethodError
+ 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 "does not permit self as a receiver" do
+ receiver = LangSendSpecs::PrivateGetter.new
+ lambda { receiver.call_self_foo }.should raise_error(NoMethodError)
+ lambda { receiver.call_self_foo_or_equals(6) }.should raise_error(NoMethodError)
+ 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 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..837f479440
--- /dev/null
+++ b/spec/ruby/language/singleton_class_spec.rb
@@ -0,0 +1,293 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../../fixtures/class', __FILE__)
+
+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 Fixnum's" do
+ lambda { 1.singleton_class }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError for symbols" do
+ lambda { :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".singleton_class.superclass.should == String
+ end
+
+ it "doesn't have singleton class" do
+ lambda { bignum_value.singleton_class.superclass.should == Bignum }.should raise_error(TypeError)
+ 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
+ lambda { CONST }.should raise_error(NameError)
+ end
+
+ it "cannot be accessed via object::CONST" do
+ lambda 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
+
+ lambda 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
+
+ lambda 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
+ lambda {
+ Object.new.singleton_class.new
+ }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when allocate is called" do
+ lambda {
+ Object.new.singleton_class.allocate
+ }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/language/string_spec.rb b/spec/ruby/language/string_spec.rb
new file mode 100644
index 0000000000..dbec2652ed
--- /dev/null
+++ b/spec/ruby/language/string_spec.rb
@@ -0,0 +1,282 @@
+# -*- encoding: binary -*-
+
+require File.expand_path('../../spec_helper', __FILE__)
+
+# 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 "allows underscore as part of a variable name in a simple interpolation" do
+ @my_ip = 'xxx'
+ "#@my_ip".should == 'xxx'
+ 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 "taints the result of interpolation when an interpolated value is tainted" do
+ "#{"".taint}".tainted?.should be_true
+
+ @ip.taint
+ "#@ip".tainted?.should be_true
+
+ $ip.taint
+ "#$ip".tainted?.should be_true
+ end
+
+ it "untrusts the result of interpolation when an interpolated value is untrusted" do
+ "#{"".untrust}".untrusted?.should be_true
+
+ @ip.untrust
+ "#@ip".untrusted?.should be_true
+
+ $ip.untrust
+ "#$ip".untrusted?.should be_true
+ end
+
+ 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::ASCII_8BIT
+ end
+
+ it "produces an ASCII string when escaping ASCII characters via \\u{}" do
+ "\u{0000}".encoding.should == Encoding::ASCII_8BIT
+ 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
+
+ ruby_version_is "2.3" do
+ describe "with a magic frozen comment" do
+ it "produce the same object each time" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_one_literal.rb")).chomp.should == "true"
+ end
+
+ it "produce the same object for literals with the same content" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_two_literals.rb")).chomp.should == "true"
+ end
+
+ it "produce the same object for literals with the same content in different files" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files.rb")).chomp.should == "true"
+ end
+
+ it "produce different objects for literals with the same content in different files if the other file doesn't have the comment" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_no_comment.rb")).chomp.should == "true"
+ 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
+
+end
+
+with_feature :encoding do
+ describe "Ruby String interpolation" do
+ it "creates a String having an Encoding compatible with all components" do
+ a = "\u3042"
+ b = "abc".encode("ascii-8bit")
+
+ str = "#{a} x #{b}"
+
+ str.should == "\xe3\x81\x82\x20\x78\x20\x61\x62\x63".force_encoding("utf-8")
+ str.encoding.should == Encoding::UTF_8
+ end
+
+ it "creates a String having the Encoding of the components when all are the same Encoding" do
+ a = "abc".force_encoding("euc-jp")
+ b = "def".force_encoding("euc-jp")
+ str = '"#{a} x #{b}"'.force_encoding("euc-jp")
+
+ result = eval(str)
+ result.should == "\x61\x62\x63\x20\x78\x20\x64\x65\x66".force_encoding("euc-jp")
+ result.encoding.should == Encoding::EUC_JP
+ end
+
+ it "raises an Encoding::CompatibilityError if the Encodings are not compatible" do
+ a = "\u3042"
+ b = "\xff".force_encoding "ascii-8bit"
+
+ lambda { "#{a} #{b}" }.should raise_error(Encoding::CompatibilityError)
+ end
+ end
+end
diff --git a/spec/ruby/language/super_spec.rb b/spec/ruby/language/super_spec.rb
new file mode 100644
index 0000000000..fb7e089fb4
--- /dev/null
+++ b/spec/ruby/language/super_spec.rb
@@ -0,0 +1,282 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/super', __FILE__)
+
+describe "The super keyword" do
+ it "calls the method on the calling class" do
+ Super::S1::A.new.foo([]).should == ["A#foo","A#bar"]
+ Super::S1::A.new.bar([]).should == ["A#bar"]
+ Super::S1::B.new.foo([]).should == ["B#foo","A#foo","B#bar","A#bar"]
+ Super::S1::B.new.bar([]).should == ["B#bar","A#bar"]
+ end
+
+ it "searches the full inheritence chain" do
+ Super::S2::B.new.foo([]).should == ["B#foo","A#baz"]
+ Super::S2::B.new.baz([]).should == ["A#baz"]
+ Super::S2::C.new.foo([]).should == ["B#foo","C#baz","A#baz"]
+ Super::S2::C.new.baz([]).should == ["C#baz","A#baz"]
+ end
+
+ it "searches class methods" do
+ Super::S3::A.new.foo([]).should == ["A#foo"]
+ Super::S3::A.foo([]).should == ["A.foo"]
+ Super::S3::A.bar([]).should == ["A.bar","A.foo"]
+ Super::S3::B.new.foo([]).should == ["A#foo"]
+ Super::S3::B.foo([]).should == ["B.foo","A.foo"]
+ Super::S3::B.bar([]).should == ["B.bar","A.bar","B.foo","A.foo"]
+ end
+
+ it "calls the method on the calling class including modules" do
+ Super::MS1::A.new.foo([]).should == ["ModA#foo","ModA#bar"]
+ Super::MS1::A.new.bar([]).should == ["ModA#bar"]
+ Super::MS1::B.new.foo([]).should == ["B#foo","ModA#foo","ModB#bar","ModA#bar"]
+ Super::MS1::B.new.bar([]).should == ["ModB#bar","ModA#bar"]
+ end
+
+ it "searches the full inheritence chain including modules" do
+ Super::MS2::B.new.foo([]).should == ["ModB#foo","A#baz"]
+ Super::MS2::B.new.baz([]).should == ["A#baz"]
+ Super::MS2::C.new.baz([]).should == ["C#baz","A#baz"]
+ Super::MS2::C.new.foo([]).should == ["ModB#foo","C#baz","A#baz"]
+ end
+
+ it "can resolve to different methods in an included module method" do
+ Super::MultiSuperTargets::A.new.foo.should == :BaseA
+ Super::MultiSuperTargets::B.new.foo.should == :BaseB
+ end
+
+ it "searches class methods including modules" do
+ Super::MS3::A.new.foo([]).should == ["A#foo"]
+ Super::MS3::A.foo([]).should == ["ModA#foo"]
+ Super::MS3::A.bar([]).should == ["ModA#bar","ModA#foo"]
+ Super::MS3::B.new.foo([]).should == ["A#foo"]
+ Super::MS3::B.foo([]).should == ["B.foo","ModA#foo"]
+ Super::MS3::B.bar([]).should == ["B.bar","ModA#bar","B.foo","ModA#foo"]
+ end
+
+ it "searches BasicObject from a module for methods defined there" do
+ Super::IncludesFromBasic.new.__send__(:foobar).should == 43
+ end
+
+ it "searches BasicObject through another module for methods defined there" do
+ Super::IncludesIntermediate.new.__send__(:foobar).should == 42
+ end
+
+ it "calls the correct method when the method visibility is modified" do
+ Super::MS4::A.new.example.should == 5
+ end
+
+ it "calls the correct method when the superclass argument list is different from the subclass" do
+ Super::S4::A.new.foo([]).should == ["A#foo"]
+ Super::S4::B.new.foo([],"test").should == ["B#foo(a,test)", "A#foo"]
+ end
+
+ it "raises an error 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
+
+ lambda {sub_normal.new.foo}.should raise_error(NoMethodError, /super/)
+ lambda {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 "calls the superclass method when in a block" do
+ Super::S6.new.here.should == :good
+ end
+
+ it "calls the superclass method when initial method is defined_method'd" do
+ Super::S7.new.here.should == :good
+ end
+
+ it "can call through a define_method multiple times (caching check)" do
+ obj = Super::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
+
+ lambda { klass.new.a(:a_called) }.should raise_error(RuntimeError)
+ end
+
+ # Rubinius ticket github#157
+ it "calls method_missing when a superclass method is not found" do
+ Super::MM_B.new.is_a?(Hash).should == false
+ end
+
+ # Rubinius ticket github#180
+ it "respects the original module a method is aliased from" do
+ Super::Alias3.new.name3.should == [:alias2, :alias1]
+ end
+
+ it "sees the included version of a module a method is alias from" do
+ Super::AliasWithSuper::Trigger.foo.should == [:b, :a]
+ end
+
+ it "find super from a singleton class" do
+ obj = Super::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 = Super::SingletonAliasCase::Foo.new
+ orig_obj.alias_on_singleton
+ orig_obj.new_foobar([]).should == [:foo, :base]
+ Super::SingletonAliasCase::Foo.new.foobar([]).should == [:foo, :base]
+ end
+
+ it "passes along modified rest args when they weren't originally empty" do
+ Super::RestArgsWithSuper::B.new.a("bar").should == ["bar", "foo"]
+ end
+
+ it "passes along modified rest args when they were originally empty" do
+ Super::RestArgsWithSuper::B.new.a.should == ["foo"]
+ end
+
+ it "invokes methods from a chain of anonymous modules" do
+ Super::AnonymousModuleIncludedTwice.new.a([]).should == ["anon", "anon", "non-anon"]
+ end
+
+ it "without explicit arguments can accept a block but still pass the original arguments" do
+ Super::ZSuperWithBlock::B.new.a.should == 14
+ end
+
+ it "passes along block via reference to method expecting a reference" do
+ Super::ZSuperWithBlock::B.new.b.should == [14, 15]
+ end
+
+ it "passes along a block via reference to a method that yields" do
+ Super::ZSuperWithBlock::B.new.c.should == 16
+ end
+
+ it "without explicit arguments passes optional arguments that have a default value" do
+ Super::ZSuperWithOptional::B.new.m(1, 2).should == 14
+ end
+
+ it "without explicit arguments passes optional arguments that have a non-default value" do
+ Super::ZSuperWithOptional::B.new.m(1, 2, 3).should == 3
+ end
+
+ it "without explicit arguments passes optional arguments that have a default value but were modified" do
+ Super::ZSuperWithOptional::C.new.m(1, 2).should == 100
+ end
+
+ it "without explicit arguments passes optional arguments that have a non-default value but were modified" do
+ Super::ZSuperWithOptional::C.new.m(1, 2, 3).should == 100
+ end
+
+ it "without explicit arguments passes rest arguments" do
+ Super::ZSuperWithRest::B.new.m(1, 2, 3).should == [1, 2, 3]
+ end
+
+ it "without explicit arguments passes rest arguments including any modifications" do
+ Super::ZSuperWithRest::B.new.m_modified(1, 2, 3).should == [1, 14, 3]
+ end
+
+ it "without explicit arguments passes arguments and rest arguments" do
+ Super::ZSuperWithRestAndOthers::B.new.m(1, 2, 3, 4, 5).should == [3, 4, 5]
+ end
+
+ it "without explicit arguments passes arguments and rest arguments including any modifications" do
+ Super::ZSuperWithRestAndOthers::B.new.m_modified(1, 2, 3, 4, 5).should == [3, 14, 5]
+ end
+
+ it "without explicit arguments that are '_'" do
+ Super::ZSuperWithUnderscores::B.new.m(1, 2).should == [1, 2]
+ end
+
+ it "without explicit arguments that are '_' including any modifications" do
+ Super::ZSuperWithUnderscores::B.new.m_modified(1, 2).should == [14, 2]
+ end
+
+ describe 'when using keyword arguments' do
+ it 'passes any given keyword arguments to the parent' do
+ b = Super::KeywordArguments::B.new
+ b.foo(:number => 10).should == {:number => 10}
+ end
+
+ it "passes any given keyword arguments including optional and required ones to the parent" do
+ class Super::KeywordArguments::C
+ eval <<-RUBY
+ def foo(a:, b: 'b', **)
+ super
+ end
+ RUBY
+ end
+ c = Super::KeywordArguments::C.new
+
+ c.foo(a: 'a', c: 'c').should == {a: 'a', b: 'b', c: 'c'}
+ end
+
+ it 'does not pass any keyword arguments to the parent when none are given' do
+ b = Super::KeywordArguments::B.new
+ b.foo.should == {}
+ end
+
+ describe 'when using splat arguments' do
+ it 'passes splat arguments and keyword arguments to the parent' do
+ b = Super::SplatAndKeyword::B.new
+
+ b.foo('bar', baz: true).should == [['bar'], {baz: true}]
+ end
+ 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..41be5b2236
--- /dev/null
+++ b/spec/ruby/language/symbol_spec.rb
@@ -0,0 +1,93 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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 "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
+end
diff --git a/spec/ruby/language/throw_spec.rb b/spec/ruby/language/throw_spec.rb
new file mode 100644
index 0000000000..d78c137708
--- /dev/null
+++ b/spec/ruby/language/throw_spec.rb
@@ -0,0 +1,80 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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
+ lambda { 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 = lambda { throw :foo, :msg }
+ catch(:foo) { c.call }.should == :msg
+ end
+
+ it "raises an ArgumentError if outside of scope of a matching catch" do
+ lambda { throw :test, 5 }.should raise_error(ArgumentError)
+ lambda { catch(:different) { throw :test, 5 } }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if used to exit a thread" do
+ lambda {
+ catch(:what) do
+ Thread.new {
+ throw :what
+ }.join
+ end
+ }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/language/undef_spec.rb b/spec/ruby/language/undef_spec.rb
new file mode 100644
index 0000000000..9e788f2a09
--- /dev/null
+++ b/spec/ruby/language/undef_spec.rb
@@ -0,0 +1,72 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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
+ lambda { @obj.meth(5) }.should raise_error(NoMethodError)
+ end
+
+ it "with a simple symbol" do
+ @undef_class.class_eval do
+ undef :meth
+ end
+ lambda { @obj.meth(5) }.should raise_error(NoMethodError)
+ end
+
+ it "with a single quoted symbol" do
+ @undef_class.class_eval do
+ undef :'meth'
+ end
+ lambda { @obj.meth(5) }.should raise_error(NoMethodError)
+ end
+
+ it "with a double quoted symbol" do
+ @undef_class.class_eval do
+ undef :"meth"
+ end
+ lambda { @obj.meth(5) }.should raise_error(NoMethodError)
+ end
+
+ it "with a interpolated symbol" do
+ @undef_class.class_eval do
+ undef :"#{'meth'}"
+ end
+ lambda { @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
+ lambda {
+ 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..681f0adfdd
--- /dev/null
+++ b/spec/ruby/language/unless_spec.rb
@@ -0,0 +1,43 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+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..08898644ce
--- /dev/null
+++ b/spec/ruby/language/until_spec.rb
@@ -0,0 +1,234 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+# 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 reevaluting 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..81ba54840a
--- /dev/null
+++ b/spec/ruby/language/variables_spec.rb
@@ -0,0 +1,760 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/variables', __FILE__)
+
+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)
+
+ lambda { 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)
+
+ lambda { *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)
+
+ lambda { 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)
+
+ lambda { 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
+ a.should == [nil, 1, nil]
+ end
+
+ it "assigns constants" do
+ module VariableSpecs
+ SINGLE_RHS_1, SINGLE_RHS_2 = 1
+ [SINGLE_RHS_1, SINGLE_RHS_2].should == [1, nil]
+ end
+ 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
+
+ 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
+
+ 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 "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)
+
+ lambda { *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)
+
+ lambda { 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)
+
+ lambda { 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)
+
+ lambda { 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
+ 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)
+
+ lambda { 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)
+
+ lambda { 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)
+
+ lambda { 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)
+
+ lambda { 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 "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
+ 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
+ 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
diff --git a/spec/ruby/language/while_spec.rb b/spec/ruby/language/while_spec.rb
new file mode 100644
index 0000000000..00e948e41f
--- /dev/null
+++ b/spec/ruby/language/while_spec.rb
@@ -0,0 +1,344 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+# 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 reevaluting 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..663110cbe6
--- /dev/null
+++ b/spec/ruby/language/yield_spec.rb
@@ -0,0 +1,179 @@
+require File.expand_path('../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/yield', __FILE__)
+
+# 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
+ lambda { @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
+ end
+
+ describe "taking a single argument" do
+ describe "when no block is given" do
+ it "raises a LocalJumpError" do
+ lambda { @y.s(1) }.should raise_error(LocalJumpError)
+ 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([], &lambda { |*a| a }).should == [[]]
+ end
+
+ it "passes nil as a value" do
+ @y.s(nil, &lambda { |*a| a }).should == [nil]
+ end
+
+ it "passes a single value" do
+ @y.s(1, &lambda { |*a| a }).should == [1]
+ end
+
+ it "passes a single, multi-value Array" do
+ @y.s([1, 2, 3], &lambda { |*a| a }).should == [[1, 2, 3]]
+ end
+
+ it "raises an ArgumentError if too few arguments are passed" do
+ lambda {
+ @y.s(1, &lambda { |a,b| [a,b] })
+ }.should raise_error(ArgumentError)
+ end
+
+ ruby_bug "#12705", "2.2"..."2.5" do
+ it "should not destructure an Array into multiple arguments" do
+ lambda {
+ @y.s([1, 2], &lambda { |a,b| [a,b] })
+ }.should raise_error(ArgumentError)
+ end
+ end
+ end
+ end
+
+ describe "taking multiple arguments" do
+ it "raises a LocalJumpError when the method is not passed a block" do
+ lambda { @y.m(1, 2, 3) }.should raise_error(LocalJumpError)
+ 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
+ lambda {
+ @y.m(1, 2, 3, &lambda { |a| })
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if too few arguments are passed to a lambda" do
+ lambda {
+ @y.m(1, 2, 3, &lambda { |a,b,c,d| })
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "taking a single splatted argument" do
+ it "raises a LocalJumpError when the method is not passed a block" do
+ lambda { @y.r(0) }.should raise_error(LocalJumpError)
+ 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
+ lambda { @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
+ lambda { @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
+
+ it "uses captured block of a block used in define_method" do
+ @y.deep(2).should == 4
+ end
+
+end