summaryrefslogtreecommitdiff
path: root/spec/ruby/language
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/language')
-rw-r--r--spec/ruby/language/BEGIN_spec.rb13
-rw-r--r--spec/ruby/language/END_spec.rb15
-rw-r--r--spec/ruby/language/README2
-rw-r--r--spec/ruby/language/alias_spec.rb58
-rw-r--r--spec/ruby/language/and_spec.rb2
-rw-r--r--spec/ruby/language/array_spec.rb11
-rw-r--r--spec/ruby/language/block_spec.rb401
-rw-r--r--spec/ruby/language/break_spec.rb56
-rw-r--r--spec/ruby/language/case_spec.rb276
-rw-r--r--spec/ruby/language/class_spec.rb85
-rw-r--r--spec/ruby/language/class_variable_spec.rb40
-rw-r--r--spec/ruby/language/comment_spec.rb13
-rw-r--r--spec/ruby/language/constants_spec.rb278
-rw-r--r--spec/ruby/language/def_spec.rb160
-rw-r--r--spec/ruby/language/defined_spec.rb147
-rw-r--r--spec/ruby/language/delegation_spec.rb65
-rw-r--r--spec/ruby/language/encoding_spec.rb16
-rw-r--r--spec/ruby/language/ensure_spec.rb271
-rw-r--r--spec/ruby/language/execution_spec.rb2
-rw-r--r--spec/ruby/language/file_spec.rb20
-rw-r--r--spec/ruby/language/fixtures/array.rb21
-rw-r--r--spec/ruby/language/fixtures/begin_file.rb3
-rw-r--r--spec/ruby/language/fixtures/block.rb4
-rw-r--r--spec/ruby/language/fixtures/break.rb38
-rw-r--r--spec/ruby/language/fixtures/break_lambda_toplevel.rb2
-rw-r--r--spec/ruby/language/fixtures/break_lambda_toplevel_block.rb2
-rw-r--r--spec/ruby/language/fixtures/break_lambda_toplevel_method.rb2
-rw-r--r--spec/ruby/language/fixtures/bytes_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/case_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/constant_visibility.rb18
-rw-r--r--spec/ruby/language/fixtures/def.rb6
-rw-r--r--spec/ruby/language/fixtures/defined.rb8
-rw-r--r--spec/ruby/language/fixtures/delegation.rb11
-rw-r--r--spec/ruby/language/fixtures/emacs_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/ensure.rb51
-rw-r--r--spec/ruby/language/fixtures/for_scope.rb15
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb2
-rw-r--r--spec/ruby/language/fixtures/hash_strings_binary.rb (renamed from spec/ruby/language/fixtures/hash_strings_ascii8bit.rb)4
-rw-r--r--spec/ruby/language/fixtures/magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/metaclass.rb1
-rw-r--r--spec/ruby/language/fixtures/no_magic_comment.rb1
-rw-r--r--spec/ruby/language/fixtures/print_magic_comment_result_at_exit.rb3
-rw-r--r--spec/ruby/language/fixtures/rescue.rb4
-rw-r--r--spec/ruby/language/fixtures/rescue_captures.rb107
-rw-r--r--spec/ruby/language/fixtures/return.rb8
-rw-r--r--spec/ruby/language/fixtures/second_line_magic_comment.rb3
-rw-r--r--spec/ruby/language/fixtures/second_token_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/send.rb3
-rwxr-xr-xspec/ruby/language/fixtures/shebang_magic_comment.rb3
-rw-r--r--spec/ruby/language/fixtures/squiggly_heredoc.rb32
-rw-r--r--spec/ruby/language/fixtures/super.rb86
-rw-r--r--spec/ruby/language/fixtures/utf16-be-nobom.rbbin0 -> 68 bytes
-rw-r--r--spec/ruby/language/fixtures/utf16-le-nobom.rbbin0 -> 69 bytes
-rw-r--r--spec/ruby/language/fixtures/utf8-bom.rb2
-rw-r--r--spec/ruby/language/fixtures/utf8-nobom.rb2
-rw-r--r--spec/ruby/language/fixtures/vim_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/yield.rb4
-rw-r--r--spec/ruby/language/for_spec.rb17
-rw-r--r--spec/ruby/language/hash_spec.rb128
-rw-r--r--spec/ruby/language/heredoc_spec.rb84
-rw-r--r--spec/ruby/language/if_spec.rb32
-rw-r--r--spec/ruby/language/keyword_arguments_spec.rb397
-rw-r--r--spec/ruby/language/lambda_spec.rb127
-rw-r--r--spec/ruby/language/line_spec.rb8
-rw-r--r--spec/ruby/language/loop_spec.rb4
-rw-r--r--spec/ruby/language/magic_comment_spec.rb110
-rw-r--r--spec/ruby/language/match_spec.rb11
-rw-r--r--spec/ruby/language/metaclass_spec.rb18
-rw-r--r--spec/ruby/language/method_spec.rb940
-rw-r--r--spec/ruby/language/module_spec.rb51
-rw-r--r--spec/ruby/language/next_spec.rb16
-rw-r--r--spec/ruby/language/not_spec.rb2
-rw-r--r--spec/ruby/language/numbered_parameters_spec.rb118
-rw-r--r--spec/ruby/language/numbers_spec.rb18
-rw-r--r--spec/ruby/language/optional_assignments_spec.rb280
-rw-r--r--spec/ruby/language/or_spec.rb14
-rw-r--r--spec/ruby/language/order_spec.rb2
-rw-r--r--spec/ruby/language/pattern_matching_spec.rb1403
-rw-r--r--spec/ruby/language/precedence_spec.rb117
-rw-r--r--spec/ruby/language/predefined/data_spec.rb21
-rw-r--r--spec/ruby/language/predefined/fixtures/data2.rb3
-rw-r--r--spec/ruby/language/predefined/fixtures/data3.rb3
-rw-r--r--spec/ruby/language/predefined/fixtures/data_offset.rb12
-rw-r--r--spec/ruby/language/predefined/fixtures/empty_data.rb3
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic_required.rb2
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_id.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_id_required.rb1
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_required_before.rb2
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_values.rb9
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_variables.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_variables_required.rb2
-rw-r--r--spec/ruby/language/predefined/toplevel_binding_spec.rb34
-rw-r--r--spec/ruby/language/predefined_spec.rb814
-rw-r--r--spec/ruby/language/private_spec.rb18
-rw-r--r--spec/ruby/language/proc_spec.rb31
-rw-r--r--spec/ruby/language/range_spec.rb30
-rw-r--r--spec/ruby/language/redo_spec.rb6
-rw-r--r--spec/ruby/language/regexp/anchors_spec.rb4
-rw-r--r--spec/ruby/language/regexp/back-references_spec.rb98
-rw-r--r--spec/ruby/language/regexp/character_classes_spec.rb47
-rw-r--r--spec/ruby/language/regexp/empty_checks_spec.rb135
-rw-r--r--spec/ruby/language/regexp/encoding_spec.rb57
-rw-r--r--spec/ruby/language/regexp/escapes_spec.rb112
-rw-r--r--spec/ruby/language/regexp/grouping_spec.rb46
-rw-r--r--spec/ruby/language/regexp/interpolation_spec.rb10
-rw-r--r--spec/ruby/language/regexp/modifiers_spec.rb22
-rw-r--r--spec/ruby/language/regexp/repetition_spec.rb115
-rw-r--r--spec/ruby/language/regexp/subexpression_call_spec.rb50
-rw-r--r--spec/ruby/language/regexp_spec.rb35
-rw-r--r--spec/ruby/language/rescue_spec.rb267
-rw-r--r--spec/ruby/language/retry_spec.rb4
-rw-r--r--spec/ruby/language/return_spec.rb360
-rw-r--r--spec/ruby/language/safe_navigator_spec.rb144
-rw-r--r--spec/ruby/language/safe_spec.rb27
-rw-r--r--spec/ruby/language/send_spec.rb81
-rw-r--r--spec/ruby/language/singleton_class_spec.rb24
-rw-r--r--spec/ruby/language/source_encoding_spec.rb61
-rw-r--r--spec/ruby/language/string_spec.rb135
-rw-r--r--spec/ruby/language/super_spec.rb235
-rw-r--r--spec/ruby/language/symbol_spec.rb4
-rw-r--r--spec/ruby/language/throw_spec.rb10
-rw-r--r--spec/ruby/language/undef_spec.rb14
-rw-r--r--spec/ruby/language/unless_spec.rb2
-rw-r--r--spec/ruby/language/until_spec.rb4
-rw-r--r--spec/ruby/language/variables_spec.rb138
-rw-r--r--spec/ruby/language/while_spec.rb4
-rw-r--r--spec/ruby/language/yield_spec.rb94
128 files changed, 7744 insertions, 1884 deletions
diff --git a/spec/ruby/language/BEGIN_spec.rb b/spec/ruby/language/BEGIN_spec.rb
index 826d5f0c89..5aef5a1d7c 100644
--- a/spec/ruby/language/BEGIN_spec.rb
+++ b/spec/ruby/language/BEGIN_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The BEGIN keyword" do
before :each do
@@ -15,7 +15,12 @@ describe "The BEGIN keyword" do
end
it "must appear in a top-level context" do
- lambda { eval "1.times { BEGIN { 1 } }" }.should raise_error(SyntaxError)
+ -> { eval "1.times { BEGIN { 1 } }" }.should raise_error(SyntaxError)
+ end
+
+ it "uses top-level for self" do
+ eval("BEGIN { ScratchPad << self.to_s }", TOPLEVEL_BINDING)
+ ScratchPad.recorded.should == ['main']
end
it "runs first in a given code unit" do
@@ -29,4 +34,8 @@ describe "The BEGIN keyword" do
ScratchPad.recorded.should == ['foo', 'bar']
end
+
+ it "returns the top-level script's filename for __FILE__" do
+ ruby_exe(fixture(__FILE__, "begin_file.rb")).chomp.should =~ /begin_file\.rb$/
+ end
end
diff --git a/spec/ruby/language/END_spec.rb b/spec/ruby/language/END_spec.rb
new file mode 100644
index 0000000000..762a8db0c0
--- /dev/null
+++ b/spec/ruby/language/END_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../spec_helper'
+
+describe "The END keyword" do
+ it "runs only once for multiple calls" do
+ ruby_exe("10.times { END { puts 'foo' }; } ").should == "foo\n"
+ end
+
+ it "runs last in a given code unit" do
+ ruby_exe("END { puts 'bar' }; puts'foo'; ").should == "foo\nbar\n"
+ end
+
+ it "runs multiple ends in LIFO order" do
+ ruby_exe("END { puts 'foo' }; END { puts 'bar' }").should == "bar\nfoo\n"
+ end
+end
diff --git a/spec/ruby/language/README b/spec/ruby/language/README
index 74eaf58709..ae08e17fb1 100644
--- a/spec/ruby/language/README
+++ b/spec/ruby/language/README
@@ -4,7 +4,7 @@ words. These words significantly describe major elements of the language,
including flow control constructs like 'for' and 'while', conditional
execution like 'if' and 'unless', exceptional execution control like 'rescue',
etc. There are also literals for the basic "types" like String, Regexp, Array
-and Fixnum.
+and Integer.
Behavioral specifications describe the behavior of concrete entities. Rather
than using concepts of computation to organize these spec files, we use
diff --git a/spec/ruby/language/alias_spec.rb b/spec/ruby/language/alias_spec.rb
index e9f0050e17..c353390679 100644
--- a/spec/ruby/language/alias_spec.rb
+++ b/spec/ruby/language/alias_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
class AliasObject
attr :foo
@@ -38,14 +38,14 @@ describe "The alias keyword" do
@obj.a.should == 5
end
- it "works with a doubule quoted symbol on the left-hand side" do
+ it "works with a double quoted symbol on the left-hand side" do
@meta.class_eval do
alias :"a" value
end
@obj.a.should == 5
end
- it "works with an interoplated symbol on the left-hand side" do
+ it "works with an interpolated symbol on the left-hand side" do
@meta.class_eval do
alias :"#{'a'}" value
end
@@ -66,14 +66,14 @@ describe "The alias keyword" do
@obj.a.should == 5
end
- it "works with a doubule quoted symbol on the right-hand side" do
+ it "works with a double quoted symbol on the right-hand side" do
@meta.class_eval do
alias a :"value"
end
@obj.a.should == 5
end
- it "works with an interoplated symbol on the right-hand side" do
+ it "works with an interpolated symbol on the right-hand side" do
@meta.class_eval do
alias a :"#{'value'}"
end
@@ -122,7 +122,7 @@ describe "The alias keyword" do
end
@obj.__value.should == 5
- lambda { AliasObject.new.__value }.should raise_error(NoMethodError)
+ -> { AliasObject.new.__value }.should raise_error(NoMethodError)
end
it "operates on the class/module metaclass when used in instance_eval" do
@@ -131,7 +131,7 @@ describe "The alias keyword" do
end
AliasObject.__klass_method.should == 7
- lambda { Object.__klass_method }.should raise_error(NoMethodError)
+ -> { Object.__klass_method }.should raise_error(NoMethodError)
end
it "operates on the class/module metaclass when used in instance_exec" do
@@ -140,7 +140,7 @@ describe "The alias keyword" do
end
AliasObject.__klass_method2.should == 7
- lambda { Object.__klass_method2 }.should raise_error(NoMethodError)
+ -> { Object.__klass_method2 }.should raise_error(NoMethodError)
end
it "operates on methods defined via attr, attr_reader, and attr_accessor" do
@@ -204,7 +204,7 @@ describe "The alias keyword" do
end
it "operates on methods with splat arguments defined in a superclass using text block for class eval" do
- class Sub < AliasObject;end
+ subclass = Class.new(AliasObject)
AliasObject.class_eval <<-code
def test(*args)
4
@@ -215,17 +215,17 @@ describe "The alias keyword" do
alias test_without_check test
alias test test_with_check
code
- Sub.new.test("testing").should == 4
+ subclass.new.test("testing").should == 4
end
- it "is not allowed against Fixnum or String instances" do
- lambda do
+ it "is not allowed against Integer or String instances" do
+ -> do
1.instance_eval do
alias :foo :to_s
end
end.should raise_error(TypeError)
- lambda do
+ -> do
:blah.instance_eval do
alias :foo :to_s
end
@@ -238,9 +238,39 @@ describe "The alias keyword" do
end
it "raises a NameError when passed a missing name" do
- lambda { @meta.class_eval { alias undef_method not_exist } }.should raise_error(NameError) { |e|
+ -> { @meta.class_eval { alias undef_method not_exist } }.should raise_error(NameError) { |e|
# a NameError and not a NoMethodError
e.class.should == NameError
}
end
+
+ it "defines the method on the aliased class when the original method is from a parent class" do
+ parent = Class.new do
+ def parent_method
+ end
+ end
+ child = Class.new(parent) do
+ alias parent_method_alias parent_method
+ end
+
+ child.instance_method(:parent_method_alias).owner.should == child
+ child.instance_methods(false).should include(:parent_method_alias)
+ end
+end
+
+describe "The alias keyword" do
+ it "can create a new global variable, synonym of the original" do
+ code = '$a = 1; alias $b $a; p [$a, $b]; $b = 2; p [$a, $b]'
+ ruby_exe(code).should == "[1, 1]\n[2, 2]\n"
+ end
+
+ it "can override an existing global variable and make them synonyms" do
+ code = '$a = 1; $b = 2; alias $b $a; p [$a, $b]; $b = 3; p [$a, $b]'
+ ruby_exe(code).should == "[1, 1]\n[3, 3]\n"
+ end
+
+ it "supports aliasing twice the same global variables" do
+ code = '$a = 1; alias $b $a; alias $b $a; p [$a, $b]'
+ ruby_exe(code).should == "[1, 1]\n"
+ end
end
diff --git a/spec/ruby/language/and_spec.rb b/spec/ruby/language/and_spec.rb
index e084fd3cef..55a2a3103a 100644
--- a/spec/ruby/language/and_spec.rb
+++ b/spec/ruby/language/and_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The '&&' statement" do
diff --git a/spec/ruby/language/array_spec.rb b/spec/ruby/language/array_spec.rb
index c3ed8c14c5..2583cffbf7 100644
--- a/spec/ruby/language/array_spec.rb
+++ b/spec/ruby/language/array_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/array', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/array'
describe "Array literals" do
it "[] should return a new array populated with the given elements" do
@@ -36,6 +36,13 @@ describe "Array literals" do
[1, *nil, 3].should == [1, 3]
[*nil, *nil, *nil].should == []
end
+
+ it "evaluates each argument exactly once" do
+ se = ArraySpec::SideEffect.new
+ se.array_result(true)
+ se.array_result(false)
+ se.call_count.should == 4
+ end
end
describe "Bareword array literal" do
diff --git a/spec/ruby/language/block_spec.rb b/spec/ruby/language/block_spec.rb
index 733e90211c..8488b945d5 100644
--- a/spec/ruby/language/block_spec.rb
+++ b/spec/ruby/language/block_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/block', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/block'
describe "A block yielded a single" do
before :all do
@@ -36,89 +36,187 @@ describe "A block yielded a single" do
m([1, 2]) { |a=5, b=4, c=3| [a, b, c] }.should == [1, 2, 3]
end
- it "assgins elements to post arguments" do
+ it "assigns elements to post arguments" do
m([1, 2]) { |a=5, b, c, d| [a, b, c, d] }.should == [5, 1, 2, nil]
end
- it "assigns elements to required arguments when a keyword rest argument is present" do
- m([1, 2]) { |a, **k| [a, k] }.should == [1, {}]
+ ruby_version_is "3.2" do
+ it "does not autosplat single argument to required arguments when a keyword rest argument is present" do
+ m([1, 2]) { |a, **k| [a, k] }.should == [[1, 2], {}]
+ end
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}]
+ ruby_version_is ''..."3.2" do
+ # https://bugs.ruby-lang.org/issues/18633
+ it "autosplats single argument to required arguments when a keyword rest argument is present" do
+ m([1, 2]) { |a, **k| [a, k] }.should == [1, {}]
+ end
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]
+ ruby_version_is ''..."3.0" do
+ it "assigns elements to mixed argument types" do
+ suppress_keyword_warning do
+ result = m([1, 2, 3, {x: 9}]) { |a, b=5, *c, d, e: 2, **k| [a, b, c, d, e, k] }
+ result.should == [1, 2, [], 3, 2, {x: 9}]
+ end
+ end
+
+ it "assigns symbol keys from a Hash to keyword arguments" do
+ suppress_keyword_warning do
+ result = m(["a" => 1, a: 10]) { |a=nil, **b| [a, b] }
+ result.should == [{"a" => 1}, a: 10]
+ end
+ end
+
+ it "assigns symbol keys from a Hash returned by #to_hash to keyword arguments" do
+ suppress_keyword_warning do
+ obj = mock("coerce block keyword arguments")
+ obj.should_receive(:to_hash).and_return({"a" => 1, b: 2})
+
+ result = m([obj]) { |a=nil, **b| [a, b] }
+ result.should == [{"a" => 1}, b: 2]
+ end
+ end
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})
+ ruby_version_is "3.0" do
+ it "assigns elements to mixed argument types" do
+ result = m([1, 2, 3, {x: 9}]) { |a, b=5, *c, d, e: 2, **k| [a, b, c, d, e, k] }
+ result.should == [1, 2, [3], {x: 9}, 2, {}]
+ end
+
+ it "does not treat final Hash as keyword arguments and does not autosplat" do
+ result = m(["a" => 1, a: 10]) { |a=nil, **b| [a, b] }
+ result.should == [[{"a" => 1, a: 10}], {}]
+ end
+
+ it "does not call #to_hash on final argument to get keyword arguments and does not autosplat" do
+ suppress_keyword_warning do
+ obj = mock("coerce block keyword arguments")
+ obj.should_not_receive(:to_hash)
- result = m([obj]) { |a=nil, **b| [a, b] }
- result.should == [{"a" => 1}, b: 2]
+ result = m([obj]) { |a=nil, **b| [a, b] }
+ result.should == [[obj], {}]
+ end
+ end
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
+ ruby_version_is ""...'3.0' do
+ it "calls #to_hash on the argument but ignores result when optional argument and keyword argument accepted" do
obj = mock("coerce block keyword arguments")
obj.should_receive(:to_hash).and_return({"a" => 1, "b" => 2})
result = m([obj]) { |a=nil, **b| [a, b] }
- result.should == [{"a" => 1, "b" => 2}, {}]
+ result.should == [obj, {}]
end
end
- it "assigns non-symbol keys to non-keyword arguments" do
- result = m(["a" => 10, b: 2]) { |a=nil, **b| [a, b] }
- result.should == [{"a" => 10}, {b: 2}]
+ ruby_version_is "3.0" do
+ it "does not call #to_hash on the argument when optional argument and keyword argument accepted and does not autosplat" do
+ obj = mock("coerce block keyword arguments")
+ obj.should_not_receive(:to_hash)
+
+ result = m([obj]) { |a=nil, **b| [a, b] }
+ result.should == [[obj], {}]
+ end
end
- it "does not treat hashes with string keys as keyword arguments" do
- result = m(["a" => 10]) { |a = nil, **b| [a, b] }
- result.should == [{"a" => 10}, {}]
+ describe "when non-symbol keys are in a keyword arguments Hash" do
+ ruby_version_is ""..."3.0" do
+ it "separates non-symbol keys and symbol keys" do
+ suppress_keyword_warning do
+ result = m(["a" => 10, b: 2]) { |a=nil, **b| [a, b] }
+ result.should == [{"a" => 10}, {b: 2}]
+ end
+ end
+ end
+ ruby_version_is "3.0" do
+ it "does not separate non-symbol keys and symbol keys and does not autosplat" do
+ suppress_keyword_warning do
+ result = m(["a" => 10, b: 2]) { |a=nil, **b| [a, b] }
+ result.should == [[{"a" => 10, b: 2}], {}]
+ end
+ end
+ end
end
- it "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})
+ ruby_version_is ""..."3.0" do
+ it "does not treat hashes with string keys as keyword arguments" do
+ result = m(["a" => 10]) { |a = nil, **b| [a, b] }
+ result.should == [{"a" => 10}, {}]
+ end
+ end
- result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] }
- result.should == [1, [2], 3, {x: 9}]
+ ruby_version_is "3.0" do
+ it "does not treat hashes with string keys as keyword arguments and does not autosplat" do
+ result = m(["a" => 10]) { |a = nil, **b| [a, b] }
+ result.should == [[{"a" => 10}], {}]
+ end
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)
+ ruby_version_is ''...'3.0' do
+ it "calls #to_hash on the last element if keyword arguments are present" do
+ suppress_keyword_warning do
+ obj = mock("destructure block keyword arguments")
+ obj.should_receive(:to_hash).and_return({x: 9})
- result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] }
- result.should == [1, [2, 3], obj, {}]
- end
+ result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] }
+ result.should == [1, [2], 3, {x: 9}]
+ end
+ 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})
+ it "assigns the last element to a non-keyword argument if #to_hash returns nil" do
+ suppress_keyword_warning do
+ obj = mock("destructure block keyword arguments")
+ obj.should_receive(:to_hash).and_return(nil)
- 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
+ result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] }
+ result.should == [1, [2, 3], obj, {}]
+ end
+ end
+
+ it "calls #to_hash on the last element when there are more arguments than parameters" do
+ suppress_keyword_warning do
+ x = mock("destructure matching block keyword argument")
+ x.should_receive(:to_hash).and_return({x: 9})
+
+ result = m([1, 2, 3, {y: 9}, 4, 5, x]) { |a, b=5, c, **k| [a, b, c, k] }
+ result.should == [1, 2, 3, {x: 9}]
+ end
+ end
+
+ it "raises a TypeError if #to_hash does not return a Hash" do
+ obj = mock("destructure block keyword arguments")
+ obj.should_receive(:to_hash).and_return(1)
- it "raises a TypeError if #to_hash does not return a Hash" do
- obj = mock("destructure block keyword arguments")
- obj.should_receive(:to_hash).and_return(1)
+ -> { m([1, 2, 3, obj]) { |a, *b, c, **k| } }.should raise_error(TypeError)
+ end
+
+ it "raises the error raised inside #to_hash" do
+ obj = mock("destructure block keyword arguments")
+ error = RuntimeError.new("error while converting to a hash")
+ obj.should_receive(:to_hash).and_raise(error)
- lambda { m([1, 2, 3, obj]) { |a, *b, c, **k| } }.should raise_error(TypeError)
+ -> { m([1, 2, 3, obj]) { |a, *b, c, **k| } }.should raise_error(error)
+ end
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)
+ ruby_version_is '3.0' do
+ it "does not call #to_hash on the last element if keyword arguments are present" do
+ obj = mock("destructure block keyword arguments")
+ obj.should_not_receive(:to_hash)
+
+ result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] }
+ result.should == [1, [2, 3], obj, {}]
+ end
+
+ it "does not call #to_hash on the last element when there are more arguments than parameters" do
+ x = mock("destructure matching block keyword argument")
+ x.should_not_receive(:to_hash)
- lambda { m([1, 2, 3, obj]) { |a, *b, c, **k| } }.should raise_error(error)
+ result = m([1, 2, 3, {y: 9}, 4, 5, x]) { |a, b=5, c, **k| [a, b, c, k] }
+ result.should == [1, 2, 3, {}]
+ end
end
it "does not call #to_ary on the Array" do
@@ -165,11 +263,54 @@ describe "A block yielded a single" do
m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil]
end
+ it "receives the object if it does not respond to #to_ary" do
+ obj = Object.new
+
+ m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil]
+ end
+
+ it "calls #respond_to? to check if object has method #to_ary" do
+ obj = mock("destructure block arguments")
+ obj.should_receive(:respond_to?).with(:to_ary, true).and_return(true)
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil]
+ end
+
+ it "receives the object if it does not respond to #respond_to?" do
+ obj = BasicObject.new
+
+ m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil]
+ end
+
+ it "calls #to_ary on the object when it is defined dynamically" do
+ obj = Object.new
+ def obj.method_missing(name, *args, &block)
+ if name == :to_ary
+ [1, 2]
+ else
+ super
+ end
+ end
+ def obj.respond_to_missing?(name, include_private)
+ name == :to_ary
+ end
+
+ m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil]
+ end
+
it "raises a TypeError if #to_ary does not return an Array" do
obj = mock("destructure block arguments")
obj.should_receive(:to_ary).and_return(1)
- lambda { m(obj) { |a, b| } }.should raise_error(TypeError)
+ -> { m(obj) { |a, b| } }.should raise_error(TypeError)
+ end
+
+ it "raises error transparently if #to_ary raises error on its own" do
+ obj = Object.new
+ def obj.to_ary; raise "Exception raised in #to_ary" end
+
+ -> { m(obj) { |a, b| } }.should raise_error(RuntimeError, "Exception raised in #to_ary")
end
end
end
@@ -217,6 +358,10 @@ describe "A block" do
it "does not raise an exception when values are yielded" do
@y.s(0) { 1 }.should == 1
end
+
+ it "may include a rescue clause" do
+ eval("@y.z do raise ArgumentError; rescue ArgumentError; 7; end").should == 7
+ end
end
describe "taking || arguments" do
@@ -227,6 +372,10 @@ describe "A block" do
it "does not raise an exception when values are yielded" do
@y.s(0) { || 1 }.should == 1
end
+
+ it "may include a rescue clause" do
+ eval('@y.z do || raise ArgumentError; rescue ArgumentError; 7; end').should == 7
+ end
end
describe "taking |a| arguments" do
@@ -252,10 +401,14 @@ describe "A block" do
it "does not destructure a single Array value" do
@y.s([1, 2]) { |a| a }.should == [1, 2]
end
+
+ it "may include a rescue clause" do
+ eval('@y.s(1) do |x| raise ArgumentError; rescue ArgumentError; 7; end').should == 7
+ end
end
describe "taking |a, b| arguments" do
- it "assgins nil to the arguments when no values are yielded" do
+ it "assigns nil to the arguments when no values are yielded" do
@y.z { |a, b| [a, b] }.should == [nil, nil]
end
@@ -314,14 +467,14 @@ describe "A block" do
obj = mock("block yield to_ary invalid")
obj.should_receive(:to_ary).and_return(1)
- lambda { @y.s(obj) { |a, b| } }.should raise_error(TypeError)
+ -> { @y.s(obj) { |a, b| } }.should raise_error(TypeError)
end
it "raises the original exception if #to_ary raises an exception" do
obj = mock("block yield to_ary raising an exception")
obj.should_receive(:to_ary).and_raise(ZeroDivisionError)
- lambda { @y.s(obj) { |a, b| } }.should raise_error(ZeroDivisionError)
+ -> { @y.s(obj) { |a, b| } }.should raise_error(ZeroDivisionError)
end
end
@@ -378,7 +531,7 @@ describe "A block" do
obj = mock("block yield to_ary invalid")
obj.should_receive(:to_ary).and_return(1)
- lambda { @y.s(obj) { |a, *b| } }.should raise_error(TypeError)
+ -> { @y.s(obj) { |a, *b| } }.should raise_error(TypeError)
end
end
@@ -459,7 +612,7 @@ describe "A block" do
@y.z { |a, | a }.should be_nil
end
- it "assgins the argument a single value yielded" do
+ it "assigns the argument a single value yielded" do
@y.s(1) { |a, | a }.should == 1
end
@@ -503,7 +656,7 @@ describe "A block" do
obj = mock("block yield to_ary invalid")
obj.should_receive(:to_ary).and_return(1)
- lambda { @y.s(obj) { |a, | } }.should raise_error(TypeError)
+ -> { @y.s(obj) { |a, | } }.should raise_error(TypeError)
end
end
@@ -545,7 +698,7 @@ describe "A block" do
obj = mock("block yield to_ary invalid")
obj.should_receive(:to_ary).and_return(1)
- lambda { @y.s(obj) { |(a, b)| } }.should raise_error(TypeError)
+ -> { @y.s(obj) { |(a, b)| } }.should raise_error(TypeError)
end
end
@@ -586,7 +739,7 @@ describe "A block" do
obj = mock("block yield to_ary invalid")
obj.should_receive(:to_ary).and_return(1)
- lambda { @y.s(obj) { |(a, b), c| } }.should raise_error(TypeError)
+ -> { @y.s(obj) { |(a, b), c| } }.should raise_error(TypeError)
end
end
@@ -626,6 +779,12 @@ describe "A block" do
end
end
+ describe "taking |*a, b:|" do
+ it "merges the hash into the splatted array" do
+ @y.k { |*a, b:| [a, b] }.should == [[], true]
+ end
+ end
+
describe "arguments with _" do
it "extracts arguments with _" do
@y.m([[1, 2, 3], 4]) { |(_, a, _), _| a }.should == 2
@@ -639,9 +798,9 @@ describe "A block" do
describe "taking identically-named arguments" do
it "raises a SyntaxError for standard arguments" do
- lambda { eval "lambda { |x,x| }" }.should raise_error(SyntaxError)
- lambda { eval "->(x,x) {}" }.should raise_error(SyntaxError)
- lambda { eval "Proc.new { |x,x| }" }.should raise_error(SyntaxError)
+ -> { eval "lambda { |x,x| }" }.should raise_error(SyntaxError)
+ -> { eval "->(x,x) {}" }.should raise_error(SyntaxError)
+ -> { eval "Proc.new { |x,x| }" }.should raise_error(SyntaxError)
end
it "accepts unnamed arguments" do
@@ -662,27 +821,27 @@ describe "Block-local variables" do
end
it "can not have the same name as one of the standard parameters" do
- lambda { eval "[1].each {|foo; foo| }" }.should raise_error(SyntaxError)
- lambda { eval "[1].each {|foo, bar; glark, bar| }" }.should raise_error(SyntaxError)
+ -> { eval "[1].each {|foo; foo| }" }.should raise_error(SyntaxError)
+ -> { eval "[1].each {|foo, bar; glark, bar| }" }.should raise_error(SyntaxError)
end
it "can not be prefixed with an asterisk" do
- lambda { eval "[1].each {|foo; *bar| }" }.should raise_error(SyntaxError)
- lambda do
+ -> { eval "[1].each {|foo; *bar| }" }.should raise_error(SyntaxError)
+ -> do
eval "[1].each {|foo, bar; glark, *fnord| }"
end.should raise_error(SyntaxError)
end
it "can not be prefixed with an ampersand" do
- lambda { eval "[1].each {|foo; &bar| }" }.should raise_error(SyntaxError)
- lambda do
+ -> { eval "[1].each {|foo; &bar| }" }.should raise_error(SyntaxError)
+ -> do
eval "[1].each {|foo, bar; glark, &fnord| }"
end.should raise_error(SyntaxError)
end
it "can not be assigned default values" do
- lambda { eval "[1].each {|foo; bar=1| }" }.should raise_error(SyntaxError)
- lambda do
+ -> { eval "[1].each {|foo; bar=1| }" }.should raise_error(SyntaxError)
+ -> do
eval "[1].each {|foo, bar; glark, fnord=:fnord| }"
end.should raise_error(SyntaxError)
end
@@ -693,8 +852,8 @@ describe "Block-local variables" do
end
it "only allow a single semi-colon in the parameter list" do
- lambda { eval "[1].each {|foo; bar; glark| }" }.should raise_error(SyntaxError)
- lambda { eval "[1].each {|; bar; glark| }" }.should raise_error(SyntaxError)
+ -> { eval "[1].each {|foo; bar; glark| }" }.should raise_error(SyntaxError)
+ -> { eval "[1].each {|; bar; glark| }" }.should raise_error(SyntaxError)
end
it "override shadowed variables from the outer scope" do
@@ -756,14 +915,20 @@ describe "Post-args" do
end.call(1, 2, 3).should == [[], 1, 2, 3]
end
- it "are required" do
- lambda {
- lambda do |*a, b|
+ it "are required for a lambda" do
+ -> {
+ -> *a, b do
[a, b]
end.call
}.should raise_error(ArgumentError)
end
+ it "are assigned to nil when not enough arguments are given to a proc" do
+ proc do |a, *b, c|
+ [a, b, c]
+ end.call.should == [nil, [], nil]
+ end
+
describe "with required args" do
it "gathers remaining args in the splat" do
@@ -826,20 +991,18 @@ describe "Post-args" do
end
describe "with a circular argument reference" do
- it "shadows an existing local with the same name as the argument" do
+ it "raises a SyntaxError if using 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
+ }.should raise_error(SyntaxError)
end
- it "shadows an existing method with the same name as the argument" do
+ it "raises a SyntaxError if there is an existing method with the same name as the argument" do
def a; 1; end
-> {
@proc = eval "proc { |a=a| a }"
- }.should complain(/circular argument reference/)
- @proc.call.should == nil
+ }.should raise_error(SyntaxError)
end
it "calls an existing method with the same name as the argument if explicitly using ()" do
@@ -863,3 +1026,77 @@ describe "Post-args" do
end
end
end
+
+describe "Anonymous block forwarding" do
+ ruby_version_is "3.1" do
+ it "forwards blocks to other functions that formally declare anonymous blocks" do
+ eval <<-EOF
+ def b(&); c(&) end
+ def c(&); yield :non_null end
+ EOF
+
+ b { |c| c }.should == :non_null
+ end
+
+ it "requires the anonymous block parameter to be declared if directly passing a block" do
+ -> { eval "def a; b(&); end; def b; end" }.should raise_error(SyntaxError)
+ end
+
+ it "works when it's the only declared parameter" do
+ eval <<-EOF
+ def inner; yield end
+ def block_only(&); inner(&) end
+ EOF
+
+ block_only { 1 }.should == 1
+ end
+
+ it "works alongside positional parameters" do
+ eval <<-EOF
+ def inner; yield end
+ def pos(arg1, &); inner(&) end
+ EOF
+
+ pos(:a) { 1 }.should == 1
+ end
+
+ it "works alongside positional arguments and splatted keyword arguments" do
+ eval <<-EOF
+ def inner; yield end
+ def pos_kwrest(arg1, **kw, &); inner(&) end
+ EOF
+
+ pos_kwrest(:a, arg: 3) { 1 }.should == 1
+ end
+
+ it "works alongside positional arguments and disallowed keyword arguments" do
+ eval <<-EOF
+ def inner; yield end
+ def no_kw(arg1, **nil, &); inner(&) end
+ EOF
+
+ no_kw(:a) { 1 }.should == 1
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "works alongside explicit keyword arguments" do
+ eval <<-EOF
+ def inner; yield end
+ def rest_kw(*a, kwarg: 1, &); inner(&) end
+ def kw(kwarg: 1, &); inner(&) end
+ def pos_kw_kwrest(arg1, kwarg: 1, **kw, &); inner(&) end
+ def pos_rkw(arg1, kwarg1:, &); inner(&) end
+ def all(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, &); inner(&) end
+ def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, **kw, &); inner(&) end
+ EOF
+
+ rest_kw { 1 }.should == 1
+ kw { 1 }.should == 1
+ pos_kw_kwrest(:a) { 1 }.should == 1
+ pos_rkw(:a, kwarg1: 3) { 1 }.should == 1
+ all(:a, :b, :c, :d, :e, okw1: 'x', okw2: 'y') { 1 }.should == 1
+ all_kwrest(:a, :b, :c, :d, :e, okw1: 'x', okw2: 'y') { 1 }.should == 1
+ end
+ end
+end
diff --git a/spec/ruby/language/break_spec.rb b/spec/ruby/language/break_spec.rb
index da3c3bd272..627cb4a071 100644
--- a/spec/ruby/language/break_spec.rb
+++ b/spec/ruby/language/break_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/break', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/break'
describe "The break statement in a block" do
before :each do
@@ -24,6 +24,24 @@ describe "The break statement in a block" do
value.should == :value
end
end
+
+ describe "captured and delegated to another method repeatedly" do
+ it "breaks out of the block" do
+ @program.looped_break_in_captured_block
+ ScratchPad.recorded.should == [:begin,
+ :preloop,
+ :predele,
+ :preyield,
+ :prebreak,
+ :postbreak,
+ :postyield,
+ :postdele,
+ :predele,
+ :preyield,
+ :prebreak,
+ :end]
+ end
+ end
end
describe "The break statement in a captured block" do
@@ -34,29 +52,29 @@ describe "The break statement in a captured block" do
describe "when the invocation of the scope creating the block is still active" do
it "raises a LocalJumpError when invoking the block from the scope creating the block" do
- lambda { @program.break_in_method }.should raise_error(LocalJumpError)
+ -> { @program.break_in_method }.should raise_error(LocalJumpError)
ScratchPad.recorded.should == [:a, :xa, :d, :b]
end
it "raises a LocalJumpError when invoking the block from a method" do
- lambda { @program.break_in_nested_method }.should raise_error(LocalJumpError)
+ -> { @program.break_in_nested_method }.should raise_error(LocalJumpError)
ScratchPad.recorded.should == [:a, :xa, :cc, :aa, :b]
end
it "raises a LocalJumpError when yielding to the block" do
- lambda { @program.break_in_yielding_method }.should raise_error(LocalJumpError)
+ -> { @program.break_in_yielding_method }.should raise_error(LocalJumpError)
ScratchPad.recorded.should == [:a, :xa, :cc, :aa, :b]
end
end
describe "from a scope that has returned" do
it "raises a LocalJumpError when calling the block from a method" do
- lambda { @program.break_in_method_captured }.should raise_error(LocalJumpError)
+ -> { @program.break_in_method_captured }.should raise_error(LocalJumpError)
ScratchPad.recorded.should == [:a, :za, :xa, :zd, :zb]
end
it "raises a LocalJumpError when yielding to the block" do
- lambda { @program.break_in_yield_captured }.should raise_error(LocalJumpError)
+ -> { @program.break_in_yield_captured }.should raise_error(LocalJumpError)
ScratchPad.recorded.should == [:a, :za, :xa, :zd, :aa, :zb]
end
end
@@ -82,7 +100,7 @@ describe "The break statement in a lambda" do
end
it "returns from the lambda" do
- l = lambda {
+ l = -> {
ScratchPad << :before
break :foo
ScratchPad << :after
@@ -93,7 +111,7 @@ describe "The break statement in a lambda" do
it "returns from the call site if the lambda is passed as a block" do
def mid(&b)
- lambda {
+ -> {
ScratchPad << :before
b.call
ScratchPad << :unreachable1
@@ -190,7 +208,7 @@ describe "Break inside a while loop" do
it "passes the value returned by a method with omitted parenthesis and passed block" do
obj = BreakSpecs::Block.new
- lambda { break obj.method :value do |x| x end }.call.should == :value
+ -> { break obj.method :value do |x| x end }.call.should == :value
end
end
@@ -344,4 +362,22 @@ describe "Executing break from within a block" do
bt2.three
ScratchPad.recorded.should == [:two_ensure, :three_post, :three_ensure]
end
+
+ it "works when passing through a super call" do
+ cls1 = Class.new { def foo; yield; end }
+ cls2 = Class.new(cls1) { def foo; super { break 1 }; end }
+
+ -> do
+ cls2.new.foo.should == 1
+ end.should_not raise_error
+ end
+
+ it "raises LocalJumpError when converted into a proc during a a super call" do
+ cls1 = Class.new { def foo(&b); b; end }
+ cls2 = Class.new(cls1) { def foo; super { break 1 }.call; end }
+
+ -> do
+ cls2.new.foo
+ end.should raise_error(LocalJumpError)
+ end
end
diff --git a/spec/ruby/language/case_spec.rb b/spec/ruby/language/case_spec.rb
index 25f5d0efc4..915c032a71 100644
--- a/spec/ruby/language/case_spec.rb
+++ b/spec/ruby/language/case_spec.rb
@@ -1,17 +1,17 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The 'case'-construct" do
it "evaluates the body of the when clause matching the case target expression" do
case 1
- when 2; false
- when 1; true
+ when 2; false
+ when 1; true
end.should == true
end
it "evaluates the body of the when clause whose array expression includes the case target expression" do
case 2
- when 3, 4; false
- when 1, 2; true
+ when 3, 4; false
+ when 1, 2; true
end.should == true
end
@@ -21,7 +21,7 @@ describe "The 'case'-construct" do
def bar; @calls << :bar; end
case true
- when foo, bar;
+ when foo, bar;
end
@calls.should == [:foo, :bar]
@@ -29,31 +29,31 @@ describe "The 'case'-construct" do
it "evaluates the body of the when clause whose range expression includes the case target expression" do
case 5
- when 21..30; false
- when 1..20; true
+ when 21..30; false
+ when 1..20; true
end.should == true
end
it "returns nil when no 'then'-bodies are given" do
case "a"
- when "a"
- when "b"
+ when "a"
+ when "b"
end.should == nil
end
it "evaluates the 'else'-body when no other expression matches" do
case "c"
- when "a"; 'foo'
- when "b"; 'bar'
- else 'zzz'
+ when "a"; 'foo'
+ when "b"; 'bar'
+ else 'zzz'
end.should == 'zzz'
end
it "returns nil when no expression matches and 'else'-body is empty" do
case "c"
- when "a"; "a"
- when "b"; "b"
- else
+ when "a"; "a"
+ when "b"; "b"
+ else
end.should == nil
end
@@ -70,105 +70,152 @@ describe "The 'case'-construct" do
it "returns the statement following 'then'" do
case "a"
- when "a" then 'foo'
- when "b" then 'bar'
+ when "a" then 'foo'
+ when "b" then 'bar'
end.should == 'foo'
end
it "tests classes with case equality" do
case "a"
- when String
- 'foo'
- when Symbol
- 'bar'
+ when String
+ 'foo'
+ when Symbol
+ 'bar'
end.should == 'foo'
end
it "tests with matching regexps" do
case "hello"
- when /abc/; false
- when /^hell/; true
+ when /abc/; false
+ when /^hell/; true
end.should == true
end
+ it "tests with matching regexps and sets $~ and captures" do
+ case "foo42"
+ when /oo(\d+)/
+ $~.should be_kind_of(MatchData)
+ $1.should == "42"
+ else
+ flunk
+ end
+ $~.should be_kind_of(MatchData)
+ $1.should == "42"
+ end
+
+ it "tests with a string interpolated in a regexp" do
+ digits = '\d+'
+ case "foo44"
+ when /oo(#{digits})/
+ $~.should be_kind_of(MatchData)
+ $1.should == "44"
+ else
+ flunk
+ end
+ $~.should be_kind_of(MatchData)
+ $1.should == "44"
+ end
+
+ it "tests with a regexp interpolated within another regexp" do
+ digits_regexp = /\d+/
+ case "foo43"
+ when /oo(#{digits_regexp})/
+ $~.should be_kind_of(MatchData)
+ $1.should == "43"
+ else
+ flunk
+ end
+ $~.should be_kind_of(MatchData)
+ $1.should == "43"
+ end
+
it "does not test with equality when given classes" do
case :symbol.class
- when Symbol
- "bar"
- when String
- "bar"
- else
- "foo"
+ when Symbol
+ "bar"
+ when String
+ "bar"
+ else
+ "foo"
end.should == "foo"
end
it "takes lists of values" do
case 'z'
- when 'a', 'b', 'c', 'd'
- "foo"
- when 'x', 'y', 'z'
- "bar"
+ when 'a', 'b', 'c', 'd'
+ "foo"
+ when 'x', 'y', 'z'
+ "bar"
end.should == "bar"
case 'b'
- when 'a', 'b', 'c', 'd'
- "foo"
- when 'x', 'y', 'z'
- "bar"
+ when 'a', 'b', 'c', 'd'
+ "foo"
+ when 'x', 'y', 'z'
+ "bar"
end.should == "foo"
end
+ it "tests an empty array" do
+ case []
+ when []
+ 'foo'
+ else
+ 'bar'
+ end.should == 'foo'
+ end
+
it "expands arrays to lists of values" do
case 'z'
- when *['a', 'b', 'c', 'd']
- "foo"
- when *['x', 'y', 'z']
- "bar"
+ when *['a', 'b', 'c', 'd']
+ "foo"
+ when *['x', 'y', 'z']
+ "bar"
end.should == "bar"
end
it "takes an expanded array in addition to a list of values" do
case 'f'
- when 'f', *['a', 'b', 'c', 'd']
- "foo"
- when *['x', 'y', 'z']
- "bar"
+ when 'f', *['a', 'b', 'c', 'd']
+ "foo"
+ when *['x', 'y', 'z']
+ "bar"
end.should == "foo"
case 'b'
- when 'f', *['a', 'b', 'c', 'd']
- "foo"
- when *['x', 'y', 'z']
- "bar"
+ when 'f', *['a', 'b', 'c', 'd']
+ "foo"
+ when *['x', 'y', 'z']
+ "bar"
end.should == "foo"
end
it "takes an expanded array before additional listed values" do
case 'f'
- when *['a', 'b', 'c', 'd'], 'f'
- "foo"
- when *['x', 'y', 'z']
- "bar"
+ when *['a', 'b', 'c', 'd'], 'f'
+ "foo"
+ when *['x', 'y', 'z']
+ "bar"
end.should == 'foo'
end
it "expands arrays from variables before additional listed values" do
a = ['a', 'b', 'c']
case 'a'
- when *a, 'd', 'e'
- "foo"
- when 'x'
- "bar"
+ when *a, 'd', 'e'
+ "foo"
+ when 'x'
+ "bar"
end.should == "foo"
end
it "expands arrays from variables before a single additional listed value" do
a = ['a', 'b', 'c']
case 'a'
- when *a, 'd'
- "foo"
- when 'x'
- "bar"
+ when *a, 'd'
+ "foo"
+ when 'x'
+ "bar"
end.should == "foo"
end
@@ -177,10 +224,10 @@ describe "The 'case'-construct" do
b = ['d', 'e', 'f']
case 'f'
- when *a, *b, 'g', 'h'
- "foo"
- when 'x'
- "bar"
+ when *a, *b, 'g', 'h'
+ "foo"
+ when 'x'
+ "bar"
end.should == "foo"
end
@@ -190,47 +237,47 @@ describe "The 'case'-construct" do
b = ['f']
case 'f'
- when 'f', *a|b
- "foo"
- when *['x', 'y', 'z']
- "bar"
+ when 'f', *a|b
+ "foo"
+ when *['x', 'y', 'z']
+ "bar"
end.should == "foo"
end
it "never matches when clauses with no values" do
case nil
- when *[]
- "foo"
+ when *[]
+ "foo"
end.should == nil
end
it "lets you define a method after the case statement" do
case (def foo; 'foo'; end; 'f')
- when 'a'
- 'foo'
- when 'f'
- 'bar'
+ when 'a'
+ 'foo'
+ when 'f'
+ 'bar'
end.should == 'bar'
end
it "raises a SyntaxError when 'else' is used when no 'when' is given" do
- lambda {
+ -> {
eval <<-CODE
case 4
- else
- true
+ else
+ true
end
CODE
}.should raise_error(SyntaxError)
end
it "raises a SyntaxError when 'else' is used before a 'when' was given" do
- lambda {
+ -> {
eval <<-CODE
case 4
- else
- true
- when 4; false
+ else
+ true
+ when 4; false
end
CODE
}.should raise_error(SyntaxError)
@@ -287,56 +334,56 @@ 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'
+ 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'
+ when false; 'foo'
+ when 2; 'bar'
+ when 1 == 1; 'baz'
end.should == 'bar'
case
- when false; 'foo'
- when nil; 'foo'
- when 1 == 1; 'bar'
+ 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'
+ 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'
+ when true, false; 'foo'
+ else 'bar'
end.should == 'foo'
case
- when false, true; 'foo'
- else 'bar'
+ when false, true; 'foo'
+ else 'bar'
end.should == 'foo'
end
it "evaluates true as only 'true' when true is the first clause" do
case 1
- when true; "bad"
- when Integer; "good"
+ when true; "bad"
+ when Integer; "good"
end.should == "good"
end
it "evaluates false as only 'false' when false is the first clause" do
case nil
- when false; "bad"
- when nil; "good"
+ when false; "bad"
+ when nil; "good"
end.should == "good"
end
@@ -352,17 +399,17 @@ describe "The 'case'-construct with no target expression" do
a2 = ['b', 'a', 'r']
case 'f'
- when *a1, *['x', 'y', 'z']
- "foo"
- when *a2, *['x', 'y', 'z']
- "bar"
+ when *a1, *['x', 'y', 'z']
+ "foo"
+ when *a2, *['x', 'y', 'z']
+ "bar"
end.should == "foo"
case 'b'
- when *a1, *['x', 'y', 'z']
- "foo"
- when *a2, *['x', 'y', 'z']
- "bar"
+ when *a1, *['x', 'y', 'z']
+ "foo"
+ when *a2, *['x', 'y', 'z']
+ "bar"
end.should == "bar"
end
@@ -386,4 +433,13 @@ describe "The 'case'-construct with no target expression" do
:called
end.should == :called
end
+
+ # Homogeneous cases are often optimized to avoid === using a jump table, and should be tested separately.
+ # See https://github.com/jruby/jruby/issues/6440
+ it "handles homogeneous cases" do
+ case
+ when 1; 'foo'
+ when 2; 'bar'
+ end.should == 'foo'
+ end
end
diff --git a/spec/ruby/language/class_spec.rb b/spec/ruby/language/class_spec.rb
index ba4af3d880..877895bf15 100644
--- a/spec/ruby/language/class_spec.rb
+++ b/spec/ruby/language/class_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/class', __FILE__)
+require_relative '../spec_helper'
+require_relative '../fixtures/class'
ClassSpecsNumber = 12
@@ -13,11 +13,22 @@ describe "The class keyword" do
ClassSpecsKeywordWithSemicolon.should be_an_instance_of(Class)
end
- ruby_version_is "2.3" do
- it "does not raise a SyntaxError when opening a class without a semicolon" do
- eval "class ClassSpecsKeywordWithoutSemicolon end"
- ClassSpecsKeywordWithoutSemicolon.should be_an_instance_of(Class)
- end
+ it "does not raise a SyntaxError when opening a class without a semicolon" do
+ eval "class ClassSpecsKeywordWithoutSemicolon end"
+ ClassSpecsKeywordWithoutSemicolon.should be_an_instance_of(Class)
+ end
+
+ it "can redefine a class when called from a block" do
+ ClassSpecs::DEFINE_CLASS.call
+ A.should be_an_instance_of(Class)
+
+ Object.send(:remove_const, :A)
+ defined?(A).should be_nil
+
+ ClassSpecs::DEFINE_CLASS.call
+ A.should be_an_instance_of(Class)
+ ensure
+ Object.send(:remove_const, :A) if defined?(::A)
end
end
@@ -32,7 +43,7 @@ describe "A class definition" do
end
it "raises TypeError if constant given as class name exists and is not a Module" do
- lambda {
+ -> {
class ClassSpecsNumber
end
}.should raise_error(TypeError)
@@ -40,19 +51,19 @@ describe "A class definition" do
# test case known to be detecting bugs (JRuby, MRI)
it "raises TypeError if the constant qualifying the class is nil" do
- lambda {
+ -> {
class nil::Foo
end
}.should raise_error(TypeError)
end
it "raises TypeError if any constant qualifying the class is not a Module" do
- lambda {
+ -> {
class ClassSpecs::Number::MyClass
end
}.should raise_error(TypeError)
- lambda {
+ -> {
class ClassSpecsNumber::MyClass
end
}.should raise_error(TypeError)
@@ -66,7 +77,7 @@ describe "A class definition" do
module ClassSpecs
class SuperclassResetToSubclass < L
end
- lambda {
+ -> {
class SuperclassResetToSubclass < M
end
}.should raise_error(TypeError, /superclass mismatch/)
@@ -79,7 +90,7 @@ describe "A class definition" do
end
SuperclassReopenedBasicObject.superclass.should == A
- lambda {
+ -> {
class SuperclassReopenedBasicObject < BasicObject
end
}.should raise_error(TypeError, /superclass mismatch/)
@@ -94,7 +105,7 @@ describe "A class definition" do
end
SuperclassReopenedObject.superclass.should == A
- lambda {
+ -> {
class SuperclassReopenedObject < Object
end
}.should raise_error(TypeError, /superclass mismatch/)
@@ -119,7 +130,7 @@ describe "A class definition" do
class NoSuperclassSet
end
- lambda {
+ -> {
class NoSuperclassSet < String
end
}.should raise_error(TypeError, /superclass mismatch/)
@@ -129,7 +140,7 @@ describe "A class definition" do
it "allows using self as the superclass if self is a class" do
ClassSpecs::I::J.superclass.should == ClassSpecs::I
- lambda {
+ -> {
class ShouldNotWork < self; end
}.should raise_error(TypeError)
end
@@ -150,7 +161,7 @@ describe "A class definition" do
it "raises a TypeError if inheriting from a metaclass" do
obj = mock("metaclass super")
meta = obj.singleton_class
- lambda { class ClassSpecs::MetaclassSuper < meta; end }.should raise_error(TypeError)
+ -> { class ClassSpecs::MetaclassSuper < meta; end }.should raise_error(TypeError)
end
it "allows the declaration of class variables in the body" do
@@ -212,16 +223,16 @@ describe "A class definition" do
describe "within a block creates a new class in the lexical scope" do
it "for named classes at the toplevel" do
klass = Class.new do
- class Howdy
+ class CS_CONST_CLASS_SPECS
end
def self.get_class_name
- Howdy.name
+ CS_CONST_CLASS_SPECS.name
end
end
- Howdy.name.should == 'Howdy'
- klass.get_class_name.should == 'Howdy'
+ klass.get_class_name.should == 'CS_CONST_CLASS_SPECS'
+ ::CS_CONST_CLASS_SPECS.name.should == 'CS_CONST_CLASS_SPECS'
end
it "for named classes in a module" do
@@ -276,7 +287,7 @@ describe "A class definition extending an object (sclass)" do
end
it "raises a TypeError when trying to extend numbers" do
- lambda {
+ -> {
eval <<-CODE
class << 1
def xyz
@@ -287,8 +298,30 @@ describe "A class definition extending an object (sclass)" do
}.should raise_error(TypeError)
end
- it "allows accessing the block of the original scope" do
- ClassSpecs.sclass_with_block { 123 }.should == 123
+ it "raises a TypeError when trying to extend non-Class" do
+ error_msg = /superclass must be a.* Class/
+ -> { class TestClass < ""; end }.should raise_error(TypeError, error_msg)
+ -> { class TestClass < 1; end }.should raise_error(TypeError, error_msg)
+ -> { class TestClass < :symbol; end }.should raise_error(TypeError, error_msg)
+ -> { class TestClass < mock('o'); end }.should raise_error(TypeError, error_msg)
+ -> { class TestClass < Module.new; end }.should raise_error(TypeError, error_msg)
+ -> { class TestClass < BasicObject.new; end }.should raise_error(TypeError, error_msg)
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "allows accessing the block of the original scope" do
+ suppress_warning do
+ ClassSpecs.sclass_with_block { 123 }.should == 123
+ end
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "does not allow accessing the block of the original scope" do
+ -> {
+ ClassSpecs.sclass_with_block { 123 }
+ }.should raise_error(SyntaxError)
+ end
end
it "can use return to cause the enclosing method to return" do
@@ -308,11 +341,11 @@ describe "Reopening a class" do
end
it "raises a TypeError when superclasses mismatch" do
- lambda { class ClassSpecs::A < Array; end }.should raise_error(TypeError)
+ -> { class ClassSpecs::A < Array; end }.should raise_error(TypeError)
end
it "adds new methods to subclasses" do
- lambda { ClassSpecs::M.m }.should raise_error(NoMethodError)
+ -> { ClassSpecs::M.m }.should raise_error(NoMethodError)
class ClassSpecs::L
def self.m
1
diff --git a/spec/ruby/language/class_variable_spec.rb b/spec/ruby/language/class_variable_spec.rb
index 463f731a93..f98deaa081 100644
--- a/spec/ruby/language/class_variable_spec.rb
+++ b/spec/ruby/language/class_variable_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/class_variables', __FILE__)
+require_relative '../spec_helper'
+require_relative '../fixtures/class_variables'
describe "A class variable" do
after :each do
@@ -70,15 +70,47 @@ describe 'A class variable definition' do
c = Class.new(b)
b.class_variable_set(:@@cv, :value)
- lambda { a.class_variable_get(:@@cv) }.should raise_error(NameError)
+ -> { a.class_variable_get(:@@cv) }.should raise_error(NameError)
b.class_variable_get(:@@cv).should == :value
c.class_variable_get(:@@cv).should == :value
# updates the same variable
c.class_variable_set(:@@cv, :next)
- lambda { a.class_variable_get(:@@cv) }.should raise_error(NameError)
+ -> { a.class_variable_get(:@@cv) }.should raise_error(NameError)
b.class_variable_get(:@@cv).should == :next
c.class_variable_get(:@@cv).should == :next
end
end
+
+ruby_version_is "3.0" do
+ describe 'Accessing a class variable' do
+ it "raises a RuntimeError when accessed from the toplevel scope (not in some module or class)" do
+ -> {
+ eval "@@cvar_toplevel1"
+ }.should raise_error(RuntimeError, 'class variable access from toplevel')
+ -> {
+ eval "@@cvar_toplevel2 = 2"
+ }.should raise_error(RuntimeError, 'class variable access from toplevel')
+ end
+
+ it "does not raise an error when checking if defined from the toplevel scope" do
+ -> {
+ eval "defined?(@@cvar_toplevel1)"
+ }.should_not raise_error
+ end
+
+ it "raises a RuntimeError when a class variable is overtaken in an ancestor class" do
+ parent = Class.new()
+ subclass = Class.new(parent)
+ subclass.class_variable_set(:@@cvar_overtaken, :subclass)
+ parent.class_variable_set(:@@cvar_overtaken, :parent)
+
+ -> {
+ subclass.class_variable_get(:@@cvar_overtaken)
+ }.should raise_error(RuntimeError, /class variable @@cvar_overtaken of .+ is overtaken by .+/)
+
+ parent.class_variable_get(:@@cvar_overtaken).should == :parent
+ end
+ end
+end
diff --git a/spec/ruby/language/comment_spec.rb b/spec/ruby/language/comment_spec.rb
new file mode 100644
index 0000000000..dd788e681c
--- /dev/null
+++ b/spec/ruby/language/comment_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../spec_helper'
+
+describe "The comment" do
+ it "can be placed between fluent dot now" do
+ code = <<~CODE
+ 10
+ # some comment
+ .to_s
+ CODE
+
+ eval(code).should == '10'
+ end
+end
diff --git a/spec/ruby/language/constants_spec.rb b/spec/ruby/language/constants_spec.rb
index 1d3cecd9c6..8586e46158 100644
--- a/spec/ruby/language/constants_spec.rb
+++ b/spec/ruby/language/constants_spec.rb
@@ -1,7 +1,7 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/constants', __FILE__)
-require File.expand_path('../fixtures/constants_sclass', __FILE__)
-require File.expand_path('../fixtures/constant_visibility', __FILE__)
+require_relative '../spec_helper'
+require_relative '../fixtures/constants'
+require_relative 'fixtures/constants_sclass'
+require_relative 'fixtures/constant_visibility'
# Read the documentation in fixtures/constants.rb for the guidelines and
# rationale for the structure and organization of these specs.
@@ -49,10 +49,10 @@ describe "Literal (A::X) constant resolution" do
end
it "does not search the singleton class of the class or module" do
- lambda do
+ -> do
ConstantSpecs::ContainerA::ChildA::CS_CONST14
end.should raise_error(NameError)
- lambda { ConstantSpecs::CS_CONST14 }.should raise_error(NameError)
+ -> { ConstantSpecs::CS_CONST14 }.should raise_error(NameError)
end
end
@@ -112,7 +112,7 @@ describe "Literal (A::X) constant resolution" do
CS_CONST108 = :const108_1
end
- lambda do
+ -> do
ConstantSpecs::ContainerB::ChildB::CS_CONST108
end.should raise_error(NameError)
@@ -122,7 +122,7 @@ describe "Literal (A::X) constant resolution" do
end
end
- lambda { ConstantSpecs::CS_CONST108 }.should raise_error(NameError)
+ -> { ConstantSpecs::CS_CONST108 }.should raise_error(NameError)
end
it "returns the updated value when a constant is reassigned" do
@@ -135,23 +135,69 @@ describe "Literal (A::X) constant resolution" do
ConstantSpecs::ClassB::CS_CONST109.should == :const109_2
end
- it "evaluates the right hand side before evaluating a constant path" do
- mod = Module.new
+ ruby_version_is "3.2" do
+ it "evaluates left-to-right" do
+ mod = Module.new
- mod.module_eval <<-EOC
- ConstantSpecsRHS::B = begin
- module ConstantSpecsRHS; end
+ mod.module_eval <<-EOC
+ order = []
+ ConstantSpecsRHS = Module.new
+ (order << :lhs; ConstantSpecsRHS)::B = (order << :rhs)
+ EOC
- "hello"
- end
- EOC
+ mod::ConstantSpecsRHS::B.should == [:lhs, :rhs]
+ end
+ end
+
+ ruby_version_is ""..."3.2" do
+ it "evaluates the right hand side before evaluating a constant path" do
+ mod = Module.new
+
+ mod.module_eval <<-EOC
+ ConstantSpecsRHS::B = begin
+ module ConstantSpecsRHS; end
- mod::ConstantSpecsRHS::B.should == 'hello'
+ "hello"
+ end
+ EOC
+
+ mod::ConstantSpecsRHS::B.should == 'hello'
+ end
end
end
it "raises a NameError if no constant is defined in the search path" do
- lambda { ConstantSpecs::ParentA::CS_CONSTX }.should raise_error(NameError)
+ -> { ConstantSpecs::ParentA::CS_CONSTX }.should raise_error(NameError)
+ end
+
+ ruby_version_is "3.0" do
+ it "uses the module or class #name to craft the error message" do
+ mod = Module.new do
+ def self.name
+ "ModuleName"
+ end
+
+ def self.inspect
+ "<unusable info>"
+ end
+ end
+
+ -> { mod::DOES_NOT_EXIST }.should raise_error(NameError, /uninitialized constant ModuleName::DOES_NOT_EXIST/)
+ end
+
+ it "uses the module or class #inspect to craft the error message if they are anonymous" do
+ mod = Module.new do
+ def self.name
+ nil
+ end
+
+ def self.inspect
+ "<unusable info>"
+ end
+ end
+
+ -> { mod::DOES_NOT_EXIST }.should raise_error(NameError, /uninitialized constant <unusable info>::DOES_NOT_EXIST/)
+ end
end
it "sends #const_missing to the original class or module scope" do
@@ -163,10 +209,10 @@ describe "Literal (A::X) constant resolution" do
end
it "raises a TypeError if a non-class or non-module qualifier is given" do
- lambda { CS_CONST1::CS_CONST }.should raise_error(TypeError)
- lambda { 1::CS_CONST }.should raise_error(TypeError)
- lambda { "mod"::CS_CONST }.should raise_error(TypeError)
- lambda { false::CS_CONST }.should raise_error(TypeError)
+ -> { CS_CONST1::CS_CONST }.should raise_error(TypeError)
+ -> { 1::CS_CONST }.should raise_error(TypeError)
+ -> { "mod"::CS_CONST }.should raise_error(TypeError)
+ -> { false::CS_CONST }.should raise_error(TypeError)
end
end
@@ -212,7 +258,7 @@ describe "Constant resolution within methods" do
end
it "does not search the lexical scope of the caller" do
- lambda { ConstantSpecs::ClassA.const16 }.should raise_error(NameError)
+ -> { ConstantSpecs::ClassA.const16 }.should raise_error(NameError)
end
it "searches the lexical scope of a block" do
@@ -225,7 +271,7 @@ describe "Constant resolution within methods" do
end
it "does not search the lexical scope of qualifying modules" do
- lambda do
+ -> do
ConstantSpecs::ContainerA::ChildA.const23
end.should raise_error(NameError)
end
@@ -302,7 +348,7 @@ describe "Constant resolution within methods" do
it "does not search the lexical scope of the caller" do
ConstantSpecs::ClassB::CS_CONST209 = :const209
- lambda { ConstantSpecs::ClassB.const209 }.should raise_error(NameError)
+ -> { ConstantSpecs::ClassB.const209 }.should raise_error(NameError)
end
it "searches the lexical scope of a block" do
@@ -337,44 +383,20 @@ describe "Constant resolution within methods" do
it "does not search the lexical scope of qualifying modules" do
ConstantSpecs::ContainerB::CS_CONST214 = :const214
- lambda do
+ -> do
ConstantSpecs::ContainerB::ChildB.const214
end.should raise_error(NameError)
end
end
it "raises a NameError if no constant is defined in the search path" do
- lambda { ConstantSpecs::ParentA.constx }.should raise_error(NameError)
+ -> { ConstantSpecs::ParentA.constx }.should raise_error(NameError)
end
it "sends #const_missing to the original class or module scope" do
ConstantSpecs::ClassA.constx.should == :CS_CONSTX
ConstantSpecs::ClassA.new.constx.should == :CS_CONSTX
end
-
- describe "with ||=" do
- it "assigns a scoped constant if previously undefined" do
- ConstantSpecs.should_not have_constant(:OpAssignUndefined)
- module ConstantSpecs
- OpAssignUndefined ||= 42
- end
- ConstantSpecs::OpAssignUndefined.should == 42
- ConstantSpecs::OpAssignUndefinedOutside ||= 42
- ConstantSpecs::OpAssignUndefinedOutside.should == 42
- ConstantSpecs.send(:remove_const, :OpAssignUndefined)
- ConstantSpecs.send(:remove_const, :OpAssignUndefinedOutside)
- end
-
- it "assigns a global constant if previously undefined" do
- OpAssignGlobalUndefined ||= 42
- ::OpAssignGlobalUndefinedExplicitScope ||= 42
- OpAssignGlobalUndefined.should == 42
- ::OpAssignGlobalUndefinedExplicitScope.should == 42
- Object.send :remove_const, :OpAssignGlobalUndefined
- Object.send :remove_const, :OpAssignGlobalUndefinedExplicitScope
- end
-
- end
end
describe "Constant resolution within a singleton class (class << obj)" do
@@ -382,25 +404,35 @@ describe "Constant resolution within a singleton class (class << obj)" do
ConstantSpecs::CS_SINGLETON1.foo.should == 1
end
- ruby_version_is "2.3" do
- it "uses its own namespace for each object" do
- a = ConstantSpecs::CS_SINGLETON2[0].foo
- b = ConstantSpecs::CS_SINGLETON2[1].foo
- [a, b].should == [1, 2]
- end
+ it "uses its own namespace for each object" do
+ a = ConstantSpecs::CS_SINGLETON2[0].foo
+ b = ConstantSpecs::CS_SINGLETON2[1].foo
+ [a, b].should == [1, 2]
+ end
- it "uses its own namespace for nested modules" do
- a = ConstantSpecs::CS_SINGLETON3[0].x
- b = ConstantSpecs::CS_SINGLETON3[1].x
- a.should_not equal(b)
- end
+ it "uses its own namespace for nested modules" do
+ a = ConstantSpecs::CS_SINGLETON3[0].x
+ b = ConstantSpecs::CS_SINGLETON3[1].x
+ a.should_not equal(b)
+ end
+
+ it "allows nested modules to have proper resolution" do
+ a = ConstantSpecs::CS_SINGLETON4_CLASSES[0].new
+ b = ConstantSpecs::CS_SINGLETON4_CLASSES[1].new
+ [a.foo, b.foo].should == [1, 2]
+ end
+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]
+describe "top-level constant lookup" do
+ context "on a class" do
+ it "does not search Object after searching other scopes" do
+ -> { String::Hash }.should raise_error(NameError)
end
end
+
+ it "searches Object unsuccessfully when searches on a module" do
+ -> { Enumerable::Hash }.should raise_error(NameError)
+ end
end
describe "Module#private_constant marked constants" do
@@ -413,24 +445,35 @@ describe "Module#private_constant marked constants" do
mod.const_set :Foo, false
}.should complain(/already initialized constant/)
- lambda {mod::Foo}.should raise_error(NameError)
+ -> {mod::Foo}.should raise_error(NameError)
+ end
+
+ it "sends #const_missing to the original class or module" do
+ mod = Module.new
+ mod.const_set :Foo, true
+ mod.send :private_constant, :Foo
+ def mod.const_missing(name)
+ name == :Foo ? name : super
+ end
+
+ mod::Foo.should == :Foo
end
describe "in a module" do
it "cannot be accessed from outside the module" do
- lambda do
+ -> do
ConstantVisibility::PrivConstModule::PRIVATE_CONSTANT_MODULE
end.should raise_error(NameError)
end
it "cannot be reopened as a module from scope where constant would be private" do
- lambda do
+ -> do
module ConstantVisibility::ModuleContainer::PrivateModule; end
end.should raise_error(NameError)
end
it "cannot be reopened as a class from scope where constant would be private" do
- lambda do
+ -> do
class ConstantVisibility::ModuleContainer::PrivateClass; end
end.should raise_error(NameError)
end
@@ -476,29 +519,42 @@ describe "Module#private_constant marked constants" do
end
it "can be accessed from classes that include the module" do
- ConstantVisibility::PrivConstModuleChild.new.private_constant_from_include.should be_true
+ ConstantVisibility::ClassIncludingPrivConstModule.new.private_constant_from_include.should be_true
+ end
+
+ it "can be accessed from modules that include the module" do
+ ConstantVisibility::ModuleIncludingPrivConstModule.private_constant_from_include.should be_true
+ end
+
+ it "raises a NameError when accessed directly from modules that include the module" do
+ -> do
+ ConstantVisibility::ModuleIncludingPrivConstModule.private_constant_self_from_include
+ end.should raise_error(NameError)
+ -> do
+ ConstantVisibility::ModuleIncludingPrivConstModule.private_constant_named_from_include
+ end.should raise_error(NameError)
end
it "is defined? from classes that include the module" do
- ConstantVisibility::PrivConstModuleChild.new.defined_from_include.should == "constant"
+ ConstantVisibility::ClassIncludingPrivConstModule.new.defined_from_include.should == "constant"
end
end
describe "in a class" do
it "cannot be accessed from outside the class" do
- lambda do
+ -> do
ConstantVisibility::PrivConstClass::PRIVATE_CONSTANT_CLASS
end.should raise_error(NameError)
end
it "cannot be reopened as a module" do
- lambda do
+ -> do
module ConstantVisibility::ClassContainer::PrivateModule; end
end.should raise_error(NameError)
end
it "cannot be reopened as a class" do
- lambda do
+ -> do
class ConstantVisibility::ClassContainer::PrivateClass; end
end.should raise_error(NameError)
end
@@ -554,7 +610,7 @@ describe "Module#private_constant marked constants" do
describe "in Object" do
it "cannot be accessed using ::Const form" do
- lambda do
+ -> do
::PRIVATE_CONSTANT_IN_OBJECT
end.should raise_error(NameError)
end
@@ -571,6 +627,40 @@ describe "Module#private_constant marked constants" do
defined?(PRIVATE_CONSTANT_IN_OBJECT).should == "constant"
end
end
+
+ describe "NameError by #private_constant" do
+ it "has :receiver and :name attributes" do
+ -> do
+ ConstantVisibility::PrivConstClass::PRIVATE_CONSTANT_CLASS
+ end.should raise_error(NameError) {|e|
+ e.receiver.should == ConstantVisibility::PrivConstClass
+ e.name.should == :PRIVATE_CONSTANT_CLASS
+ }
+
+ -> do
+ ConstantVisibility::PrivConstModule::PRIVATE_CONSTANT_MODULE
+ end.should raise_error(NameError) {|e|
+ e.receiver.should == ConstantVisibility::PrivConstModule
+ e.name.should == :PRIVATE_CONSTANT_MODULE
+ }
+ end
+
+ it "has the defined class as the :name attribute" do
+ -> do
+ ConstantVisibility::PrivConstClassChild::PRIVATE_CONSTANT_CLASS
+ end.should raise_error(NameError) {|e|
+ e.receiver.should == ConstantVisibility::PrivConstClass
+ e.name.should == :PRIVATE_CONSTANT_CLASS
+ }
+
+ -> do
+ ConstantVisibility::ClassIncludingPrivConstModule::PRIVATE_CONSTANT_MODULE
+ end.should raise_error(NameError) {|e|
+ e.receiver.should == ConstantVisibility::PrivConstModule
+ e.name.should == :PRIVATE_CONSTANT_MODULE
+ }
+ end
+ end
end
describe "Module#public_constant marked constants" do
@@ -622,3 +712,39 @@ describe "Module#public_constant marked constants" do
end
end
end
+
+describe 'Allowed characters' do
+ it 'allows not ASCII characters in the middle of a name' do
+ mod = Module.new
+ mod.const_set("BBἍBB", 1)
+
+ eval("mod::BBἍBB").should == 1
+ end
+
+ it 'does not allow not ASCII characters that cannot be upcased or lowercased at the beginning' do
+ -> do
+ Module.new.const_set("થBB", 1)
+ end.should raise_error(NameError, /wrong constant name/)
+ end
+
+ it 'allows not ASCII upcased characters at the beginning' do
+ mod = Module.new
+ mod.const_set("ἍBB", 1)
+
+ eval("mod::ἍBB").should == 1
+ end
+end
+
+describe 'Assignment' do
+ context 'dynamic assignment' do
+ it 'raises SyntaxError' do
+ -> do
+ eval <<-CODE
+ def test
+ B = 1
+ end
+ CODE
+ end.should raise_error(SyntaxError, /dynamic constant assignment/)
+ end
+ end
+end
diff --git a/spec/ruby/language/def_spec.rb b/spec/ruby/language/def_spec.rb
index 55ee283b90..c8531343c0 100644
--- a/spec/ruby/language/def_spec.rb
+++ b/spec/ruby/language/def_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/def', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/def'
# Language-level method behaviour
describe "Redefining a method" do
@@ -79,6 +79,38 @@ describe "Defining a method" do
end
end
+describe "An instance method" do
+ it "raises an error with too few arguments" do
+ def foo(a, b); end
+ -> { foo 1 }.should raise_error(ArgumentError, 'wrong number of arguments (given 1, expected 2)')
+ end
+
+ it "raises an error with too many arguments" do
+ def foo(a); end
+ -> { foo 1, 2 }.should raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 1)')
+ end
+
+ it "raises FrozenError with the correct class name" do
+ -> {
+ Module.new do
+ self.freeze
+ def foo; end
+ end
+ }.should raise_error(FrozenError) { |e|
+ e.message.should.start_with? "can't modify frozen module"
+ }
+
+ -> {
+ Class.new do
+ self.freeze
+ def foo; end
+ end
+ }.should raise_error(FrozenError){ |e|
+ e.message.should.start_with? "can't modify frozen class"
+ }
+ end
+end
+
describe "An instance method definition with a splat" do
it "accepts an unnamed '*' argument" do
def foo(*); end;
@@ -101,12 +133,12 @@ describe "An instance method definition with a splat" do
end
it "allows only a single * argument" do
- lambda { eval 'def foo(a, *b, *c); end' }.should raise_error(SyntaxError)
+ -> { eval 'def foo(a, *b, *c); end' }.should raise_error(SyntaxError)
end
it "requires the presence of any arguments that precede the *" do
def foo(a, b, *c); end
- lambda { foo 1 }.should raise_error(ArgumentError)
+ -> { foo 1 }.should raise_error(ArgumentError, 'wrong number of arguments (given 1, expected 2+)')
end
end
@@ -139,7 +171,7 @@ describe "An instance method with a default argument" do
def foo(a, b = 2)
[a,b]
end
- lambda { foo }.should raise_error(ArgumentError)
+ -> { foo }.should raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1..2)')
foo(1).should == [1, 2]
end
@@ -147,7 +179,7 @@ describe "An instance method with a default argument" do
def foo(a, b = 2, *c)
[a,b,c]
end
- lambda { foo }.should raise_error(ArgumentError)
+ -> { foo }.should raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1+)')
foo(1).should == [1,2,[]]
end
@@ -165,7 +197,7 @@ describe "An instance method with a default argument" do
foo(2,3,3).should == [2,3,[3]]
end
- it "shadows an existing method with the same name as the local" do
+ it "raises a SyntaxError when there is an existing method with the same name as the local variable" do
def bar
1
end
@@ -173,9 +205,7 @@ describe "An instance method with a default argument" do
eval "def foo(bar = bar)
bar
end"
- }.should complain(/circular argument reference/)
- foo.should == nil
- foo(2).should == 2
+ }.should raise_error(SyntaxError)
end
it "calls a method with the same name as the local when explicitly using ()" do
@@ -234,10 +264,29 @@ describe "A singleton method definition" do
(obj==2).should == 2
end
- it "raises RuntimeError if frozen" do
+ it "raises FrozenError if frozen" do
+ obj = Object.new
+ obj.freeze
+ -> { def obj.foo; end }.should raise_error(FrozenError)
+ end
+
+ it "raises FrozenError with the correct class name" do
obj = Object.new
obj.freeze
- lambda { def obj.foo; end }.should raise_error(RuntimeError)
+ -> { def obj.foo; end }.should raise_error(FrozenError){ |e|
+ e.message.should.start_with? "can't modify frozen object"
+ }
+
+ c = obj.singleton_class
+ -> { def c.foo; end }.should raise_error(FrozenError){ |e|
+ e.message.should.start_with? "can't modify frozen Class"
+ }
+
+ m = Module.new
+ m.freeze
+ -> { def m.foo; end }.should raise_error(FrozenError){ |e|
+ e.message.should.start_with? "can't modify frozen Module"
+ }
end
end
@@ -310,7 +359,7 @@ describe "A method defined with extreme default arguments" do
end
it "may use a lambda as a default" do
- def foo(output = 'a', prc = lambda {|n| output * n})
+ def foo(output = 'a', prc = -> n { output * n })
prc.call(5)
end
foo.should == 'aaaaa'
@@ -356,7 +405,7 @@ describe "A singleton method defined with extreme default arguments" do
it "may use a lambda as a default" do
a = Object.new
- def a.foo(output = 'a', prc = lambda {|n| output * n})
+ def a.foo(output = 'a', prc = -> n { output * n })
prc.call(5)
end
a.foo.should == 'aaaaa'
@@ -372,7 +421,7 @@ describe "A method definition inside a metaclass scope" do
end
DefSpecSingleton.a_class_method.should == DefSpecSingleton
- lambda { Object.a_class_method }.should raise_error(NoMethodError)
+ -> { Object.a_class_method }.should raise_error(NoMethodError)
end
it "can create a singleton method" do
@@ -382,15 +431,15 @@ describe "A method definition inside a metaclass scope" do
end
obj.a_singleton_method.should == obj
- lambda { Object.new.a_singleton_method }.should raise_error(NoMethodError)
+ -> { Object.new.a_singleton_method }.should raise_error(NoMethodError)
end
- it "raises RuntimeError if frozen" do
+ it "raises FrozenError if frozen" do
obj = Object.new
obj.freeze
class << obj
- lambda { def foo; end }.should raise_error(RuntimeError)
+ -> { def foo; end }.should raise_error(FrozenError)
end
end
end
@@ -426,11 +475,11 @@ describe "A nested method definition" do
end
end
- lambda { DefSpecNested.a_class_method }.should raise_error(NoMethodError)
+ -> { DefSpecNested.a_class_method }.should raise_error(NoMethodError)
DefSpecNested.create_class_method.should == DefSpecNested
DefSpecNested.a_class_method.should == DefSpecNested
- lambda { Object.a_class_method }.should raise_error(NoMethodError)
- lambda { DefSpecNested.new.a_class_method }.should raise_error(NoMethodError)
+ -> { Object.a_class_method }.should raise_error(NoMethodError)
+ -> { DefSpecNested.new.a_class_method }.should raise_error(NoMethodError)
end
it "creates a singleton method when evaluated in the metaclass of an instance" do
@@ -448,7 +497,7 @@ describe "A nested method definition" do
obj.a_singleton_method.should == obj
other = DefSpecNested.new
- lambda { other.a_singleton_method }.should raise_error(NoMethodError)
+ -> { other.a_singleton_method }.should raise_error(NoMethodError)
end
it "creates a method in the surrounding context when evaluated in a def expr.method" do
@@ -488,7 +537,7 @@ describe "A nested method definition" do
DefSpecNested.should_not have_instance_method :body_method
end
- it "defines methods as public by default" do
+ it "creates an instance method inside Class.new" do
cls = Class.new do
def do_def
def new_def
@@ -500,6 +549,41 @@ describe "A nested method definition" do
obj = cls.new
obj.do_def
obj.new_def.should == 1
+
+ cls.new.new_def.should == 1
+
+ -> { Object.new.new_def }.should raise_error(NoMethodError)
+ end
+end
+
+describe "A method definition always resets the visibility to public for nested definitions" do
+ it "in Class.new" do
+ cls = Class.new do
+ private
+ def do_def
+ def new_def
+ 1
+ end
+ end
+ end
+
+ obj = cls.new
+ -> { obj.do_def }.should raise_error(NoMethodError, /private/)
+ obj.send :do_def
+ obj.new_def.should == 1
+
+ cls.new.new_def.should == 1
+
+ -> { Object.new.new_def }.should raise_error(NoMethodError)
+ end
+
+ it "at the toplevel" do
+ obj = Object.new
+ -> { obj.toplevel_define_other_method }.should raise_error(NoMethodError, /private/)
+ toplevel_define_other_method
+ nested_method_in_toplevel_method.should == 42
+
+ Object.new.nested_method_in_toplevel_method.should == 42
end
end
@@ -512,7 +596,7 @@ describe "A method definition inside an instance_eval" do
obj.an_instance_eval_method.should == obj
other = Object.new
- lambda { other.an_instance_eval_method }.should raise_error(NoMethodError)
+ -> { other.an_instance_eval_method }.should raise_error(NoMethodError)
end
it "creates a singleton method when evaluated inside a metaclass" do
@@ -525,7 +609,7 @@ describe "A method definition inside an instance_eval" do
obj.a_metaclass_eval_method.should == obj
other = Object.new
- lambda { other.a_metaclass_eval_method }.should raise_error(NoMethodError)
+ -> { other.a_metaclass_eval_method }.should raise_error(NoMethodError)
end
it "creates a class method when the receiver is a class" do
@@ -534,7 +618,7 @@ describe "A method definition inside an instance_eval" do
end
DefSpecNested.an_instance_eval_class_method.should == DefSpecNested
- lambda { Object.an_instance_eval_class_method }.should raise_error(NoMethodError)
+ -> { Object.an_instance_eval_class_method }.should raise_error(NoMethodError)
end
it "creates a class method when the receiver is an anonymous class" do
@@ -546,7 +630,7 @@ describe "A method definition inside an instance_eval" do
end
m.klass_method.should == :test
- lambda { Object.klass_method }.should raise_error(NoMethodError)
+ -> { Object.klass_method }.should raise_error(NoMethodError)
end
it "creates a class method when instance_eval is within class" do
@@ -559,7 +643,7 @@ describe "A method definition inside an instance_eval" do
end
m.klass_method.should == :test
- lambda { Object.klass_method }.should raise_error(NoMethodError)
+ -> { Object.klass_method }.should raise_error(NoMethodError)
end
end
@@ -572,7 +656,7 @@ describe "A method definition inside an instance_exec" do
end
DefSpecNested.an_instance_exec_class_method.should == 1
- lambda { Object.an_instance_exec_class_method }.should raise_error(NoMethodError)
+ -> { Object.an_instance_exec_class_method }.should raise_error(NoMethodError)
end
it "creates a class method when the receiver is an anonymous class" do
@@ -586,7 +670,7 @@ describe "A method definition inside an instance_exec" do
end
m.klass_method.should == 1
- lambda { Object.klass_method }.should raise_error(NoMethodError)
+ -> { Object.klass_method }.should raise_error(NoMethodError)
end
it "creates a class method when instance_exec is within class" do
@@ -601,7 +685,7 @@ describe "A method definition inside an instance_exec" do
end
m.klass_method.should == 2
- lambda { Object.klass_method }.should raise_error(NoMethodError)
+ -> { Object.klass_method }.should raise_error(NoMethodError)
end
end
@@ -621,7 +705,7 @@ describe "A method definition in an eval" do
other = DefSpecNested.new
other.an_eval_instance_method.should == other
- lambda { Object.new.an_eval_instance_method }.should raise_error(NoMethodError)
+ -> { Object.new.an_eval_instance_method }.should raise_error(NoMethodError)
end
it "creates a class method" do
@@ -637,8 +721,8 @@ describe "A method definition in an eval" do
DefSpecNestedB.eval_class_method.should == DefSpecNestedB
DefSpecNestedB.an_eval_class_method.should == DefSpecNestedB
- lambda { Object.an_eval_class_method }.should raise_error(NoMethodError)
- lambda { DefSpecNestedB.new.an_eval_class_method}.should raise_error(NoMethodError)
+ -> { Object.an_eval_class_method }.should raise_error(NoMethodError)
+ -> { DefSpecNestedB.new.an_eval_class_method}.should raise_error(NoMethodError)
end
it "creates a singleton method" do
@@ -656,7 +740,7 @@ describe "A method definition in an eval" do
obj.an_eval_singleton_method.should == obj
other = DefSpecNested.new
- lambda { other.an_eval_singleton_method }.should raise_error(NoMethodError)
+ -> { other.an_eval_singleton_method }.should raise_error(NoMethodError)
end
end
@@ -682,7 +766,7 @@ describe "a method definition that sets more than one default parameter all to t
end
it "only allows overriding the default value of the first such parameter in each set" do
- lambda { foo(1,2) }.should raise_error(ArgumentError)
+ -> { foo(1,2) }.should raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 0..1)')
end
def bar(a=b=c=1,d=2)
@@ -693,7 +777,7 @@ describe "a method definition that sets more than one default parameter all to t
bar.should == [1,1,1,2]
bar(3).should == [3,nil,nil,2]
bar(3,4).should == [3,nil,nil,4]
- lambda { bar(3,4,5) }.should raise_error(ArgumentError)
+ -> { bar(3,4,5) }.should raise_error(ArgumentError, 'wrong number of arguments (given 3, expected 0..2)')
end
end
@@ -703,7 +787,7 @@ describe "The def keyword" do
module DefSpecsLambdaVisibility
private
- lambda {
+ -> {
def some_method; end
}.call
end
diff --git a/spec/ruby/language/defined_spec.rb b/spec/ruby/language/defined_spec.rb
index 1247658381..ae2bf45bda 100644
--- a/spec/ruby/language/defined_spec.rb
+++ b/spec/ruby/language/defined_spec.rb
@@ -1,25 +1,29 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/defined', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/defined'
describe "The defined? keyword for literals" do
it "returns 'self' for self" do
ret = defined?(self)
ret.should == "self"
+ ret.frozen?.should == true
end
it "returns 'nil' for nil" do
ret = defined?(nil)
ret.should == "nil"
+ ret.frozen?.should == true
end
it "returns 'true' for true" do
ret = defined?(true)
ret.should == "true"
+ ret.frozen?.should == true
end
it "returns 'false' for false" do
ret = defined?(false)
ret.should == "false"
+ ret.frozen?.should == true
end
describe "for a literal Array" do
@@ -27,15 +31,16 @@ describe "The defined? keyword for literals" do
it "returns 'expression' if each element is defined" do
ret = defined?([Object, Array])
ret.should == "expression"
+ ret.frozen?.should == true
end
it "returns nil if one element is not defined" do
- ret = defined?([NonExistantConstant, Array])
+ ret = defined?([NonExistentConstant, Array])
ret.should == nil
end
it "returns nil if all elements are not defined" do
- ret = defined?([NonExistantConstant, AnotherNonExistantConstant])
+ ret = defined?([NonExistentConstant, AnotherNonExistentConstant])
ret.should == nil
end
@@ -43,9 +48,25 @@ describe "The defined? keyword for literals" do
end
describe "The defined? keyword when called with a method name" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "does not call the method" do
+ defined?(DefinedSpecs.side_effects).should == "method"
+ ScratchPad.recorded.should != :defined_specs_side_effects
+ end
+
+ it "does not execute the arguments" do
+ defined?(DefinedSpecs.any_args(DefinedSpecs.side_effects)).should == "method"
+ ScratchPad.recorded.should != :defined_specs_side_effects
+ end
+
describe "without a receiver" do
it "returns 'method' if the method is defined" do
- defined?(puts).should == "method"
+ ret = defined?(puts)
+ ret.should == "method"
+ ret.frozen?.should == true
end
it "returns nil if the method is not defined" do
@@ -167,7 +188,9 @@ describe "The defined? keyword for an expression" do
end
it "returns 'assignment' for assigning a local variable" do
- defined?(x = 2).should == "assignment"
+ ret = defined?(x = 2)
+ ret.should == "assignment"
+ ret.frozen?.should == true
end
it "returns 'assignment' for assigning an instance variable" do
@@ -275,9 +298,7 @@ describe "The defined? keyword for an expression" do
end
it "returns nil for an expression with '!' and an unset class variable" do
- -> {
- @result = defined?(!@@defined_specs_undefined_class_variable)
- }.should complain(/class variable access from toplevel/)
+ @result = eval("class singleton_class::A; defined?(!@@doesnt_exist) end", binding, __FILE__, __LINE__)
@result.should be_nil
end
@@ -286,9 +307,7 @@ describe "The defined? keyword for an expression" do
end
it "returns nil for an expression with 'not' and an unset class variable" do
- -> {
- @result = defined?(not @@defined_specs_undefined_class_variable)
- }.should complain(/class variable access from toplevel/)
+ @result = eval("class singleton_class::A; defined?(not @@doesnt_exist) end", binding, __FILE__, __LINE__)
@result.should be_nil
end
@@ -474,7 +493,9 @@ end
describe "The defined? keyword for variables" do
it "returns 'local-variable' when called with the name of a local variable" do
- DefinedSpecs::Basic.new.local_variable_defined.should == "local-variable"
+ ret = DefinedSpecs::Basic.new.local_variable_defined
+ ret.should == "local-variable"
+ ret.frozen?.should == true
end
it "returns 'local-variable' when called with the name of a local variable assigned to nil" do
@@ -490,7 +511,9 @@ describe "The defined? keyword for variables" do
end
it "returns 'instance-variable' for an instance variable that has been assigned" do
- DefinedSpecs::Basic.new.instance_variable_defined.should == "instance-variable"
+ ret = DefinedSpecs::Basic.new.instance_variable_defined
+ ret.should == "instance-variable"
+ ret.frozen?.should == true
end
it "returns 'instance-variable' for an instance variable that has been assigned to nil" do
@@ -505,6 +528,12 @@ describe "The defined? keyword for variables" do
DefinedSpecs::Basic.new.global_variable_read.should be_nil
end
+ it "returns 'global-variable' for a global variable that has been assigned nil" do
+ ret = DefinedSpecs::Basic.new.global_variable_defined_as_nil
+ ret.should == "global-variable"
+ ret.frozen?.should == true
+ end
+
# MRI appears to special case defined? for $! and $~ in that it returns
# 'global-variable' even when they are not set (or they are always "set"
# but the value may be nil). In other words, 'defined?($~)' will return
@@ -543,16 +572,12 @@ describe "The defined? keyword for variables" do
defined?($+).should be_nil
end
- it "returns nil for $1-$9" do
+ it "returns nil for any last match global" do
defined?($1).should be_nil
- defined?($2).should be_nil
- defined?($3).should be_nil
defined?($4).should be_nil
- defined?($5).should be_nil
- defined?($6).should be_nil
defined?($7).should be_nil
- defined?($8).should be_nil
- defined?($9).should be_nil
+ defined?($10).should be_nil
+ defined?($200).should be_nil
end
end
@@ -587,13 +612,10 @@ describe "The defined? keyword for variables" do
end
it "returns nil for non-captures" do
- defined?($3).should be_nil
defined?($4).should be_nil
- defined?($5).should be_nil
- defined?($6).should be_nil
defined?($7).should be_nil
- defined?($8).should be_nil
- defined?($9).should be_nil
+ defined?($10).should be_nil
+ defined?($200).should be_nil
end
end
@@ -622,16 +644,12 @@ describe "The defined? keyword for variables" do
defined?($+).should be_nil
end
- it "returns nil for $1-$9" do
+ it "returns nil for any last match global" do
defined?($1).should be_nil
- defined?($2).should be_nil
- defined?($3).should be_nil
defined?($4).should be_nil
- defined?($5).should be_nil
- defined?($6).should be_nil
defined?($7).should be_nil
- defined?($8).should be_nil
- defined?($9).should be_nil
+ defined?($10).should be_nil
+ defined?($200).should be_nil
end
end
@@ -666,13 +684,10 @@ describe "The defined? keyword for variables" do
end
it "returns nil for non-captures" do
- defined?($3).should be_nil
defined?($4).should be_nil
- defined?($5).should be_nil
- defined?($6).should be_nil
defined?($7).should be_nil
- defined?($8).should be_nil
- defined?($9).should be_nil
+ defined?($10).should be_nil
+ defined?($200).should be_nil
end
end
it "returns 'global-variable' for a global variable that has been assigned" do
@@ -688,7 +703,9 @@ describe "The defined? keyword for variables" do
# get to the defined? call so it really has nothing to do with 'defined?'.
it "returns 'class variable' when called with the name of a class variable" do
- DefinedSpecs::Basic.new.class_variable_defined.should == "class variable"
+ ret = DefinedSpecs::Basic.new.class_variable_defined
+ ret.should == "class variable"
+ ret.frozen?.should == true
end
it "returns 'local-variable' when called with the name of a block local" do
@@ -699,7 +716,9 @@ end
describe "The defined? keyword for a simple constant" do
it "returns 'constant' when the constant is defined" do
- defined?(DefinedSpecs).should == "constant"
+ ret = defined?(DefinedSpecs)
+ ret.should == "constant"
+ ret.frozen?.should == true
end
it "returns nil when the constant is not defined" do
@@ -750,7 +769,7 @@ describe "The defined? keyword for a scoped constant" do
end
it "returns nil when an undefined constant is scoped to a defined constant" do
- defined?(DefinedSpecs::Child::B).should be_nil
+ defined?(DefinedSpecs::Child::Undefined).should be_nil
end
it "returns nil when a constant is scoped to an undefined constant" do
@@ -766,8 +785,8 @@ describe "The defined? keyword for a scoped constant" 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"
+ it "returns nil when a constant is defined on top-level but not on the class" do
+ defined?(DefinedSpecs::Basic::String).should be_nil
end
it "returns 'constant' if the scoped-scoped constant is defined" do
@@ -785,7 +804,7 @@ describe "The defined? keyword for a top-level scoped constant" do
end
it "returns nil when an undefined constant is scoped to a defined constant" do
- defined?(::DefinedSpecs::Child::B).should be_nil
+ defined?(::DefinedSpecs::Child::Undefined).should be_nil
end
it "returns nil when the undefined constant is scoped to an undefined constant" do
@@ -907,17 +926,21 @@ describe "The defined? keyword for a variable scoped constant" do
end
it "returns nil if the class scoped constant is not defined" do
- -> {
- @@defined_specs_obj = DefinedSpecs::Basic
- defined?(@@defined_specs_obj::Undefined).should be_nil
- }.should complain(/class variable access from toplevel/)
+ eval(<<-END, binding, __FILE__, __LINE__)
+ class singleton_class::A
+ @@defined_specs_obj = DefinedSpecs::Basic
+ defined?(@@defined_specs_obj::Undefined).should be_nil
+ end
+ END
end
it "returns 'constant' if the constant is defined in the scope of the class variable" do
- -> {
- @@defined_specs_obj = DefinedSpecs::Basic
- defined?(@@defined_specs_obj::A).should == "constant"
- }.should complain(/class variable access from toplevel/)
+ eval(<<-END, binding, __FILE__, __LINE__)
+ class singleton_class::A
+ @@defined_specs_obj = DefinedSpecs::Basic
+ defined?(@@defined_specs_obj::A).should == "constant"
+ end
+ END
end
it "returns nil if the local scoped constant is not defined" do
@@ -951,12 +974,26 @@ describe "The defined? keyword for yield" do
end
it "returns 'yield' if a block is passed to a method not taking a block parameter" do
- DefinedSpecs::Basic.new.yield_block.should == "yield"
+ ret = DefinedSpecs::Basic.new.yield_block
+ ret.should == "yield"
+ ret.frozen?.should == true
end
it "returns 'yield' if a block is passed to a method taking a block parameter" do
DefinedSpecs::Basic.new.yield_block_parameter.should == "yield"
end
+
+ it "returns 'yield' when called within a block" do
+ def yielder
+ yield
+ end
+
+ def call_defined
+ yielder { defined?(yield) }
+ end
+
+ call_defined() { }.should == "yield"
+ end
end
describe "The defined? keyword for super" do
@@ -982,7 +1019,9 @@ describe "The defined? keyword for super" do
end
it "returns 'super' when a superclass method exists" do
- DefinedSpecs::Super.new.method_no_args.should == "super"
+ ret = DefinedSpecs::Super.new.method_no_args
+ ret.should == "super"
+ ret.frozen?.should == true
end
it "returns 'super' from a block when a superclass method exists" do
diff --git a/spec/ruby/language/delegation_spec.rb b/spec/ruby/language/delegation_spec.rb
new file mode 100644
index 0000000000..3f24a79d5c
--- /dev/null
+++ b/spec/ruby/language/delegation_spec.rb
@@ -0,0 +1,65 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/delegation'
+
+describe "delegation with def(...)" do
+ it "delegates rest and kwargs" do
+ a = Class.new(DelegationSpecs::Target)
+ a.class_eval(<<-RUBY)
+ def delegate(...)
+ target(...)
+ end
+ RUBY
+
+ a.new.delegate(1, b: 2).should == [[1], {b: 2}]
+ end
+
+ it "delegates block" do
+ a = Class.new(DelegationSpecs::Target)
+ a.class_eval(<<-RUBY)
+ def delegate_block(...)
+ target_block(...)
+ end
+ RUBY
+
+ a.new.delegate_block(1, b: 2) { |x| x }.should == [{b: 2}, [1]]
+ end
+
+ it "parses as open endless Range when brackets are omitted" do
+ a = Class.new(DelegationSpecs::Target)
+ suppress_warning do
+ a.class_eval(<<-RUBY)
+ def delegate(...)
+ target ...
+ end
+ RUBY
+ end
+
+ a.new.delegate(1, b: 2).should == Range.new([[], {}], nil, true)
+ end
+end
+
+ruby_version_is "2.7.3" do
+ describe "delegation with def(x, ...)" do
+ it "delegates rest and kwargs" do
+ a = Class.new(DelegationSpecs::Target)
+ a.class_eval(<<-RUBY)
+ def delegate(x, ...)
+ target(...)
+ end
+ RUBY
+
+ a.new.delegate(0, 1, b: 2).should == [[1], {b: 2}]
+ end
+
+ it "delegates block" do
+ a = Class.new(DelegationSpecs::Target)
+ a.class_eval(<<-RUBY)
+ def delegate_block(x, ...)
+ target_block(...)
+ end
+ RUBY
+
+ a.new.delegate_block(0, 1, b: 2) { |x| x }.should == [{b: 2}, [1]]
+ end
+ end
+end
diff --git a/spec/ruby/language/encoding_spec.rb b/spec/ruby/language/encoding_spec.rb
index 070fa52bba..5430c9cb98 100644
--- a/spec/ruby/language/encoding_spec.rb
+++ b/spec/ruby/language/encoding_spec.rb
@@ -1,7 +1,7 @@
# -*- encoding: us-ascii -*-
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/coding_us_ascii', __FILE__)
-require File.expand_path('../fixtures/coding_utf_8', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/coding_us_ascii'
+require_relative 'fixtures/coding_utf_8'
describe "The __ENCODING__ pseudo-variable" do
it "is an instance of Encoding" do
@@ -14,14 +14,14 @@ describe "The __ENCODING__ pseudo-variable" do
it "is the evaluated strings's one inside an eval" do
eval("__ENCODING__".force_encoding("US-ASCII")).should == Encoding::US_ASCII
- eval("__ENCODING__".force_encoding("ASCII-8BIT")).should == Encoding::ASCII_8BIT
+ eval("__ENCODING__".force_encoding("BINARY")).should == Encoding::BINARY
end
it "is the encoding specified by a magic comment inside an eval" do
- code = "# encoding: ASCII-8BIT\n__ENCODING__".force_encoding("US-ASCII")
- eval(code).should == Encoding::ASCII_8BIT
+ code = "# encoding: BINARY\n__ENCODING__".force_encoding("US-ASCII")
+ eval(code).should == Encoding::BINARY
- code = "# encoding: us-ascii\n__ENCODING__".force_encoding("ASCII-8BIT")
+ code = "# encoding: us-ascii\n__ENCODING__".force_encoding("BINARY")
eval(code).should == Encoding::US_ASCII
end
@@ -31,6 +31,6 @@ describe "The __ENCODING__ pseudo-variable" do
end
it "raises a SyntaxError if assigned to" do
- lambda { eval("__ENCODING__ = 1") }.should raise_error(SyntaxError)
+ -> { eval("__ENCODING__ = 1") }.should raise_error(SyntaxError)
end
end
diff --git a/spec/ruby/language/ensure_spec.rb b/spec/ruby/language/ensure_spec.rb
index ae04feb739..e893904bcb 100644
--- a/spec/ruby/language/ensure_spec.rb
+++ b/spec/ruby/language/ensure_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/ensure', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/ensure'
describe "An ensure block inside a begin block" do
before :each do
@@ -7,50 +7,44 @@ describe "An ensure block inside a begin block" do
end
it "is executed when an exception is raised in it's corresponding begin block" do
- begin
- lambda {
- begin
- ScratchPad << :begin
- raise "An exception occurred!"
- ensure
- ScratchPad << :ensure
- end
- }.should raise_error(RuntimeError)
+ -> {
+ begin
+ ScratchPad << :begin
+ raise EnsureSpec::Error
+ ensure
+ ScratchPad << :ensure
+ end
+ }.should raise_error(EnsureSpec::Error)
- ScratchPad.recorded.should == [:begin, :ensure]
- end
+ ScratchPad.recorded.should == [:begin, :ensure]
end
it "is executed when an exception is raised and rescued in it's corresponding begin block" do
begin
+ ScratchPad << :begin
+ raise "An exception occurred!"
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+
+ ScratchPad.recorded.should == [:begin, :rescue, :ensure]
+ end
+
+ it "is executed even when a symbol is thrown in it's corresponding begin block" do
+ catch(:symbol) do
begin
ScratchPad << :begin
- raise "An exception occurred!"
+ throw(:symbol)
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
+ ScratchPad.recorded.should == [:begin, :ensure]
end
it "is executed when nothing is raised or thrown in it's corresponding begin block" do
@@ -72,6 +66,18 @@ describe "An ensure block inside a begin block" do
:ensure
end.should == :begin
end
+
+ it "sets exception cause if raises exception in block and in ensure" do
+ -> {
+ begin
+ raise "from block"
+ ensure
+ raise "from ensure"
+ end
+ }.should raise_error(RuntimeError, "from ensure") { |e|
+ e.cause.message.should == "from block"
+ }
+ end
end
describe "The value of an ensure expression," do
@@ -102,7 +108,7 @@ describe "An ensure block inside a method" do
end
it "is executed when an exception is raised in the method" do
- lambda { @obj.raise_in_method_with_ensure }.should raise_error(RuntimeError)
+ -> { @obj.raise_in_method_with_ensure }.should raise_error(EnsureSpec::Error)
@obj.executed.should == [:method, :ensure]
end
@@ -123,4 +129,203 @@ describe "An ensure block inside a method" do
it "has an impact on the method's explicit return value" do
@obj.explicit_return_in_method_with_ensure.should == :ensure
end
+
+ it "has an impact on the method's explicit return value from rescue if returns explicitly" do
+ @obj.explicit_return_in_rescue_and_explicit_return_in_ensure.should == "returned in ensure"
+ end
+
+ it "has no impact on the method's explicit return value from rescue if returns implicitly" do
+ @obj.explicit_return_in_rescue_and_implicit_return_in_ensure.should == "returned in rescue"
+ end
+
+ it "suppresses exception raised in method if returns value explicitly" do
+ @obj.raise_and_explicit_return_in_ensure.should == "returned in ensure"
+ end
+
+ it "suppresses exception raised in rescue if returns value explicitly" do
+ @obj.raise_in_rescue_and_explicit_return_in_ensure.should == "returned in ensure"
+ end
+
+ it "overrides exception raised in rescue if raises exception itself" do
+ -> {
+ @obj.raise_in_rescue_and_raise_in_ensure
+ }.should raise_error(RuntimeError, "raised in ensure")
+ end
+
+ it "suppresses exception raised in method if raises exception itself" do
+ -> {
+ @obj.raise_in_method_and_raise_in_ensure
+ }.should raise_error(RuntimeError, "raised in ensure")
+ end
+end
+
+describe "An ensure block inside a class" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "is executed when an exception is raised" do
+ -> {
+ eval <<-ruby
+ class EnsureInClassExample
+ ScratchPad << :class
+ raise EnsureSpec::Error
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+ }.should raise_error(EnsureSpec::Error)
+
+ ScratchPad.recorded.should == [:class, :ensure]
+ end
+
+ it "is executed when an exception is raised and rescued" do
+ eval <<-ruby
+ class EnsureInClassExample
+ ScratchPad << :class
+ raise
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+
+ ScratchPad.recorded.should == [:class, :rescue, :ensure]
+ end
+
+ it "is executed even when a symbol is thrown" do
+ catch(:symbol) do
+ eval <<-ruby
+ class EnsureInClassExample
+ ScratchPad << :class
+ throw(:symbol)
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+ end
+
+ ScratchPad.recorded.should == [:class, :ensure]
+ end
+
+ it "is executed when nothing is raised or thrown" do
+ eval <<-ruby
+ class EnsureInClassExample
+ ScratchPad << :class
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+
+ ScratchPad.recorded.should == [:class, :ensure]
+ end
+
+ it "has no return value" do
+ result = eval <<-ruby
+ class EnsureInClassExample
+ :class
+ ensure
+ :ensure
+ end
+ ruby
+
+ result.should == :class
+ end
+end
+
+describe "An ensure block inside {} block" do
+ it "is not allowed" do
+ -> {
+ eval <<-ruby
+ lambda {
+ raise
+ ensure
+ }
+ ruby
+ }.should raise_error(SyntaxError)
+ end
+end
+
+describe "An ensure block inside 'do end' block" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "is executed when an exception is raised in it's corresponding begin block" do
+ -> {
+ eval(<<-ruby).call
+ lambda do
+ ScratchPad << :begin
+ raise EnsureSpec::Error
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+ }.should raise_error(EnsureSpec::Error)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "is executed when an exception is raised and rescued in it's corresponding begin block" do
+ eval(<<-ruby).call
+ lambda do
+ ScratchPad << :begin
+ raise "An exception occurred!"
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+
+ ScratchPad.recorded.should == [:begin, :rescue, :ensure]
+ end
+
+ it "is executed even when a symbol is thrown in it's corresponding begin block" do
+ catch(:symbol) do
+ eval(<<-ruby).call
+ lambda do
+ ScratchPad << :begin
+ throw(:symbol)
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "is executed when nothing is raised or thrown in it's corresponding begin block" do
+ eval(<<-ruby).call
+ lambda do
+ ScratchPad << :begin
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "has no return value" do
+ result = eval(<<-ruby).call
+ lambda do
+ :begin
+ ensure
+ :ensure
+ end
+ ruby
+
+ result.should == :begin
+ end
end
diff --git a/spec/ruby/language/execution_spec.rb b/spec/ruby/language/execution_spec.rb
index 3e6e7ff48c..4e0310946d 100644
--- a/spec/ruby/language/execution_spec.rb
+++ b/spec/ruby/language/execution_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "``" do
it "returns the output of the executed sub-process" do
diff --git a/spec/ruby/language/file_spec.rb b/spec/ruby/language/file_spec.rb
index 409400ca83..136b262d07 100644
--- a/spec/ruby/language/file_spec.rb
+++ b/spec/ruby/language/file_spec.rb
@@ -1,10 +1,10 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/code_loading', __FILE__)
-require File.expand_path('../shared/__FILE__', __FILE__)
+require_relative '../spec_helper'
+require_relative '../fixtures/code_loading'
+require_relative 'shared/__FILE__'
describe "The __FILE__ pseudo-variable" do
it "raises a SyntaxError if assigned to" do
- lambda { eval("__FILE__ = 1") }.should raise_error(SyntaxError)
+ -> { eval("__FILE__ = 1") }.should raise_error(SyntaxError)
end
it "equals (eval) inside an eval" do
@@ -12,18 +12,10 @@ describe "The __FILE__ pseudo-variable" do
end
end
-describe "The __FILE__ pseudo-variable" do
- it_behaves_like :language___FILE__, :require, CodeLoadingSpecs::Method.new
-end
-
-describe "The __FILE__ pseudo-variable" do
+describe "The __FILE__ pseudo-variable with require" do
it_behaves_like :language___FILE__, :require, Kernel
end
-describe "The __FILE__ pseudo-variable" do
- it_behaves_like :language___FILE__, :load, CodeLoadingSpecs::Method.new
-end
-
-describe "The __FILE__ pseudo-variable" do
+describe "The __FILE__ pseudo-variable with load" do
it_behaves_like :language___FILE__, :load, Kernel
end
diff --git a/spec/ruby/language/fixtures/array.rb b/spec/ruby/language/fixtures/array.rb
index 4d8ce74ed6..c1036575ff 100644
--- a/spec/ruby/language/fixtures/array.rb
+++ b/spec/ruby/language/fixtures/array.rb
@@ -8,4 +8,25 @@ module ArraySpec
[a, b, c, d]
end
end
+
+ class SideEffect
+ def initialize()
+ @call_count = 0
+ end
+
+ attr_reader :call_count
+
+ def array_result(a_number)
+ [result(a_number), result(a_number)]
+ end
+
+ def result(a_number)
+ @call_count += 1
+ if a_number
+ 1
+ else
+ :thing
+ end
+ end
+ end
end
diff --git a/spec/ruby/language/fixtures/begin_file.rb b/spec/ruby/language/fixtures/begin_file.rb
new file mode 100644
index 0000000000..73cae61a08
--- /dev/null
+++ b/spec/ruby/language/fixtures/begin_file.rb
@@ -0,0 +1,3 @@
+BEGIN {
+ puts __FILE__
+}
diff --git a/spec/ruby/language/fixtures/block.rb b/spec/ruby/language/fixtures/block.rb
index 9848d18776..33baac6aeb 100644
--- a/spec/ruby/language/fixtures/block.rb
+++ b/spec/ruby/language/fixtures/block.rb
@@ -15,6 +15,10 @@ module BlockSpecs
def r(a)
yield(*a)
end
+
+ def k(*a)
+ yield(*a, b: true)
+ end
end
# TODO: rewrite all specs that use Yield to use Yielder
diff --git a/spec/ruby/language/fixtures/break.rb b/spec/ruby/language/fixtures/break.rb
index 50e7fcf5d9..217c20a2c0 100644
--- a/spec/ruby/language/fixtures/break.rb
+++ b/spec/ruby/language/fixtures/break.rb
@@ -106,6 +106,34 @@ module BreakSpecs
note :d
end
+ def looped_break_in_captured_block
+ note :begin
+ looped_delegate_block do |i|
+ note :prebreak
+ break if i == 1
+ note :postbreak
+ end
+ note :end
+ end
+
+ def looped_delegate_block(&block)
+ note :preloop
+ 2.times do |i|
+ note :predele
+ yield_value(i, &block)
+ note :postdele
+ end
+ note :postloop
+ end
+ private :looped_delegate_block
+
+ def yield_value(value)
+ note :preyield
+ yield value
+ note :postyield
+ end
+ private :yield_value
+
def method(v)
yield v
end
@@ -135,7 +163,7 @@ module BreakSpecs
# on the call stack when the lambda is invoked.
def break_in_defining_scope(value=true)
note :a
- note lambda {
+ note -> {
note :b
if value
break :break
@@ -149,7 +177,7 @@ module BreakSpecs
def break_in_nested_scope
note :a
- l = lambda do
+ l = -> do
note :b
break :break
note :c
@@ -169,7 +197,7 @@ module BreakSpecs
def break_in_nested_scope_yield
note :a
- l = lambda do
+ l = -> do
note :b
break :break
note :c
@@ -189,7 +217,7 @@ module BreakSpecs
def break_in_nested_scope_block
note :a
- l = lambda do
+ l = -> do
note :b
break :break
note :c
@@ -223,7 +251,7 @@ module BreakSpecs
# active on the call stack when the lambda is invoked.
def create_lambda
note :la
- l = lambda do
+ l = -> do
note :lb
break :break
note :lc
diff --git a/spec/ruby/language/fixtures/break_lambda_toplevel.rb b/spec/ruby/language/fixtures/break_lambda_toplevel.rb
index 05af1d3fdc..da5abbaf00 100644
--- a/spec/ruby/language/fixtures/break_lambda_toplevel.rb
+++ b/spec/ruby/language/fixtures/break_lambda_toplevel.rb
@@ -1,6 +1,6 @@
print "a,"
-print lambda {
+print -> {
print "b,"
break "break,"
print "c,"
diff --git a/spec/ruby/language/fixtures/break_lambda_toplevel_block.rb b/spec/ruby/language/fixtures/break_lambda_toplevel_block.rb
index a35cb8a8a1..3dcee62424 100644
--- a/spec/ruby/language/fixtures/break_lambda_toplevel_block.rb
+++ b/spec/ruby/language/fixtures/break_lambda_toplevel_block.rb
@@ -1,6 +1,6 @@
print "a,"
-l = lambda {
+l = -> {
print "b,"
break "break,"
print "c,"
diff --git a/spec/ruby/language/fixtures/break_lambda_toplevel_method.rb b/spec/ruby/language/fixtures/break_lambda_toplevel_method.rb
index 200040d614..a5936a3d70 100644
--- a/spec/ruby/language/fixtures/break_lambda_toplevel_method.rb
+++ b/spec/ruby/language/fixtures/break_lambda_toplevel_method.rb
@@ -1,6 +1,6 @@
print "a,"
-l = lambda {
+l = -> {
print "b,"
break "break,"
print "c,"
diff --git a/spec/ruby/language/fixtures/bytes_magic_comment.rb b/spec/ruby/language/fixtures/bytes_magic_comment.rb
new file mode 100644
index 0000000000..2bc2bcfb07
--- /dev/null
+++ b/spec/ruby/language/fixtures/bytes_magic_comment.rb
@@ -0,0 +1,2 @@
+# encoding: big5
+$magic_comment_result = 'An'.bytes.inspect
diff --git a/spec/ruby/language/fixtures/case_magic_comment.rb b/spec/ruby/language/fixtures/case_magic_comment.rb
new file mode 100644
index 0000000000..96f35a7c94
--- /dev/null
+++ b/spec/ruby/language/fixtures/case_magic_comment.rb
@@ -0,0 +1,2 @@
+# CoDiNg: bIg5
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/constant_visibility.rb b/spec/ruby/language/fixtures/constant_visibility.rb
index 022554430e..af38b2d8f2 100644
--- a/spec/ruby/language/fixtures/constant_visibility.rb
+++ b/spec/ruby/language/fixtures/constant_visibility.rb
@@ -65,7 +65,7 @@ module ConstantVisibility
end
end
- class PrivConstModuleChild
+ class ClassIncludingPrivConstModule
include PrivConstModule
def private_constant_from_include
@@ -77,6 +77,22 @@ module ConstantVisibility
end
end
+ module ModuleIncludingPrivConstModule
+ include PrivConstModule
+
+ def self.private_constant_from_include
+ PRIVATE_CONSTANT_MODULE
+ end
+
+ def self.private_constant_self_from_include
+ self::PRIVATE_CONSTANT_MODULE
+ end
+
+ def self.private_constant_named_from_include
+ PrivConstModule::PRIVATE_CONSTANT_MODULE
+ end
+ end
+
class PrivConstClassChild < PrivConstClass
def private_constant_from_subclass
PRIVATE_CONSTANT_CLASS
diff --git a/spec/ruby/language/fixtures/def.rb b/spec/ruby/language/fixtures/def.rb
index 81bfce73d0..e07060ed74 100644
--- a/spec/ruby/language/fixtures/def.rb
+++ b/spec/ruby/language/fixtures/def.rb
@@ -1,3 +1,9 @@
+def toplevel_define_other_method
+ def nested_method_in_toplevel_method
+ 42
+ end
+end
+
def some_toplevel_method
end
diff --git a/spec/ruby/language/fixtures/defined.rb b/spec/ruby/language/fixtures/defined.rb
index d26e553c4b..a9552619bf 100644
--- a/spec/ruby/language/fixtures/defined.rb
+++ b/spec/ruby/language/fixtures/defined.rb
@@ -19,6 +19,9 @@ module DefinedSpecs
DefinedSpecs
end
+ def self.any_args(*)
+ end
+
class Basic
A = 42
@@ -94,6 +97,11 @@ module DefinedSpecs
defined? $defined_specs_global_variable_defined
end
+ def global_variable_defined_as_nil
+ $defined_specs_global_variable_defined_as_nil = nil
+ defined? $defined_specs_global_variable_defined_as_nil
+ end
+
def class_variable_undefined
defined? @@class_variable_undefined
end
diff --git a/spec/ruby/language/fixtures/delegation.rb b/spec/ruby/language/fixtures/delegation.rb
new file mode 100644
index 0000000000..527d928390
--- /dev/null
+++ b/spec/ruby/language/fixtures/delegation.rb
@@ -0,0 +1,11 @@
+module DelegationSpecs
+ class Target
+ def target(*args, **kwargs)
+ [args, kwargs]
+ end
+
+ def target_block(*args, **kwargs)
+ yield [kwargs, args]
+ end
+ end
+end
diff --git a/spec/ruby/language/fixtures/emacs_magic_comment.rb b/spec/ruby/language/fixtures/emacs_magic_comment.rb
new file mode 100644
index 0000000000..2b09f3e74c
--- /dev/null
+++ b/spec/ruby/language/fixtures/emacs_magic_comment.rb
@@ -0,0 +1,2 @@
+# -*- encoding: big5 -*-
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/ensure.rb b/spec/ruby/language/fixtures/ensure.rb
index 0dad7d8401..6047ac5bc0 100644
--- a/spec/ruby/language/fixtures/ensure.rb
+++ b/spec/ruby/language/fixtures/ensure.rb
@@ -8,7 +8,7 @@ module EnsureSpec
def raise_in_method_with_ensure
@executed << :method
- raise "An Exception"
+ raise EnsureSpec::Error
ensure
@executed << :ensure
end
@@ -40,6 +40,50 @@ module EnsureSpec
ensure
return :ensure
end
+
+ def explicit_return_in_rescue_and_explicit_return_in_ensure
+ raise
+ rescue
+ return 2
+ ensure
+ return "returned in ensure"
+ end
+
+ def explicit_return_in_rescue_and_implicit_return_in_ensure
+ raise
+ rescue
+ return "returned in rescue"
+ ensure
+ 3
+ end
+
+ def raise_and_explicit_return_in_ensure
+ raise
+ ensure
+ return "returned in ensure"
+ end
+
+ def raise_in_rescue_and_explicit_return_in_ensure
+ raise
+ rescue
+ raise
+ ensure
+ return "returned in ensure"
+ end
+
+ def raise_in_rescue_and_raise_in_ensure
+ raise
+ rescue
+ raise "raised in rescue"
+ ensure
+ raise "raised in ensure"
+ end
+
+ def raise_in_method_and_raise_in_ensure
+ raise
+ ensure
+ raise "raised in ensure"
+ end
end
end
@@ -70,3 +114,8 @@ module EnsureSpec
end
end
end
+
+module EnsureSpec
+ class Error < RuntimeError
+ end
+end
diff --git a/spec/ruby/language/fixtures/for_scope.rb b/spec/ruby/language/fixtures/for_scope.rb
new file mode 100644
index 0000000000..9c44a23a2c
--- /dev/null
+++ b/spec/ruby/language/fixtures/for_scope.rb
@@ -0,0 +1,15 @@
+module ForSpecs
+ class ForInClassMethod
+ m = :same_variable_set_outside
+
+ def self.foo
+ all = []
+ for m in [:bar, :baz]
+ all << m
+ end
+ all
+ end
+
+ READER = -> { m }
+ end
+end
diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb b/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb
index a4d655ad02..cccc5969bd 100644
--- a/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb
@@ -1,3 +1,3 @@
# frozen_string_literal: true
-p "abc".object_id == "abc".object_id
+p "abc".equal?("abc")
diff --git a/spec/ruby/language/fixtures/hash_strings_ascii8bit.rb b/spec/ruby/language/fixtures/hash_strings_binary.rb
index 4ac11b9930..44b99cbf80 100644
--- a/spec/ruby/language/fixtures/hash_strings_ascii8bit.rb
+++ b/spec/ruby/language/fixtures/hash_strings_binary.rb
@@ -1,6 +1,6 @@
-# encoding: ascii-8bit
+# encoding: binary
-module HashStringsASCII8BIT
+module HashStringsBinary
def self.literal_hash
{"foo" => "bar"}
end
diff --git a/spec/ruby/language/fixtures/magic_comment.rb b/spec/ruby/language/fixtures/magic_comment.rb
new file mode 100644
index 0000000000..120ef6ff4a
--- /dev/null
+++ b/spec/ruby/language/fixtures/magic_comment.rb
@@ -0,0 +1,2 @@
+# encoding: big5
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/metaclass.rb b/spec/ruby/language/fixtures/metaclass.rb
index a1990b9225..a8f837e701 100644
--- a/spec/ruby/language/fixtures/metaclass.rb
+++ b/spec/ruby/language/fixtures/metaclass.rb
@@ -31,4 +31,3 @@ module MetaClassSpecs
class D < C; end
end
-
diff --git a/spec/ruby/language/fixtures/no_magic_comment.rb b/spec/ruby/language/fixtures/no_magic_comment.rb
new file mode 100644
index 0000000000..743a0f9503
--- /dev/null
+++ b/spec/ruby/language/fixtures/no_magic_comment.rb
@@ -0,0 +1 @@
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/print_magic_comment_result_at_exit.rb b/spec/ruby/language/fixtures/print_magic_comment_result_at_exit.rb
new file mode 100644
index 0000000000..aa82cf4471
--- /dev/null
+++ b/spec/ruby/language/fixtures/print_magic_comment_result_at_exit.rb
@@ -0,0 +1,3 @@
+at_exit {
+ print $magic_comment_result
+}
diff --git a/spec/ruby/language/fixtures/rescue.rb b/spec/ruby/language/fixtures/rescue.rb
index 3fa5df1eb5..b906e17a2f 100644
--- a/spec/ruby/language/fixtures/rescue.rb
+++ b/spec/ruby/language/fixtures/rescue.rb
@@ -60,4 +60,8 @@ module RescueSpecs
ScratchPad << :outside_begin
:return_val
end
+
+ def self.raise_standard_error
+ raise StandardError, "an error occurred"
+ end
end
diff --git a/spec/ruby/language/fixtures/rescue_captures.rb b/spec/ruby/language/fixtures/rescue_captures.rb
new file mode 100644
index 0000000000..69f9b83904
--- /dev/null
+++ b/spec/ruby/language/fixtures/rescue_captures.rb
@@ -0,0 +1,107 @@
+module RescueSpecs
+ class Captor
+ attr_accessor :captured_error
+
+ def self.should_capture_exception
+ captor = new
+ captor.capture('some text').should == :caught # Ensure rescue body still runs
+ captor.captured_error.message.should == 'some text'
+ end
+ end
+
+ class ClassVariableCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => @@captured_error
+ :caught
+ end
+
+ def captured_error
+ self.class.remove_class_variable(:@@captured_error)
+ end
+ end
+
+ class ConstantCaptor < Captor
+ # Using lambda gets around the dynamic constant assignment warning
+ CAPTURE = -> msg {
+ begin
+ raise msg
+ rescue => CapturedError
+ :caught
+ end
+ }
+
+ def capture(msg)
+ CAPTURE.call(msg)
+ end
+
+ def captured_error
+ self.class.send(:remove_const, :CapturedError)
+ end
+ end
+
+ class GlobalVariableCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => $captured_error
+ :caught
+ end
+
+ def captured_error
+ $captured_error.tap do
+ $captured_error = nil # Can't remove globals, only nil them out
+ end
+ end
+ end
+
+ class InstanceVariableCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => @captured_error
+ :caught
+ end
+ end
+
+ class LocalVariableCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => captured_error
+ @captured_error = captured_error
+ :caught
+ end
+ end
+
+ class SafeNavigationSetterCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => self&.captured_error
+ :caught
+ end
+ end
+
+ class SetterCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => self.captured_error
+ :caught
+ end
+ end
+
+ class SquareBracketsCaptor < Captor
+ def capture(msg)
+ @hash = {}
+
+ raise msg
+ rescue => self[:error]
+ :caught
+ end
+
+ def []=(key, value)
+ @hash[key] = value
+ end
+
+ def captured_error
+ @hash[:error]
+ end
+ end
+end
diff --git a/spec/ruby/language/fixtures/return.rb b/spec/ruby/language/fixtures/return.rb
index 0414c356e8..f6b143f3fa 100644
--- a/spec/ruby/language/fixtures/return.rb
+++ b/spec/ruby/language/fixtures/return.rb
@@ -101,18 +101,14 @@ module ReturnSpecs
# return value will go into val before we run the ensure.
#
# If lamb's return keeps unwinding incorrectly, val will still
- # have it's old value.
+ # have its old value.
#
# We can therefore use val to figure out what happened.
begin
val = foo()
ensure
- if val != :good
- return :bad
- end
+ return val
end
-
- return val
end
end
diff --git a/spec/ruby/language/fixtures/second_line_magic_comment.rb b/spec/ruby/language/fixtures/second_line_magic_comment.rb
new file mode 100644
index 0000000000..a3dd50393b
--- /dev/null
+++ b/spec/ruby/language/fixtures/second_line_magic_comment.rb
@@ -0,0 +1,3 @@
+
+# encoding: big5
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/second_token_magic_comment.rb b/spec/ruby/language/fixtures/second_token_magic_comment.rb
new file mode 100644
index 0000000000..8d443e68f3
--- /dev/null
+++ b/spec/ruby/language/fixtures/second_token_magic_comment.rb
@@ -0,0 +1,2 @@
+1 + 1 # encoding: big5
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/send.rb b/spec/ruby/language/fixtures/send.rb
index c3013616b2..918241e171 100644
--- a/spec/ruby/language/fixtures/send.rb
+++ b/spec/ruby/language/fixtures/send.rb
@@ -53,8 +53,9 @@ module LangSendSpecs
end
class PrivateGetter
- attr_reader :foo
+ attr_accessor :foo
private :foo
+ private :foo=
def call_self_foo
self.foo
diff --git a/spec/ruby/language/fixtures/shebang_magic_comment.rb b/spec/ruby/language/fixtures/shebang_magic_comment.rb
new file mode 100755
index 0000000000..f8e5e7d8e4
--- /dev/null
+++ b/spec/ruby/language/fixtures/shebang_magic_comment.rb
@@ -0,0 +1,3 @@
+#!/usr/bin/ruby
+# encoding: big5
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/squiggly_heredoc.rb b/spec/ruby/language/fixtures/squiggly_heredoc.rb
index afc87514c7..984a629e5b 100644
--- a/spec/ruby/language/fixtures/squiggly_heredoc.rb
+++ b/spec/ruby/language/fixtures/squiggly_heredoc.rb
@@ -29,6 +29,22 @@ module SquigglyHeredocSpecs
HERE
end
+ def self.backslash
+ <<~HERE
+ a
+ b\
+ c
+ HERE
+ end
+
+ def self.least_indented_on_the_first_line
+ <<~HERE
+ a
+ b
+ c
+ HERE
+ end
+
def self.least_indented_on_the_last_line
<<~HERE
a
@@ -36,4 +52,20 @@ module SquigglyHeredocSpecs
c
HERE
end
+
+ def self.least_indented_on_the_first_line_single
+ <<~'HERE'
+ a
+ b
+ c
+ HERE
+ end
+
+ def self.least_indented_on_the_last_line_single
+ <<~'HERE'
+ a
+ b
+ c
+ HERE
+ end
end
diff --git a/spec/ruby/language/fixtures/super.rb b/spec/ruby/language/fixtures/super.rb
index 09a454bdf4..218f1e4970 100644
--- a/spec/ruby/language/fixtures/super.rb
+++ b/spec/ruby/language/fixtures/super.rb
@@ -1,4 +1,4 @@
-module Super
+module SuperSpecs
module S1
class A
def foo(a)
@@ -282,7 +282,7 @@ module Super
#
# When name3 is called then, Alias2 (NOT Alias3) is presented as the
# current module to Alias2#name, so that when super is called,
- # Alias2->superclass is next.
+ # Alias2's superclass is next.
#
# Otherwise, Alias2 is next, which is where name was to begin with,
# causing the wrong #name method to be called.
@@ -377,12 +377,12 @@ module Super
end
def b
- block_ref = lambda { 15 }
+ block_ref = -> { 15 }
[super { 14 }, super(&block_ref)]
end
def c
- block_ref = lambda { 16 }
+ block_ref = -> { 16 }
super(&block_ref)
end
end
@@ -455,6 +455,84 @@ module Super
end
end
+ module ZSuperWithRestAndPost
+ class A
+ def m(*args, a, b)
+ args
+ end
+
+ def m_modified(*args, a, b)
+ args
+ end
+ end
+
+ class B < A
+ def m(*args, a, b)
+ super
+ end
+
+ def m_modified(*args, a, b)
+ args[1] = 14
+ super
+ end
+ end
+ end
+
+ module ZSuperWithRestOthersAndPost
+ class A
+ def m(a, *args, b)
+ args
+ end
+
+ def m_modified(a, *args, b)
+ args
+ end
+ end
+
+ class B < A
+ def m(a, *args, b)
+ super
+ end
+
+ def m_modified(a, *args, b)
+ args[1] = 14
+ super
+ end
+ end
+ end
+
+ module ZSuperWithRestReassigned
+ class A
+ def a(*args)
+ args
+ end
+ end
+
+ class B < A
+ def a(*args)
+ args = ["foo"]
+
+ super
+ end
+ end
+ end
+
+ module ZSuperWithRestReassignedWithScalar
+ class A
+ def a(*args)
+ args
+ end
+ end
+
+ class B < A
+ def a(*args)
+ args = "foo"
+
+ super
+ end
+ end
+ end
+
module ZSuperWithUnderscores
class A
def m(*args)
diff --git a/spec/ruby/language/fixtures/utf16-be-nobom.rb b/spec/ruby/language/fixtures/utf16-be-nobom.rb
new file mode 100644
index 0000000000..99e2ce8ce8
--- /dev/null
+++ b/spec/ruby/language/fixtures/utf16-be-nobom.rb
Binary files differ
diff --git a/spec/ruby/language/fixtures/utf16-le-nobom.rb b/spec/ruby/language/fixtures/utf16-le-nobom.rb
new file mode 100644
index 0000000000..98de9697ca
--- /dev/null
+++ b/spec/ruby/language/fixtures/utf16-le-nobom.rb
Binary files differ
diff --git a/spec/ruby/language/fixtures/utf8-bom.rb b/spec/ruby/language/fixtures/utf8-bom.rb
new file mode 100644
index 0000000000..50c223a922
--- /dev/null
+++ b/spec/ruby/language/fixtures/utf8-bom.rb
@@ -0,0 +1,2 @@
+# encoding: utf-8
+puts 'hello'
diff --git a/spec/ruby/language/fixtures/utf8-nobom.rb b/spec/ruby/language/fixtures/utf8-nobom.rb
new file mode 100644
index 0000000000..75f5563b95
--- /dev/null
+++ b/spec/ruby/language/fixtures/utf8-nobom.rb
@@ -0,0 +1,2 @@
+# encoding: utf-8
+puts 'hello'
diff --git a/spec/ruby/language/fixtures/vim_magic_comment.rb b/spec/ruby/language/fixtures/vim_magic_comment.rb
new file mode 100644
index 0000000000..60cbe7a3bf
--- /dev/null
+++ b/spec/ruby/language/fixtures/vim_magic_comment.rb
@@ -0,0 +1,2 @@
+# vim: filetype=ruby, fileencoding=big5, tabsize=3, shiftwidth=3
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/yield.rb b/spec/ruby/language/fixtures/yield.rb
index a195616640..9f7a2ba238 100644
--- a/spec/ruby/language/fixtures/yield.rb
+++ b/spec/ruby/language/fixtures/yield.rb
@@ -21,6 +21,10 @@ module YieldSpecs
yield(*a)
end
+ def k(a)
+ yield(*a, b: true)
+ end
+
def rs(a, b, c)
yield(a, b, *c)
end
diff --git a/spec/ruby/language/for_spec.rb b/spec/ruby/language/for_spec.rb
index c9d043fa25..0ad5ea88af 100644
--- a/spec/ruby/language/for_spec.rb
+++ b/spec/ruby/language/for_spec.rb
@@ -1,4 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/for_scope'
# for name[, name]... in expr [do]
# body
@@ -32,14 +33,13 @@ describe "The for expression" do
end
it "iterates over any object responding to 'each'" do
- class XYZ
- def each
- (0..10).each { |i| yield i }
- end
+ obj = Object.new
+ def obj.each
+ (0..10).each { |i| yield i }
end
j = 0
- for i in XYZ.new
+ for i in obj
j += i
end
j.should == 55
@@ -131,6 +131,11 @@ describe "The for expression" do
a.should == 123
end
+ it "does not try to access variables outside the method" do
+ ForSpecs::ForInClassMethod.foo.should == [:bar, :baz]
+ ForSpecs::ForInClassMethod::READER.call.should == :same_variable_set_outside
+ end
+
it "returns expr" do
for i in 1..3; end.should == (1..3)
for i,j in { 1 => 10, 2 => 20 }; end.should == { 1 => 10, 2 => 20 }
diff --git a/spec/ruby/language/hash_spec.rb b/spec/ruby/language/hash_spec.rb
index edd9d4fbb2..c84564d3ea 100644
--- a/spec/ruby/language/hash_spec.rb
+++ b/spec/ruby/language/hash_spec.rb
@@ -1,7 +1,7 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/hash_strings_ascii8bit', __FILE__)
-require File.expand_path('../fixtures/hash_strings_utf8', __FILE__)
-require File.expand_path('../fixtures/hash_strings_usascii', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/hash_strings_binary'
+require_relative 'fixtures/hash_strings_utf8'
+require_relative 'fixtures/hash_strings_usascii'
describe "Hash literal" do
it "{} should return an empty hash" do
@@ -38,7 +38,7 @@ describe "Hash literal" do
key.reverse!
h["foo"].should == "bar"
h.keys.first.should == "foo"
- h.keys.first.frozen?.should == true
+ h.keys.first.should.frozen?
key.should == "oof"
end
@@ -48,6 +48,26 @@ describe "Hash literal" do
}.should complain(/key :foo is duplicated|duplicated key/)
@h.keys.size.should == 1
@h.should == {foo: :foo}
+ -> {
+ @h = eval "{%q{a} => :bar, %q{a} => :foo}"
+ }.should complain(/key "a" is duplicated|duplicated key/)
+ @h.keys.size.should == 1
+ @h.should == {%q{a} => :foo}
+ -> {
+ @h = eval "{1000 => :bar, 1000 => :foo}"
+ }.should complain(/key 1000 is duplicated|duplicated key/)
+ @h.keys.size.should == 1
+ @h.should == {1000 => :foo}
+ end
+
+ ruby_version_is "3.1" do
+ it "checks duplicated float keys on initialization" do
+ -> {
+ @h = eval "{1.0 => :bar, 1.0 => :foo}"
+ }.should complain(/key 1.0 is duplicated|duplicated key/)
+ @h.keys.size.should == 1
+ @h.should == {1.0 => :foo}
+ end
end
it "accepts a hanging comma" do
@@ -63,7 +83,7 @@ describe "Hash literal" do
end
it "with '==>' in the middle raises SyntaxError" do
- lambda { eval("{:a ==> 1}") }.should raise_error(SyntaxError)
+ -> { eval("{:a ==> 1}") }.should raise_error(SyntaxError)
end
it "constructs a new hash with the given elements" do
@@ -128,27 +148,111 @@ describe "Hash literal" do
{a: 1, **obj, c: 3}.should == {a:1, b: 2, c: 3, d: 4}
end
- it "raises a TypeError if any splatted elements keys are not symbols" do
+ it "allows splatted elements keys that are not symbols" do
h = {1 => 2, b: 3}
- lambda { {a: 1, **h} }.should raise_error(TypeError)
+ {a: 1, **h}.should == {a: 1, 1 => 2, b: 3}
end
it "raises a TypeError if #to_hash does not return a Hash" do
obj = mock("hash splat")
obj.should_receive(:to_hash).and_return(obj)
- lambda { {**obj} }.should raise_error(TypeError)
+ -> { {**obj} }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the object does not respond to #to_hash" do
+ obj = 42
+ -> { {**obj} }.should raise_error(TypeError)
+ -> { {a: 1, **obj} }.should raise_error(TypeError)
end
it "does not change encoding of literal string keys during creation" do
- ascii8bit_hash = HashStringsASCII8BIT.literal_hash
+ binary_hash = HashStringsBinary.literal_hash
utf8_hash = HashStringsUTF8.literal_hash
usascii_hash = HashStringsUSASCII.literal_hash
- ascii8bit_hash.keys.first.encoding.should == Encoding::ASCII_8BIT
- ascii8bit_hash.keys.first.should == utf8_hash.keys.first
+ binary_hash.keys.first.encoding.should == Encoding::BINARY
+ binary_hash.keys.first.should == utf8_hash.keys.first
utf8_hash.keys.first.encoding.should == Encoding::UTF_8
utf8_hash.keys.first.should == usascii_hash.keys.first
usascii_hash.keys.first.encoding.should == Encoding::US_ASCII
end
end
+
+describe "The ** operator" do
+ it "makes a copy when calling a method taking a keyword rest argument" do
+ def m(**h)
+ h.delete(:one); h
+ end
+
+ h = { one: 1, two: 2 }
+ m(**h).should == { two: 2 }
+ m(**h).should_not.equal?(h)
+ h.should == { one: 1, two: 2 }
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "makes a caller-side copy when calling a method taking a positional Hash" do
+ def m(h)
+ h.delete(:one); h
+ end
+
+ h = { one: 1, two: 2 }
+ m(**h).should == { two: 2 }
+ m(**h).should_not.equal?(h)
+ h.should == { one: 1, two: 2 }
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "does not copy when calling a method taking a positional Hash" do
+ def m(h)
+ h.delete(:one); h
+ end
+
+ h = { one: 1, two: 2 }
+ m(**h).should == { two: 2 }
+ m(**h).should.equal?(h)
+ h.should == { two: 2 }
+ end
+ end
+
+ ruby_version_is "3.1" do
+ describe "hash with omitted value" do
+ it "accepts short notation 'key' for 'key: value' syntax" do
+ a, b, c = 1, 2, 3
+ h = eval('{a:}')
+ {a: 1}.should == h
+ h = eval('{a:, b:, c:}')
+ {a: 1, b: 2, c: 3}.should == h
+ end
+
+ it "ignores hanging comma on short notation" do
+ a, b, c = 1, 2, 3
+ h = eval('{a:, b:, c:,}')
+ {a: 1, b: 2, c: 3}.should == h
+ end
+
+ it "accepts mixed syntax" do
+ a, e = 1, 5
+ h = eval('{a:, b: 2, "c" => 3, :d => 4, e:}')
+ eval('{a: 1, :b => 2, "c" => 3, "d": 4, e: 5}').should == h
+ end
+
+ it "works with methods and local vars" do
+ a = Class.new
+ a.class_eval(<<-RUBY)
+ def bar
+ "baz"
+ end
+
+ def foo(val)
+ {bar:, val:}
+ end
+ RUBY
+
+ a.new.foo(1).should == {bar: "baz", val: 1}
+ end
+ end
+ end
+end
diff --git a/spec/ruby/language/heredoc_spec.rb b/spec/ruby/language/heredoc_spec.rb
index a57a7b0bb9..b3b160fd11 100644
--- a/spec/ruby/language/heredoc_spec.rb
+++ b/spec/ruby/language/heredoc_spec.rb
@@ -1,6 +1,6 @@
# -*- encoding: us-ascii -*-
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "Heredoc string" do
@@ -13,6 +13,7 @@ describe "Heredoc string" do
foo bar#{@ip}
HERE
s.should == "foo barxxx\n"
+ s.encoding.should == Encoding::US_ASCII
end
it 'allow HEREDOC with <<"identifier", interpolated' do
@@ -20,6 +21,7 @@ HERE
foo bar#{@ip}
HERE
s.should == "foo barxxx\n"
+ s.encoding.should == Encoding::US_ASCII
end
it "allows HEREDOC with <<'identifier', no interpolation" do
@@ -27,6 +29,7 @@ HERE
foo bar#{@ip}
HERE
s.should == 'foo bar#{@ip}' + "\n"
+ s.encoding.should == Encoding::US_ASCII
end
it "allows HEREDOC with <<-identifier, allowing to indent identifier, interpolated" do
@@ -35,6 +38,7 @@ HERE
HERE
s.should == " foo barxxx\n"
+ s.encoding.should == Encoding::US_ASCII
end
it 'allows HEREDOC with <<-"identifier", allowing to indent identifier, interpolated' do
@@ -43,6 +47,7 @@ HERE
HERE
s.should == " foo barxxx\n"
+ s.encoding.should == Encoding::US_ASCII
end
it "allows HEREDOC with <<-'identifier', allowing to indent identifier, no interpolation" do
@@ -51,37 +56,54 @@ HERE
HERE
s.should == ' foo bar#{@ip}' + "\n"
+ s.encoding.should == Encoding::US_ASCII
end
- ruby_version_is "2.3" do
- it "allows HEREDOC with <<~'identifier', allowing to indent identifier and content" do
- require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
- SquigglyHeredocSpecs.message.should == "character density, n.:\n The number of very weird people in the office.\n"
- end
-
- it "trims trailing newline character for blank HEREDOC with <<~'identifier'" do
- require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
- SquigglyHeredocSpecs.blank.should == ""
- end
-
- it 'allows HEREDOC with <<~identifier, interpolated' do
- require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
- SquigglyHeredocSpecs.unquoted.should == "unquoted interpolated\n"
- end
-
- it 'allows HEREDOC with <<~"identifier", interpolated' do
- require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
- SquigglyHeredocSpecs.doublequoted.should == "doublequoted interpolated\n"
- end
-
- it "allows HEREDOC with <<~'identifier', no interpolation" do
- require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
- SquigglyHeredocSpecs.singlequoted.should == "singlequoted \#{\"interpolated\"}\n"
- end
-
- it "selects the least-indented line and removes its indentation from all the lines" do
- require File.expand_path('../fixtures/squiggly_heredoc', __FILE__)
- SquigglyHeredocSpecs.least_indented_on_the_last_line.should == " a\n b\nc\n"
- end
+ it 'raises SyntaxError if quoted HEREDOC identifier is ending not on same line' do
+ -> {
+ eval %{<<"HERE\n"\nraises syntax error\nHERE}
+ }.should raise_error(SyntaxError)
+ end
+
+ it "allows HEREDOC with <<~'identifier', allowing to indent identifier and content" do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.message.should == "character density, n.:\n The number of very weird people in the office.\n"
+ end
+
+ it "trims trailing newline character for blank HEREDOC with <<~'identifier'" do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.blank.should == ""
+ end
+
+ it 'allows HEREDOC with <<~identifier, interpolated' do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.unquoted.should == "unquoted interpolated\n"
+ end
+
+ it 'allows HEREDOC with <<~"identifier", interpolated' do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.doublequoted.should == "doublequoted interpolated\n"
+ end
+
+ it "allows HEREDOC with <<~'identifier', no interpolation" do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.singlequoted.should == "singlequoted \#{\"interpolated\"}\n"
+ end
+
+ it "allows HEREDOC with <<~'identifier', no interpolation, with backslash" do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.backslash.should == "a\nbc\n"
+ end
+
+ it "selects the least-indented line and removes its indentation from all the lines" do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.least_indented_on_the_first_line.should == "a\n b\n c\n"
+ SquigglyHeredocSpecs.least_indented_on_the_last_line.should == " a\n b\nc\n"
+ end
+
+ it "selects the least-indented line and removes its indentation from all the lines for <<~'identifier'" do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.least_indented_on_the_first_line_single.should == "a\n b\n c\n"
+ SquigglyHeredocSpecs.least_indented_on_the_last_line_single.should == " a\n b\nc\n"
end
end
diff --git a/spec/ruby/language/if_spec.rb b/spec/ruby/language/if_spec.rb
index 284d852462..d1d95c1607 100644
--- a/spec/ruby/language/if_spec.rb
+++ b/spec/ruby/language/if_spec.rb
@@ -1,22 +1,20 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The if expression" do
- ruby_version_is '2.4' do
- describe "accepts multiple assignments in conditional expression" do
- before(:each) { ScratchPad.record([]) }
- after(:each) { ScratchPad.clear }
-
- it 'with non-nil values' do
- ary = [1, 2]
- eval "if (a, b = ary); ScratchPad.record [a, b]; end"
- ScratchPad.recorded.should == [1, 2]
- end
-
- it 'with nil values' do
- ary = nil
- eval "if (a, b = ary); else; ScratchPad.record [a, b]; end"
- ScratchPad.recorded.should == [nil, nil]
- end
+ describe "accepts multiple assignments in conditional expression" do
+ before(:each) { ScratchPad.record([]) }
+ after(:each) { ScratchPad.clear }
+
+ it 'with non-nil values' do
+ ary = [1, 2]
+ eval "if (a, b = ary); ScratchPad.record [a, b]; end"
+ ScratchPad.recorded.should == [1, 2]
+ end
+
+ it 'with nil values' do
+ ary = nil
+ eval "if (a, b = ary); else; ScratchPad.record [a, b]; end"
+ ScratchPad.recorded.should == [nil, nil]
end
end
diff --git a/spec/ruby/language/keyword_arguments_spec.rb b/spec/ruby/language/keyword_arguments_spec.rb
new file mode 100644
index 0000000000..c47b7b0ae9
--- /dev/null
+++ b/spec/ruby/language/keyword_arguments_spec.rb
@@ -0,0 +1,397 @@
+require_relative '../spec_helper'
+
+ruby_version_is "3.0" do
+ describe "Keyword arguments" do
+ def target(*args, **kwargs)
+ [args, kwargs]
+ end
+
+ it "are separated from positional arguments" do
+ def m(*args, **kwargs)
+ [args, kwargs]
+ end
+
+ empty = {}
+ m(**empty).should == [[], {}]
+ m(empty).should == [[{}], {}]
+
+ m(a: 1).should == [[], {a: 1}]
+ m({a: 1}).should == [[{a: 1}], {}]
+ end
+
+ it "when the receiving method has not keyword parameters it treats kwargs as positional" do
+ def m(*a)
+ a
+ end
+
+ m(a: 1).should == [{a: 1}]
+ m({a: 1}).should == [{a: 1}]
+ end
+
+ context "empty kwargs are treated as if they were not passed" do
+ it "when calling a method" do
+ def m(*a)
+ a
+ end
+
+ empty = {}
+ m(**empty).should == []
+ m(empty).should == [{}]
+ end
+
+ it "when yielding to a block" do
+ def y(*args, **kwargs)
+ yield(*args, **kwargs)
+ end
+
+ empty = {}
+ y(**empty) { |*a| a }.should == []
+ y(empty) { |*a| a }.should == [{}]
+ end
+ end
+
+ it "extra keywords are not allowed without **kwrest" do
+ def m(*a, kw:)
+ a
+ end
+
+ m(kw: 1).should == []
+ -> { m(kw: 1, kw2: 2) }.should raise_error(ArgumentError, 'unknown keyword: :kw2')
+ -> { m(kw: 1, true => false) }.should raise_error(ArgumentError, 'unknown keyword: true')
+ -> { m(kw: 1, a: 1, b: 2, c: 3) }.should raise_error(ArgumentError, 'unknown keywords: :a, :b, :c')
+ end
+
+ it "raises ArgumentError exception when required keyword argument is not passed" do
+ def m(a:, b:, c:)
+ [a, b, c]
+ end
+
+ -> { m(a: 1, b: 2) }.should raise_error(ArgumentError, /missing keyword: :c/)
+ -> { m() }.should raise_error(ArgumentError, /missing keywords: :a, :b, :c/)
+ end
+
+ it "raises ArgumentError for missing keyword arguments even if there are extra ones" do
+ def m(a:)
+ a
+ end
+
+ -> { m(b: 1) }.should raise_error(ArgumentError, /missing keyword: :a/)
+ end
+
+ it "handle * and ** at the same call site" do
+ def m(*a)
+ a
+ end
+
+ m(*[], **{}).should == []
+ m(*[], 42, **{}).should == [42]
+ end
+
+ context "**" do
+ it "does not copy a non-empty Hash for a method taking (*args)" do
+ def m(*args)
+ args[0]
+ end
+
+ h = {a: 1}
+ m(**h).should.equal?(h)
+ end
+
+ it "copies the given Hash for a method taking (**kwargs)" do
+ def m(**kw)
+ kw
+ end
+
+ empty = {}
+ m(**empty).should == empty
+ m(**empty).should_not.equal?(empty)
+
+ h = {a: 1}
+ m(**h).should == h
+ m(**h).should_not.equal?(h)
+ end
+ end
+
+ context "delegation" do
+ it "works with (*args, **kwargs)" do
+ def m(*args, **kwargs)
+ target(*args, **kwargs)
+ end
+
+ empty = {}
+ m(**empty).should == [[], {}]
+ m(empty).should == [[{}], {}]
+
+ m(a: 1).should == [[], {a: 1}]
+ m({a: 1}).should == [[{a: 1}], {}]
+ end
+
+ it "works with proc { |*args, **kwargs| }" do
+ m = proc do |*args, **kwargs|
+ target(*args, **kwargs)
+ end
+
+ empty = {}
+ m.(**empty).should == [[], {}]
+ m.(empty).should == [[{}], {}]
+
+ m.(a: 1).should == [[], {a: 1}]
+ m.({a: 1}).should == [[{a: 1}], {}]
+
+ # no autosplatting for |*args, **kwargs|
+ m.([1, 2]).should == [[[1, 2]], {}]
+ end
+
+ it "works with -> (*args, **kwargs) {}" do
+ m = -> *args, **kwargs do
+ target(*args, **kwargs)
+ end
+
+ empty = {}
+ m.(**empty).should == [[], {}]
+ m.(empty).should == [[{}], {}]
+
+ m.(a: 1).should == [[], {a: 1}]
+ m.({a: 1}).should == [[{a: 1}], {}]
+ end
+
+ it "works with (...)" do
+ instance_eval <<~DEF
+ def m(...)
+ target(...)
+ end
+ DEF
+
+ empty = {}
+ m(**empty).should == [[], {}]
+ m(empty).should == [[{}], {}]
+
+ m(a: 1).should == [[], {a: 1}]
+ m({a: 1}).should == [[{a: 1}], {}]
+ end
+
+ it "works with call(*ruby2_keyword_args)" do
+ class << self
+ ruby2_keywords def m(*args)
+ target(*args)
+ end
+ end
+
+ empty = {}
+ m(**empty).should == [[], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+ m(empty).should == [[{}], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+
+ m(a: 1).should == [[], {a: 1}]
+ m({a: 1}).should == [[{a: 1}], {}]
+
+ kw = {a: 1}
+
+ m(**kw).should == [[], {a: 1}]
+ m(**kw)[1].should == kw
+ m(**kw)[1].should_not.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ Hash.ruby2_keywords_hash?(m(**kw)[1]).should == false
+
+ m(kw).should == [[{a: 1}], {}]
+ m(kw)[0][0].should.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ end
+
+ it "works with super(*ruby2_keyword_args)" do
+ parent = Class.new do
+ def m(*args, **kwargs)
+ [args, kwargs]
+ end
+ end
+
+ child = Class.new(parent) do
+ ruby2_keywords def m(*args)
+ super(*args)
+ end
+ end
+
+ obj = child.new
+
+ empty = {}
+ obj.m(**empty).should == [[], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+ obj.m(empty).should == [[{}], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+
+ obj.m(a: 1).should == [[], {a: 1}]
+ obj.m({a: 1}).should == [[{a: 1}], {}]
+
+ kw = {a: 1}
+
+ obj.m(**kw).should == [[], {a: 1}]
+ obj.m(**kw)[1].should == kw
+ obj.m(**kw)[1].should_not.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ Hash.ruby2_keywords_hash?(obj.m(**kw)[1]).should == false
+
+ obj.m(kw).should == [[{a: 1}], {}]
+ obj.m(kw)[0][0].should.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ end
+
+ it "works with zsuper" do
+ parent = Class.new do
+ def m(*args, **kwargs)
+ [args, kwargs]
+ end
+ end
+
+ child = Class.new(parent) do
+ ruby2_keywords def m(*args)
+ super
+ end
+ end
+
+ obj = child.new
+
+ empty = {}
+ obj.m(**empty).should == [[], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+ obj.m(empty).should == [[{}], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+
+ obj.m(a: 1).should == [[], {a: 1}]
+ obj.m({a: 1}).should == [[{a: 1}], {}]
+
+ kw = {a: 1}
+
+ obj.m(**kw).should == [[], {a: 1}]
+ obj.m(**kw)[1].should == kw
+ obj.m(**kw)[1].should_not.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ Hash.ruby2_keywords_hash?(obj.m(**kw)[1]).should == false
+
+ obj.m(kw).should == [[{a: 1}], {}]
+ obj.m(kw)[0][0].should.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ end
+
+ it "works with yield(*ruby2_keyword_args)" do
+ class << self
+ def y(args)
+ yield(*args)
+ end
+
+ ruby2_keywords def m(*outer_args)
+ y(outer_args, &-> *args, **kwargs { target(*args, **kwargs) })
+ end
+ end
+
+ empty = {}
+ m(**empty).should == [[], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+ m(empty).should == [[{}], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+
+ m(a: 1).should == [[], {a: 1}]
+ m({a: 1}).should == [[{a: 1}], {}]
+
+ kw = {a: 1}
+
+ m(**kw).should == [[], {a: 1}]
+ m(**kw)[1].should == kw
+ m(**kw)[1].should_not.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ Hash.ruby2_keywords_hash?(m(**kw)[1]).should == false
+
+ m(kw).should == [[{a: 1}], {}]
+ m(kw)[0][0].should.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ end
+
+ it "does not work with (*args)" do
+ class << self
+ def m(*args)
+ target(*args)
+ end
+ end
+
+ empty = {}
+ m(**empty).should == [[], {}]
+ m(empty).should == [[{}], {}]
+
+ m(a: 1).should == [[{a: 1}], {}]
+ m({a: 1}).should == [[{a: 1}], {}]
+ end
+
+ ruby_version_is "3.1" do
+ describe "omitted values" do
+ it "accepts short notation 'key' for 'key: value' syntax" do
+ def m(a:, b:)
+ [a, b]
+ end
+
+ a = 1
+ b = 2
+
+ eval('m(a:, b:).should == [1, 2]')
+ end
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "does not work with call(*ruby2_keyword_args) with missing ruby2_keywords in between" do
+ class << self
+ def n(*args) # Note the missing ruby2_keywords here
+ target(*args)
+ end
+
+ ruby2_keywords def m(*args)
+ n(*args)
+ end
+ end
+
+ empty = {}
+ m(**empty).should == [[], {}]
+ m(empty).should == [[{}], {}]
+
+ m(a: 1).should == [[{a: 1}], {}]
+ m({a: 1}).should == [[{a: 1}], {}]
+ end
+ end
+
+ ruby_version_is ""..."3.2" do
+ # https://bugs.ruby-lang.org/issues/18625
+ it "works with call(*ruby2_keyword_args) with missing ruby2_keywords in between due to CRuby bug #18625" do
+ class << self
+ def n(*args) # Note the missing ruby2_keywords here
+ target(*args)
+ end
+
+ ruby2_keywords def m(*args)
+ n(*args)
+ end
+ end
+
+ empty = {}
+ m(**empty).should == [[], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+ m(empty).should == [[{}], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+
+ m(a: 1).should == [[], {a: 1}]
+ m({a: 1}).should == [[{a: 1}], {}]
+
+ kw = {a: 1}
+
+ m(**kw).should == [[], {a: 1}]
+ m(**kw)[1].should == kw
+ m(**kw)[1].should_not.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ Hash.ruby2_keywords_hash?(m(**kw)[1]).should == false
+
+ m(kw).should == [[{a: 1}], {}]
+ m(kw)[0][0].should.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/language/lambda_spec.rb b/spec/ruby/language/lambda_spec.rb
index 43e2d60ae3..a3f01ec04b 100644
--- a/spec/ruby/language/lambda_spec.rb
+++ b/spec/ruby/language/lambda_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/classes'
describe "A lambda literal -> () { }" do
SpecEvaluate.desc = "for definition"
@@ -7,7 +7,7 @@ describe "A lambda literal -> () { }" do
it "returns a Proc object when used in a BasicObject method" do
klass = Class.new(BasicObject) do
def create_lambda
- -> () { }
+ -> { }
end
end
@@ -15,11 +15,19 @@ describe "A lambda literal -> () { }" do
end
it "does not execute the block" do
- ->() { fail }.should be_an_instance_of(Proc)
+ -> { fail }.should be_an_instance_of(Proc)
end
it "returns a lambda" do
- -> () { }.lambda?.should be_true
+ -> { }.lambda?.should be_true
+ end
+
+ it "may include a rescue clause" do
+ eval('-> do raise ArgumentError; rescue ArgumentError; 7; end').should be_an_instance_of(Proc)
+ end
+
+ it "may include a ensure clause" do
+ eval('-> do 1; ensure; 2; end').should be_an_instance_of(Proc)
end
it "has its own scope for local variables" do
@@ -101,7 +109,7 @@ describe "A lambda literal -> () { }" do
@a = -> (a:) { a }
ruby
- lambda { @a.() }.should raise_error(ArgumentError)
+ -> { @a.() }.should raise_error(ArgumentError)
@a.(a: 1).should == 1
end
@@ -119,7 +127,7 @@ describe "A lambda literal -> () { }" do
@a.().should be_nil
@a.(a: 1, b: 2).should be_nil
- lambda { @a.(1) }.should raise_error(ArgumentError)
+ -> { @a.(1) }.should raise_error(ArgumentError)
end
evaluate <<-ruby do
@@ -143,8 +151,8 @@ describe "A lambda literal -> () { }" do
ruby
@a.(1, 2).should == [1, 2]
- lambda { @a.() }.should raise_error(ArgumentError)
- lambda { @a.(1) }.should raise_error(ArgumentError)
+ -> { @a.() }.should raise_error(ArgumentError)
+ -> { @a.(1) }.should raise_error(ArgumentError)
end
evaluate <<-ruby do
@@ -169,16 +177,34 @@ describe "A lambda literal -> () { }" do
result.should == [1, 2, 3, [4, 5], 6, [7, 8], 9, 10, 11, 12]
end
- evaluate <<-ruby do
- @a = -> (*, **k) { k }
- ruby
+ ruby_version_is ''...'3.0' do
+ evaluate <<-ruby do
+ @a = -> (*, **k) { k }
+ ruby
- @a.().should == {}
- @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
+ @a.().should == {}
+ @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
+
+ suppress_keyword_warning do
+ h = mock("keyword splat")
+ h.should_receive(:to_hash).and_return({a: 1})
+ @a.(h).should == {a: 1}
+ end
+ end
+ end
+
+ ruby_version_is '3.0' do
+ evaluate <<-ruby do
+ @a = -> (*, **k) { k }
+ ruby
+
+ @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}
+ h = mock("keyword splat")
+ h.should_not_receive(:to_hash)
+ @a.(h).should == {}
+ end
end
evaluate <<-ruby do
@@ -255,25 +281,23 @@ describe "A lambda literal -> () { }" do
end
describe "with circular optional argument reference" do
- it "shadows an existing local with the same name as the argument" do
+ it "raises a SyntaxError if using an existing local with the same name as the argument" do
a = 1
-> {
@proc = eval "-> (a=a) { a }"
- }.should complain(/circular argument reference/)
- @proc.call.should == nil
+ }.should raise_error(SyntaxError)
end
- it "shadows an existing method with the same name as the argument" do
+ it "raises a SyntaxError if there is 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
+ }.should raise_error(SyntaxError)
end
it "calls an existing method with the same name as the argument if explicitly using ()" do
def a; 1; end
- -> (a=a()) { a }.call.should == 1
+ -> a=a() { a }.call.should == 1
end
end
end
@@ -302,7 +326,13 @@ describe "A lambda expression 'lambda { ... }'" do
end
it "requires a block" do
- lambda { lambda }.should raise_error(ArgumentError)
+ suppress_warning do
+ lambda { lambda }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "may include a rescue clause" do
+ eval('lambda do raise ArgumentError; rescue ArgumentError; 7; end').should be_an_instance_of(Proc)
end
context "with an implicit block" do
@@ -310,14 +340,13 @@ describe "A lambda expression 'lambda { ... }'" do
def meth; lambda; end
end
- it "can be created" do
+ it "raises ArgumentError" do
implicit_lambda = nil
- -> {
- implicit_lambda = meth { 1 }
- }.should complain(/tried to create Proc object without a block/)
-
- implicit_lambda.lambda?.should be_true
- implicit_lambda.call.should == 1
+ suppress_warning do
+ -> {
+ meth { 1 }
+ }.should raise_error(ArgumentError, /tried to create Proc object without a block/)
+ end
end
end
@@ -485,16 +514,34 @@ describe "A lambda expression 'lambda { ... }'" do
result.should == [1, 2, 3, [4, 5], 6, [7, 8], 9, 10, 11, 12]
end
- evaluate <<-ruby do
- @a = lambda { |*, **k| k }
- ruby
+ ruby_version_is ''...'3.0' do
+ evaluate <<-ruby do
+ @a = lambda { |*, **k| k }
+ ruby
- @a.().should == {}
- @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
+ @a.().should == {}
+ @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
+
+ suppress_keyword_warning do
+ h = mock("keyword splat")
+ h.should_receive(:to_hash).and_return({a: 1})
+ @a.(h).should == {a: 1}
+ end
+ end
+ end
+
+ ruby_version_is '3.0' do
+ evaluate <<-ruby do
+ @a = lambda { |*, **k| k }
+ ruby
- h = mock("keyword splat")
- h.should_receive(:to_hash).and_return({a: 1})
- @a.(h).should == {a: 1}
+ @a.().should == {}
+ @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
+
+ h = mock("keyword splat")
+ h.should_not_receive(:to_hash)
+ @a.(h).should == {}
+ end
end
evaluate <<-ruby do
diff --git a/spec/ruby/language/line_spec.rb b/spec/ruby/language/line_spec.rb
index d9fd307dab..fcadaa71d7 100644
--- a/spec/ruby/language/line_spec.rb
+++ b/spec/ruby/language/line_spec.rb
@@ -1,10 +1,10 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/code_loading', __FILE__)
-require File.expand_path('../shared/__LINE__', __FILE__)
+require_relative '../spec_helper'
+require_relative '../fixtures/code_loading'
+require_relative 'shared/__LINE__'
describe "The __LINE__ pseudo-variable" do
it "raises a SyntaxError if assigned to" do
- lambda { eval("__LINE__ = 1") }.should raise_error(SyntaxError)
+ -> { eval("__LINE__ = 1") }.should raise_error(SyntaxError)
end
before :each do
diff --git a/spec/ruby/language/loop_spec.rb b/spec/ruby/language/loop_spec.rb
index 4e60e0d8e6..fd17b53910 100644
--- a/spec/ruby/language/loop_spec.rb
+++ b/spec/ruby/language/loop_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The loop expression" do
it "repeats the given block until a break is called" do
@@ -15,7 +15,7 @@ describe "The loop expression" do
inner_loop = 123
break
end
- lambda { inner_loop }.should raise_error(NameError)
+ -> { inner_loop }.should raise_error(NameError)
end
it "returns the value passed to break if interrupted by break" do
diff --git a/spec/ruby/language/magic_comment_spec.rb b/spec/ruby/language/magic_comment_spec.rb
index 2f6e3b5c3a..f2bf3a08e5 100644
--- a/spec/ruby/language/magic_comment_spec.rb
+++ b/spec/ruby/language/magic_comment_spec.rb
@@ -1,62 +1,92 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
-describe "Magic comment" do
- it "is optional" do
- eval("__ENCODING__").should be_an_instance_of(Encoding)
+# See core/kernel/eval_spec.rb for more magic comments specs for eval()
+describe :magic_comments, shared: true do
+ before :each do
+ @default = @method == :locale ? Encoding.find('locale') : Encoding::UTF_8
end
- it "determines __ENCODING__" do
- eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
-# encoding: ASCII-8BIT
-__ENCODING__
-EOS
+ it "are optional" do
+ @object.call('no_magic_comment.rb').should == @default.name
end
- it "is case-insensitive" do
- eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
-# CoDiNg: aScIi-8bIt
-__ENCODING__
-EOS
+ it "are case-insensitive" do
+ @object.call('case_magic_comment.rb').should == Encoding::Big5.name
end
it "must be at the first line" do
- eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::US_ASCII
-
-# encoding: ASCII-8BIT
-__ENCODING__
-EOS
+ @object.call('second_line_magic_comment.rb').should == @default.name
end
it "must be the first token of the line" do
- eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::US_ASCII
-1+1 # encoding: ASCII-8BIT
-__ENCODING__
-EOS
- eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
- # encoding: ASCII-8BIT
-__ENCODING__
-EOS
+ @object.call('second_token_magic_comment.rb').should == @default.name
end
it "can be after the shebang" do
- eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
-#!/usr/bin/ruby -Ku
-# encoding: ASCII-8BIT
-__ENCODING__
-EOS
+ @object.call('shebang_magic_comment.rb').should == Encoding::Big5.name
end
it "can take Emacs style" do
- eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
-# -*- encoding: ascii-8bit -*-
-__ENCODING__
-EOS
+ @object.call('emacs_magic_comment.rb').should == Encoding::Big5.name
end
it "can take vim style" do
- eval(<<EOS.force_encoding("US-ASCII")).should == Encoding::ASCII_8BIT
-# vim: filetype=ruby, fileencoding=ascii-8bit, tabsize=3, shiftwidth=3
-__ENCODING__
-EOS
+ @object.call('vim_magic_comment.rb').should == Encoding::Big5.name
+ end
+
+ it "determine __ENCODING__" do
+ @object.call('magic_comment.rb').should == Encoding::Big5.name
+ end
+
+ it "do not cause bytes to be mangled by passing them through the wrong encoding" do
+ @object.call('bytes_magic_comment.rb').should == [167, 65, 166, 110].inspect
+ end
+end
+
+describe "Magic comments" do
+ describe "in stdin" do
+ it_behaves_like :magic_comments, :locale, -> file {
+ print_at_exit = fixture(__FILE__, "print_magic_comment_result_at_exit.rb")
+ ruby_exe(nil, args: "< #{fixture(__FILE__, file)}", options: "-r#{print_at_exit}")
+ }
+ end
+
+ platform_is_not :windows do
+ describe "in an -e argument" do
+ it_behaves_like :magic_comments, :locale, -> file {
+ print_at_exit = fixture(__FILE__, "print_magic_comment_result_at_exit.rb")
+ # Use UTF-8, as it is the default source encoding for files
+ code = File.read(fixture(__FILE__, file), encoding: 'utf-8')
+ IO.popen([*ruby_exe, "-r", print_at_exit, "-e", code], &:read)
+ }
+ end
+ end
+
+ describe "in the main file" do
+ it_behaves_like :magic_comments, :UTF8, -> file {
+ print_at_exit = fixture(__FILE__, "print_magic_comment_result_at_exit.rb")
+ ruby_exe(fixture(__FILE__, file), options: "-r#{print_at_exit}")
+ }
+ end
+
+ describe "in a loaded file" do
+ it_behaves_like :magic_comments, :UTF8, -> file {
+ load fixture(__FILE__, file)
+ $magic_comment_result
+ }
+ end
+
+ describe "in a required file" do
+ it_behaves_like :magic_comments, :UTF8, -> file {
+ require fixture(__FILE__, file)
+ $magic_comment_result
+ }
+ end
+
+ describe "in an eval" do
+ it_behaves_like :magic_comments, :UTF8, -> file {
+ # Use UTF-8, as it is the default source encoding for files
+ eval(File.read(fixture(__FILE__, file), encoding: 'utf-8'))
+ }
end
end
diff --git a/spec/ruby/language/match_spec.rb b/spec/ruby/language/match_spec.rb
index 81604e94b2..ebf677cabc 100644
--- a/spec/ruby/language/match_spec.rb
+++ b/spec/ruby/language/match_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/match_operators', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/match_operators'
describe "The !~ operator" do
before :each do
@@ -48,6 +48,13 @@ describe "The =~ operator with named captures" do
end
end
+ describe "on syntax of 'string_literal' =~ /regexp/" do
+ it "does not set local variables" do
+ 'string literal' =~ /(?<matched>str)(?<unmatched>lit)?/
+ local_variables.should == []
+ end
+ end
+
describe "on syntax of string_variable =~ /regexp/" do
it "does not set local variables" do
@string =~ /(?<matched>foo)(?<unmatched>bar)?/
diff --git a/spec/ruby/language/metaclass_spec.rb b/spec/ruby/language/metaclass_spec.rb
index b3bcd9ef18..fc83067977 100644
--- a/spec/ruby/language/metaclass_spec.rb
+++ b/spec/ruby/language/metaclass_spec.rb
@@ -1,6 +1,6 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/class', __FILE__)
-require File.expand_path('../fixtures/metaclass', __FILE__)
+require_relative '../spec_helper'
+require_relative '../fixtures/class'
+require_relative 'fixtures/metaclass'
describe "self in a metaclass body (class << obj)" do
it "is TrueClass for true" do
@@ -16,11 +16,11 @@ describe "self in a metaclass body (class << obj)" do
end
it "raises a TypeError for numbers" do
- lambda { class << 1; self; end }.should raise_error(TypeError)
+ -> { class << 1; self; end }.should raise_error(TypeError)
end
it "raises a TypeError for symbols" do
- lambda { class << :symbol; self; end }.should raise_error(TypeError)
+ -> { class << :symbol; self; end }.should raise_error(TypeError)
end
it "is a singleton Class instance" do
@@ -64,11 +64,11 @@ describe "A constant on a metaclass" do
class << @object
CONST
end
- lambda { CONST }.should raise_error(NameError)
+ -> { CONST }.should raise_error(NameError)
end
it "cannot be accessed via object::CONST" do
- lambda do
+ -> do
@object::CONST
end.should raise_error(TypeError)
end
@@ -79,7 +79,7 @@ describe "A constant on a metaclass" do
CONST = 100
end
- lambda do
+ -> do
@object::CONST
end.should raise_error(NameError)
end
@@ -96,7 +96,7 @@ describe "A constant on a metaclass" do
it "is not preserved when the object is duped" do
@object = @object.dup
- lambda do
+ -> do
class << @object; CONST; end
end.should raise_error(NameError)
end
diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb
index ca939dbab6..b80b314f6f 100644
--- a/spec/ruby/language/method_spec.rb
+++ b/spec/ruby/language/method_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "A method send" do
evaluate <<-ruby do
@@ -40,7 +40,7 @@ describe "A method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { m(*x) }.should raise_error(TypeError)
+ -> { m(*x) }.should raise_error(TypeError)
end
end
@@ -74,7 +74,7 @@ describe "A method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { m(*x, 2, 3) }.should raise_error(TypeError)
+ -> { m(*x, 2, 3) }.should raise_error(TypeError)
end
end
@@ -108,7 +108,7 @@ describe "A method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { m(1, *x, 2, 3) }.should raise_error(TypeError)
+ -> { m(1, *x, 2, 3) }.should raise_error(TypeError)
end
it "copies the splatted array" do
@@ -153,7 +153,27 @@ describe "A method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { m(1, 2, *x) }.should raise_error(TypeError)
+ -> { m(1, 2, *x) }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a block argument" do
+ before :all do
+ def m(x)
+ if block_given?
+ [true, yield(x + 'b')]
+ else
+ [false]
+ end
+ end
+ end
+
+ it "that refers to a proc passes the proc as the block" do
+ m('a', &-> y { y + 'c'}).should == [true, 'abc']
+ end
+
+ it "that is nil passes no block" do
+ m('a', &nil).should == [false]
end
end
end
@@ -197,7 +217,7 @@ describe "An element assignment method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { @o[*x] = 1 }.should raise_error(TypeError)
+ -> { @o[*x] = 1 }.should raise_error(TypeError)
end
end
@@ -235,7 +255,7 @@ describe "An element assignment method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { @o[*x, 2, 3] = 4 }.should raise_error(TypeError)
+ -> { @o[*x, 2, 3] = 4 }.should raise_error(TypeError)
end
end
@@ -273,7 +293,7 @@ describe "An element assignment method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { @o[1, 2, *x, 3] = 4 }.should raise_error(TypeError)
+ -> { @o[1, 2, *x, 3] = 4 }.should raise_error(TypeError)
end
end
@@ -311,7 +331,7 @@ describe "An element assignment method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { @o[1, 2, 3, *x] = 4 }.should raise_error(TypeError)
+ -> { @o[1, 2, 3, *x] = 4 }.should raise_error(TypeError)
end
end
end
@@ -348,7 +368,7 @@ describe "An attribute assignment method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { @o.send :m=, *x, 1 }.should raise_error(TypeError)
+ -> { @o.send :m=, *x, 1 }.should raise_error(TypeError)
end
end
@@ -383,7 +403,7 @@ describe "An attribute assignment method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { @o.send :m=, *x, 2, 3, 4 }.should raise_error(TypeError)
+ -> { @o.send :m=, *x, 2, 3, 4 }.should raise_error(TypeError)
end
end
@@ -418,7 +438,7 @@ describe "An attribute assignment method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { @o.send :m=, 1, 2, *x, 3, 4 }.should raise_error(TypeError)
+ -> { @o.send :m=, 1, 2, *x, 3, 4 }.should raise_error(TypeError)
end
end
@@ -453,7 +473,7 @@ describe "An attribute assignment method send" do
x = mock("splat argument")
x.should_receive(:to_a).and_return(1)
- lambda { @o.send :m=, 1, 2, 3, *x, 4 }.should raise_error(TypeError)
+ -> { @o.send :m=, 1, 2, 3, *x, 4 }.should raise_error(TypeError)
end
end
end
@@ -512,6 +532,15 @@ describe "A method" do
end
evaluate <<-ruby do
+ def m() end
+ ruby
+
+ m().should be_nil
+ m(*[]).should be_nil
+ m(**{}).should be_nil
+ end
+
+ evaluate <<-ruby do
def m(*) end
ruby
@@ -527,15 +556,26 @@ describe "A method" do
m().should == []
m(1).should == [1]
m(1, 2, 3).should == [1, 2, 3]
+ m(*[]).should == []
+ m(**{}).should == []
end
evaluate <<-ruby do
def m(a:) a end
ruby
- lambda { m() }.should raise_error(ArgumentError)
+ -> { m() }.should raise_error(ArgumentError)
m(a: 1).should == 1
- lambda { m("a" => 1, a: 1) }.should raise_error(ArgumentError)
+ suppress_keyword_warning do
+ -> { m("a" => 1, a: 1) }.should raise_error(ArgumentError)
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a:, **kw) [a, kw] end
+ ruby
+
+ -> { m(b: 1) }.should raise_error(ArgumentError)
end
evaluate <<-ruby do
@@ -552,7 +592,7 @@ describe "A method" do
m().should be_nil
m(a: 1, b: 2).should be_nil
- lambda { m(1) }.should raise_error(ArgumentError)
+ -> { m(1) }.should raise_error(ArgumentError)
end
evaluate <<-ruby do
@@ -561,7 +601,19 @@ describe "A method" do
m().should == {}
m(a: 1, b: 2).should == { a: 1, b: 2 }
- lambda { m(2) }.should raise_error(ArgumentError)
+ m(*[]).should == {}
+ m(**{}).should == {}
+ suppress_warning {
+ eval "m(**{a: 1, b: 2}, **{a: 4, c: 7})"
+ }.should == { a: 4, b: 2, c: 7 }
+ -> { m(2) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(**k); k end;
+ ruby
+
+ m("a" => 1).should == { "a" => 1 }
end
evaluate <<-ruby do
@@ -600,7 +652,7 @@ describe "A method" do
m(2, 3).should be_nil
m([2, 3, 4], [5, 6]).should be_nil
- lambda { m a: 1 }.should raise_error(ArgumentError)
+ -> { m a: 1 }.should raise_error(ArgumentError)
end
evaluate <<-ruby do
@@ -692,34 +744,73 @@ describe "A method" do
ruby
m(1, b: 2).should == [1, 2]
- lambda { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ suppress_keyword_warning do
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
end
- evaluate <<-ruby do
- def m(a, b: 1) [a, b] end
- ruby
+ ruby_version_is ""..."3.0" do
+ evaluate <<-ruby do
+ def m(a, b: 1) [a, b] end
+ ruby
- m(2).should == [2, 1]
- m(1, b: 2).should == [1, 2]
- m("a" => 1, b: 2).should == [{"a" => 1, b: 2}, 1]
- end
+ m(2).should == [2, 1]
+ m(1, b: 2).should == [1, 2]
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == [{"a" => 1, b: 2}, 1]
+ end
+ end
- evaluate <<-ruby do
- def m(a, **) a end
- ruby
+ 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}
+ m(1).should == 1
+ m(1, a: 2, b: 3).should == 1
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == {"a" => 1, b: 2}
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a, **k) [a, k] end
+ ruby
+
+ m(1).should == [1, {}]
+ m(1, a: 2, b: 3).should == [1, {a: 2, b: 3}]
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == [{"a" => 1, b: 2}, {}]
+ end
+ end
end
- evaluate <<-ruby do
- def m(a, **k) [a, k] end
- ruby
+ ruby_version_is "3.0" do
+ evaluate <<-ruby do
+ def m(a, b: 1) [a, b] end
+ ruby
- m(1).should == [1, {}]
- m(1, a: 2, b: 3).should == [1, {a: 2, b: 3}]
- m("a" => 1, b: 2).should == [{"a" => 1, b: 2}, {}]
+ m(2).should == [2, 1]
+ m(1, b: 2).should == [1, 2]
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(a, **) a end
+ ruby
+
+ m(1).should == 1
+ m(1, a: 2, b: 3).should == 1
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(a, **k) [a, k] end
+ ruby
+
+ m(1).should == [1, {}]
+ m(1, a: 2, b: 3).should == [1, {a: 2, b: 3}]
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
end
evaluate <<-ruby do
@@ -794,8 +885,8 @@ describe "A method" do
def m(a=1, (*b), (*c)) [a, b, c] end
ruby
- lambda { m() }.should raise_error(ArgumentError)
- lambda { m(2) }.should raise_error(ArgumentError)
+ -> { m() }.should raise_error(ArgumentError)
+ -> { m(2) }.should raise_error(ArgumentError)
m(2, 3).should == [1, [2], [3]]
m(2, [3, 4], [5, 6]).should == [2, [3, 4], [5, 6]]
end
@@ -830,23 +921,50 @@ describe "A method" do
result.should == [[1, 2, 3], 4, [5, 6], 7, [], 8]
end
- evaluate <<-ruby do
- def m(a=1, b:) [a, b] end
- ruby
+ ruby_version_is ""..."3.0" do
+ evaluate <<-ruby do
+ def m(a=1, b:) [a, b] end
+ ruby
- m(b: 2).should == [1, 2]
- m(2, b: 1).should == [2, 1]
- m("a" => 1, b: 2).should == [{"a" => 1}, 2]
+ m(b: 2).should == [1, 2]
+ m(2, b: 1).should == [2, 1]
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == [{"a" => 1}, 2]
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, b: 2) [a, b] end
+ ruby
+
+ m().should == [1, 2]
+ m(2).should == [2, 2]
+ m(b: 3).should == [1, 3]
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == [{"a" => 1}, 2]
+ end
+ end
end
- evaluate <<-ruby do
- def m(a=1, b: 2) [a, b] end
- ruby
+ ruby_version_is "3.0" do
+ evaluate <<-ruby do
+ def m(a=1, b:) [a, b] end
+ ruby
- m().should == [1, 2]
- m(2).should == [2, 2]
- m(b: 3).should == [1, 3]
- m("a" => 1, b: 2).should == [{"a" => 1}, 2]
+ m(b: 2).should == [1, 2]
+ m(2, b: 1).should == [2, 1]
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, b: 2) [a, b] end
+ ruby
+
+ m().should == [1, 2]
+ m(2).should == [2, 2]
+ m(b: 3).should == [1, 3]
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
end
evaluate <<-ruby do
@@ -855,7 +973,7 @@ describe "A method" do
m().should == 1
m(2, a: 1, b: 0).should == 2
- m("a" => 1, a: 2).should == {"a" => 1}
+ m("a" => 1, a: 2).should == 1
end
evaluate <<-ruby do
@@ -895,149 +1013,290 @@ describe "A method" do
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
+ ruby_version_is ""...'3.0' do
+ evaluate <<-ruby do
+ def m(*, a:) a 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(a: 1).should == 1
+ m(1, 2, a: 3).should == 3
+ suppress_keyword_warning do
+ m("a" => 1, a: 2).should == 2
+ end
+ end
- m().should be_nil
- m(a: 1, b: 2).should be_nil
- m(1, 2, 3, a: 4, b: 5).should be_nil
+ evaluate <<-ruby do
+ def m(*a, b:) [a, b] end
+ ruby
- h = mock("keyword splat")
- h.should_receive(:to_hash).and_return({a: 1})
- m(h).should be_nil
+ m(b: 1).should == [[], 1]
+ m(1, 2, b: 3).should == [[1, 2], 3]
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == [[{"a" => 1}], 2]
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(*, a: 1) a end
+ ruby
+
+ m().should == 1
+ m(1, 2).should == 1
+ m(a: 2).should == 2
+ m(1, a: 2).should == 2
+ suppress_keyword_warning do
+ m("a" => 1, a: 2).should == 2
+ end
+ end
- 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, b: 1) [a, b] end
+ ruby
- evaluate <<-ruby do
- def m(*a, **) a end
- ruby
+ m().should == [[], 1]
+ m(1, 2, 3, b: 4).should == [[1, 2, 3], 4]
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == [[{"a" => 1}], 2]
+ end
- 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]
+ a = mock("splat")
+ a.should_not_receive(:to_ary)
+ m(*a).should == [[a], 1]
+ end
+
+ evaluate <<-ruby do
+ def m(*a, **) a end
+ ruby
+
+ m().should == []
+ m(1, 2, 3, a: 4, b: 5).should == [1, 2, 3]
+ m("a" => 1, a: 1).should == []
+ m(1, **{a: 2}).should == [1]
+
+ h = mock("keyword splat")
+ h.should_receive(:to_hash)
+ -> { m(**h) }.should raise_error(TypeError)
+ end
+
+ evaluate <<-ruby do
+ def m(*, **k) k end
+ ruby
+
+ m().should == {}
+ m(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
+ m("a" => 1, a: 1).should == {"a" => 1, a: 1}
+
+ h = mock("keyword splat")
+ h.should_receive(:to_hash).and_return({a: 1})
+ suppress_warning do
+ m(h).should == {a: 1}
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a = nil, **k) [a, k] end
+ ruby
+
+ m().should == [nil, {}]
+ m("a" => 1).should == [nil, {"a" => 1}]
+ m(a: 1).should == [nil, {a: 1}]
+ m("a" => 1, a: 1).should == [nil, {"a" => 1, a: 1}]
+ m({ "a" => 1 }, a: 1).should == [{"a" => 1}, {a: 1}]
+ suppress_warning do
+ m({a: 1}, {}).should == [{a: 1}, {}]
+
+ h = {"a" => 1, b: 2}
+ m(h).should == [{"a" => 1}, {b: 2}]
+ h.should == {"a" => 1, b: 2}
+
+ h = {"a" => 1}
+ m(h).first.should == h
+
+ h = {}
+ r = m(h)
+ r.first.should be_nil
+ r.last.should == {}
+
+ hh = {}
+ h = mock("keyword splat empty hash")
+ h.should_receive(:to_hash).and_return(hh)
+ r = m(h)
+ r.first.should be_nil
+ r.last.should == {}
+
+ h = mock("keyword splat")
+ h.should_receive(:to_hash).and_return({"a" => 1, a: 2})
+ m(h).should == [{"a" => 1}, {a: 2}]
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(*a, **k) [a, k] end
+ ruby
+
+ m().should == [[], {}]
+ m(1).should == [[1], {}]
+ m(a: 1, b: 2).should == [[], {a: 1, b: 2}]
+ m(1, 2, 3, a: 2).should == [[1, 2, 3], {a: 2}]
+
+ m("a" => 1).should == [[], {"a" => 1}]
+ m(a: 1).should == [[], {a: 1}]
+ m("a" => 1, a: 1).should == [[], {"a" => 1, a: 1}]
+ m({ "a" => 1 }, a: 1).should == [[{"a" => 1}], {a: 1}]
+ suppress_warning do
+ m({a: 1}, {}).should == [[{a: 1}], {}]
+ end
+ m({a: 1}, {"a" => 1}).should == [[{a: 1}, {"a" => 1}], {}]
- h = mock("keyword splat")
- h.should_receive(:to_hash)
- lambda { m(**h) }.should raise_error(TypeError)
- end
+ bo = BasicObject.new
+ def bo.to_a; [1, 2, 3]; end
+ def bo.to_hash; {:b => 2, :c => 3}; end
- evaluate <<-ruby do
- def m(*, **k) k end
- ruby
+ m(*bo, **bo).should == [[1, 2, 3], {:b => 2, :c => 3}]
+ end
- m().should == {}
- m(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
- m("a" => 1, a: 1).should == {a: 1}
+ evaluate <<-ruby do
+ def m(*, a:) a end
+ ruby
- h = mock("keyword splat")
- h.should_receive(:to_hash).and_return({a: 1})
- m(h).should == {a: 1}
- end
+ m(a: 1).should == 1
+ m(1, 2, a: 3).should == 3
+ suppress_keyword_warning do
+ m("a" => 1, a: 2).should == 2
+ end
+ end
- evaluate <<-ruby do
- def m(a = nil, **k) [a, k] end
- ruby
+ evaluate <<-ruby do
+ def m(*a, b:) [a, b] 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}, {}]
+ m(b: 1).should == [[], 1]
+ m(1, 2, b: 3).should == [[1, 2], 3]
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == [[{"a" => 1}], 2]
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(*, a: 1) a end
+ ruby
+
+ m().should == 1
+ m(1, 2).should == 1
+ m(a: 2).should == 2
+ m(1, a: 2).should == 2
+ suppress_keyword_warning do
+ m("a" => 1, a: 2).should == 2
+ end
+ end
- h = {"a" => 1, b: 2}
- m(h).should == [{"a" => 1}, {b: 2}]
- h.should == {"a" => 1, b: 2}
+ evaluate <<-ruby do
+ def m(*a, b: 1) [a, b] end
+ ruby
- h = {"a" => 1}
- m(h).first.should == h
+ m().should == [[], 1]
+ m(1, 2, 3, b: 4).should == [[1, 2, 3], 4]
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == [[{"a" => 1}], 2]
+ end
- h = {}
- r = m(h)
- r.first.should be_nil
- r.last.should == {}
+ a = mock("splat")
+ a.should_not_receive(:to_ary)
+ m(*a).should == [[a], 1]
+ end
+
+ evaluate <<-ruby do
+ def m(*a, **) a end
+ ruby
+
+ m().should == []
+ m(1, 2, 3, a: 4, b: 5).should == [1, 2, 3]
+ m("a" => 1, a: 1).should == []
+ m(1, **{a: 2}).should == [1]
+
+ h = mock("keyword splat")
+ h.should_receive(:to_hash)
+ -> { m(**h) }.should raise_error(TypeError)
+ end
+
+ evaluate <<-ruby do
+ def m(*, **k) k end
+ ruby
+
+ m().should == {}
+ m(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
+ m("a" => 1, a: 1).should == {"a" => 1, a: 1}
+
+ h = mock("keyword splat")
+ h.should_receive(:to_hash).and_return({a: 1})
+ suppress_keyword_warning do
+ m(h).should == {a: 1}
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a = nil, **k) [a, k] end
+ ruby
+
+ m().should == [nil, {}]
+ m("a" => 1).should == [nil, {"a" => 1}]
+ m(a: 1).should == [nil, {a: 1}]
+ m("a" => 1, a: 1).should == [nil, {"a" => 1, a: 1}]
+ m({ "a" => 1 }, a: 1).should == [{"a" => 1}, {a: 1}]
+ suppress_keyword_warning do
+ m({a: 1}, {}).should == [{a: 1}, {}]
+ end
- 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 = {"a" => 1, b: 2}
+ suppress_keyword_warning do
+ m(h).should == [{"a" => 1}, {b: 2}]
+ end
+ h.should == {"a" => 1, b: 2}
- h = mock("keyword splat")
- h.should_receive(:to_hash).and_return({"a" => 1, a: 2})
- m(h).should == [{"a" => 1}, {a: 2}]
- end
+ h = {"a" => 1}
+ m(h).first.should == h
- evaluate <<-ruby do
- def m(*a, **k) [a, k] end
- ruby
+ h = {}
+ suppress_keyword_warning do
+ m(h).should == [nil, {}]
+ end
- 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}]
+ hh = {}
+ h = mock("keyword splat empty hash")
+ h.should_receive(:to_hash).and_return({a: 1})
+ suppress_keyword_warning do
+ m(h).should == [nil, {a: 1}]
+ end
- 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}], {}]
+ h = mock("keyword splat")
+ h.should_receive(:to_hash).and_return({"a" => 1})
+ m(h).should == [h, {}]
+ end
+
+ evaluate <<-ruby do
+ def m(*a, **k) [a, k] end
+ ruby
+
+ m().should == [[], {}]
+ m(1).should == [[1], {}]
+ m(a: 1, b: 2).should == [[], {a: 1, b: 2}]
+ m(1, 2, 3, a: 2).should == [[1, 2, 3], {a: 2}]
+
+ m("a" => 1).should == [[], {"a" => 1}]
+ m(a: 1).should == [[], {a: 1}]
+ m("a" => 1, a: 1).should == [[], {"a" => 1, a: 1}]
+ m({ "a" => 1 }, a: 1).should == [[{"a" => 1}], {a: 1}]
+ suppress_keyword_warning do
+ m({a: 1}, {}).should == [[{a: 1}], {}]
+ end
+ m({a: 1}, {"a" => 1}).should == [[{a: 1}, {"a" => 1}], {}]
- bo = BasicObject.new
- def bo.to_a; [1, 2, 3]; end
- def bo.to_hash; {:b => 2, :c => 3}; end
+ 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}]
+ m(*bo, **bo).should == [[1, 2, 3], {:b => 2, :c => 3}]
+ end
end
evaluate <<-ruby do
@@ -1063,7 +1322,9 @@ describe "A method" do
ruby
m(a: 1, b: 2).should == [1, 2]
- lambda { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ suppress_keyword_warning do
+ -> { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ end
end
evaluate <<-ruby do
@@ -1072,7 +1333,9 @@ describe "A method" do
m(a: 1).should == [1, 1]
m(a: 1, b: 2).should == [1, 2]
- lambda { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ suppress_keyword_warning do
+ -> { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ end
end
evaluate <<-ruby do
@@ -1081,7 +1344,7 @@ describe "A method" do
m(a: 1).should == 1
m(a: 1, b: 2).should == 1
- lambda { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ m("a" => 1, a: 1, b: 2).should == 1
end
evaluate <<-ruby do
@@ -1090,7 +1353,7 @@ describe "A method" do
m(a: 1).should == [1, {}]
m(a: 1, b: 2, c: 3).should == [1, {b: 2, c: 3}]
- lambda { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ m("a" => 1, a: 1, b: 2).should == [1, {"a" => 1, b: 2}]
end
evaluate <<-ruby do
@@ -1188,21 +1451,133 @@ describe "A method" do
end
evaluate <<-ruby do
- def m(a, b = nil, c = nil, d, e: nil, **f)
- [a, b, c, d, e, f]
+ def m(a, **nil); a end;
+ ruby
+
+ m({a: 1}).should == {a: 1}
+ m({"a" => 1}).should == {"a" => 1}
+
+ -> { m(a: 1) }.should raise_error(ArgumentError)
+ -> { m(**{a: 1}) }.should raise_error(ArgumentError)
+ -> { m("a" => 1) }.should raise_error(ArgumentError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ evaluate <<-ruby do
+ def m(a, b = nil, c = nil, d, e: nil, **f)
+ [a, b, c, d, e, f]
+ end
+ ruby
+
+ result = m(1, 2)
+ result.should == [1, nil, nil, 2, nil, {}]
+
+ suppress_warning do
+ result = m(1, 2, {foo: :bar})
+ result.should == [1, nil, nil, 2, nil, {foo: :bar}]
end
- ruby
- result = m(1, 2)
- result.should == [1, nil, nil, 2, nil, {}]
+ result = m(1, {foo: :bar})
+ result.should == [1, nil, nil, {foo: :bar}, nil, {}]
+ end
+ end
- result = m(1, 2, {foo: :bar})
- result.should == [1, nil, nil, 2, nil, {foo: :bar}]
+ ruby_version_is '3.0' do
+ evaluate <<-ruby do
+ def m(a, b = nil, c = nil, d, e: nil, **f)
+ [a, b, c, d, e, f]
+ end
+ ruby
- result = m(1, {foo: :bar})
- result.should == [1, nil, nil, {foo: :bar}, nil, {}]
+ result = m(1, 2)
+ result.should == [1, nil, nil, 2, nil, {}]
+
+ result = m(1, 2, {foo: :bar})
+ result.should == [1, 2, nil, {foo: :bar}, nil, {}]
+
+ result = m(1, {foo: :bar})
+ result.should == [1, nil, nil, {foo: :bar}, nil, {}]
+ end
+ end
+ end
+
+ context 'when passing an empty keyword splat to a method that does not accept keywords' do
+ evaluate <<-ruby do
+ def m(*a); a; end
+ ruby
+
+ h = {}
+ m(**h).should == []
end
end
+
+ ruby_version_is ''...'3.0' do
+ context 'when passing an empty keyword splat to a method that does not accept keywords' do
+ evaluate <<-ruby do
+ def m(a); a; end
+ ruby
+ h = {}
+
+ -> do
+ m(**h).should == {}
+ end.should complain(/warning: Passing the keyword argument as the last hash parameter is deprecated/)
+ end
+ end
+ end
+
+ ruby_version_is ''...'3.0' do
+ context "assigns keyword arguments from a passed Hash without modifying it" do
+ evaluate <<-ruby do
+ def m(a: nil); a; end
+ ruby
+
+ options = {a: 1}.freeze
+ -> do
+ suppress_warning do
+ m(options).should == 1
+ end
+ end.should_not raise_error
+ options.should == {a: 1}
+ end
+ end
+ end
+
+ ruby_version_is '3.0' do
+ context 'when passing an empty keyword splat to a method that does not accept keywords' do
+ evaluate <<-ruby do
+ def m(a); a; end
+ ruby
+ h = {}
+
+ -> do
+ m(**h).should == {}
+ end.should raise_error(ArgumentError)
+ end
+ end
+
+ context "raises ArgumentError if passing hash as keyword arguments" do
+ evaluate <<-ruby do
+ def m(a: nil); a; end
+ ruby
+
+ options = {a: 1}.freeze
+ -> do
+ m(options)
+ end.should raise_error(ArgumentError)
+ end
+ end
+ end
+
+ it "assigns the last Hash to the last optional argument if the Hash contains non-Symbol keys and is not passed as keywords" do
+ def m(a = nil, b = {}, v: false)
+ [a, b, v]
+ end
+
+ h = { "key" => "value" }
+ m(:a, h).should == [:a, h, false]
+ m(:a, h, v: true).should == [:a, h, true]
+ m(v: true).should == [nil, {}, true]
+ end
end
describe "A method call with a space between method name and parentheses" do
@@ -1232,11 +1607,11 @@ describe "A method call with a space between method name and parentheses" do
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
@@ -1294,3 +1669,188 @@ describe "An array-dereference method ([])" do
end
end
end
+
+ruby_version_is "3.0" do
+ describe "An endless method definition" do
+ context "without arguments" do
+ evaluate <<-ruby do
+ def m() = 42
+ ruby
+
+ m.should == 42
+ end
+
+ context "without parenthesis" do
+ evaluate <<-ruby do
+ def m = 42
+ ruby
+
+ m.should == 42
+ end
+ end
+ end
+
+ context "with arguments" do
+ evaluate <<-ruby do
+ def m(a, b) = a + b
+ ruby
+
+ m(1, 4).should == 5
+ end
+ end
+
+ context "with multiline body" do
+ evaluate <<-ruby do
+ def m(n) =
+ if n > 2
+ m(n - 2) + m(n - 1)
+ else
+ 1
+ end
+ ruby
+
+ m(6).should == 8
+ end
+ end
+
+ context "with args forwarding" do
+ evaluate <<-ruby do
+ def mm(word, num:)
+ word * num
+ end
+
+ def m(...) = mm(...) + mm(...)
+ ruby
+
+ m("meow", num: 2).should == "meow" * 4
+ end
+ end
+
+ ruby_version_is ""..."3.0" do
+ context "inside 'endless' method definitions" do
+ it "does not allow method calls without parenthesis" do
+ -> {
+ eval("def greet(person) = 'Hi, '.concat person")
+ }.should raise_error(SyntaxError)
+ end
+ end
+ end
+ end
+
+ describe "Keyword arguments are now separated from positional arguments" do
+ context "when the method has only positional parameters" do
+ it "treats incoming keyword arguments as positional for compatibility" do
+ def foo(a, b, c, hsh)
+ hsh[:key]
+ end
+
+ foo(1, 2, 3, key: 42).should == 42
+ end
+ end
+
+ context "when the method takes a ** parameter" do
+ it "captures the passed literal keyword arguments" do
+ def foo(a, b, c, **hsh)
+ hsh[:key]
+ end
+
+ foo(1, 2, 3, key: 42).should == 42
+ end
+
+ it "captures the passed ** keyword arguments" do
+ def foo(a, b, c, **hsh)
+ hsh[:key]
+ end
+
+ h = { key: 42 }
+ foo(1, 2, 3, **h).should == 42
+ end
+
+ it "does not convert a positional Hash to keyword arguments" do
+ def foo(a, b, c, **hsh)
+ hsh[:key]
+ end
+
+ -> {
+ foo(1, 2, 3, { key: 42 })
+ }.should raise_error(ArgumentError, 'wrong number of arguments (given 4, expected 3)')
+ end
+ end
+
+ context "when the method takes a key: parameter" do
+ context "when it's called with a positional Hash and no **" do
+ it "raises ArgumentError" do
+ def foo(a, b, c, key: 1)
+ key
+ end
+
+ -> {
+ foo(1, 2, 3, { key: 42 })
+ }.should raise_error(ArgumentError, 'wrong number of arguments (given 4, expected 3)')
+ end
+ end
+
+ context "when it's called with **" do
+ it "captures the passed keyword arguments" do
+ def foo(a, b, c, key: 1)
+ key
+ end
+
+ h = { key: 42 }
+ foo(1, 2, 3, **h).should == 42
+ end
+ end
+ end
+ end
+end
+
+ruby_version_is "3.1" do
+ describe "kwarg with omitted value in a method call" do
+ context "accepts short notation 'kwarg' in method call" do
+ evaluate <<-ruby do
+ def call(*args, **kwargs) = [args, kwargs]
+ ruby
+
+ a, b, c = 1, 2, 3
+ arr, h = eval('call a:')
+ h.should == {a: 1}
+ arr.should == []
+
+ arr, h = eval('call(a:, b:, c:)')
+ h.should == {a: 1, b: 2, c: 3}
+ arr.should == []
+
+ arr, h = eval('call(a:, b: 10, c:)')
+ h.should == {a: 1, b: 10, c: 3}
+ arr.should == []
+ end
+ end
+
+ context "with methods and local variables" do
+ evaluate <<-ruby do
+ def call(*args, **kwargs) = [args, kwargs]
+
+ def bar
+ "baz"
+ end
+
+ def foo(val)
+ call bar:, val:
+ end
+ ruby
+
+ foo(1).should == [[], {bar: "baz", val: 1}]
+ end
+ end
+ end
+
+ describe "Inside 'endless' method definitions" do
+ it "allows method calls without parenthesis" do
+ eval <<-ruby
+ def greet(person) = "Hi, ".concat person
+ ruby
+
+ greet("Homer").should == "Hi, Homer"
+ end
+ end
+end
diff --git a/spec/ruby/language/module_spec.rb b/spec/ruby/language/module_spec.rb
index d5ca71b3b4..e361bf58f5 100644
--- a/spec/ruby/language/module_spec.rb
+++ b/spec/ruby/language/module_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/module', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/module'
describe "The module keyword" do
it "creates a new module without semicolon" do
@@ -28,35 +28,44 @@ describe "The module keyword" do
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
+ ruby_version_is '3.2' do
+ it "does not reopen a module included in Object" do
+ module IncludedModuleSpecs; Reopened = true; end
+ ModuleSpecs::IncludedInObject::IncludedModuleSpecs.should_not == Object::IncludedModuleSpecs
+ end
+ end
+
+ ruby_version_is ''...'3.2' do
+ it "reopens a module included in Object" do
+ module IncludedModuleSpecs; Reopened = true; end
+ ModuleSpecs::IncludedInObject::IncludedModuleSpecs::Reopened.should be_true
+ end
end
it "raises a TypeError if the constant is a Class" do
- lambda do
+ -> do
module ModuleSpecs::Modules::Klass; end
end.should raise_error(TypeError)
end
it "raises a TypeError if the constant is a String" do
- lambda { module ModuleSpecs::Modules::A; end }.should raise_error(TypeError)
+ -> { module ModuleSpecs::Modules::A; end }.should raise_error(TypeError)
end
- it "raises a TypeError if the constant is a Fixnum" do
- lambda { module ModuleSpecs::Modules::B; end }.should raise_error(TypeError)
+ it "raises a TypeError if the constant is an Integer" do
+ -> { module ModuleSpecs::Modules::B; end }.should raise_error(TypeError)
end
it "raises a TypeError if the constant is nil" do
- lambda { module ModuleSpecs::Modules::C; end }.should raise_error(TypeError)
+ -> { module ModuleSpecs::Modules::C; end }.should raise_error(TypeError)
end
it "raises a TypeError if the constant is true" do
- lambda { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError)
+ -> { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError)
end
it "raises a TypeError if the constant is false" do
- lambda { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError)
+ -> { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError)
end
end
@@ -69,10 +78,20 @@ describe "Assigning an anonymous module to a constant" do
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
+ ruby_version_is ""..."3.0" do
+ it "does not set the name of a module scoped by an anonymous module" do
+ a, b = Module.new, Module.new
+ a::B = b
+ b.name.should be_nil
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "sets the name of a module scoped by an anonymous module" do
+ a, b = Module.new, Module.new
+ a::B = b
+ b.name.should.end_with? '::B'
+ end
end
it "sets the name of contained modules when assigning a toplevel anonymous module" do
diff --git a/spec/ruby/language/next_spec.rb b/spec/ruby/language/next_spec.rb
index 67da5224dc..6fbfc4a54d 100644
--- a/spec/ruby/language/next_spec.rb
+++ b/spec/ruby/language/next_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/next', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/next'
describe "The next statement from within the block" do
before :each do
@@ -8,7 +8,7 @@ describe "The next statement from within the block" do
it "ends block execution" do
a = []
- lambda {
+ -> {
a << 1
next
a << 2
@@ -17,15 +17,15 @@ describe "The next statement from within the block" do
end
it "causes block to return nil if invoked without arguments" do
- lambda { 123; next; 456 }.call.should == nil
+ -> { 123; next; 456 }.call.should == nil
end
it "causes block to return nil if invoked with an empty expression" do
- lambda { next (); 456 }.call.should be_nil
+ -> { next (); 456 }.call.should be_nil
end
it "returns the argument passed" do
- lambda { 123; next 234; 345 }.call.should == 234
+ -> { 123; next 234; 345 }.call.should == 234
end
it "returns to the invoking method" do
@@ -102,14 +102,14 @@ describe "The next statement from within the block" do
it "passes the value returned by a method with omitted parenthesis and passed block" do
obj = NextSpecs::Block.new
- lambda { next obj.method :value do |x| x end }.call.should == :value
+ -> { next obj.method :value do |x| x end }.call.should == :value
end
end
describe "The next statement" do
describe "in a method" do
it "is invalid and raises a SyntaxError" do
- lambda {
+ -> {
eval("def m; next; end")
}.should raise_error(SyntaxError)
end
diff --git a/spec/ruby/language/not_spec.rb b/spec/ruby/language/not_spec.rb
index a0cf6b15a6..052af9b256 100644
--- a/spec/ruby/language/not_spec.rb
+++ b/spec/ruby/language/not_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The not keyword" do
it "negates a `true' value" do
diff --git a/spec/ruby/language/numbered_parameters_spec.rb b/spec/ruby/language/numbered_parameters_spec.rb
new file mode 100644
index 0000000000..424d7a06e3
--- /dev/null
+++ b/spec/ruby/language/numbered_parameters_spec.rb
@@ -0,0 +1,118 @@
+require_relative '../spec_helper'
+
+describe "Numbered parameters" do
+ it "provides default parameters _1, _2, ... in a block" do
+ -> { _1 }.call("a").should == "a"
+ proc { _1 }.call("a").should == "a"
+ lambda { _1 }.call("a").should == "a"
+ ["a"].map { _1 }.should == ["a"]
+ end
+
+ it "assigns nil to not passed parameters" do
+ proc { [_1, _2] }.call("a").should == ["a", nil]
+ proc { [_1, _2] }.call("a", "b").should == ["a", "b"]
+ end
+
+ it "supports variables _1-_9 only for the first 9 passed parameters" do
+ block = proc { [_1, _2, _3, _4, _5, _6, _7, _8, _9] }
+ result = block.call(1, 2, 3, 4, 5, 6, 7, 8, 9)
+ result.should == [1, 2, 3, 4, 5, 6, 7, 8, 9]
+ end
+
+ it "does not support more than 9 parameters" do
+ -> {
+ proc { [_10] }.call(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+ }.should raise_error(NameError, /undefined local variable or method `_10'/)
+ end
+
+ it "can not be used in both outer and nested blocks at the same time" do
+ -> {
+ eval("-> { _1; -> { _2 } }")
+ }.should raise_error(SyntaxError, /numbered parameter is already used in/m)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "can be overwritten with local variable" do
+ suppress_warning do
+ eval <<~CODE
+ _1 = 0
+ proc { _1 }.call("a").should == 0
+ CODE
+ end
+ end
+
+ it "warns when numbered parameter is overwritten with local variable" do
+ -> {
+ eval("_1 = 0")
+ }.should complain(/warning: `_1' is reserved for numbered parameter; consider another name/)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "cannot be overwritten with local variable" do
+ -> {
+ eval <<~CODE
+ _1 = 0
+ proc { _1 }.call("a").should == 0
+ CODE
+ }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/)
+ end
+
+ it "errors when numbered parameter is overwritten with local variable" do
+ -> {
+ eval("_1 = 0")
+ }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/)
+ end
+ end
+
+ it "raises SyntaxError when block parameters are specified explicitly" do
+ -> { eval("-> () { _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ -> { eval("-> (x) { _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+
+ -> { eval("proc { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ -> { eval("proc { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+
+ -> { eval("lambda { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ -> { eval("lambda { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+
+ -> { eval("['a'].map { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ -> { eval("['a'].map { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ end
+
+ describe "assigning to a numbered parameter" do
+ ruby_version_is ''...'3.0' do
+ it "warns" do
+ -> { eval("proc { _1 = 0 }") }.should complain(/warning: `_1' is reserved for numbered parameter; consider another name/)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "raises SyntaxError" do
+ -> { eval("proc { _1 = 0 }") }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/)
+ end
+ end
+ end
+
+ it "affects block arity" do
+ -> { _1 }.arity.should == 1
+ -> { _2 }.arity.should == 2
+ -> { _3 }.arity.should == 3
+ -> { _4 }.arity.should == 4
+ -> { _5 }.arity.should == 5
+ -> { _6 }.arity.should == 6
+ -> { _7 }.arity.should == 7
+ -> { _8 }.arity.should == 8
+ -> { _9 }.arity.should == 9
+
+ -> { _9 }.arity.should == 9
+ proc { _9 }.arity.should == 9
+ lambda { _9 }.arity.should == 9
+ end
+
+ it "does not work in methods" do
+ obj = Object.new
+ def obj.foo; _1 end
+
+ -> { obj.foo("a") }.should raise_error(ArgumentError, /wrong number of arguments/)
+ end
+end
diff --git a/spec/ruby/language/numbers_spec.rb b/spec/ruby/language/numbers_spec.rb
index e8c82f09a7..a8e023efb6 100644
--- a/spec/ruby/language/numbers_spec.rb
+++ b/spec/ruby/language/numbers_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "A number literal" do
@@ -11,7 +11,7 @@ describe "A number literal" do
end
it "cannot have a leading underscore" do
- lambda { eval("_4_2") }.should raise_error(NameError)
+ -> { eval("_4_2") }.should raise_error(NameError)
end
it "can have a decimal point" do
@@ -20,8 +20,8 @@ describe "A number literal" do
it "must have a digit before the decimal point" do
0.75.should == 0.75
- lambda { eval(".75") }.should raise_error(SyntaxError)
- lambda { eval("-.75") }.should raise_error(SyntaxError)
+ -> { eval(".75") }.should raise_error(SyntaxError)
+ -> { eval("-.75") }.should raise_error(SyntaxError)
end
it "can have an exponent" do
@@ -45,7 +45,15 @@ describe "A number literal" do
eval('-3r').should == Rational(-3, 1)
end
- it "can be an bignum literal with trailing 'r' to represent a Rational" do
+ it "can be an float literal with trailing 'r' to represent a Rational in a canonical form" do
+ eval('1.0r').should == Rational(1, 1)
+ end
+
+ it "can be a float literal with trailing 'r' to represent a Rational" do
+ eval('0.0174532925199432957r').should == Rational(174532925199432957, 10000000000000000000)
+ end
+
+ it "can be a bignum literal with trailing 'r' to represent a Rational" do
eval('1111111111111111111111111111111111111111111111r').should == Rational(1111111111111111111111111111111111111111111111, 1)
eval('-1111111111111111111111111111111111111111111111r').should == Rational(-1111111111111111111111111111111111111111111111, 1)
end
diff --git a/spec/ruby/language/optional_assignments_spec.rb b/spec/ruby/language/optional_assignments_spec.rb
index 0ab28985ed..02461655d6 100644
--- a/spec/ruby/language/optional_assignments_spec.rb
+++ b/spec/ruby/language/optional_assignments_spec.rb
@@ -1,4 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
+require_relative '../fixtures/constants'
describe 'Optional variable assignments' do
describe 'using ||=' do
@@ -42,6 +43,18 @@ describe 'Optional variable assignments' do
a.should == 10
end
+
+ it 'returns the new value if set to false' do
+ a = false
+
+ (a ||= 20).should == 20
+ end
+
+ it 'returns the original value if truthy' do
+ a = 10
+
+ (a ||= 20).should == 10
+ end
end
describe 'using a accessor' do
@@ -89,6 +102,49 @@ describe 'Optional variable assignments' do
@a.b.should == 10
end
+
+ it 'returns the new value if set to false' do
+ def @a.b=(x)
+ :v
+ end
+
+ @a.b = false
+ (@a.b ||= 20).should == 20
+ end
+
+ it 'returns the original value if truthy' do
+ def @a.b=(x)
+ @b = x
+ :v
+ end
+
+ @a.b = 10
+ (@a.b ||= 20).should == 10
+ end
+
+ it 'works when writer is private' do
+ klass = Class.new do
+ def t
+ self.b = false
+ (self.b ||= 10).should == 10
+ (self.b ||= 20).should == 10
+ end
+
+ def b
+ @b
+ end
+
+ def b=(x)
+ @b = x
+ :v
+ end
+
+ private :b=
+ end
+
+ klass.new.t
+ end
+
end
end
@@ -181,10 +237,120 @@ describe 'Optional variable assignments' do
@a.b.should == 20
end
end
+
+ describe 'using a #[]' do
+ before do
+ @a = {}
+ klass = Class.new do
+ def [](k)
+ @hash ||= {}
+ @hash[k]
+ end
+
+ def []=(k, v)
+ @hash ||= {}
+ @hash[k] = v
+ 7
+ end
+ end
+ @b = klass.new
+ end
+
+ it 'leaves new variable unassigned' do
+ @a[:k] &&= 10
+
+ @a.key?(:k).should == false
+ end
+
+ it 'leaves false' do
+ @a[:k] = false
+ @a[:k] &&= 10
+
+ @a[:k].should == false
+ end
+
+ it 'leaves nil' do
+ @a[:k] = nil
+ @a[:k] &&= 10
+
+ @a[:k].should == nil
+ end
+
+ it 'does not evaluate the right side when not needed' do
+ @a[:k] = nil
+ @a[:k] &&= raise('should not be executed')
+ @a[:k].should == nil
+ end
+
+ it 'does re-assign a variable with a truthy value' do
+ @a[:k] = 10
+ @a[:k] &&= 20
+
+ @a[:k].should == 20
+ end
+
+ it 'does re-assign a variable with a truthy value when using an inline rescue' do
+ @a[:k] = 10
+ @a[:k] &&= 20 rescue 30
+
+ @a[:k].should == 20
+ end
+
+ it 'returns the assigned value, not the result of the []= method with ||=' do
+ (@b[:k] ||= 12).should == 12
+ end
+
+ it 'correctly handles a splatted argument for the index' do
+ (@b[*[:k]] ||= 12).should == 12
+ end
+
+ it "evaluates the index precisely once" do
+ ary = [:x, :y]
+ @a[:x] = 15
+ @a[ary.pop] ||= 25
+ ary.should == [:x]
+ @a.should == { x: 15, y: 25 }
+ end
+
+ it "evaluates the index arguments in the correct order" do
+ ary = Class.new(Array) do
+ def [](x, y)
+ super(x + 3 * y)
+ end
+
+ def []=(x, y, value)
+ super(x + 3 * y, value)
+ end
+ end.new
+ ary[0, 0] = 1
+ ary[1, 0] = 1
+ ary[2, 0] = nil
+ ary[3, 0] = 1
+ ary[4, 0] = 1
+ ary[5, 0] = 1
+ ary[6, 0] = nil
+
+ foo = [0, 2]
+
+ ary[foo.pop, foo.pop] ||= 2
+
+ ary[2, 0].should == 2
+ ary[6, 0].should == nil
+ end
+
+ it 'returns the assigned value, not the result of the []= method with +=' do
+ @b[:k] = 17
+ (@b[:k] += 12).should == 29
+ end
+ end
end
- describe 'using compunded constants' do
- before do
+ describe 'using compounded constants' do
+ before :each do
+ Object.send(:remove_const, :A) if defined? Object::A
+ end
+
+ after :each do
Object.send(:remove_const, :A) if defined? Object::A
end
@@ -208,7 +374,7 @@ describe 'Optional variable assignments' do
end
it 'with &&= assignments will fail with non-existent constants' do
- lambda { Object::A &&= 10 }.should raise_error(NameError)
+ -> { Object::A &&= 10 }.should raise_error(NameError)
end
it 'with operator assignments' do
@@ -220,7 +386,111 @@ describe 'Optional variable assignments' do
end
it 'with operator assignments will fail with non-existent constants' do
- lambda { Object::A += 10 }.should raise_error(NameError)
+ -> { Object::A += 10 }.should raise_error(NameError)
+ end
+ end
+end
+
+describe 'Optional constant assignment' do
+ describe 'with ||=' do
+ it "assigns a scoped constant if previously undefined" do
+ ConstantSpecs.should_not have_constant(:OpAssignUndefined)
+ module ConstantSpecs
+ OpAssignUndefined ||= 42
+ end
+ ConstantSpecs::OpAssignUndefined.should == 42
+ ConstantSpecs::OpAssignUndefinedOutside ||= 42
+ ConstantSpecs::OpAssignUndefinedOutside.should == 42
+ ConstantSpecs.send(:remove_const, :OpAssignUndefined)
+ ConstantSpecs.send(:remove_const, :OpAssignUndefinedOutside)
+ end
+
+ it "assigns a global constant if previously undefined" do
+ OpAssignGlobalUndefined ||= 42
+ ::OpAssignGlobalUndefinedExplicitScope ||= 42
+ OpAssignGlobalUndefined.should == 42
+ ::OpAssignGlobalUndefinedExplicitScope.should == 42
+ Object.send :remove_const, :OpAssignGlobalUndefined
+ Object.send :remove_const, :OpAssignGlobalUndefinedExplicitScope
+ end
+
+ it 'correctly defines non-existing constants' do
+ ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT1 ||= :assigned
+ ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT1.should == :assigned
+ end
+
+ it 'correctly overwrites nil constants' do
+ suppress_warning do # already initialized constant
+ ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT1 = nil
+ ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT1 ||= :assigned
+ ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT1.should == :assigned
+ end
+ end
+
+ it 'causes side-effects of the module part to be applied only once (for undefined constant)' do
+ x = 0
+ (x += 1; ConstantSpecs::ClassA)::OR_ASSIGNED_CONSTANT2 ||= :assigned
+ x.should == 1
+ ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT2.should == :assigned
+ end
+
+ it 'causes side-effects of the module part to be applied (for nil constant)' do
+ suppress_warning do # already initialized constant
+ ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT2 = nil
+ x = 0
+ (x += 1; ConstantSpecs::ClassA)::NIL_OR_ASSIGNED_CONSTANT2 ||= :assigned
+ x.should == 1
+ ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT2.should == :assigned
+ end
+ end
+
+ it 'does not evaluate the right-hand side if the module part raises an exception (for undefined constant)' do
+ x = 0
+ y = 0
+
+ -> {
+ (x += 1; raise Exception; ConstantSpecs::ClassA)::OR_ASSIGNED_CONSTANT3 ||= (y += 1; :assigned)
+ }.should raise_error(Exception)
+
+ x.should == 1
+ y.should == 0
+ defined?(ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT3).should == nil
+ end
+
+ it 'does not evaluate the right-hand side if the module part raises an exception (for nil constant)' do
+ ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT3 = nil
+ x = 0
+ y = 0
+
+ -> {
+ (x += 1; raise Exception; ConstantSpecs::ClassA)::NIL_OR_ASSIGNED_CONSTANT3 ||= (y += 1; :assigned)
+ }.should raise_error(Exception)
+
+ x.should == 1
+ y.should == 0
+ ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT3.should == nil
+ end
+ end
+
+ describe "with &&=" do
+ it "re-assigns a scoped constant if already true" do
+ module ConstantSpecs
+ OpAssignTrue = true
+ end
+ suppress_warning do
+ ConstantSpecs::OpAssignTrue &&= 1
+ end
+ ConstantSpecs::OpAssignTrue.should == 1
+ ConstantSpecs.send :remove_const, :OpAssignTrue
+ end
+
+ it "leaves scoped constant if not true" do
+ module ConstantSpecs
+ OpAssignFalse = false
+ end
+ ConstantSpecs::OpAssignFalse &&= 1
+ ConstantSpecs::OpAssignFalse.should == false
+ ConstantSpecs.send :remove_const, :OpAssignFalse
end
end
end
diff --git a/spec/ruby/language/or_spec.rb b/spec/ruby/language/or_spec.rb
index 150d693996..fb75e788f1 100644
--- a/spec/ruby/language/or_spec.rb
+++ b/spec/ruby/language/or_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The || operator" do
it "evaluates to true if any of its operands are true" do
@@ -35,15 +35,15 @@ describe "The || operator" do
it "has a higher precedence than 'break' in 'break true || false'" do
# see also 'break true or false' below
- lambda { break false || true }.call.should be_true
+ -> { break false || true }.call.should be_true
end
it "has a higher precedence than 'next' in 'next true || false'" do
- lambda { next false || true }.call.should be_true
+ -> { next false || true }.call.should be_true
end
it "has a higher precedence than 'return' in 'return true || false'" do
- lambda { return false || true }.call.should be_true
+ -> { return false || true }.call.should be_true
end
end
@@ -77,14 +77,14 @@ describe "The or operator" do
it "has a lower precedence than 'break' in 'break true or false'" do
# see also 'break true || false' above
- lambda { eval "break true or false" }.should raise_error(SyntaxError, /void value expression/)
+ -> { eval "break true or false" }.should raise_error(SyntaxError, /void value expression/)
end
it "has a lower precedence than 'next' in 'next true or false'" do
- lambda { eval "next true or false" }.should raise_error(SyntaxError, /void value expression/)
+ -> { eval "next true or false" }.should raise_error(SyntaxError, /void value expression/)
end
it "has a lower precedence than 'return' in 'return true or false'" do
- lambda { eval "return true or false" }.should raise_error(SyntaxError, /void value expression/)
+ -> { eval "return true or false" }.should raise_error(SyntaxError, /void value expression/)
end
end
diff --git a/spec/ruby/language/order_spec.rb b/spec/ruby/language/order_spec.rb
index d02bf04077..d550f6b3f4 100644
--- a/spec/ruby/language/order_spec.rb
+++ b/spec/ruby/language/order_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "A method call" do
before :each do
diff --git a/spec/ruby/language/pattern_matching_spec.rb b/spec/ruby/language/pattern_matching_spec.rb
new file mode 100644
index 0000000000..f3cc86fa0b
--- /dev/null
+++ b/spec/ruby/language/pattern_matching_spec.rb
@@ -0,0 +1,1403 @@
+require_relative '../spec_helper'
+
+describe "Pattern matching" do
+ # TODO: Remove excessive eval calls when Ruby 3 is the minimum version.
+ # It is best to keep the eval's longer if other Ruby impls cannot parse pattern matching yet.
+
+ before :each do
+ ScratchPad.record []
+ end
+
+ ruby_version_is "3.0" do
+ it "can be standalone assoc operator that deconstructs value" do
+ suppress_warning do
+ eval(<<-RUBY).should == [0, 1]
+ [0, 1] => [a, b]
+ [a, b]
+ RUBY
+ end
+ end
+
+ describe "find pattern" do
+ it "captures preceding elements to the pattern" do
+ eval(<<~RUBY).should == [0, 1]
+ case [0, 1, 2, 3]
+ in [*pre, 2, 3]
+ pre
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "captures following elements to the pattern" do
+ eval(<<~RUBY).should == [2, 3]
+ case [0, 1, 2, 3]
+ in [0, 1, *post]
+ post
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "captures both preceding and following elements to the pattern" do
+ eval(<<~RUBY).should == [[0, 1], [3, 4]]
+ case [0, 1, 2, 3, 4]
+ in [*pre, 2, *post]
+ [pre, post]
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "can capture the entirety of the pattern" do
+ eval(<<~RUBY).should == [0, 1, 2, 3, 4]
+ case [0, 1, 2, 3, 4]
+ in [*everything]
+ everything
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "will match an empty Array-like structure" do
+ eval(<<~RUBY).should == []
+ case []
+ in [*everything]
+ everything
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "can be nested" do
+ eval(<<~RUBY).should == [[0, [2, 4, 6]], [[4, 16, 64]], 27]
+ case [0, [2, 4, 6], [3, 9, 27], [4, 16, 64]]
+ in [*pre, [*, 9, a], *post]
+ [pre, post, a]
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "can be nested with an array pattern" do
+ eval(<<~RUBY).should == [[4, 16, 64]]
+ case [0, [2, 4, 6], [3, 9, 27], [4, 16, 64]]
+ in [_, _, [*, 9, *], *post]
+ post
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "can be nested within a hash pattern" do
+ eval(<<~RUBY).should == [27]
+ case {a: [3, 9, 27]}
+ in {a: [*, 9, *post]}
+ post
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "can nest hash and array patterns" do
+ eval(<<~RUBY).should == [42, 2]
+ case [0, {a: 42, b: [0, 1]}, {a: 42, b: [1, 2]}]
+ in [*, {a:, b: [1, c]}, *]
+ [a, c]
+ else
+ false
+ end
+ RUBY
+ end
+ end
+ end
+
+ it "extends case expression with case/in construction" do
+ eval(<<~RUBY).should == :bar
+ case [0, 1]
+ in [0]
+ :foo
+ in [0, 1]
+ :bar
+ end
+ RUBY
+ end
+
+ it "allows using then operator" do
+ eval(<<~RUBY).should == :bar
+ case [0, 1]
+ in [0] then :foo
+ in [0, 1] then :bar
+ end
+ RUBY
+ end
+
+ describe "warning" do
+ before :each do
+ @experimental, Warning[:experimental] = Warning[:experimental], true
+ end
+
+ after :each do
+ Warning[:experimental] = @experimental
+ end
+
+ context 'when regular form' do
+ before :each do
+ @src = 'case [0, 1]; in [a, b]; end'
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "warns about pattern matching is experimental feature" do
+ -> { eval @src }.should complain(/pattern matching is experimental, and the behavior may change in future versions of Ruby!/i)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "does not warn about pattern matching is experimental feature" do
+ -> { eval @src }.should_not complain
+ end
+ end
+ end
+
+ context 'when one-line form' do
+ ruby_version_is '3.0' do
+ before :each do
+ @src = '[0, 1] => [a, b]'
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "warns about pattern matching is experimental feature" do
+ -> { eval @src }.should complain(/pattern matching is experimental, and the behavior may change in future versions of Ruby!/i)
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "does not warn about pattern matching is experimental feature" do
+ -> { eval @src }.should_not complain
+ end
+ end
+ end
+ end
+ end
+
+ it "binds variables" do
+ eval(<<~RUBY).should == 1
+ case [0, 1]
+ in [0, a]
+ a
+ end
+ RUBY
+ end
+
+ it "cannot mix in and when operators" do
+ -> {
+ eval <<~RUBY
+ case []
+ when 1 == 1
+ in []
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /syntax error, unexpected `in'/)
+
+ -> {
+ eval <<~RUBY
+ case []
+ in []
+ when 1 == 1
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /syntax error, unexpected `when'/)
+ end
+
+ it "checks patterns until the first matching" do
+ eval(<<~RUBY).should == :bar
+ case [0, 1]
+ in [0]
+ :foo
+ in [0, 1]
+ :bar
+ in [0, 1]
+ :baz
+ end
+ RUBY
+ end
+
+ it "executes else clause if no pattern matches" do
+ eval(<<~RUBY).should == false
+ case [0, 1]
+ in [0]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "raises NoMatchingPatternError if no pattern matches and no else clause" do
+ -> {
+ eval <<~RUBY
+ case [0, 1]
+ in [0]
+ end
+ RUBY
+ }.should raise_error(NoMatchingPatternError, /\[0, 1\]/)
+ end
+
+ it "does not allow calculation or method calls in a pattern" do
+ -> {
+ eval <<~RUBY
+ case 0
+ in 1 + 1
+ true
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /unexpected/)
+ end
+
+ it "evaluates the case expression once for multiple patterns, caching the result" do
+ eval(<<~RUBY).should == true
+ case (ScratchPad << :foo; 1)
+ in 0
+ false
+ in 1
+ true
+ end
+ RUBY
+
+ ScratchPad.recorded.should == [:foo]
+ end
+
+ describe "guards" do
+ it "supports if guard" do
+ eval(<<~RUBY).should == false
+ case 0
+ in 0 if false
+ true
+ else
+ false
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case 0
+ in 0 if true
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "supports unless guard" do
+ eval(<<~RUBY).should == false
+ case 0
+ in 0 unless true
+ true
+ else
+ false
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case 0
+ in 0 unless false
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "makes bound variables visible in guard" do
+ eval(<<~RUBY).should == true
+ case [0, 1]
+ in [a, 1] if a >= 0
+ true
+ end
+ RUBY
+ end
+
+ it "does not evaluate guard if pattern does not match" do
+ eval <<~RUBY
+ case 0
+ in 1 if (ScratchPad << :foo) || true
+ else
+ end
+ RUBY
+
+ ScratchPad.recorded.should == []
+ end
+
+ it "takes guards into account when there are several matching patterns" do
+ eval(<<~RUBY).should == :bar
+ case 0
+ in 0 if false
+ :foo
+ in 0 if true
+ :bar
+ end
+ RUBY
+ end
+
+ it "executes else clause if no guarded pattern matches" do
+ eval(<<~RUBY).should == false
+ case 0
+ in 0 if false
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "raises NoMatchingPatternError if no guarded pattern matches and no else clause" do
+ -> {
+ eval <<~RUBY
+ case [0, 1]
+ in [0, 1] if false
+ end
+ RUBY
+ }.should raise_error(NoMatchingPatternError, /\[0, 1\]/)
+ end
+ end
+
+ describe "value pattern" do
+ it "matches an object such that pattern === object" do
+ eval(<<~RUBY).should == true
+ case 0
+ in 0
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case 0
+ in (-1..1)
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case 0
+ in Integer
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case "0"
+ in /0/
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case "0"
+ in ->(s) { s == "0" }
+ true
+ end
+ RUBY
+ end
+
+ it "allows string literal with interpolation" do
+ x = "x"
+
+ eval(<<~RUBY).should == true
+ case "x"
+ in "#{x + ""}"
+ true
+ end
+ RUBY
+ end
+ end
+
+ describe "variable pattern" do
+ it "matches a value and binds variable name to this value" do
+ eval(<<~RUBY).should == 0
+ case 0
+ in a
+ a
+ end
+ RUBY
+ end
+
+ it "makes bounded variable visible outside a case statement scope" do
+ eval(<<~RUBY).should == 0
+ case 0
+ in a
+ end
+
+ a
+ RUBY
+ end
+
+ it "create local variables even if a pattern doesn't match" do
+ eval(<<~RUBY).should == [0, nil, nil]
+ case 0
+ in a
+ in b
+ in c
+ end
+
+ [a, b, c]
+ RUBY
+ end
+
+ it "allow using _ name to drop values" do
+ eval(<<~RUBY).should == 0
+ case [0, 1]
+ in [a, _]
+ a
+ end
+ RUBY
+ end
+
+ it "supports using _ in a pattern several times" do
+ eval(<<~RUBY).should == true
+ case [0, 1, 2]
+ in [0, _, _]
+ true
+ end
+ RUBY
+ end
+
+ it "supports using any name with _ at the beginning in a pattern several times" do
+ eval(<<~RUBY).should == true
+ case [0, 1, 2]
+ in [0, _x, _x]
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case {a: 0, b: 1, c: 2}
+ in {a: 0, b: _x, c: _x}
+ true
+ end
+ RUBY
+ end
+
+ it "does not support using variable name (except _) several times" do
+ -> {
+ eval <<~RUBY
+ case [0]
+ in [a, a]
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /duplicated variable name/)
+ end
+
+ it "supports existing variables in a pattern specified with ^ operator" do
+ a = 0
+
+ eval(<<~RUBY).should == true
+ case 0
+ in ^a
+ true
+ end
+ RUBY
+ end
+
+ it "allows applying ^ operator to bound variables" do
+ eval(<<~RUBY).should == 1
+ case [1, 1]
+ in [n, ^n]
+ n
+ end
+ RUBY
+
+ eval(<<~RUBY).should == false
+ case [1, 2]
+ in [n, ^n]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "requires bound variable to be specified in a pattern before ^ operator when it relies on a bound variable" do
+ -> {
+ eval <<~RUBY
+ case [1, 2]
+ in [^n, n]
+ true
+ else
+ false
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /n: no such local variable/)
+ end
+ end
+
+ describe "alternative pattern" do
+ it "matches if any of patterns matches" do
+ eval(<<~RUBY).should == true
+ case 0
+ in 0 | 1 | 2
+ true
+ end
+ RUBY
+ end
+
+ it "does not support variable binding" do
+ -> {
+ eval <<~RUBY
+ case [0, 1]
+ in [0, 0] | [0, a]
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /illegal variable in alternative pattern/)
+ end
+
+ it "support underscore prefixed variables in alternation" do
+ eval(<<~RUBY).should == true
+ case [0, 1]
+ in [1, _]
+ false
+ in [0, 0] | [0, _a]
+ true
+ end
+ RUBY
+ end
+
+ it "can be used as a nested pattern" do
+ eval(<<~RUBY).should == true
+ case [[1], ["2"]]
+ in [[0] | nil, _]
+ false
+ in [[1], [1]]
+ false
+ in [[1], [2 | "2"]]
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case [1, 2]
+ in [0, _] | {a: 0}
+ false
+ in {a: 1, b: 2} | [1, 2]
+ true
+ end
+ RUBY
+ end
+ end
+
+ describe "AS pattern" do
+ it "binds a variable to a value if pattern matches" do
+ eval(<<~RUBY).should == 0
+ case 0
+ in Integer => n
+ n
+ end
+ RUBY
+ end
+
+ it "can be used as a nested pattern" do
+ eval(<<~RUBY).should == [2, 3]
+ case [1, [2, 3]]
+ in [1, Array => ary]
+ ary
+ end
+ RUBY
+ end
+ end
+
+ describe "Array pattern" do
+ it "supports form Constant(pat, pat, ...)" do
+ eval(<<~RUBY).should == true
+ case [0, 1, 2]
+ in Array(0, 1, 2)
+ true
+ end
+ RUBY
+ end
+
+ it "supports form Constant[pat, pat, ...]" do
+ eval(<<~RUBY).should == true
+ case [0, 1, 2]
+ in Array[0, 1, 2]
+ true
+ end
+ RUBY
+ end
+
+ it "supports form [pat, pat, ...]" do
+ eval(<<~RUBY).should == true
+ case [0, 1, 2]
+ in [0, 1, 2]
+ true
+ end
+ RUBY
+ end
+
+ it "supports form pat, pat, ..." do
+ eval(<<~RUBY).should == true
+ case [0, 1, 2]
+ in 0, 1, 2
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == 1
+ case [0, 1, 2]
+ in 0, a, 2
+ a
+ end
+ RUBY
+
+ eval(<<~RUBY).should == [1, 2]
+ case [0, 1, 2]
+ in 0, *rest
+ rest
+ end
+ RUBY
+ end
+
+ it "matches an object with #deconstruct method which returns an array and each element in array matches element in pattern" do
+ obj = Object.new
+ def obj.deconstruct; [0, 1] end
+
+ eval(<<~RUBY).should == true
+ case obj
+ in [Integer, Integer]
+ true
+ end
+ RUBY
+ end
+
+ ruby_version_is "3.0" do
+ it "calls #deconstruct once for multiple patterns, caching the result" do
+ obj = Object.new
+
+ def obj.deconstruct
+ ScratchPad << :deconstruct
+ [0, 1]
+ end
+
+ eval(<<~RUBY).should == true
+ case obj
+ in [1, 2]
+ false
+ in [0, 1]
+ true
+ end
+ RUBY
+
+ ScratchPad.recorded.should == [:deconstruct]
+ end
+ end
+
+ it "calls #deconstruct even on objects that are already an array" do
+ obj = [1, 2]
+ def obj.deconstruct
+ ScratchPad << :deconstruct
+ [3, 4]
+ end
+
+ eval(<<~RUBY).should == true
+ case obj
+ in [3, 4]
+ true
+ else
+ false
+ end
+ RUBY
+
+ ScratchPad.recorded.should == [:deconstruct]
+ end
+
+ it "does not match object if Constant === object returns false" do
+ eval(<<~RUBY).should == false
+ case [0, 1, 2]
+ in String[0, 1, 2]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "does not match object without #deconstruct method" do
+ obj = Object.new
+ obj.should_receive(:respond_to?).with(:deconstruct)
+
+ eval(<<~RUBY).should == false
+ case obj
+ in Object[]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "raises TypeError if #deconstruct method does not return array" do
+ obj = Object.new
+ def obj.deconstruct; "" end
+
+ -> {
+ eval <<~RUBY
+ case obj
+ in Object[]
+ else
+ end
+ RUBY
+ }.should raise_error(TypeError, /deconstruct must return Array/)
+ end
+
+ it "accepts a subclass of Array from #deconstruct" do
+ obj = Object.new
+ def obj.deconstruct
+ subarray = Class.new(Array).new(2)
+ def subarray.[](n)
+ n
+ end
+ subarray
+ end
+
+ eval(<<~RUBY).should == true
+ case obj
+ in [1, 2]
+ false
+ in [0, 1]
+ true
+ end
+ RUBY
+ end
+
+ it "does not match object if elements of array returned by #deconstruct method does not match elements in pattern" do
+ obj = Object.new
+ def obj.deconstruct; [1] end
+
+ eval(<<~RUBY).should == false
+ case obj
+ in Object[0]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "binds variables" do
+ eval(<<~RUBY).should == [0, 1, 2]
+ case [0, 1, 2]
+ in [a, b, c]
+ [a, b, c]
+ end
+ RUBY
+ end
+
+ it "supports splat operator *rest" do
+ eval(<<~RUBY).should == [1, 2]
+ case [0, 1, 2]
+ in [0, *rest]
+ rest
+ end
+ RUBY
+ end
+
+ it "does not match partially by default" do
+ eval(<<~RUBY).should == false
+ case [0, 1, 2, 3]
+ in [1, 2]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "does match partially from the array beginning if list + , syntax used" do
+ eval(<<~RUBY).should == true
+ case [0, 1, 2, 3]
+ in [0, 1,]
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case [0, 1, 2, 3]
+ in 0, 1,;
+ true
+ end
+ RUBY
+ end
+
+ it "matches [] with []" do
+ eval(<<~RUBY).should == true
+ case []
+ in []
+ true
+ end
+ RUBY
+ end
+
+ it "matches anything with *" do
+ eval(<<~RUBY).should == true
+ case [0, 1]
+ in *;
+ true
+ end
+ RUBY
+ end
+
+ it "can be used as a nested pattern" do
+ eval(<<~RUBY).should == true
+ case [[1], ["2"]]
+ in [[0] | nil, _]
+ false
+ in [[1], [1]]
+ false
+ in [[1], [2 | "2"]]
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case [1, 2]
+ in [0, _] | {a: 0}
+ false
+ in {a: 1, b: 2} | [1, 2]
+ true
+ end
+ RUBY
+ end
+ end
+
+ describe "Hash pattern" do
+ it "supports form Constant(id: pat, id: pat, ...)" do
+ eval(<<~RUBY).should == true
+ case {a: 0, b: 1}
+ in Hash(a: 0, b: 1)
+ true
+ end
+ RUBY
+ end
+
+ it "supports form Constant[id: pat, id: pat, ...]" do
+ eval(<<~RUBY).should == true
+ case {a: 0, b: 1}
+ in Hash[a: 0, b: 1]
+ true
+ end
+ RUBY
+ end
+
+ it "supports form {id: pat, id: pat, ...}" do
+ eval(<<~RUBY).should == true
+ case {a: 0, b: 1}
+ in {a: 0, b: 1}
+ true
+ end
+ RUBY
+ end
+
+ it "supports form id: pat, id: pat, ..." do
+ eval(<<~RUBY).should == true
+ case {a: 0, b: 1}
+ in a: 0, b: 1
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == [0, 1]
+ case {a: 0, b: 1}
+ in a: a, b: b
+ [a, b]
+ end
+ RUBY
+
+ eval(<<~RUBY).should == { b: 1, c: 2 }
+ case {a: 0, b: 1, c: 2}
+ in a: 0, **rest
+ rest
+ end
+ RUBY
+ end
+
+ it "supports a: which means a: a" do
+ eval(<<~RUBY).should == [0, 1]
+ case {a: 0, b: 1}
+ in Hash(a:, b:)
+ [a, b]
+ end
+ RUBY
+
+ a = b = nil
+ eval(<<~RUBY).should == [0, 1]
+ case {a: 0, b: 1}
+ in Hash[a:, b:]
+ [a, b]
+ end
+ RUBY
+
+ a = b = nil
+ eval(<<~RUBY).should == [0, 1]
+ case {a: 0, b: 1}
+ in {a:, b:}
+ [a, b]
+ end
+ RUBY
+
+ a = nil
+ eval(<<~RUBY).should == [0, {b: 1, c: 2}]
+ case {a: 0, b: 1, c: 2}
+ in {a:, **rest}
+ [a, rest]
+ end
+ RUBY
+
+ a = b = nil
+ eval(<<~RUBY).should == [0, 1]
+ case {a: 0, b: 1}
+ in a:, b:
+ [a, b]
+ end
+ RUBY
+ end
+
+ it "can mix key (a:) and key-value (a: b) declarations" do
+ eval(<<~RUBY).should == [0, 1]
+ case {a: 0, b: 1}
+ in Hash(a:, b: x)
+ [a, x]
+ end
+ RUBY
+ end
+
+ it "supports 'string': key literal" do
+ eval(<<~RUBY).should == true
+ case {a: 0}
+ in {"a": 0}
+ true
+ end
+ RUBY
+ end
+
+ it "does not support non-symbol keys" do
+ -> {
+ eval <<~RUBY
+ case {a: 1}
+ in {"a" => 1}
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /unexpected/)
+ end
+
+ it "does not support string interpolation in keys" do
+ x = "a"
+
+ -> {
+ eval <<~'RUBY'
+ case {a: 1}
+ in {"#{x}": 1}
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /symbol literal with interpolation is not allowed/)
+ end
+
+ it "raise SyntaxError when keys duplicate in pattern" do
+ -> {
+ eval <<~RUBY
+ case {a: 1}
+ in {a: 1, b: 2, a: 3}
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /duplicated key name/)
+ end
+
+ it "matches an object with #deconstruct_keys method which returns a Hash with equal keys and each value in Hash matches value in pattern" do
+ obj = Object.new
+ def obj.deconstruct_keys(*); {a: 1} end
+
+ eval(<<~RUBY).should == true
+ case obj
+ in {a: 1}
+ true
+ end
+ RUBY
+ end
+
+ it "calls #deconstruct_keys per pattern" do
+ obj = Object.new
+
+ def obj.deconstruct_keys(*)
+ ScratchPad << :deconstruct_keys
+ {a: 1}
+ end
+
+ eval(<<~RUBY).should == true
+ case obj
+ in {b: 1}
+ false
+ in {a: 1}
+ true
+ end
+ RUBY
+
+ ScratchPad.recorded.should == [:deconstruct_keys, :deconstruct_keys]
+ end
+
+ it "does not match object if Constant === object returns false" do
+ eval(<<~RUBY).should == false
+ case {a: 1}
+ in String[a: 1]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "does not match object without #deconstruct_keys method" do
+ obj = Object.new
+ obj.should_receive(:respond_to?).with(:deconstruct_keys)
+
+ eval(<<~RUBY).should == false
+ case obj
+ in Object[a: 1]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "does not match object if #deconstruct_keys method does not return Hash" do
+ obj = Object.new
+ def obj.deconstruct_keys(*); "" end
+
+ -> {
+ eval <<~RUBY
+ case obj
+ in Object[a: 1]
+ end
+ RUBY
+ }.should raise_error(TypeError, /deconstruct_keys must return Hash/)
+ end
+
+ it "does not match object if #deconstruct_keys method returns Hash with non-symbol keys" do
+ obj = Object.new
+ def obj.deconstruct_keys(*); {"a" => 1} end
+
+ eval(<<~RUBY).should == false
+ case obj
+ in Object[a: 1]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "does not match object if elements of Hash returned by #deconstruct_keys method does not match values in pattern" do
+ obj = Object.new
+ def obj.deconstruct_keys(*); {a: 1} end
+
+ eval(<<~RUBY).should == false
+ case obj
+ in Object[a: 2]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "passes keys specified in pattern as arguments to #deconstruct_keys method" do
+ obj = Object.new
+
+ def obj.deconstruct_keys(*args)
+ ScratchPad << args
+ {a: 1, b: 2, c: 3}
+ end
+
+ eval <<~RUBY
+ case obj
+ in Object[a: 1, b: 2, c: 3]
+ end
+ RUBY
+
+ ScratchPad.recorded.sort.should == [[[:a, :b, :c]]]
+ end
+
+ it "passes keys specified in pattern to #deconstruct_keys method if pattern contains double splat operator **" do
+ obj = Object.new
+
+ def obj.deconstruct_keys(*args)
+ ScratchPad << args
+ {a: 1, b: 2, c: 3}
+ end
+
+ eval <<~RUBY
+ case obj
+ in Object[a: 1, b: 2, **]
+ end
+ RUBY
+
+ ScratchPad.recorded.sort.should == [[[:a, :b]]]
+ end
+
+ it "passes nil to #deconstruct_keys method if pattern contains double splat operator **rest" do
+ obj = Object.new
+
+ def obj.deconstruct_keys(*args)
+ ScratchPad << args
+ {a: 1, b: 2}
+ end
+
+ eval <<~RUBY
+ case obj
+ in Object[a: 1, **rest]
+ end
+ RUBY
+
+ ScratchPad.recorded.should == [[nil]]
+ end
+
+ it "binds variables" do
+ eval(<<~RUBY).should == [0, 1, 2]
+ case {a: 0, b: 1, c: 2}
+ in {a: x, b: y, c: z}
+ [x, y, z]
+ end
+ RUBY
+ end
+
+ it "supports double splat operator **rest" do
+ eval(<<~RUBY).should == {b: 1, c: 2}
+ case {a: 0, b: 1, c: 2}
+ in {a: 0, **rest}
+ rest
+ end
+ RUBY
+ end
+
+ it "treats **nil like there should not be any other keys in a matched Hash" do
+ eval(<<~RUBY).should == true
+ case {a: 1, b: 2}
+ in {a: 1, b: 2, **nil}
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == false
+ case {a: 1, b: 2}
+ in {a: 1, **nil}
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "can match partially" do
+ eval(<<~RUBY).should == true
+ case {a: 1, b: 2}
+ in {a: 1}
+ true
+ end
+ RUBY
+ end
+
+ it "matches {} with {}" do
+ eval(<<~RUBY).should == true
+ case {}
+ in {}
+ true
+ end
+ RUBY
+ end
+
+ it "matches anything with **" do
+ eval(<<~RUBY).should == true
+ case {a: 1}
+ in **;
+ true
+ end
+ RUBY
+ end
+
+ it "can be used as a nested pattern" do
+ eval(<<~RUBY).should == true
+ case {a: {a: 1, b: 1}, b: {a: 1, b: 2}}
+ in {a: {a: 0}}
+ false
+ in {a: {a: 1}, b: {b: 1}}
+ false
+ in {a: {a: 1}, b: {b: 2}}
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case [{a: 1, b: [1]}, {a: 1, c: ["2"]}]
+ in [{a:, c:},]
+ false
+ in [{a: 1, b:}, {a: 1, c: [Integer]}]
+ false
+ in [_, {a: 1, c: [String]}]
+ true
+ end
+ RUBY
+ end
+ end
+
+ describe "refinements" do
+ it "are used for #deconstruct" do
+ refinery = Module.new do
+ refine Array do
+ def deconstruct
+ [0]
+ end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinery
+
+ result = eval(<<~RUBY)
+ case []
+ in [0]
+ true
+ end
+ RUBY
+ end
+
+ result.should == true
+ end
+
+ it "are used for #deconstruct_keys" do
+ refinery = Module.new do
+ refine Hash do
+ def deconstruct_keys(_)
+ {a: 0}
+ end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinery
+
+ result = eval(<<~RUBY)
+ case {}
+ in a: 0
+ true
+ end
+ RUBY
+ end
+
+ result.should == true
+ end
+
+ it "are used for #=== in constant pattern" do
+ refinery = Module.new do
+ refine Array.singleton_class do
+ def ===(obj)
+ obj.is_a?(Hash)
+ end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinery
+
+ result = eval(<<~RUBY)
+ case {}
+ in Array
+ true
+ end
+ RUBY
+ end
+
+ result.should == true
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "can omit parentheses in one line pattern matching" do
+ eval(<<~RUBY).should == [1, 2]
+ [1, 2] => a, b
+ [a, b]
+ RUBY
+
+ eval(<<~RUBY).should == 1
+ {a: 1} => a:
+ a
+ RUBY
+ end
+
+ it "supports pinning instance variables" do
+ eval(<<~RUBY).should == true
+ @a = /a/
+ case 'abc'
+ in ^@a
+ true
+ end
+ RUBY
+ end
+
+ it "supports pinning class variables" do
+ result = nil
+ Module.new do
+ result = module_eval(<<~RUBY)
+ @@a = 0..10
+
+ case 2
+ in ^@@a
+ true
+ end
+ RUBY
+ end
+
+ result.should == true
+ end
+
+ it "supports pinning global variables" do
+ eval(<<~RUBY).should == true
+ $a = /a/
+ case 'abc'
+ in ^$a
+ true
+ end
+ RUBY
+ end
+
+ it "supports pinning expressions" do
+ eval(<<~RUBY).should == true
+ case 'abc'
+ in ^(/a/)
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case {name: '2.6', released_at: Time.new(2018, 12, 25)}
+ in {released_at: ^(Time.new(2010)..Time.new(2020))}
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case 0
+ in ^(0+0)
+ true
+ end
+ RUBY
+ end
+ end
+end
diff --git a/spec/ruby/language/precedence_spec.rb b/spec/ruby/language/precedence_spec.rb
index 90734022ff..c5adcca2c0 100644
--- a/spec/ruby/language/precedence_spec.rb
+++ b/spec/ruby/language/precedence_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/precedence', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/precedence'
# Specifying the behavior of operators in combination could
# lead to combinatorial explosion. A better way seems to be
@@ -14,46 +14,44 @@ require File.expand_path('../fixtures/precedence', __FILE__)
# the level below (as well as showing associativity within
# the precedence level).
-=begin
-Excerpted from 'Programming Ruby: The Pragmatic Programmer's Guide'
-Second Edition by Dave Thomas, Chad Fowler, and Andy Hunt, page 324
-
-Table 22.4. Ruby operators (high to low precedence)
-Method Operator Description
------------------------------------------------------------------------
- :: .
- x* [ ] [ ]= Element reference, element set
- x ** Exponentiation
- x ! ~ + - Not, complement, unary plus and minus
- (method names for the last two are +@ and -@)
- x * / % Multiply, divide, and modulo
- x + - Plus and minus
- x >> << Right and left shift
- x & “And” (bitwise for integers)
- x ^ | Exclusive “or” and regular “or” (bitwise for integers)
- x <= < > >= Comparison operators
- x <=> == === != =~ !~ Equality and pattern match operators (!=
- and !~ may not be defined as methods)
- && Logical “and”
- || Logical “or”
- .. ... Range (inclusive and exclusive)
- ? : Ternary if-then-else
- = %= /= -= += |= &= Assignment
- >>= <<= *= &&= ||= **=
- defined? Check if symbol defined
- not Logical negation
- or and Logical composition
- if unless while until Expression modifiers
- begin/end Block expression
------------------------------------------------------------------------
-
-* Operators marked with 'x' in the Method column are implemented as methods
-and can be overridden (except != and !~ as noted). (But see the specs
-below for implementations that define != and !~ as methods.)
-
-** These are not included in the excerpted table but are shown here for
-completeness.
-=end
+# Excerpted from 'Programming Ruby: The Pragmatic Programmer's Guide'
+# Second Edition by Dave Thomas, Chad Fowler, and Andy Hunt, page 324
+#
+# Table 22.4. Ruby operators (high to low precedence)
+# Method Operator Description
+# -----------------------------------------------------------------------
+# :: .
+# x* [ ] [ ]= Element reference, element set
+# x ** Exponentiation
+# x ! ~ + - Not, complement, unary plus and minus
+# (method names for the last two are +@ and -@)
+# x * / % Multiply, divide, and modulo
+# x + - Plus and minus
+# x >> << Right and left shift
+# x & “And” (bitwise for integers)
+# x ^ | Exclusive “or” and regular “or” (bitwise for integers)
+# x <= < > >= Comparison operators
+# x <=> == === != =~ !~ Equality and pattern match operators (!=
+# and !~ may not be defined as methods)
+# && Logical “and”
+# || Logical “or”
+# .. ... Range (inclusive and exclusive)
+# ? : Ternary if-then-else
+# = %= /= -= += |= &= Assignment
+# >>= <<= *= &&= ||= **=
+# defined? Check if symbol defined
+# not Logical negation
+# or and Logical composition
+# if unless while until Expression modifiers
+# begin/end Block expression
+# -----------------------------------------------------------------------
+#
+# * Operators marked with 'x' in the Method column are implemented as methods
+# and can be overridden (except != and !~ as noted). (But see the specs
+# below for implementations that define != and !~ as methods.)
+#
+# ** These are not included in the excerpted table but are shown here for
+# completeness.
# -----------------------------------------------------------------------
# It seems that this table is not correct anymore
@@ -136,7 +134,7 @@ describe "Operators" do
# Guard against the Mathn library
# TODO: Make these specs not rely on specific behaviour / result values
# by using mocks.
- conflicts_with :Prime do
+ guard -> { !defined?(Math.rsqrt) } do
(2*1/2).should_not == 2*(1/2)
end
@@ -253,12 +251,12 @@ describe "Operators" do
end
it "<=> == === != =~ !~ are non-associative" do
- lambda { eval("1 <=> 2 <=> 3") }.should raise_error(SyntaxError)
- lambda { eval("1 == 2 == 3") }.should raise_error(SyntaxError)
- lambda { eval("1 === 2 === 3") }.should raise_error(SyntaxError)
- lambda { eval("1 != 2 != 3") }.should raise_error(SyntaxError)
- lambda { eval("1 =~ 2 =~ 3") }.should raise_error(SyntaxError)
- lambda { eval("1 !~ 2 !~ 3") }.should raise_error(SyntaxError)
+ -> { eval("1 <=> 2 <=> 3") }.should raise_error(SyntaxError)
+ -> { eval("1 == 2 == 3") }.should raise_error(SyntaxError)
+ -> { eval("1 === 2 === 3") }.should raise_error(SyntaxError)
+ -> { eval("1 != 2 != 3") }.should raise_error(SyntaxError)
+ -> { eval("1 =~ 2 =~ 3") }.should raise_error(SyntaxError)
+ -> { eval("1 !~ 2 !~ 3") }.should raise_error(SyntaxError)
end
it "<=> == === != =~ !~ have higher precedence than &&" do
@@ -292,19 +290,18 @@ describe "Operators" do
end
it ".. ... are non-associative" do
- lambda { eval("1..2..3") }.should raise_error(SyntaxError)
- lambda { eval("1...2...3") }.should raise_error(SyntaxError)
+ -> { eval("1..2..3") }.should raise_error(SyntaxError)
+ -> { eval("1...2...3") }.should raise_error(SyntaxError)
end
-# XXX: this is commented now due to a bug in compiler, which cannot
-# distinguish between range and flip-flop operator so far. zenspider is
-# currently working on a new lexer, which will be able to do that.
-# As soon as it's done, these piece should be reenabled.
-#
-# it ".. ... have higher precedence than ? :" do
-# (1..2 ? 3 : 4).should == 3
-# (1...2 ? 3 : 4).should == 3
-# end
+ it ".. ... have higher precedence than ? :" do
+ # Use variables to avoid warnings
+ from = 1
+ to = 2
+ # These are flip-flop, not Range instances
+ (from..to ? 3 : 4).should == 3
+ (from...to ? 3 : 4).should == 3
+ end
it "? : is right-associative" do
(true ? 2 : 3 ? 4 : 5).should == 2
diff --git a/spec/ruby/language/predefined/data_spec.rb b/spec/ruby/language/predefined/data_spec.rb
index f616879527..921d236ee9 100644
--- a/spec/ruby/language/predefined/data_spec.rb
+++ b/spec/ruby/language/predefined/data_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "The DATA constant" do
it "exists when the main script contains __END__" do
@@ -23,6 +23,25 @@ describe "The DATA constant" do
str.chomp.should == "data only"
end
+ it "returns a File object with the right offset" do
+ ruby_exe(fixture(__FILE__, "data_offset.rb")).should == "File\n121\n"
+ end
+
+ it "is set even if there is no data after __END__" do
+ ruby_exe(fixture(__FILE__, "empty_data.rb")).should == "31\n\"\"\n"
+ end
+
+ it "is set even if there is no newline after __END__" do
+ path = tmp("no_newline_data.rb")
+ code = File.binread(fixture(__FILE__, "empty_data.rb"))
+ touch(path, "wb") { |f| f.write code.chomp }
+ begin
+ ruby_exe(path).should == "30\n\"\"\n"
+ ensure
+ rm_r path
+ end
+ end
+
it "rewinds to the head of the main script" do
ruby_exe(fixture(__FILE__, "data5.rb")).chomp.should == "DATA.rewind"
end
diff --git a/spec/ruby/language/predefined/fixtures/data2.rb b/spec/ruby/language/predefined/fixtures/data2.rb
index 0f714b06d4..a764ca56d1 100644
--- a/spec/ruby/language/predefined/fixtures/data2.rb
+++ b/spec/ruby/language/predefined/fixtures/data2.rb
@@ -1,4 +1,3 @@
-
-require File.expand_path("../data4.rb", __FILE__)
+require_relative 'data4'
p Object.const_defined?(:DATA)
diff --git a/spec/ruby/language/predefined/fixtures/data3.rb b/spec/ruby/language/predefined/fixtures/data3.rb
index 6cbf63dae6..e37313c68b 100644
--- a/spec/ruby/language/predefined/fixtures/data3.rb
+++ b/spec/ruby/language/predefined/fixtures/data3.rb
@@ -1,5 +1,4 @@
-
-require File.expand_path("../data4.rb", __FILE__)
+require_relative 'data4'
puts DATA.read
diff --git a/spec/ruby/language/predefined/fixtures/data_offset.rb b/spec/ruby/language/predefined/fixtures/data_offset.rb
new file mode 100644
index 0000000000..9829b3f87f
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data_offset.rb
@@ -0,0 +1,12 @@
+# some comment
+
+foo = <<HEREDOC
+some heredoc to make the
+spec more interesting
+HEREDOC
+
+p DATA.class
+p DATA.pos
+
+__END__
+data offset
diff --git a/spec/ruby/language/predefined/fixtures/empty_data.rb b/spec/ruby/language/predefined/fixtures/empty_data.rb
new file mode 100644
index 0000000000..c6d9bc6f1f
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/empty_data.rb
@@ -0,0 +1,3 @@
+p DATA.pos
+p DATA.read
+__END__
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic.rb
new file mode 100644
index 0000000000..f7809109fa
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic.rb
@@ -0,0 +1,4 @@
+p TOPLEVEL_BINDING.local_variables.sort
+TOPLEVEL_BINDING.local_variable_set(:dynamic_set_main, 2)
+p TOPLEVEL_BINDING.local_variables.sort
+main_script = 3
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic_required.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic_required.rb
new file mode 100644
index 0000000000..7ccf329680
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic_required.rb
@@ -0,0 +1,2 @@
+TOPLEVEL_BINDING.local_variable_set(:dynamic_set_required, 1)
+p TOPLEVEL_BINDING.local_variables
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_id.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_id.rb
new file mode 100644
index 0000000000..3626ea1f10
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_id.rb
@@ -0,0 +1,4 @@
+a = TOPLEVEL_BINDING.object_id
+require_relative 'toplevel_binding_id_required'
+c = eval('TOPLEVEL_BINDING.object_id')
+p [a, $b, c].uniq.size
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_id_required.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_id_required.rb
new file mode 100644
index 0000000000..b31b6e32a0
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_id_required.rb
@@ -0,0 +1 @@
+$b = TOPLEVEL_BINDING.object_id
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_required_before.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_required_before.rb
new file mode 100644
index 0000000000..58924a5800
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_required_before.rb
@@ -0,0 +1,2 @@
+required = true
+p [:required_before, TOPLEVEL_BINDING.local_variables]
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_values.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_values.rb
new file mode 100644
index 0000000000..42bd67f347
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_values.rb
@@ -0,0 +1,9 @@
+p TOPLEVEL_BINDING.local_variable_get(:a)
+p TOPLEVEL_BINDING.local_variable_get(:b)
+a = 1
+p TOPLEVEL_BINDING.local_variable_get(:a)
+p TOPLEVEL_BINDING.local_variable_get(:b)
+b = 2
+a = 3
+p TOPLEVEL_BINDING.local_variable_get(:a)
+p TOPLEVEL_BINDING.local_variable_get(:b)
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_variables.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_variables.rb
new file mode 100644
index 0000000000..151f4340ef
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_variables.rb
@@ -0,0 +1,4 @@
+main_script = 1
+require_relative 'toplevel_binding_variables_required'
+eval('eval_var = 3')
+p TOPLEVEL_BINDING.local_variables
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_variables_required.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_variables_required.rb
new file mode 100644
index 0000000000..614547fe16
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_variables_required.rb
@@ -0,0 +1,2 @@
+required = 2
+p [:required_after, TOPLEVEL_BINDING.local_variables]
diff --git a/spec/ruby/language/predefined/toplevel_binding_spec.rb b/spec/ruby/language/predefined/toplevel_binding_spec.rb
new file mode 100644
index 0000000000..69ac28618c
--- /dev/null
+++ b/spec/ruby/language/predefined/toplevel_binding_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+
+describe "The TOPLEVEL_BINDING constant" do
+ it "only includes local variables defined in the main script, not in required files or eval" do
+ binding_toplevel_variables = ruby_exe(fixture(__FILE__, "toplevel_binding_variables.rb"))
+ binding_toplevel_variables.should == "[:required_after, [:main_script]]\n[:main_script]\n"
+ end
+
+ it "has no local variables in files required before the main script" do
+ required = fixture(__FILE__, 'toplevel_binding_required_before.rb')
+ out = ruby_exe("a=1; p TOPLEVEL_BINDING.local_variables.sort; b=2", options: "-r#{required}")
+ out.should == "[:required_before, []]\n[:a, :b]\n"
+ end
+
+ it "merges local variables of the main script with dynamically-defined Binding variables" do
+ required = fixture(__FILE__, 'toplevel_binding_dynamic_required.rb')
+ out = ruby_exe(fixture(__FILE__, 'toplevel_binding_dynamic.rb'), options: "-r#{required}")
+ out.should == <<EOS
+[:dynamic_set_required]
+[:dynamic_set_required, :main_script]
+[:dynamic_set_main, :dynamic_set_required, :main_script]
+EOS
+ end
+
+ it "gets updated variables values as they are defined and set" do
+ out = ruby_exe(fixture(__FILE__, "toplevel_binding_values.rb"))
+ out.should == "nil\nnil\n1\nnil\n3\n2\n"
+ end
+
+ it "is always the same object for all top levels" do
+ binding_toplevel_id = ruby_exe(fixture(__FILE__, "toplevel_binding_id.rb"))
+ binding_toplevel_id.should == "1\n"
+ end
+end
diff --git a/spec/ruby/language/predefined_spec.rb b/spec/ruby/language/predefined_spec.rb
index f827fb2eb5..d1cda25918 100644
--- a/spec/ruby/language/predefined_spec.rb
+++ b/spec/ruby/language/predefined_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
require 'stringio'
# The following tables are excerpted from Programming Ruby: The Pragmatic Programmer's Guide'
@@ -7,56 +7,52 @@ require 'stringio'
# Entries marked [r/o] are read-only and an error will be raised of the program attempts to
# modify them. Entries marked [thread] are thread local.
-=begin
-Exception Information
----------------------------------------------------------------------------------------------------
-
-$! Exception The exception object passed to raise. [thread]
-$@ Array The stack backtrace generated by the last exception. [thread]
-=end
-
-=begin
-Pattern Matching Variables
----------------------------------------------------------------------------------------------------
-
-These variables are set to nil after an unsuccessful pattern match.
-
-$& String The string matched (following a successful pattern match). This variable is
- local to the current scope. [r/o, thread]
-$+ String The contents of the highest-numbered group matched following a successful
- pattern match. Thus, in "cat" =~/(c|a)(t|z)/, $+ will be set to “t”. This
- variable is local to the current scope. [r/o, thread]
-$` String The string preceding the match in a successful pattern match. This variable
- is local to the current scope. [r/o, thread]
-$' String The string following the match in a successful pattern match. This variable
- is local to the current scope. [r/o, thread]
-$1 to $9 String The contents of successive groups matched in a successful pattern match. In
- "cat" =~/(c|a)(t|z)/, $1 will be set to “a” and $2 to “t”. This variable
- is local to the current scope. [r/o, thread]
-$~ MatchData An object that encapsulates the results of a successful pattern match. The
- variables $&, $`, $', and $1 to $9 are all derived from $~. Assigning to $~
- changes the values of these derived variables. This variable is local to the
- current scope. [thread]
-=end
+# Exception Information
+# ---------------------------------------------------------------------------------------------------
+#
+# $! Exception The exception object passed to raise. [thread]
+# $@ Array The stack backtrace generated by the last exception. [thread]
+
+# Pattern Matching Variables
+# ---------------------------------------------------------------------------------------------------
+#
+# These variables are set to nil after an unsuccessful pattern match.
+#
+# $& String The string matched (following a successful pattern match). This variable is
+# local to the current scope. [r/o, thread]
+# $+ String The contents of the highest-numbered group matched following a successful
+# pattern match. Thus, in "cat" =~/(c|a)(t|z)/, $+ will be set to “t”. This
+# variable is local to the current scope. [r/o, thread]
+# $` String The string preceding the match in a successful pattern match. This variable
+# is local to the current scope. [r/o, thread]
+# $' String The string following the match in a successful pattern match. This variable
+# is local to the current scope. [r/o, thread]
+# $1 to $<N> String The contents of successive groups matched in a successful pattern match. In
+# "cat" =~/(c|a)(t|z)/, $1 will be set to “a” and $2 to “t”. This variable
+# is local to the current scope. [r/o, thread]
+# $~ MatchData An object that encapsulates the results of a successful pattern match. The
+# variables $&, $`, $', and $1 to $<N> are all derived from $~. Assigning to $~
+# changes the values of these derived variables. This variable is local to the
+# current scope. [thread]
describe "Predefined global $~" do
it "is set to contain the MatchData object of the last match if successful" do
md = /foo/.match 'foo'
$~.should be_kind_of(MatchData)
- $~.object_id.should == md.object_id
+ $~.should equal md
/bar/ =~ 'bar'
$~.should be_kind_of(MatchData)
- $~.object_id.should_not == md.object_id
+ $~.should_not equal md
end
it "is set to nil if the last match was unsuccessful" do
/foo/ =~ 'foo'
- $~.nil?.should == false
+ $~.should_not.nil?
/foo/ =~ 'bar'
- $~.nil?.should == true
+ $~.should.nil?
end
it "is set at the method-scoped level rather than block-scoped" do
@@ -92,8 +88,8 @@ describe "Predefined global $~" do
$~ = /foo/.match("foo")
$~.should be_an_instance_of(MatchData)
- lambda { $~ = Object.new }.should raise_error(TypeError)
- lambda { $~ = 1 }.should raise_error(TypeError)
+ -> { $~ = Object.new }.should raise_error(TypeError)
+ -> { $~ = 1 }.should raise_error(TypeError)
end
it "changes the value of derived capture globals when assigned" do
@@ -136,11 +132,9 @@ describe "Predefined global $&" do
$&.should == 'foo'
end
- with_feature :encoding do
- it "sets the encoding to the encoding of the source String" do
- "abc".force_encoding(Encoding::EUC_JP) =~ /b/
- $&.encoding.should equal(Encoding::EUC_JP)
- end
+ it "sets the encoding to the encoding of the source String" do
+ "abc".force_encoding(Encoding::EUC_JP) =~ /b/
+ $&.encoding.should equal(Encoding::EUC_JP)
end
end
@@ -151,16 +145,14 @@ describe "Predefined global $`" do
$`.should == 'bar'
end
- with_feature :encoding do
- it "sets the encoding to the encoding of the source String" do
- "abc".force_encoding(Encoding::EUC_JP) =~ /b/
- $`.encoding.should equal(Encoding::EUC_JP)
- end
+ it "sets the encoding to the encoding of the source String" do
+ "abc".force_encoding(Encoding::EUC_JP) =~ /b/
+ $`.encoding.should equal(Encoding::EUC_JP)
+ end
- it "sets an empty result to the encoding of the source String" do
- "abc".force_encoding(Encoding::ISO_8859_1) =~ /a/
- $`.encoding.should equal(Encoding::ISO_8859_1)
- end
+ it "sets an empty result to the encoding of the source String" do
+ "abc".force_encoding(Encoding::ISO_8859_1) =~ /a/
+ $`.encoding.should equal(Encoding::ISO_8859_1)
end
end
@@ -171,16 +163,14 @@ describe "Predefined global $'" do
$'.should == 'baz'
end
- with_feature :encoding do
- it "sets the encoding to the encoding of the source String" do
- "abc".force_encoding(Encoding::EUC_JP) =~ /b/
- $'.encoding.should equal(Encoding::EUC_JP)
- end
+ it "sets the encoding to the encoding of the source String" do
+ "abc".force_encoding(Encoding::EUC_JP) =~ /b/
+ $'.encoding.should equal(Encoding::EUC_JP)
+ end
- it "sets an empty result to the encoding of the source String" do
- "abc".force_encoding(Encoding::ISO_8859_1) =~ /c/
- $'.encoding.should equal(Encoding::ISO_8859_1)
- end
+ it "sets an empty result to the encoding of the source String" do
+ "abc".force_encoding(Encoding::ISO_8859_1) =~ /c/
+ $'.encoding.should equal(Encoding::ISO_8859_1)
end
end
@@ -196,11 +186,9 @@ describe "Predefined global $+" do
$+.should == 'a'
end
- with_feature :encoding do
- it "sets the encoding to the encoding of the source String" do
- "abc".force_encoding(Encoding::EUC_JP) =~ /(b)/
- $+.encoding.should equal(Encoding::EUC_JP)
- end
+ it "sets the encoding to the encoding of the source String" do
+ "abc".force_encoding(Encoding::EUC_JP) =~ /(b)/
+ $+.encoding.should equal(Encoding::EUC_JP)
end
end
@@ -225,11 +213,9 @@ describe "Predefined globals $1..N" do
test("-").should == nil
end
- with_feature :encoding do
- it "sets the encoding to the encoding of the source String" do
- "abc".force_encoding(Encoding::EUC_JP) =~ /(b)/
- $1.encoding.should equal(Encoding::EUC_JP)
- end
+ it "sets the encoding to the encoding of the source String" do
+ "abc".force_encoding(Encoding::EUC_JP) =~ /(b)/
+ $1.encoding.should equal(Encoding::EUC_JP)
end
end
@@ -243,12 +229,12 @@ describe "Predefined global $stdout" do
end
it "raises TypeError error if assigned to nil" do
- lambda { $stdout = nil }.should raise_error(TypeError)
+ -> { $stdout = nil }.should raise_error(TypeError)
end
it "raises TypeError error if assigned to object that doesn't respond to #write" do
obj = mock('object')
- lambda { $stdout = obj }.should raise_error(TypeError)
+ -> { $stdout = obj }.should raise_error(TypeError)
obj.stub!(:write)
$stdout = obj
@@ -408,6 +394,22 @@ describe "Predefined global $!" do
$!.should == nil
end
+ it "should be cleared when an exception is rescued even when a non-local return from block" do
+ def foo
+ [ 1 ].each do
+ begin
+ raise StandardError.new('err')
+ rescue => e
+ $!.should == e
+ return
+ end
+ end
+ end
+
+ foo
+ $!.should == nil
+ end
+
it "should not be cleared when an exception is not rescued" do
e = StandardError.new
begin
@@ -500,44 +502,43 @@ describe "Predefined global $!" do
end
end
-=begin
-Input/Output Variables
----------------------------------------------------------------------------------------------------
-
-$/ String The input record separator (newline by default). This is the value that rou-
- tines such as Kernel#gets use to determine record boundaries. If set to
- nil, gets will read the entire file.
-$-0 String Synonym for $/.
-$\ String The string appended to the output of every call to methods such as
- Kernel#print and IO#write. The default value is nil.
-$, String The separator string output between the parameters to methods such as
- Kernel#print and Array#join. Defaults to nil, which adds no text.
-$. Fixnum The number of the last line read from the current input file.
-$; String The default separator pattern used by String#split. May be set from the
- command line using the -F flag.
-$< Object An object that provides access to the concatenation of the contents of all
- the files given as command-line arguments or $stdin (in the case where
- there are no arguments). $< supports methods similar to a File object:
- binmode, close, closed?, each, each_byte, each_line, eof, eof?,
- file, filename, fileno, getc, gets, lineno, lineno=, path, pos, pos=,
- read, readchar, readline, readlines, rewind, seek, skip, tell, to_a,
- to_i, to_io, to_s, along with the methods in Enumerable. The method
- file returns a File object for the file currently being read. This may change
- as $< reads through the files on the command line. [r/o]
-$> IO The destination of output for Kernel#print and Kernel#printf. The
- default value is $stdout.
-$_ String The last line read by Kernel#gets or Kernel#readline. Many string-
- related functions in the Kernel module operate on $_ by default. The vari-
- able is local to the current scope. [thread]
-$-F String Synonym for $;.
-$stderr IO The current standard error output.
-$stdin IO The current standard input.
-$stdout IO The current standard output. Assignment to $stdout is deprecated: use
- $stdout.reopen instead.
-=end
+# Input/Output Variables
+# ---------------------------------------------------------------------------------------------------
+#
+# $/ String The input record separator (newline by default). This is the value that rou-
+# tines such as Kernel#gets use to determine record boundaries. If set to
+# nil, gets will read the entire file.
+# $-0 String Synonym for $/.
+# $\ String The string appended to the output of every call to methods such as
+# Kernel#print and IO#write. The default value is nil.
+# $, String The separator string output between the parameters to methods such as
+# Kernel#print and Array#join. Defaults to nil, which adds no text.
+# $. Integer The number of the last line read from the current input file.
+# $; String The default separator pattern used by String#split. May be set from the
+# command line using the -F flag.
+# $< Object An object that provides access to the concatenation of the contents of all
+# the files given as command-line arguments or $stdin (in the case where
+# there are no arguments). $< supports methods similar to a File object:
+# binmode, close, closed?, each, each_byte, each_line, eof, eof?,
+# file, filename, fileno, getc, gets, lineno, lineno=, path, pos, pos=,
+# read, readchar, readline, readlines, rewind, seek, skip, tell, to_a,
+# to_i, to_io, to_s, along with the methods in Enumerable. The method
+# file returns a File object for the file currently being read. This may change
+# as $< reads through the files on the command line. [r/o]
+# $> IO The destination of output for Kernel#print and Kernel#printf. The
+# default value is $stdout.
+# $_ String The last line read by Kernel#gets or Kernel#readline. Many string-
+# related functions in the Kernel module operate on $_ by default. The vari-
+# able is local to the current scope. [thread]
+# $-F String Synonym for $;.
+# $stderr IO The current standard error output.
+# $stdin IO The current standard input.
+# $stdout IO The current standard output. Assignment to $stdout is deprecated: use
+# $stdout.reopen instead.
describe "Predefined global $/" do
before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
@dollar_slash = $/
@dollar_dash_zero = $-0
end
@@ -545,6 +546,7 @@ describe "Predefined global $/" do
after :each do
$/ = @dollar_slash
$-0 = @dollar_dash_zero
+ $VERBOSE = @verbose
end
it "can be assigned a String" do
@@ -562,7 +564,6 @@ describe "Predefined global $/" do
($/ = "xyz").should == "xyz"
end
-
it "changes $-0" do
$/ = "xyz"
$-0.should equal($/)
@@ -572,20 +573,21 @@ describe "Predefined global $/" do
obj = mock("$/ value")
obj.should_not_receive(:to_str)
- lambda { $/ = obj }.should raise_error(TypeError)
+ -> { $/ = obj }.should raise_error(TypeError)
end
- it "raises a TypeError if assigned a Fixnum" do
- lambda { $/ = 1 }.should raise_error(TypeError)
+ it "raises a TypeError if assigned an Integer" do
+ -> { $/ = 1 }.should raise_error(TypeError)
end
it "raises a TypeError if assigned a boolean" do
- lambda { $/ = true }.should raise_error(TypeError)
+ -> { $/ = true }.should raise_error(TypeError)
end
end
describe "Predefined global $-0" do
before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
@dollar_slash = $/
@dollar_dash_zero = $-0
end
@@ -593,6 +595,7 @@ describe "Predefined global $-0" do
after :each do
$/ = @dollar_slash
$-0 = @dollar_dash_zero
+ $VERBOSE = @verbose
end
it "can be assigned a String" do
@@ -619,15 +622,54 @@ describe "Predefined global $-0" do
obj = mock("$-0 value")
obj.should_not_receive(:to_str)
- lambda { $-0 = obj }.should raise_error(TypeError)
+ -> { $-0 = obj }.should raise_error(TypeError)
end
- it "raises a TypeError if assigned a Fixnum" do
- lambda { $-0 = 1 }.should raise_error(TypeError)
+ it "raises a TypeError if assigned an Integer" do
+ -> { $-0 = 1 }.should raise_error(TypeError)
end
it "raises a TypeError if assigned a boolean" do
- lambda { $-0 = true }.should raise_error(TypeError)
+ -> { $-0 = true }.should raise_error(TypeError)
+ end
+end
+
+describe "Predefined global $\\" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ @dollar_backslash = $\
+ end
+
+ after :each do
+ $\ = @dollar_backslash
+ $VERBOSE = @verbose
+ end
+
+ it "can be assigned a String" do
+ str = "abc"
+ $\ = str
+ $\.should equal(str)
+ end
+
+ it "can be assigned nil" do
+ $\ = nil
+ $\.should be_nil
+ end
+
+ it "returns the value assigned" do
+ ($\ = "xyz").should == "xyz"
+ end
+
+ it "does not call #to_str to convert the object to a String" do
+ obj = mock("$\\ value")
+ obj.should_not_receive(:to_str)
+
+ -> { $\ = obj }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if assigned not String" do
+ -> { $\ = 1 }.should raise_error(TypeError)
+ -> { $\ = true }.should raise_error(TypeError)
end
end
@@ -641,7 +683,48 @@ describe "Predefined global $," do
end
it "raises TypeError if assigned a non-String" do
- lambda { $, = Object.new }.should raise_error(TypeError)
+ -> { $, = Object.new }.should raise_error(TypeError)
+ end
+
+ it "warns if assigned non-nil" do
+ -> { $, = "_" }.should complain(/warning: `\$,' is deprecated/)
+ end
+end
+
+describe "Predefined global $." do
+ it "can be assigned an Integer" do
+ $. = 123
+ $..should == 123
+ end
+
+ it "can be assigned a Float" do
+ $. = 123.5
+ $..should == 123
+ end
+
+ it "should call #to_int to convert the object to an Integer" do
+ obj = mock("good-value")
+ obj.should_receive(:to_int).and_return(321)
+
+ $. = obj
+ $..should == 321
+ end
+
+ it "raises TypeError if object can't be converted to an Integer" do
+ obj = mock("bad-value")
+ obj.should_receive(:to_int).and_return('abc')
+
+ -> { $. = obj }.should raise_error(TypeError)
+ end
+end
+
+describe "Predefined global $;" do
+ after :each do
+ $; = nil
+ end
+
+ it "warns if assigned non-nil" do
+ -> { $; = "_" }.should complain(/warning: `\$;' is deprecated/)
end
end
@@ -714,56 +797,52 @@ describe "Predefined global $_" do
end
end
-=begin
-Execution Environment Variables
----------------------------------------------------------------------------------------------------
-
-$0 String The name of the top-level Ruby program being executed. Typically this will
- be the program’s filename. On some operating systems, assigning to this
- variable will change the name of the process reported (for example) by the
- ps(1) command.
-$* Array An array of strings containing the command-line options from the invoca-
- tion of the program. Options used by the Ruby interpreter will have been
- removed. [r/o]
-$" Array An array containing the filenames of modules loaded by require. [r/o]
-$$ Fixnum The process number of the program being executed. [r/o]
-$? Process::Status The exit status of the last child process to terminate. [r/o, thread]
-$: Array An array of strings, where each string specifies a directory to be searched for
- Ruby scripts and binary extensions used by the load and require methods.
- The initial value is the value of the arguments passed via the -I command-
- line option, followed by an installation-defined standard library location, fol-
- lowed by the current directory (“.”). This variable may be set from within a
- program to alter the default search path; typically, programs use $: << dir
- to append dir to the path. [r/o]
-$-a Object True if the -a option is specified on the command line. [r/o]
-$-d Object Synonym for $DEBUG.
-$DEBUG Object Set to true if the -d command-line option is specified.
-__FILE__ String The name of the current source file. [r/o]
-$F Array The array that receives the split input line if the -a command-line option is
- used.
-$FILENAME String The name of the current input file. Equivalent to $<.filename. [r/o]
-$-i String If in-place edit mode is enabled (perhaps using the -i command-line
- option), $-i holds the extension used when creating the backup file. If you
- set a value into $-i, enables in-place edit mode.
-$-I Array Synonym for $:. [r/o]
-$-K String Sets the multibyte coding system for strings and regular expressions. Equiv-
- alent to the -K command-line option.
-$-l Object Set to true if the -l option (which enables line-end processing) is present
- on the command line. [r/o]
-__LINE__ String The current line number in the source file. [r/o]
-$LOAD_PATH Array A synonym for $:. [r/o]
-$-p Object Set to true if the -p option (which puts an implicit while gets . . . end
- loop around your program) is present on the command line. [r/o]
-$SAFE Fixnum The current safe level. This variable’s value may never be
- reduced by assignment. [thread] (Not implemented in Rubinius)
-$VERBOSE Object Set to true if the -v, --version, -W, or -w option is specified on the com-
- mand line. Set to false if no option, or -W1 is given. Set to nil if -W0
- was specified. Setting this option to true causes the interpreter and some
- library routines to report additional information. Setting to nil suppresses
- all warnings (including the output of Kernel.warn).
-$-v Object Synonym for $VERBOSE.
-$-w Object Synonym for $VERBOSE.
-=end
+# Execution Environment Variables
+# ---------------------------------------------------------------------------------------------------
+#
+# $0 String The name of the top-level Ruby program being executed. Typically this will
+# be the program’s filename. On some operating systems, assigning to this
+# variable will change the name of the process reported (for example) by the
+# ps(1) command.
+# $* Array An array of strings containing the command-line options from the invoca-
+# tion of the program. Options used by the Ruby interpreter will have been
+# removed. [r/o]
+# $" Array An array containing the filenames of modules loaded by require. [r/o]
+# $$ Integer The process number of the program being executed. [r/o]
+# $? Process::Status The exit status of the last child process to terminate. [r/o, thread]
+# $: Array An array of strings, where each string specifies a directory to be searched for
+# Ruby scripts and binary extensions used by the load and require methods.
+# The initial value is the value of the arguments passed via the -I command-
+# line option, followed by an installation-defined standard library location, fol-
+# lowed by the current directory (“.”). This variable may be set from within a
+# program to alter the default search path; typically, programs use $: << dir
+# to append dir to the path. [r/o]
+# $-a Object True if the -a option is specified on the command line. [r/o]
+# $-d Object Synonym for $DEBUG.
+# $DEBUG Object Set to true if the -d command-line option is specified.
+# __FILE__ String The name of the current source file. [r/o]
+# $F Array The array that receives the split input line if the -a command-line option is
+# used.
+# $FILENAME String The name of the current input file. Equivalent to $<.filename. [r/o]
+# $-i String If in-place edit mode is enabled (perhaps using the -i command-line
+# option), $-i holds the extension used when creating the backup file. If you
+# set a value into $-i, enables in-place edit mode.
+# $-I Array Synonym for $:. [r/o]
+# $-K String Sets the multibyte coding system for strings and regular expressions. Equiv-
+# alent to the -K command-line option.
+# $-l Object Set to true if the -l option (which enables line-end processing) is present
+# on the command line. [r/o]
+# __LINE__ String The current line number in the source file. [r/o]
+# $LOAD_PATH Array A synonym for $:. [r/o]
+# $-p Object Set to true if the -p option (which puts an implicit while gets . . . end
+# loop around your program) is present on the command line. [r/o]
+# $VERBOSE Object Set to true if the -v, --version, -W, or -w option is specified on the com-
+# mand line. Set to false if no option, or -W1 is given. Set to nil if -W0
+# was specified. Setting this option to true causes the interpreter and some
+# library routines to report additional information. Setting to nil suppresses
+# all warnings (including the output of Kernel.warn).
+# $-v Object Synonym for $VERBOSE.
+# $-w Object Synonym for $VERBOSE.
describe "Execution variable $:" do
it "is initialized to an array of strings" do
$:.is_a?(Array).should == true
@@ -782,34 +861,46 @@ describe "Execution variable $:" do
it "can be changed via <<" do
$: << "foo"
$:.should include("foo")
+ ensure
+ $:.delete("foo")
end
it "is read-only" do
- lambda {
+ -> {
$: = []
}.should raise_error(NameError)
- lambda {
+ -> {
$LOAD_PATH = []
}.should raise_error(NameError)
- lambda {
+ -> {
$-I = []
}.should raise_error(NameError)
end
+
+ it "default $LOAD_PATH entries until sitelibdir included have @gem_prelude_index set" do
+ skip "no sense in ruby itself" if MSpecScript.instance_variable_defined?(:@testing_ruby)
+
+ $:.should.include?(RbConfig::CONFIG['sitelibdir'])
+ idx = $:.index(RbConfig::CONFIG['sitelibdir'])
+
+ $:[idx..-1].all? { |p| p.instance_variable_defined?(:@gem_prelude_index) }.should be_true
+ $:[0...idx].all? { |p| !p.instance_variable_defined?(:@gem_prelude_index) }.should be_true
+ end
end
describe "Global variable $\"" do
it "is an alias for $LOADED_FEATURES" do
- $".object_id.should == $LOADED_FEATURES.object_id
+ $".should equal $LOADED_FEATURES
end
it "is read-only" do
- lambda {
+ -> {
$" = []
}.should raise_error(NameError)
- lambda {
+ -> {
$LOADED_FEATURES = []
}.should raise_error(NameError)
end
@@ -817,7 +908,7 @@ end
describe "Global variable $<" do
it "is read-only" do
- lambda {
+ -> {
$< = nil
}.should raise_error(NameError)
end
@@ -825,7 +916,7 @@ end
describe "Global variable $FILENAME" do
it "is read-only" do
- lambda {
+ -> {
$FILENAME = "-"
}.should raise_error(NameError)
end
@@ -833,7 +924,7 @@ end
describe "Global variable $?" do
it "is read-only" do
- lambda {
+ -> {
$? = nil
}.should raise_error(NameError)
end
@@ -846,19 +937,19 @@ end
describe "Global variable $-a" do
it "is read-only" do
- lambda { $-a = true }.should raise_error(NameError)
+ -> { $-a = true }.should raise_error(NameError)
end
end
describe "Global variable $-l" do
it "is read-only" do
- lambda { $-l = true }.should raise_error(NameError)
+ -> { $-l = true }.should raise_error(NameError)
end
end
describe "Global variable $-p" do
it "is read-only" do
- lambda { $-p = true }.should raise_error(NameError)
+ -> { $-p = true }.should raise_error(NameError)
end
end
@@ -880,6 +971,14 @@ describe "Global variable $-d" do
end
describe "Global variable $VERBOSE" do
+ before :each do
+ @verbose = $VERBOSE
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
it "converts truthy values to true" do
[true, 1, 0, [], ""].each do |true_value|
$VERBOSE = true_value
@@ -957,26 +1056,24 @@ describe "Global variable $0" do
end
it "raises a TypeError when not given an object that can be coerced to a String" do
- lambda { $0 = nil }.should raise_error(TypeError)
+ -> { $0 = nil }.should raise_error(TypeError)
end
end
-=begin
-Standard Objects
----------------------------------------------------------------------------------------------------
-
-ARGF Object A synonym for $<.
-ARGV Array A synonym for $*.
-ENV Object A hash-like object containing the program’s environment variables. An
- instance of class Object, ENV implements the full set of Hash methods. Used
- to query and set the value of an environment variable, as in ENV["PATH"]
- and ENV["term"]="ansi".
-false FalseClass Singleton instance of class FalseClass. [r/o]
-nil NilClass The singleton instance of class NilClass. The value of uninitialized
- instance and global variables. [r/o]
-self Object The receiver (object) of the current method. [r/o]
-true TrueClass Singleton instance of class TrueClass. [r/o]
-=end
+# Standard Objects
+# ---------------------------------------------------------------------------------------------------
+#
+# ARGF Object A synonym for $<.
+# ARGV Array A synonym for $*.
+# ENV Object A hash-like object containing the program’s environment variables. An
+# instance of class Object, ENV implements the full set of Hash methods. Used
+# to query and set the value of an environment variable, as in ENV["PATH"]
+# and ENV["term"]="ansi".
+# false FalseClass Singleton instance of class FalseClass. [r/o]
+# nil NilClass The singleton instance of class NilClass. The value of uninitialized
+# instance and global variables. [r/o]
+# self Object The receiver (object) of the current method. [r/o]
+# true TrueClass Singleton instance of class TrueClass. [r/o]
describe "The predefined standard objects" do
it "includes ARGF" do
@@ -999,7 +1096,7 @@ describe "The predefined standard object nil" do
end
it "raises a SyntaxError if assigned to" do
- lambda { eval("nil = true") }.should raise_error(SyntaxError)
+ -> { eval("nil = true") }.should raise_error(SyntaxError)
end
end
@@ -1009,7 +1106,7 @@ describe "The predefined standard object true" do
end
it "raises a SyntaxError if assigned to" do
- lambda { eval("true = false") }.should raise_error(SyntaxError)
+ -> { eval("true = false") }.should raise_error(SyntaxError)
end
end
@@ -1019,85 +1116,88 @@ describe "The predefined standard object false" do
end
it "raises a SyntaxError if assigned to" do
- lambda { eval("false = nil") }.should raise_error(SyntaxError)
+ -> { eval("false = nil") }.should raise_error(SyntaxError)
end
end
describe "The self pseudo-variable" do
it "raises a SyntaxError if assigned to" do
- lambda { eval("self = 1") }.should raise_error(SyntaxError)
+ -> { eval("self = 1") }.should raise_error(SyntaxError)
end
end
-=begin
-Global Constants
----------------------------------------------------------------------------------------------------
-
-The following constants are defined by the Ruby interpreter.
-
-DATA IO If the main program file contains the directive __END__, then
- the constant DATA will be initialized so that reading from it will
- return lines following __END__ from the source file.
-FALSE FalseClass Synonym for false.
-NIL NilClass Synonym for nil.
-RUBY_PLATFORM String The identifier of the platform running this program. This string
- is in the same form as the platform identifier used by the GNU
- configure utility (which is not a coincidence).
-RUBY_RELEASE_DATE String The date of this release.
-RUBY_VERSION String The version number of the interpreter.
-STDERR IO The actual standard error stream for the program. The initial
- value of $stderr.
-STDIN IO The actual standard input stream for the program. The initial
- value of $stdin.
-STDOUT IO The actual standard output stream for the program. The initial
- value of $stdout.
-SCRIPT_LINES__ Hash If a constant SCRIPT_LINES__ is defined and references a Hash,
- Ruby will store an entry containing the contents of each file it
- parses, with the file’s name as the key and an array of strings as
- the value.
-TOPLEVEL_BINDING Binding A Binding object representing the binding at Ruby’s top level—
- the level where programs are initially executed.
-TRUE TrueClass Synonym for true.
-=end
+# Global Constants
+# ---------------------------------------------------------------------------------------------------
+#
+# The following constants are defined by the Ruby interpreter.
+#
+# DATA IO If the main program file contains the directive __END__, then
+# the constant DATA will be initialized so that reading from it will
+# return lines following __END__ from the source file.
+# FALSE FalseClass Synonym for false (deprecated, removed in Ruby 3).
+# NIL NilClass Synonym for nil (deprecated, removed in Ruby 3).
+# RUBY_PLATFORM String The identifier of the platform running this program. This string
+# is in the same form as the platform identifier used by the GNU
+# configure utility (which is not a coincidence).
+# RUBY_RELEASE_DATE String The date of this release.
+# RUBY_VERSION String The version number of the interpreter.
+# STDERR IO The actual standard error stream for the program. The initial
+# value of $stderr.
+# STDIN IO The actual standard input stream for the program. The initial
+# value of $stdin.
+# STDOUT IO The actual standard output stream for the program. The initial
+# value of $stdout.
+# SCRIPT_LINES__ Hash If a constant SCRIPT_LINES__ is defined and references a Hash,
+# Ruby will store an entry containing the contents of each file it
+# parses, with the file’s name as the key and an array of strings as
+# the value.
+# TOPLEVEL_BINDING Binding A Binding object representing the binding at Ruby’s top level—
+# the level where programs are initially executed.
+# TRUE TrueClass Synonym for true (deprecated, removed in Ruby 3).
describe "The predefined global constants" do
- ruby_version_is ""..."2.4" do
- it "includes TRUE" do
- Object.const_defined?(:TRUE).should == true
- TRUE.should equal(true)
+ describe "TRUE" do
+ ruby_version_is "3.0" do
+ it "is no longer defined" do
+ Object.const_defined?(:TRUE).should == false
+ end
end
- it "includes FALSE" do
- Object.const_defined?(:FALSE).should == true
- FALSE.should equal(false)
+ ruby_version_is ""..."3.0" do
+ it "includes TRUE" do
+ Object.const_defined?(:TRUE).should == true
+ -> { TRUE }.should complain(/constant ::TRUE is deprecated/)
+ end
end
+ end
- it "includes NIL" do
- Object.const_defined?(:NIL).should == true
- NIL.should equal(nil)
+ describe "FALSE" do
+ ruby_version_is "3.0" do
+ it "is no longer defined" do
+ Object.const_defined?(:FALSE).should == false
+ end
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/)
+ ruby_version_is ""..."3.0" do
+ it "includes FALSE" do
+ Object.const_defined?(:FALSE).should == true
+ -> { FALSE }.should complain(/constant ::FALSE is deprecated/)
+ end
end
+ end
- it "includes FALSE" do
- Object.const_defined?(:FALSE).should == true
- -> {
- FALSE.should equal(false)
- }.should complain(/constant ::FALSE is deprecated/)
+ describe "NIL" do
+ ruby_version_is "3.0" do
+ it "is no longer defined" do
+ Object.const_defined?(:NIL).should == false
+ end
end
- it "includes NIL" do
- Object.const_defined?(:NIL).should == true
- -> {
- NIL.should equal(nil)
- }.should complain(/constant ::NIL is deprecated/)
+ ruby_version_is ""..."3.0" do
+ it "includes NIL" do
+ Object.const_defined?(:NIL).should == true
+ -> { NIL }.should complain(/constant ::NIL is deprecated/)
+ end
end
end
@@ -1128,22 +1228,21 @@ describe "The predefined global constants" do
it "includes TOPLEVEL_BINDING" do
Object.const_defined?(:TOPLEVEL_BINDING).should == true
end
-
end
-with_feature :encoding do
- describe "The predefined global constant" do
- before :each do
- @external = Encoding.default_external
- @internal = Encoding.default_internal
- end
+describe "The predefined global constant" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+ end
- after :each do
- Encoding.default_external = @external
- Encoding.default_internal = @internal
- end
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
- describe "STDIN" do
+ describe "STDIN" do
+ platform_is_not :windows do
it "has the same external encoding as Encoding.default_external" do
STDIN.external_encoding.should equal(Encoding.default_external)
end
@@ -1153,19 +1252,6 @@ with_feature :encoding do
STDIN.external_encoding.should equal(Encoding::ISO_8859_16)
end
- it "has the encodings set by #set_encoding" do
- code = "STDIN.set_encoding Encoding::IBM775, Encoding::IBM866; " \
- "p [STDIN.external_encoding.name, STDIN.internal_encoding.name]"
- ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
- end
-
- it "retains the encoding set by #set_encoding when Encoding.default_external is changed" do
- code = "STDIN.set_encoding Encoding::IBM775, Encoding::IBM866; " \
- "Encoding.default_external = Encoding::ISO_8859_16;" \
- "p [STDIN.external_encoding.name, STDIN.internal_encoding.name]"
- ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
- end
-
it "has nil for the internal encoding" do
STDIN.internal_encoding.should be_nil
end
@@ -1176,65 +1262,133 @@ with_feature :encoding do
end
end
- describe "STDOUT" do
- it "has nil for the external encoding" do
- STDOUT.external_encoding.should be_nil
- end
+ it "has the encodings set by #set_encoding" do
+ code = "STDIN.set_encoding Encoding::IBM775, Encoding::IBM866; " \
+ "p [STDIN.external_encoding.name, STDIN.internal_encoding.name]"
+ ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
+ end
- it "has nil for the external encoding despite Encoding.default_external being changed" do
- Encoding.default_external = Encoding::ISO_8859_1
- STDOUT.external_encoding.should be_nil
- end
+ it "retains the encoding set by #set_encoding when Encoding.default_external is changed" do
+ code = "STDIN.set_encoding Encoding::IBM775, Encoding::IBM866; " \
+ "Encoding.default_external = Encoding::ISO_8859_16;" \
+ "p [STDIN.external_encoding.name, STDIN.internal_encoding.name]"
+ ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
+ end
+ end
- it "has the encodings set by #set_encoding" do
- code = "STDOUT.set_encoding Encoding::IBM775, Encoding::IBM866; " \
- "p [STDOUT.external_encoding.name, STDOUT.internal_encoding.name]"
- ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
- end
+ describe "STDOUT" do
+ it "has nil for the external encoding" do
+ STDOUT.external_encoding.should be_nil
+ end
- it "has nil for the internal encoding" do
- STDOUT.internal_encoding.should be_nil
- end
+ it "has nil for the external encoding despite Encoding.default_external being changed" do
+ Encoding.default_external = Encoding::ISO_8859_1
+ STDOUT.external_encoding.should be_nil
+ end
- it "has nil for the internal encoding despite Encoding.default_internal being changed" do
- Encoding.default_internal = Encoding::IBM437
- STDOUT.internal_encoding.should be_nil
- end
+ it "has the encodings set by #set_encoding" do
+ code = "STDOUT.set_encoding Encoding::IBM775, Encoding::IBM866; " \
+ "p [STDOUT.external_encoding.name, STDOUT.internal_encoding.name]"
+ ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
end
- describe "STDERR" do
- it "has nil for the external encoding" do
- STDERR.external_encoding.should be_nil
- end
+ it "has nil for the internal encoding" do
+ STDOUT.internal_encoding.should be_nil
+ end
- it "has nil for the external encoding despite Encoding.default_external being changed" do
- Encoding.default_external = Encoding::ISO_8859_1
- STDERR.external_encoding.should be_nil
- end
+ it "has nil for the internal encoding despite Encoding.default_internal being changed" do
+ Encoding.default_internal = Encoding::IBM437
+ STDOUT.internal_encoding.should be_nil
+ end
+ end
- it "has the encodings set by #set_encoding" do
- code = "STDERR.set_encoding Encoding::IBM775, Encoding::IBM866; " \
- "p [STDERR.external_encoding.name, STDERR.internal_encoding.name]"
- ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
- end
+ describe "STDERR" do
+ it "has nil for the external encoding" do
+ STDERR.external_encoding.should be_nil
+ end
- it "has nil for the internal encoding" do
- STDERR.internal_encoding.should be_nil
- end
+ it "has nil for the external encoding despite Encoding.default_external being changed" do
+ Encoding.default_external = Encoding::ISO_8859_1
+ STDERR.external_encoding.should be_nil
+ end
- it "has nil for the internal encoding despite Encoding.default_internal being changed" do
- Encoding.default_internal = Encoding::IBM437
- STDERR.internal_encoding.should be_nil
- end
+ it "has the encodings set by #set_encoding" do
+ code = "STDERR.set_encoding Encoding::IBM775, Encoding::IBM866; " \
+ "p [STDERR.external_encoding.name, STDERR.internal_encoding.name]"
+ ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
end
- describe "ARGV" do
- it "contains Strings encoded in locale Encoding" do
- code = fixture __FILE__, "argv_encoding.rb"
- result = ruby_exe(code, args: "a b")
- encoding = Encoding.default_external
- result.chomp.should == %{["#{encoding}", "#{encoding}"]}
- end
+ it "has nil for the internal encoding" do
+ STDERR.internal_encoding.should be_nil
end
+
+ it "has nil for the internal encoding despite Encoding.default_internal being changed" do
+ Encoding.default_internal = Encoding::IBM437
+ STDERR.internal_encoding.should be_nil
+ end
+ end
+
+ describe "ARGV" do
+ it "contains Strings encoded in locale Encoding" do
+ code = fixture __FILE__, "argv_encoding.rb"
+ result = ruby_exe(code, args: "a b")
+ encoding = Encoding.default_external
+ result.chomp.should == %{["#{encoding}", "#{encoding}"]}
+ end
+ end
+end
+
+describe "$LOAD_PATH.resolve_feature_path" do
+ it "returns what will be loaded without actual loading, .rb file" do
+ extension, path = $LOAD_PATH.resolve_feature_path('set')
+ extension.should == :rb
+ path.should.end_with?('/set.rb')
+ end
+
+ it "returns what will be loaded without actual loading, .so file" do
+ require 'rbconfig'
+ skip "no dynamically loadable standard extension" if RbConfig::CONFIG["EXTSTATIC"] == "static"
+
+ extension, path = $LOAD_PATH.resolve_feature_path('etc')
+ extension.should == :so
+ path.should.end_with?("/etc.#{RbConfig::CONFIG['DLEXT']}")
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "raises LoadError if feature cannot be found" do
+ -> { $LOAD_PATH.resolve_feature_path('noop') }.should raise_error(LoadError)
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "return nil if feature cannot be found" do
+ $LOAD_PATH.resolve_feature_path('noop').should be_nil
+ end
+ end
+end
+
+# Some other pre-defined global variables
+
+describe "Predefined global $=" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ @dollar_assign = $=
+ end
+
+ after :each do
+ $= = @dollar_assign
+ $VERBOSE = @verbose
+ end
+
+ it "warns when accessed" do
+ -> { a = $= }.should complain(/is no longer effective/)
+ end
+
+ it "warns when assigned" do
+ -> { $= = "_" }.should complain(/is no longer effective/)
+ end
+
+ it "returns the value assigned" do
+ ($= = "xyz").should == "xyz"
end
end
diff --git a/spec/ruby/language/private_spec.rb b/spec/ruby/language/private_spec.rb
index 796c0c1711..ddf185e6d2 100644
--- a/spec/ruby/language/private_spec.rb
+++ b/spec/ruby/language/private_spec.rb
@@ -1,15 +1,15 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/private', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/private'
describe "The private keyword" do
it "marks following methods as being private" do
a = Private::A.new
a.methods.should_not include(:bar)
- lambda { a.bar }.should raise_error(NoMethodError)
+ -> { a.bar }.should raise_error(NoMethodError)
b = Private::B.new
b.methods.should_not include(:bar)
- lambda { b.bar }.should raise_error(NoMethodError)
+ -> { b.bar }.should raise_error(NoMethodError)
end
# def expr.meth() methods are always public
@@ -22,7 +22,7 @@ describe "The private keyword" do
c.methods.should include(:baz)
c.baz
Private::B.public_class_method1.should == 1
- lambda { Private::B.private_class_method1 }.should raise_error(NoMethodError)
+ -> { Private::B.private_class_method1 }.should raise_error(NoMethodError)
end
it "is no longer in effect when the class is closed" do
@@ -42,12 +42,12 @@ describe "The private keyword" do
klass.class_eval do
private :foo
end
- lambda { f.foo }.should raise_error(NoMethodError)
+ -> { f.foo }.should raise_error(NoMethodError)
end
- it "changes visiblity of previously called methods with same send/call site" do
+ it "changes visibility of previously called methods with same send/call site" do
g = ::Private::G.new
- lambda {
+ -> {
2.times do
g.foo
module ::Private
@@ -61,7 +61,7 @@ describe "The private keyword" do
it "changes the visibility of the existing method in the subclass" do
::Private::A.new.foo.should == 'foo'
- lambda {::Private::H.new.foo}.should raise_error(NoMethodError)
+ -> { ::Private::H.new.foo }.should raise_error(NoMethodError)
::Private::H.new.send(:foo).should == 'foo'
end
end
diff --git a/spec/ruby/language/proc_spec.rb b/spec/ruby/language/proc_spec.rb
index bbef318826..f8a29962b0 100644
--- a/spec/ruby/language/proc_spec.rb
+++ b/spec/ruby/language/proc_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "A Proc" do
it "captures locals from the surrounding scope" do
@@ -21,7 +21,7 @@ describe "A Proc" do
@l.call.should == 1
end
- it "raises an ArgumentErro if a value is passed" do
+ it "raises an ArgumentError if a value is passed" do
lambda { @l.call(0) }.should raise_error(ArgumentError)
end
end
@@ -217,4 +217,31 @@ describe "A Proc" do
lambda { @l.call(obj) }.should raise_error(TypeError)
end
end
+
+ describe "taking |*a, **kw| arguments" do
+ before :each do
+ @p = proc { |*a, **kw| [a, kw] }
+ end
+
+ ruby_version_is ""..."3.0" do
+ it 'autosplats keyword arguments and warns' do
+ -> {
+ @p.call([1, {a: 1}]).should == [[1], {a: 1}]
+ }.should complain(/warning: Using the last argument as keyword parameters is deprecated; maybe \*\* should be added to the call/)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it 'does not autosplat keyword arguments' do
+ @p.call([1, {a: 1}]).should == [[[1, {a: 1}]], {}]
+ end
+ end
+ end
+
+ describe "taking |required keyword arguments, **kw| arguments" do
+ it "raises ArgumentError for missing required argument" do
+ p = proc { |a:, **kw| [a, kw] }
+ -> { p.call() }.should raise_error(ArgumentError)
+ end
+ end
end
diff --git a/spec/ruby/language/range_spec.rb b/spec/ruby/language/range_spec.rb
new file mode 100644
index 0000000000..ccc9f55537
--- /dev/null
+++ b/spec/ruby/language/range_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Literal Ranges" do
+ it "creates range object" do
+ (1..10).should == Range.new(1, 10)
+ end
+
+ it "creates range with excluded right boundary" do
+ (1...10).should == Range.new(1, 10, true)
+ end
+
+ it "creates a simple range as an object literal" do
+ ary = []
+ 2.times do
+ ary.push(1..3)
+ end
+ ary[0].should.equal?(ary[1])
+ end
+
+ it "creates endless ranges" do
+ (1..).should == Range.new(1, nil)
+ (1...).should == Range.new(1, nil, true)
+ end
+
+ it "creates beginless ranges" do
+ (..1).should == Range.new(nil, 1)
+ (...1).should == Range.new(nil, 1, true)
+ end
+end
diff --git a/spec/ruby/language/redo_spec.rb b/spec/ruby/language/redo_spec.rb
index 53fd30b4f2..57532553b3 100644
--- a/spec/ruby/language/redo_spec.rb
+++ b/spec/ruby/language/redo_spec.rb
@@ -1,9 +1,9 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The redo statement" do
it "restarts block execution if used within block" do
a = []
- lambda {
+ -> {
a << 1
redo if a.size < 2
a << 2
@@ -58,7 +58,7 @@ describe "The redo statement" do
describe "in a method" do
it "is invalid and raises a SyntaxError" do
- lambda {
+ -> {
eval("def m; redo; end")
}.should raise_error(SyntaxError)
end
diff --git a/spec/ruby/language/regexp/anchors_spec.rb b/spec/ruby/language/regexp/anchors_spec.rb
index c6a620a221..0129e255da 100644
--- a/spec/ruby/language/regexp/anchors_spec.rb
+++ b/spec/ruby/language/regexp/anchors_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
describe "Regexps with anchors" do
it "supports ^ (line start anchor)" do
diff --git a/spec/ruby/language/regexp/back-references_spec.rb b/spec/ruby/language/regexp/back-references_spec.rb
index 607f4463fd..26750c20c5 100644
--- a/spec/ruby/language/regexp/back-references_spec.rb
+++ b/spec/ruby/language/regexp/back-references_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
describe "Regexps with back-references" do
it "saves match data in the $~ pseudo-global variable" do
@@ -7,7 +7,7 @@ describe "Regexps with back-references" do
$~.to_a.should == ["ll"]
end
- it "saves captures in numbered $[1-9] variables" do
+ it "saves captures in numbered $[1-N] variables" do
"1234567890" =~ /(1)(2)(3)(4)(5)(6)(7)(8)(9)(0)/
$~.to_a.should == ["1234567890", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
$1.should == "1"
@@ -19,6 +19,7 @@ describe "Regexps with back-references" do
$7.should == "7"
$8.should == "8"
$9.should == "9"
+ $10.should == "0"
end
it "will not clobber capture variables across threads" do
@@ -45,4 +46,95 @@ describe "Regexps with back-references" do
it "resets nested \<n> backreference before match of outer subexpression" do
/(a\1?){2}/.match("aaaa").to_a.should == ["aa", "a"]
end
+
+ it "does not reset enclosed capture groups" do
+ /((a)|(b))+/.match("ab").captures.should == [ "b", "a", "b" ]
+ end
+
+ it "can match an optional quote, followed by content, followed by a matching quote, as the whole string" do
+ /^("|)(.*)\1$/.match('x').to_a.should == ["x", "", "x"]
+ end
+
+ it "allows forward references" do
+ /(?:(\2)|(.))+/.match("aa").to_a.should == [ "aa", "a", "a" ]
+ end
+
+ it "disallows forward references >= 10" do
+ (/\10()()()()()()()()()()/ =~ "\x08").should == 0
+ end
+
+ it "fails when trying to match a backreference to an unmatched capture group" do
+ /\1()/.match("").should == nil
+ /(?:(a)|b)\1/.match("b").should == nil
+ end
+
+ it "ignores backreferences > 1000" do
+ /\99999/.match("99999")[0].should == "99999"
+ end
+
+ it "0 is not a valid backreference" do
+ -> { Regexp.new("\\k<0>") }.should raise_error(RegexpError)
+ end
+
+ it "allows numeric conditional backreferences" do
+ /(a)(?(1)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?(<1>)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?('1')a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+
+ it "allows either <> or '' in named conditional backreferences" do
+ -> { Regexp.new("(?<a>a)(?(a)a|b)") }.should raise_error(RegexpError)
+ /(?<a>a)(?(<a>)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a>a)(?('a')a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+
+ it "allows negative numeric backreferences" do
+ /(a)\k<-1>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)\g<-1>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?(<-1>)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?('-1')a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+
+ it "delimited numeric backreferences can start with 0" do
+ /(a)\k<01>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)\g<01>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?(01)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?(<01>)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?('01')a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+
+ it "regular numeric backreferences cannot start with 0" do
+ /(a)\01/.match("aa").should == nil
+ /(a)\01/.match("a\x01").to_a.should == [ "a\x01", "a" ]
+ end
+
+ it "named capture groups invalidate numeric backreferences" do
+ -> { Regexp.new("(?<a>a)\\1") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a>a)\\k<1>") }.should raise_error(RegexpError)
+ -> { Regexp.new("(a)(?<a>a)\\1") }.should raise_error(RegexpError)
+ -> { Regexp.new("(a)(?<a>a)\\k<1>") }.should raise_error(RegexpError)
+ end
+
+ it "treats + or - as the beginning of a level specifier in \\k<> backreferences and (?(...)...|...) conditional backreferences" do
+ -> { Regexp.new("(?<a+>a)\\k<a+>") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a+b>a)\\k<a+b>") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a+1>a)\\k<a+1>") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a->a)\\k<a->") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a-b>a)\\k<a-b>") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a-1>a)\\k<a-1>") }.should raise_error(RegexpError)
+
+ -> { Regexp.new("(?<a+>a)(?(<a+>)a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a+b>a)(?(<a+b>)a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a+1>a)(?(<a+1>)a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a->a)(?(<a->)a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a-b>a)(?(<a-b>)a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a-1>a)(?(<a-1>)a|b)") }.should raise_error(RegexpError)
+
+ -> { Regexp.new("(?<a+>a)(?('a+')a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a+b>a)(?('a+b')a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a+1>a)(?('a+1')a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a->a)(?('a-')a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a-b>a)(?('a-b')a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a-1>a)(?('a-1')a|b)") }.should raise_error(RegexpError)
+ end
end
diff --git a/spec/ruby/language/regexp/character_classes_spec.rb b/spec/ruby/language/regexp/character_classes_spec.rb
index ce66d8e65f..12a51178b2 100644
--- a/spec/ruby/language/regexp/character_classes_spec.rb
+++ b/spec/ruby/language/regexp/character_classes_spec.rb
@@ -1,6 +1,6 @@
# coding: utf-8
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
describe "Regexp with character classes" do
it "supports \\w (word character)" do
@@ -89,7 +89,7 @@ describe "Regexp with character classes" do
/[^[:lower:]A-C]+/.match("abcABCDEF123def").to_a.should == ["DEF123"] # negated character class
/[:alnum:]+/.match("a:l:n:u:m").to_a.should == ["a:l:n:u:m"] # should behave like regular character class composed of the individual letters
/[\[:alnum:]+/.match("[:a:l:n:u:m").to_a.should == ["[:a:l:n:u:m"] # should behave like regular character class composed of the individual letters
- lambda { eval('/[[:alpha:]-[:digit:]]/') }.should raise_error(SyntaxError) # can't use character class as a start value of range
+ -> { eval('/[[:alpha:]-[:digit:]]/') }.should raise_error(SyntaxError) # can't use character class as a start value of range
end
it "matches ASCII characters with [[:ascii:]]" do
@@ -609,25 +609,34 @@ describe "Regexp with character classes" do
"루비(Ruby)".match(/\p{Hangul}+/u).to_a.should == ["루비"]
end
- ruby_version_is "2.4" do
- it "supports \\X (unicode 9.0 with UTR #51 workarounds)" do
- # simple emoji without any fancy modifier or ZWJ
- /\X/.match("\u{1F98A}").to_a.should == ["🦊"]
+ it "supports negated property condition" do
+ "a".match(/\P{L}/).should be_nil
+ "1".match(/\P{N}/).should be_nil
+ end
+
+ ruby_bug "#17340", ''...'3.0' do
+ it "raises a RegexpError for an unterminated unicode property" do
+ -> { Regexp.new('\p{') }.should raise_error(RegexpError)
+ end
+ end
- # skin tone modifier
- /\X/.match("\u{1F918}\u{1F3FD}").to_a.should == ["🤘🏽"]
+ it "supports \\X (unicode 9.0 with UTR #51 workarounds)" do
+ # simple emoji without any fancy modifier or ZWJ
+ /\X/.match("\u{1F98A}").to_a.should == ["🦊"]
- # emoji joined with ZWJ
- /\X/.match("\u{1F3F3}\u{FE0F}\u{200D}\u{1F308}").to_a.should == ["🏳️‍🌈"]
- /\X/.match("\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}").to_a.should == ["👩‍👩‍👧‍👦"]
+ # skin tone modifier
+ /\X/.match("\u{1F918}\u{1F3FD}").to_a.should == ["🤘🏽"]
- # without the ZWJ
- /\X+/.match("\u{1F3F3}\u{FE0F}\u{1F308}").to_a.should == ["🏳️🌈"]
- /\X+/.match("\u{1F469}\u{1F469}\u{1F467}\u{1F466}").to_a.should == ["👩👩👧👦"]
+ # emoji joined with ZWJ
+ /\X/.match("\u{1F3F3}\u{FE0F}\u{200D}\u{1F308}").to_a.should == ["🏳️‍🌈"]
+ /\X/.match("\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}").to_a.should == ["👩‍👩‍👧‍👦"]
- # both of the ZWJ combined
- /\X+/.match("\u{1F3F3}\u{FE0F}\u{200D}\u{1F308}\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}")
- .to_a.should == ["🏳️‍🌈👩‍👩‍👧‍👦"]
- end
+ # without the ZWJ
+ /\X+/.match("\u{1F3F3}\u{FE0F}\u{1F308}").to_a.should == ["🏳️🌈"]
+ /\X+/.match("\u{1F469}\u{1F469}\u{1F467}\u{1F466}").to_a.should == ["👩👩👧👦"]
+
+ # both of the ZWJ combined
+ /\X+/.match("\u{1F3F3}\u{FE0F}\u{200D}\u{1F308}\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}")
+ .to_a.should == ["🏳️‍🌈👩‍👩‍👧‍👦"]
end
end
diff --git a/spec/ruby/language/regexp/empty_checks_spec.rb b/spec/ruby/language/regexp/empty_checks_spec.rb
new file mode 100644
index 0000000000..391e65b003
--- /dev/null
+++ b/spec/ruby/language/regexp/empty_checks_spec.rb
@@ -0,0 +1,135 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "empty checks in Regexps" do
+
+ it "allow extra empty iterations" do
+ /()?/.match("").to_a.should == ["", ""]
+ /(a*)?/.match("").to_a.should == ["", ""]
+ /(a*)*/.match("").to_a.should == ["", ""]
+ # The bounds are high to avoid DFA-based matchers in implementations
+ # and to check backtracking behavior.
+ /(?:a|()){500,1000}/.match("a" * 500).to_a.should == ["a" * 500, ""]
+
+ # Variations with non-greedy loops.
+ /()??/.match("").to_a.should == ["", nil]
+ /(a*?)?/.match("").to_a.should == ["", ""]
+ /(a*)??/.match("").to_a.should == ["", nil]
+ /(a*?)??/.match("").to_a.should == ["", nil]
+ /(a*?)*/.match("").to_a.should == ["", ""]
+ /(a*)*?/.match("").to_a.should == ["", nil]
+ /(a*?)*?/.match("").to_a.should == ["", nil]
+ end
+
+ it "allow empty iterations in the middle of a loop" do
+ # One empty iteration between a's and b's.
+ /(a|\2b|())*/.match("aaabbb").to_a.should == ["aaabbb", "", ""]
+ /(a|\2b|()){2,4}/.match("aaabbb").to_a.should == ["aaa", "", ""]
+
+ # Two empty iterations between a's and b's.
+ /(a|\2b|\3()|())*/.match("aaabbb").to_a.should == ["aaabbb", "", "", ""]
+ /(a|\2b|\3()|()){2,4}/.match("aaabbb").to_a.should == ["aaa", "", nil, ""]
+
+ # Check that the empty iteration correctly updates the loop counter.
+ /(a|\2b|()){20,24}/.match("a" * 20 + "b" * 5).to_a.should == ["a" * 20 + "b" * 3, "b", ""]
+
+ # Variations with non-greedy loops.
+ /(a|\2b|())*?/.match("aaabbb").to_a.should == ["", nil, nil]
+ /(a|\2b|()){2,4}/.match("aaabbb").to_a.should == ["aaa", "", ""]
+ /(a|\2b|\3()|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil]
+ /(a|\2b|\3()|()){2,4}/.match("aaabbb").to_a.should == ["aaa", "", nil, ""]
+ /(a|\2b|()){20,24}/.match("a" * 20 + "b" * 5).to_a.should == ["a" * 20 + "b" * 3, "b", ""]
+ end
+
+ it "make the Regexp proceed past the quantified expression on failure" do
+ # If the contents of the ()* quantified group are empty (i.e., they fail
+ # the empty check), the loop will abort. It will not try to backtrack
+ # and try other alternatives (e.g. matching the "a") like in other Regexp
+ # dialects such as ECMAScript.
+ /(?:|a)*/.match("aaa").to_a.should == [""]
+ /(?:()|a)*/.match("aaa").to_a.should == ["", ""]
+ /(|a)*/.match("aaa").to_a.should == ["", ""]
+ /(()|a)*/.match("aaa").to_a.should == ["", "", ""]
+
+ # Same expressions, but with backreferences, to force the use of non-DFA-based
+ # engines.
+ /()\1(?:|a)*/.match("aaa").to_a.should == ["", ""]
+ /()\1(?:()|a)*/.match("aaa").to_a.should == ["", "", ""]
+ /()\1(|a)*/.match("aaa").to_a.should == ["", "", ""]
+ /()\1(()|a)*/.match("aaa").to_a.should == ["", "", "", ""]
+
+ # Variations with other zero-width contents of the quantified
+ # group: backreferences, capture groups, lookarounds
+ /()(?:\1|a)*/.match("aaa").to_a.should == ["", ""]
+ /()(?:()\1|a)*/.match("aaa").to_a.should == ["", "", ""]
+ /()(?:(\1)|a)*/.match("aaa").to_a.should == ["", "", ""]
+ /()(?:\1()|a)*/.match("aaa").to_a.should == ["", "", ""]
+ /()(\1|a)*/.match("aaa").to_a.should == ["", "", ""]
+ /()(()\1|a)*/.match("aaa").to_a.should == ["", "", "", ""]
+ /()((\1)|a)*/.match("aaa").to_a.should == ["", "", "", ""]
+ /()(\1()|a)*/.match("aaa").to_a.should == ["", "", "", ""]
+
+ /(?:(?=a)|a)*/.match("aaa").to_a.should == [""]
+ /(?:(?=a)()|a)*/.match("aaa").to_a.should == ["", ""]
+ /(?:()(?=a)|a)*/.match("aaa").to_a.should == ["", ""]
+ /(?:((?=a))|a)*/.match("aaa").to_a.should == ["", ""]
+ /()\1(?:(?=a)|a)*/.match("aaa").to_a.should == ["", ""]
+ /()\1(?:(?=a)()|a)*/.match("aaa").to_a.should == ["", "", ""]
+ /()\1(?:()(?=a)|a)*/.match("aaa").to_a.should == ["", "", ""]
+ /()\1(?:((?=a))|a)*/.match("aaa").to_a.should == ["", "", ""]
+
+ # Variations with non-greedy loops.
+ /(?:|a)*?/.match("aaa").to_a.should == [""]
+ /(?:()|a)*?/.match("aaa").to_a.should == ["", nil]
+ /(|a)*?/.match("aaa").to_a.should == ["", nil]
+ /(()|a)*?/.match("aaa").to_a.should == ["", nil, nil]
+
+ /()\1(?:|a)*?/.match("aaa").to_a.should == ["", ""]
+ /()\1(?:()|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ /()\1(|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ /()\1(()|a)*?/.match("aaa").to_a.should == ["", "", nil, nil]
+
+ /()(?:\1|a)*?/.match("aaa").to_a.should == ["", ""]
+ /()(?:()\1|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ /()(?:(\1)|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ /()(?:\1()|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ /()(\1|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ /()(()\1|a)*?/.match("aaa").to_a.should == ["", "", nil, nil]
+ /()((\1)|a)*?/.match("aaa").to_a.should == ["", "", nil, nil]
+ /()(\1()|a)*?/.match("aaa").to_a.should == ["", "", nil, nil]
+
+ /(?:(?=a)|a)*?/.match("aaa").to_a.should == [""]
+ /(?:(?=a)()|a)*?/.match("aaa").to_a.should == ["", nil]
+ /(?:()(?=a)|a)*?/.match("aaa").to_a.should == ["", nil]
+ /(?:((?=a))|a)*?/.match("aaa").to_a.should == ["", nil]
+ /()\1(?:(?=a)|a)*?/.match("aaa").to_a.should == ["", ""]
+ /()\1(?:(?=a)()|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ /()\1(?:()(?=a)|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ /()\1(?:((?=a))|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ end
+
+ it "shouldn't cause the Regexp parser to get stuck in a loop" do
+ /(|a|\2b|())*/.match("aaabbb").to_a.should == ["", "", nil]
+ /(a||\2b|())*/.match("aaabbb").to_a.should == ["aaa", "", nil]
+ /(a|\2b||())*/.match("aaabbb").to_a.should == ["aaa", "", nil]
+ /(a|\2b|()|)*/.match("aaabbb").to_a.should == ["aaabbb", "", ""]
+ /(()|a|\3b|())*/.match("aaabbb").to_a.should == ["", "", "", nil]
+ /(a|()|\3b|())*/.match("aaabbb").to_a.should == ["aaa", "", "", nil]
+ /(a|\2b|()|())*/.match("aaabbb").to_a.should == ["aaabbb", "", "", nil]
+ /(a|\3b|()|())*/.match("aaabbb").to_a.should == ["aaa", "", "", nil]
+ /(a|()|())*/.match("aaa").to_a.should == ["aaa", "", "", nil]
+ /^(()|a|())*$/.match("aaa").to_a.should == ["aaa", "", "", nil]
+
+ # Variations with non-greedy loops.
+ /(|a|\2b|())*?/.match("aaabbb").to_a.should == ["", nil, nil]
+ /(a||\2b|())*?/.match("aaabbb").to_a.should == ["", nil, nil]
+ /(a|\2b||())*?/.match("aaabbb").to_a.should == ["", nil, nil]
+ /(a|\2b|()|)*?/.match("aaabbb").to_a.should == ["", nil, nil]
+ /(()|a|\3b|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil]
+ /(a|()|\3b|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil]
+ /(a|\2b|()|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil]
+ /(a|\3b|()|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil]
+ /(a|()|())*?/.match("aaa").to_a.should == ["", nil, nil, nil]
+ /^(()|a|())*?$/.match("aaa").to_a.should == ["aaa", "a", "", nil]
+ end
+end
diff --git a/spec/ruby/language/regexp/encoding_spec.rb b/spec/ruby/language/regexp/encoding_spec.rb
index 1f62244a28..febc3fdb37 100644
--- a/spec/ruby/language/regexp/encoding_spec.rb
+++ b/spec/ruby/language/regexp/encoding_spec.rb
@@ -1,6 +1,6 @@
# -*- encoding: binary -*-
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
describe "Regexps with encoding modifiers" do
it "supports /e (EUC encoding)" do
@@ -38,20 +38,28 @@ describe "Regexps with encoding modifiers" do
/#{/./}/n.match("\303\251").to_a.should == ["\303"]
end
+ it "warns when using /n with a match string with non-ASCII characters and an encoding other than ASCII-8BIT" do
+ -> { /./n.match("\303\251".force_encoding('utf-8')) }.should complain(%r{historical binary regexp match /.../n against UTF-8 string})
+ end
+
it 'uses US-ASCII as /n encoding if all chars are 7-bit' do
/./n.encoding.should == Encoding::US_ASCII
end
- it 'uses ASCII-8BIT as /n encoding if not all chars are 7-bit' do
- /\xFF/n.encoding.should == Encoding::ASCII_8BIT
+ it 'uses BINARY when is not initialized' do
+ Regexp.allocate.encoding.should == Encoding::BINARY
+ end
+
+ it 'uses BINARY as /n encoding if not all chars are 7-bit' do
+ /\xFF/n.encoding.should == Encoding::BINARY
end
it 'preserves US-ASCII as /n encoding through interpolation if all chars are 7-bit' do
/.#{/./}/n.encoding.should == Encoding::US_ASCII
end
- it 'preserves ASCII-8BIT as /n encoding through interpolation if all chars are 7-bit' do
- /\xFF#{/./}/n.encoding.should == Encoding::ASCII_8BIT
+ it 'preserves BINARY as /n encoding through interpolation if all chars are 7-bit' do
+ /\xFF#{/./}/n.encoding.should == Encoding::BINARY
end
it "supports /s (Windows_31J encoding)" do
@@ -100,4 +108,41 @@ describe "Regexps with encoding modifiers" do
it "selects last of multiple encoding specifiers" do
/foo/ensuensuens.should == /foo/s
end
+
+ it "raises Encoding::CompatibilityError when trying match against different encodings" do
+ -> { /\A[[:space:]]*\z/.match(" ".encode("UTF-16LE")) }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "raises Encoding::CompatibilityError when trying match? against different encodings" do
+ -> { /\A[[:space:]]*\z/.match?(" ".encode("UTF-16LE")) }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "raises Encoding::CompatibilityError when trying =~ against different encodings" do
+ -> { /\A[[:space:]]*\z/ =~ " ".encode("UTF-16LE") }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "raises Encoding::CompatibilityError when the regexp has a fixed, non-ASCII-compatible encoding" do
+ -> { Regexp.new("".force_encoding("UTF-16LE"), Regexp::FIXEDENCODING) =~ " ".encode("UTF-8") }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "raises Encoding::CompatibilityError when the regexp has a fixed encoding and the match string has non-ASCII characters" do
+ -> { Regexp.new("".force_encoding("US-ASCII"), Regexp::FIXEDENCODING) =~ "\303\251".force_encoding('UTF-8') }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "raises ArgumentError when trying to match a broken String" do
+ s = "\x80".force_encoding('UTF-8')
+ -> { s =~ /./ }.should raise_error(ArgumentError, "invalid byte sequence in UTF-8")
+ end
+
+ it "computes the Regexp Encoding for each interpolated Regexp instance" do
+ make_regexp = -> str { /#{str}/ }
+
+ r = make_regexp.call("été".force_encoding(Encoding::UTF_8))
+ r.should.fixed_encoding?
+ r.encoding.should == Encoding::UTF_8
+
+ r = make_regexp.call("abc".force_encoding(Encoding::UTF_8))
+ r.should_not.fixed_encoding?
+ r.encoding.should == Encoding::US_ASCII
+ end
end
diff --git a/spec/ruby/language/regexp/escapes_spec.rb b/spec/ruby/language/regexp/escapes_spec.rb
index 50ac22e51e..16a4d8c23b 100644
--- a/spec/ruby/language/regexp/escapes_spec.rb
+++ b/spec/ruby/language/regexp/escapes_spec.rb
@@ -1,9 +1,11 @@
# -*- encoding: binary -*-
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+# TODO: synchronize with spec/core/regexp/new_spec.rb -
+# escaping is also tested there
describe "Regexps with escape characters" do
- it "they're supported" do
+ it "supports escape sequences" do
/\t/.match("\t").to_a.should == ["\t"] # horizontal tab
/\v/.match("\v").to_a.should == ["\v"] # vertical tab
/\n/.match("\n").to_a.should == ["\n"] # newline
@@ -15,9 +17,7 @@ describe "Regexps with escape characters" do
# \nnn octal char (encoded byte value)
end
- it "support quoting meta-characters via escape sequence" do
- /\\/.match("\\").to_a.should == ["\\"]
- /\//.match("/").to_a.should == ["/"]
+ it "supports quoting meta-characters via escape sequence" do
# parenthesis, etc
/\(/.match("(").to_a.should == ["("]
/\)/.match(")").to_a.should == [")"]
@@ -25,6 +25,8 @@ describe "Regexps with escape characters" do
/\]/.match("]").to_a.should == ["]"]
/\{/.match("{").to_a.should == ["{"]
/\}/.match("}").to_a.should == ["}"]
+ /\</.match("<").to_a.should == ["<"]
+ /\>/.match(">").to_a.should == [">"]
# alternation separator
/\|/.match("|").to_a.should == ["|"]
# quantifiers
@@ -37,23 +39,93 @@ describe "Regexps with escape characters" do
/\$/.match("$").to_a.should == ["$"]
end
+ it "supports quoting meta-characters via escape sequence when used as a terminator" do
+ # parenthesis, etc
+ # %r[[, %r((, etc literals - are forbidden
+ %r(\().match("(").to_a.should == ["("]
+ %r(\)).match(")").to_a.should == [")"]
+ %r)\().match("(").to_a.should == ["("]
+ %r)\)).match(")").to_a.should == [")"]
+
+ %r[\[].match("[").to_a.should == ["["]
+ %r[\]].match("]").to_a.should == ["]"]
+ %r]\[].match("[").to_a.should == ["["]
+ %r]\]].match("]").to_a.should == ["]"]
+
+ %r{\{}.match("{").to_a.should == ["{"]
+ %r{\}}.match("}").to_a.should == ["}"]
+ %r}\{}.match("{").to_a.should == ["{"]
+ %r}\}}.match("}").to_a.should == ["}"]
+
+ %r<\<>.match("<").to_a.should == ["<"]
+ %r<\>>.match(">").to_a.should == [">"]
+ %r>\<>.match("<").to_a.should == ["<"]
+ %r>\>>.match(">").to_a.should == [">"]
+
+ # alternation separator
+ %r|\||.match("|").to_a.should == ["|"]
+ # quantifiers
+ %r?\??.match("?").to_a.should == ["?"]
+ %r.\...match(".").to_a.should == ["."]
+ %r*\**.match("*").to_a.should == ["*"]
+ %r+\++.match("+").to_a.should == ["+"]
+ # line anchors
+ %r^\^^.match("^").to_a.should == ["^"]
+ %r$\$$.match("$").to_a.should == ["$"]
+ end
+
+ it "supports quoting non-meta-characters via escape sequence when used as a terminator" do
+ non_meta_character_terminators = [
+ '!', '"', '#', '%', '&', "'", ',', '-', ':', ';', '@', '_', '`', '/', '=', '~'
+ ]
+
+ non_meta_character_terminators.each do |c|
+ pattern = eval("%r" + c + "\\" + c + c)
+ pattern.match(c).to_a.should == [c]
+ end
+ end
+
+ it "does not change semantics of escaped non-meta-character when used as a terminator" do
+ all_terminators = [*("!".."/"), *(":".."@"), *("[".."`"), *("{".."~")]
+ meta_character_terminators = ["$", "^", "*", "+", ".", "?", "|", "}", ")", ">", "]"]
+ special_cases = ['(', '{', '[', '<', '\\']
+
+ # it should be equivalent to
+ # [ '!', '"', '#', '%', '&', "'", ',', '-', ':', ';', '@', '_', '`', '/', '=', '~' ]
+ non_meta_character_terminators = all_terminators - meta_character_terminators - special_cases
+
+ non_meta_character_terminators.each do |c|
+ pattern = eval("%r" + c + "\\" + c + c)
+ pattern.should == /#{c}/
+ end
+ end
+
+ it "does not change semantics of escaped meta-character when used as a terminator" do
+ meta_character_terminators = ["$", "^", "*", "+", ".", "?", "|", "}", ")", ">", "]"]
+
+ meta_character_terminators.each do |c|
+ pattern = eval("%r" + c + "\\" + c + c)
+ pattern.should == eval("/\\#{c}/")
+ end
+ end
+
it "allows any character to be escaped" do
/\y/.match("y").to_a.should == ["y"]
end
- it "support \\x (hex characters)" do
+ it "supports \\x (hex characters)" do
/\xA/.match("\nxyz").to_a.should == ["\n"]
/\x0A/.match("\n").to_a.should == ["\n"]
/\xAA/.match("\nA").should be_nil
/\x0AA/.match("\nA").to_a.should == ["\nA"]
/\xAG/.match("\nG").to_a.should == ["\nG"]
# Non-matches
- lambda { eval('/\xG/') }.should raise_error(SyntaxError)
+ -> { eval('/\xG/') }.should raise_error(SyntaxError)
# \x{7HHHHHHH} wide hexadecimal char (character code point value)
end
- it "support \\c (control characters)" do
+ it "supports \\c (control characters)" do
#/\c \c@\c`/.match("\00\00\00").to_a.should == ["\00\00\00"]
/\c#\cc\cC/.match("\03\03\03").to_a.should == ["\03\03\03"]
/\c'\cG\cg/.match("\a\a\a").to_a.should == ["\a\a\a"]
@@ -67,15 +139,31 @@ describe "Regexps with escape characters" do
/\cJ/.match("\r").should be_nil
# Parsing precedence
- /\cJ+/.match("\n\n").to_a.should == ["\n\n"] # Quantifers apply to entire escape sequence
+ /\cJ+/.match("\n\n").to_a.should == ["\n\n"] # Quantifiers apply to entire escape sequence
/\\cJ/.match("\\cJ").to_a.should == ["\\cJ"]
- lambda { eval('/[abc\x]/') }.should raise_error(SyntaxError) # \x is treated as a escape sequence even inside a character class
+ -> { eval('/[abc\x]/') }.should raise_error(SyntaxError) # \x is treated as a escape sequence even inside a character class
# Syntax error
- lambda { eval('/\c/') }.should raise_error(SyntaxError)
+ -> { eval('/\c/') }.should raise_error(SyntaxError)
# \cx control char (character code point value)
# \C-x control char (character code point value)
# \M-x meta (x|0x80) (character code point value)
# \M-\C-x meta control char (character code point value)
end
+
+ it "handles three digit octal escapes starting with 0" do
+ /[\000-\b]/.match("\x00")[0].should == "\x00"
+ end
+
+ it "handles control escapes with \\C-x syntax" do
+ /\C-*\C-J\C-j/.match("\n\n\n")[0].should == "\n\n\n"
+ end
+
+ it "supports the \\K keep operator" do
+ /a\Kb/.match("ab")[0].should == "b"
+ end
+
+ it "supports the \\R line break escape" do
+ /\R/.match("\n")[0].should == "\n"
+ end
end
diff --git a/spec/ruby/language/regexp/grouping_spec.rb b/spec/ruby/language/regexp/grouping_spec.rb
index 443cab7ee0..2f04a04018 100644
--- a/spec/ruby/language/regexp/grouping_spec.rb
+++ b/spec/ruby/language/regexp/grouping_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
describe "Regexps with grouping" do
it "support ()" do
@@ -12,7 +12,7 @@ describe "Regexps with grouping" do
end
it "raises a SyntaxError when parentheses aren't balanced" do
- lambda { eval "/(hay(st)ack/" }.should raise_error(SyntaxError)
+ -> { eval "/(hay(st)ack/" }.should raise_error(SyntaxError)
end
it "supports (?: ) (non-capturing group)" do
@@ -20,4 +20,44 @@ describe "Regexps with grouping" do
# Parsing precedence
/(?:xdigit:)/.match("xdigit:").to_a.should == ["xdigit:"]
end
+
+ it "group names cannot start with digits or minus" do
+ -> { Regexp.new("(?<1a>a)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<-a>a)") }.should raise_error(RegexpError)
+ end
+
+ it "ignore capture groups in line comments" do
+ /^
+ (a) # there is a capture group on this line
+ b # there is no capture group on this line (not even here)
+ $/x.match("ab").to_a.should == [ "ab", "a" ]
+ end
+
+ it "does not consider # inside a character class as a comment" do
+ # From https://github.com/rubocop/rubocop/blob/39fcf1c568/lib/rubocop/cop/utils/format_string.rb#L18
+ regexp = /
+ % (?<type>%) # line comment
+ | % (?<flags>(?-mix:[ #0+-]|(?-mix:(\d+)\$))*) (?#group comment)
+ (?:
+ (?: (?-mix:(?<width>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:\.(?<precision>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:<(?<name>\w+)>)?
+ | (?-mix:(?<width>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:<(?<name>\w+)>) (?-mix:\.(?<precision>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))?
+ | (?-mix:<(?<name>\w+)>) (?<more_flags>(?-mix:[ #0+-]|(?-mix:(\d+)\$))*) (?-mix:(?<width>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:\.(?<precision>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))?
+ ) (?-mix:(?<type>[bBdiouxXeEfgGaAcps]))
+ | (?-mix:(?<width>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:\.(?<precision>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:\{(?<name>\w+)\})
+ )
+ /x
+ regexp.named_captures.should == {
+ "type" => [1, 13],
+ "flags" => [2],
+ "width" => [3, 6, 11, 14],
+ "precision" => [4, 8, 12, 15],
+ "name" => [5, 7, 9, 16],
+ "more_flags" => [10]
+ }
+ match = regexp.match("%6.3f")
+ match[:width].should == '6'
+ match[:precision].should == '3'
+ match[:type].should == 'f'
+ match.to_a.should == [ "%6.3f", nil, "", "6", "3"] + [nil] * 8 + ["f"] + [nil] * 3
+ end
end
diff --git a/spec/ruby/language/regexp/interpolation_spec.rb b/spec/ruby/language/regexp/interpolation_spec.rb
index 5536c718f1..6951fd38ca 100644
--- a/spec/ruby/language/regexp/interpolation_spec.rb
+++ b/spec/ruby/language/regexp/interpolation_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
describe "Regexps with interpolation" do
@@ -36,14 +36,14 @@ describe "Regexps with interpolation" do
it "gives precedence to escape sequences over substitution" do
str = "J"
- /\c#{str}/.to_s.should == '(?-mix:\c#' + '{str})'
+ /\c#{str}/.to_s.should include('{str}')
end
it "throws RegexpError for malformed interpolation" do
s = ""
- lambda { /(#{s}/ }.should raise_error(RegexpError)
+ -> { /(#{s}/ }.should raise_error(RegexpError)
s = "("
- lambda { /#{s}/ }.should raise_error(RegexpError)
+ -> { /#{s}/ }.should raise_error(RegexpError)
end
it "allows interpolation in extended mode" do
diff --git a/spec/ruby/language/regexp/modifiers_spec.rb b/spec/ruby/language/regexp/modifiers_spec.rb
index a7052a941c..2f5522bc8a 100644
--- a/spec/ruby/language/regexp/modifiers_spec.rb
+++ b/spec/ruby/language/regexp/modifiers_spec.rb
@@ -1,7 +1,7 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
-describe "Regexps with modifers" do
+describe "Regexps with modifiers" do
it "supports /i (case-insensitive)" do
/foo/i.match("FOO").to_a.should == ["FOO"]
end
@@ -36,14 +36,12 @@ describe "Regexps with modifers" do
/foo/imox.match("foo").to_a.should == ["foo"]
/foo/imoximox.match("foo").to_a.should == ["foo"]
- lambda { eval('/foo/a') }.should raise_error(SyntaxError)
+ -> { eval('/foo/a') }.should raise_error(SyntaxError)
end
- ruby_version_is "2.4" do
- it "supports (?~) (absent operator)" do
- Regexp.new("(?~foo)").match("hello").to_a.should == ["hello"]
- "foo".scan(Regexp.new("(?~foo)")).should == ["fo","o",""]
- end
+ it "supports (?~) (absent operator)" do
+ Regexp.new("(?~foo)").match("hello").to_a.should == ["hello"]
+ "foo".scan(Regexp.new("(?~foo)")).should == ["fo","o",""]
end
it "supports (?imx-imx) (inline modifiers)" do
@@ -78,7 +76,7 @@ describe "Regexps with modifers" do
/(?i-i)foo/.match("FOO").should be_nil
/(?ii)foo/.match("FOO").to_a.should == ["FOO"]
/(?-)foo/.match("foo").to_a.should == ["foo"]
- lambda { eval('/(?o)/') }.should raise_error(SyntaxError)
+ -> { eval('/(?o)/') }.should raise_error(SyntaxError)
end
it "supports (?imx-imx:expr) (scoped inline modifiers)" do
@@ -98,7 +96,7 @@ describe "Regexps with modifers" do
/(?i-i:foo)/.match("FOO").should be_nil
/(?ii:foo)/.match("FOO").to_a.should == ["FOO"]
/(?-:)foo/.match("foo").to_a.should == ["foo"]
- lambda { eval('/(?o:)/') }.should raise_error(SyntaxError)
+ -> { eval('/(?o:)/') }.should raise_error(SyntaxError)
end
it "supports . with /m" do
@@ -106,7 +104,7 @@ describe "Regexps with modifers" do
/./m.match("\n").to_a.should == ["\n"]
end
- it "supports ASII/Unicode modifiers" do
+ it "supports ASCII/Unicode modifiers" do
eval('/(?a)[[:alpha:]]+/').match("a\u3042").to_a.should == ["a"]
eval('/(?d)[[:alpha:]]+/').match("a\u3042").to_a.should == ["a\u3042"]
eval('/(?u)[[:alpha:]]+/').match("a\u3042").to_a.should == ["a\u3042"]
diff --git a/spec/ruby/language/regexp/repetition_spec.rb b/spec/ruby/language/regexp/repetition_spec.rb
index 2fc8a74a47..9a191d74e2 100644
--- a/spec/ruby/language/regexp/repetition_spec.rb
+++ b/spec/ruby/language/regexp/repetition_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
describe "Regexps with repetition" do
it "supports * (0 or more of previous subexpression)" do
@@ -34,24 +34,109 @@ describe "Regexps with repetition" do
/.([0-9]){3,5}?foo/.match("9876543210foo").to_a.should == ["543210foo", "0"]
end
- ruby_version_is ""..."2.4" do
- it "does not treat {m,n}+ as possessive" do
+ it "does not treat {m,n}+ as possessive" do
+ -> {
@regexp = eval "/foo(A{0,1}+)Abar/"
- @regexp.match("fooAAAbar").to_a.should == ["fooAAAbar", "AA"]
- end
- end
-
- ruby_version_is "2.4" do
- it "does not treat {m,n}+ as possessive" do
- -> {
- @regexp = eval "/foo(A{0,1}+)Abar/"
- }.should complain(/nested repeat operato/)
- @regexp.match("fooAAAbar").to_a.should == ["fooAAAbar", "AA"]
- end
+ }.should complain(/nested repeat operator/)
+ @regexp.match("fooAAAbar").to_a.should == ["fooAAAbar", "AA"]
end
it "supports ? (0 or 1 of previous subexpression)" do
/a?/.match("aaa").to_a.should == ["a"]
/a?/.match("bbb").to_a.should == [""]
end
+
+ it "handles incomplete range quantifiers" do
+ /a{}/.match("a{}")[0].should == "a{}"
+ /a{,}/.match("a{,}")[0].should == "a{,}"
+ /a{1/.match("a{1")[0].should == "a{1"
+ /a{1,2/.match("a{1,2")[0].should == "a{1,2"
+ /a{,5}/.match("aaa")[0].should == "aaa"
+ end
+
+ it "lets us use quantifiers on assertions" do
+ /a^?b/.match("ab")[0].should == "ab"
+ /a$?b/.match("ab")[0].should == "ab"
+ /a\A?b/.match("ab")[0].should == "ab"
+ /a\Z?b/.match("ab")[0].should == "ab"
+ /a\z?b/.match("ab")[0].should == "ab"
+ /a\G?b/.match("ab")[0].should == "ab"
+ /a\b?b/.match("ab")[0].should == "ab"
+ /a\B?b/.match("ab")[0].should == "ab"
+ /a(?=c)?b/.match("ab")[0].should == "ab"
+ /a(?!=b)?b/.match("ab")[0].should == "ab"
+ /a(?<=c)?b/.match("ab")[0].should == "ab"
+ /a(?<!a)?b/.match("ab")[0].should == "ab"
+ end
+
+ it "does not delete optional assertions" do
+ /(?=(a))?/.match("a").to_a.should == [ "", "a" ]
+ end
+
+ it "supports nested quantifiers" do
+ suppress_warning do
+ eval <<-RUBY
+ /a***/.match("aaa")[0].should == "aaa"
+
+ # a+?* should not be reduced, it should be equivalent to (a+?)*
+ # NB: the capture group prevents regex engines from reducing the two quantifiers
+ # https://bugs.ruby-lang.org/issues/17341
+ /a+?*/.match("")[0].should == ""
+ /(a+?)*/.match("")[0].should == ""
+
+ /a+?*/.match("a")[0].should == "a"
+ /(a+?)*/.match("a")[0].should == "a"
+
+ ruby_bug '#17341', ''...'3.0' do
+ /a+?*/.match("aa")[0].should == "aa"
+ end
+ /(a+?)*/.match("aa")[0].should == "aa"
+
+ # a+?+ should not be reduced, it should be equivalent to (a+?)+
+ # https://bugs.ruby-lang.org/issues/17341
+ /a+?+/.match("").should == nil
+ /(a+?)+/.match("").should == nil
+
+ /a+?+/.match("a")[0].should == "a"
+ /(a+?)+/.match("a")[0].should == "a"
+
+ ruby_bug '#17341', ''...'3.0' do
+ /a+?+/.match("aa")[0].should == "aa"
+ end
+ /(a+?)+/.match("aa")[0].should == "aa"
+
+ # both a**? and a+*? should be equivalent to (a+)??
+ # this quantifier would rather match nothing, but if that's not possible,
+ # it will greedily take everything
+ /a**?/.match("")[0].should == ""
+ /(a*)*?/.match("")[0].should == ""
+ /a+*?/.match("")[0].should == ""
+ /(a+)*?/.match("")[0].should == ""
+ /(a+)??/.match("")[0].should == ""
+
+ /a**?/.match("aaa")[0].should == ""
+ /(a*)*?/.match("aaa")[0].should == ""
+ /a+*?/.match("aaa")[0].should == ""
+ /(a+)*?/.match("aaa")[0].should == ""
+ /(a+)??/.match("aaa")[0].should == ""
+
+ /b.**?b/.match("baaabaaab")[0].should == "baaabaaab"
+ /b(.*)*?b/.match("baaabaaab")[0].should == "baaabaaab"
+ /b.+*?b/.match("baaabaaab")[0].should == "baaabaaab"
+ /b(.+)*?b/.match("baaabaaab")[0].should == "baaabaaab"
+ /b(.+)??b/.match("baaabaaab")[0].should == "baaabaaab"
+ RUBY
+ end
+ end
+
+ it "treats ? after {n} quantifier as another quantifier, not as non-greedy marker" do
+ /a{2}?/.match("").to_a.should == [""]
+ end
+
+ it "matches zero-width capture groups in optional iterations of loops" do
+ /()?/.match("").to_a.should == ["", ""]
+ /(a*)?/.match("").to_a.should == ["", ""]
+ /(a*)*/.match("").to_a.should == ["", ""]
+ /(?:a|()){500,1000}/.match("a" * 500).to_a.should == ["a" * 500, ""]
+ end
end
diff --git a/spec/ruby/language/regexp/subexpression_call_spec.rb b/spec/ruby/language/regexp/subexpression_call_spec.rb
new file mode 100644
index 0000000000..16b64cb327
--- /dev/null
+++ b/spec/ruby/language/regexp/subexpression_call_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexps with subexpression calls" do
+ it "allows numeric subexpression calls" do
+ /(a)\g<1>/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+
+ it "treats subexpression calls as distinct from simple back-references" do
+ # Back-references only match a string which is equal to the original captured string.
+ /(?<three_digits>[0-9]{3})-\k<three_digits>/.match("123-123")[0].should == "123-123"
+ /(?<three_digits>[0-9]{3})-\k<three_digits>/.match("123-456").should == nil
+ # However, subexpression calls reuse the previous expression and can match a different
+ # string.
+ /(?<three_digits>[0-9]{3})-\g<three_digits>/.match("123-456")[0].should == "123-456"
+ end
+
+ it "allows recursive subexpression calls" do
+ # This pattern matches well-nested parenthesized expression.
+ parens = /^ (?<parens> (?: \( \g<parens> \) | [^()] )* ) $/x
+ parens.match("((a)(b))c(d)")[0].should == "((a)(b))c(d)"
+ parens.match("((a)(b)c(d)").should == nil
+ end
+
+ it "allows access to back-references from the current level" do
+ # Using \\k<first_char-0> accesses the last value captured in first_char
+ # on the current stack level.
+ mirror = /^ (?<mirror> (?: (?<first_char>.) \g<mirror> \k<first_char-0> )? ) $/x
+ mirror.match("abccba")[0].should == "abccba"
+ mirror.match("abccbd").should == nil
+
+ # OTOH, using \\k<first_char> accesses the last value captured in first_char,
+ # regardless of the stack level. Therefore, it can't be used to implement
+ # the mirror language.
+ broken_mirror = /^ (?<mirror> (?: (?<first_char>.) \g<mirror> \k<first_char> )? ) $/x
+ broken_mirror.match("abccba").should == nil
+ # This matches because the 'c' is captured in first_char and that value is
+ # then used for all subsequent back-references, regardless of nesting.
+ broken_mirror.match("abcccc")[0].should == "abcccc"
+ end
+
+ it "allows + and - in group names and referential constructs that don't use levels, i.e. subexpression calls" do
+ /(?<a+>a)\g<a+>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a+b>a)\g<a+b>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a+1>a)\g<a+1>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a->a)\g<a->/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a-b>a)\g<a-b>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a-1>a)\g<a-1>/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+end
diff --git a/spec/ruby/language/regexp_spec.rb b/spec/ruby/language/regexp_spec.rb
index d4b0c81128..d49689349d 100644
--- a/spec/ruby/language/regexp_spec.rb
+++ b/spec/ruby/language/regexp_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/classes'
describe "Literal Regexps" do
it "matches against $_ (last input) in a conditional if no explicit matchee provided" do
@@ -18,6 +18,12 @@ describe "Literal Regexps" do
/Hello/.should be_kind_of(Regexp)
end
+ ruby_version_is "3.0" do
+ it "is frozen" do
+ /Hello/.should.frozen?
+ end
+ end
+
it "caches the Regexp object" do
rs = []
2.times do |i|
@@ -27,7 +33,7 @@ describe "Literal Regexps" do
end
it "throws SyntaxError for malformed literals" do
- lambda { eval('/(/') }.should raise_error(SyntaxError)
+ -> { eval('/(/') }.should raise_error(SyntaxError)
end
#############################################################################
@@ -54,7 +60,7 @@ describe "Literal Regexps" do
it "disallows first part of paired delimiters to be used as non-paired delimiters" do
LanguageSpecs.paired_delimiters.each do |p0, p1|
- lambda { eval("%r#{p0} foo #{p0}") }.should raise_error(SyntaxError)
+ -> { eval("%r#{p0} foo #{p0}") }.should raise_error(SyntaxError)
end
end
@@ -65,11 +71,11 @@ describe "Literal Regexps" do
end
it "disallows alphabets as non-paired delimiter with %r" do
- lambda { eval('%ra foo a') }.should raise_error(SyntaxError)
+ -> { eval('%ra foo a') }.should raise_error(SyntaxError)
end
it "disallows spaces after %r and delimiter" do
- lambda { eval('%r !foo!') }.should raise_error(SyntaxError)
+ -> { eval('%r !foo!') }.should raise_error(SyntaxError)
end
it "allows unescaped / to be used with %r" do
@@ -90,14 +96,13 @@ describe "Literal Regexps" do
/./.match("\0").to_a.should == ["\0"]
end
-
it "supports | (alternations)" do
/a|b/.match("a").to_a.should == ["a"]
end
it "supports (?> ) (embedded subexpression)" do
/(?>foo)(?>bar)/.match("foobar").to_a.should == ["foobar"]
- /(?>foo*)obar/.match("foooooooobar").should be_nil # it is possesive
+ /(?>foo*)obar/.match("foooooooobar").should be_nil # it is possessive
end
it "supports (?# )" do
@@ -109,6 +114,13 @@ describe "Literal Regexps" do
/foo.(?<=\d)/.match("fooA foo1").to_a.should == ["foo1"]
end
+ ruby_bug "#13671", ""..."3.3" do # https://bugs.ruby-lang.org/issues/13671
+ it "handles a lookbehind with ss characters" do
+ r = Regexp.new("(?<!dss)", Regexp::IGNORECASE)
+ r.should =~ "✨"
+ end
+ end
+
it "supports (?<! ) (negative lookbehind)" do
/foo.(?<!\d)/.match("foo1 fooA").to_a.should == ["fooA"]
end
@@ -147,4 +159,11 @@ describe "Literal Regexps" do
pattern.should_not =~ 'fooF'
pattern.should_not =~ 'T'
end
+
+ it "support handling unicode 9.0 characters with POSIX bracket expressions" do
+ char_lowercase = "\u{104D8}" # OSAGE SMALL LETTER A
+ /[[:lower:]]/.match(char_lowercase).to_s.should == char_lowercase
+ char_uppercase = "\u{104B0}" # OSAGE CAPITAL LETTER A
+ /[[:upper:]]/.match(char_uppercase).to_s.should == char_uppercase
+ end
end
diff --git a/spec/ruby/language/rescue_spec.rb b/spec/ruby/language/rescue_spec.rb
index 281bb8fde8..b91b52fa0f 100644
--- a/spec/ruby/language/rescue_spec.rb
+++ b/spec/ruby/language/rescue_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/rescue', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/rescue'
class SpecificExampleException < StandardError
end
@@ -23,16 +23,68 @@ describe "The rescue keyword" do
end.should == :caught
end
- it "can capture the raised exception in a local variable" do
+ describe 'can capture the raised exception' do
+ before :all do
+ require_relative 'fixtures/rescue_captures'
+ end
+
+ it 'in a local variable' do
+ RescueSpecs::LocalVariableCaptor.should_capture_exception
+ end
+
+ it 'in a class variable' do
+ RescueSpecs::ClassVariableCaptor.should_capture_exception
+ end
+
+ it 'in a constant' do
+ RescueSpecs::ConstantCaptor.should_capture_exception
+ end
+
+ it 'in a global variable' do
+ RescueSpecs::GlobalVariableCaptor.should_capture_exception
+ end
+
+ it 'in an instance variable' do
+ RescueSpecs::InstanceVariableCaptor.should_capture_exception
+ end
+
+ it 'using a safely navigated setter method' do
+ RescueSpecs::SafeNavigationSetterCaptor.should_capture_exception
+ end
+
+ it 'using a setter method' do
+ RescueSpecs::SetterCaptor.should_capture_exception
+ end
+
+ it 'using a square brackets setter' do
+ RescueSpecs::SquareBracketsCaptor.should_capture_exception
+ end
+ end
+
+ it "returns value from `rescue` if an exception was raised" do
begin
- raise SpecificExampleException, "some text"
- rescue SpecificExampleException => e
- e.message.should == "some text"
+ raise
+ rescue
+ :caught
+ end.should == :caught
+ end
+
+ it "returns value from `else` section if no exceptions were raised" do
+ result = begin
+ :begin
+ rescue
+ :rescue
+ else
+ :else
+ ensure
+ :ensure
end
+
+ result.should == :else
end
it "can rescue multiple raised exceptions with a single rescue block" do
- [lambda{raise ArbitraryException}, lambda{raise SpecificExampleException}].map do |block|
+ [->{raise ArbitraryException}, ->{raise SpecificExampleException}].map do |block|
begin
block.call
rescue SpecificExampleException, ArbitraryException
@@ -50,7 +102,7 @@ describe "The rescue keyword" do
end
caught_it.should be_true
caught = []
- [lambda{raise ArbitraryException}, lambda{raise SpecificExampleException}].each do |block|
+ [->{raise ArbitraryException}, ->{raise SpecificExampleException}].each do |block|
begin
block.call
rescue *exception_list
@@ -63,6 +115,18 @@ describe "The rescue keyword" do
end
end
+ it "converts the splatted list of exceptions using #to_a" do
+ exceptions = mock("to_a")
+ exceptions.should_receive(:to_a).and_return(exception_list)
+ caught_it = false
+ begin
+ raise SpecificExampleException, "not important"
+ rescue *exceptions
+ caught_it = true
+ end
+ caught_it.should be_true
+ end
+
it "can combine a splatted list of exceptions with a literal list of exceptions" do
caught_it = false
begin
@@ -72,7 +136,7 @@ describe "The rescue keyword" do
end
caught_it.should be_true
caught = []
- [lambda{raise ArbitraryException}, lambda{raise SpecificExampleException}].each do |block|
+ [->{raise ArbitraryException}, ->{raise SpecificExampleException}].each do |block|
begin
block.call
rescue ArbitraryException, *exception_list
@@ -86,7 +150,7 @@ describe "The rescue keyword" do
end
it "will only rescue the specified exceptions when doing a splat rescue" do
- lambda do
+ -> do
begin
raise OtherCustomException, "not rescued!"
rescue *exception_list
@@ -94,6 +158,45 @@ describe "The rescue keyword" do
end.should raise_error(OtherCustomException)
end
+ it "can rescue different types of exceptions in different ways" do
+ begin
+ raise Exception
+ rescue RuntimeError
+ rescue StandardError
+ rescue Exception
+ ScratchPad << :exception
+ end
+
+ ScratchPad.recorded.should == [:exception]
+ end
+
+ it "rescues exception within the first suitable section in order of declaration" do
+ begin
+ raise StandardError
+ rescue RuntimeError
+ ScratchPad << :runtime_error
+ rescue StandardError
+ ScratchPad << :standard_error
+ rescue Exception
+ ScratchPad << :exception
+ end
+
+ ScratchPad.recorded.should == [:standard_error]
+ end
+
+ it "rescues the exception in the deepest rescue block declared to handle the appropriate exception type" do
+ begin
+ begin
+ RescueSpecs.raise_standard_error
+ rescue ArgumentError
+ end
+ rescue StandardError => e
+ e.backtrace.first.should include ":in `raise_standard_error'"
+ else
+ fail("exception wasn't handled by the correct rescue block")
+ end
+ end
+
it "will execute an else block only if no exceptions were raised" do
result = begin
ScratchPad << :one
@@ -147,6 +250,18 @@ describe "The rescue keyword" do
ScratchPad.recorded.should == [:one, :else_ran, :ensure_ran, :outside_begin]
end
+ it "raises SyntaxError when else is used without rescue and ensure" do
+ -> {
+ eval <<-ruby
+ begin
+ ScratchPad << :begin
+ else
+ ScratchPad << :else
+ end
+ ruby
+ }.should raise_error(SyntaxError, /else without rescue is useless/)
+ end
+
it "will not execute an else block if an exception was raised" do
result = begin
ScratchPad << :one
@@ -203,7 +318,7 @@ describe "The rescue keyword" do
end
it "will not rescue errors raised in an else block in the rescue block above it" do
- lambda do
+ -> do
begin
ScratchPad << :one
rescue Exception
@@ -223,14 +338,31 @@ describe "The rescue keyword" do
a.should == 'ac'
end
- it "without classes will not rescue Exception" do
- lambda do
+ context "without rescue expression" do
+ it "will rescue only StandardError and its subclasses" do
begin
- raise Exception
+ raise StandardError
rescue
- 'Exception wrongly rescued'
+ ScratchPad << :caught
end
- end.should raise_error(Exception)
+
+ ScratchPad.recorded.should == [:caught]
+ end
+
+ it "will not rescue exceptions except StandardError" do
+ [ Exception.new, NoMemoryError.new, ScriptError.new, SecurityError.new,
+ SignalException.new('INT'), SystemExit.new, SystemStackError.new
+ ].each do |exception|
+ -> {
+ begin
+ raise exception
+ rescue
+ ScratchPad << :caught
+ end
+ }.should raise_error(exception.class)
+ end
+ ScratchPad.recorded.should == []
+ end
end
it "uses === to compare against rescued classes" do
@@ -253,7 +385,7 @@ describe "The rescue keyword" do
it "only accepts Module or Class in rescue clauses" do
rescuer = 42
- lambda {
+ -> {
begin
raise "error"
rescue rescuer
@@ -265,7 +397,7 @@ describe "The rescue keyword" do
it "only accepts Module or Class in splatted rescue clauses" do
rescuer = [42]
- lambda {
+ -> {
begin
raise "error"
rescue *rescuer
@@ -276,11 +408,23 @@ describe "The rescue keyword" do
end
it "evaluates rescue expressions only when needed" do
- invalid_rescuer = Object.new
begin
- :foo
- rescue rescuer
- end.should == :foo
+ ScratchPad << :foo
+ rescue -> { ScratchPad << :bar; StandardError }.call
+ end
+
+ ScratchPad.recorded.should == [:foo]
+ end
+
+ it "suppresses exception from block when raises one from rescue expression" do
+ -> {
+ begin
+ raise "from block"
+ rescue (raise "from rescue expression")
+ end
+ }.should raise_error(RuntimeError, "from rescue expression") { |e|
+ e.cause.message.should == "from block"
+ }
end
it "should splat the handling Error classes" do
@@ -291,10 +435,81 @@ describe "The rescue keyword" do
end.should == :expected
end
- ruby_version_is "2.4" do
- it "allows 'rescue' in method arguments" do
- two = eval '1.+ (raise("Error") rescue 1)'
- two.should == 2
+ it "allows rescue in class" do
+ eval <<-ruby
+ class RescueInClassExample
+ raise SpecificExampleException
+ rescue SpecificExampleException
+ ScratchPad << :caught
+ end
+ ruby
+
+ ScratchPad.recorded.should == [:caught]
+ end
+
+ it "does not allow rescue in {} block" do
+ -> {
+ eval <<-ruby
+ lambda {
+ raise SpecificExampleException
+ rescue SpecificExampleException
+ :caught
+ }
+ ruby
+ }.should raise_error(SyntaxError)
+ end
+
+ it "allows rescue in 'do end' block" do
+ lambda = eval <<-ruby
+ lambda do
+ raise SpecificExampleException
+ rescue SpecificExampleException
+ ScratchPad << :caught
+ end.call
+ ruby
+
+ ScratchPad.recorded.should == [:caught]
+ end
+
+ it "allows 'rescue' in method arguments" do
+ two = eval '1.+ (raise("Error") rescue 1)'
+ two.should == 2
+ end
+
+ it "requires the 'rescue' in method arguments to be wrapped in parens" do
+ -> { eval '1.+(1 rescue 1)' }.should raise_error(SyntaxError)
+ eval('1.+((1 rescue 1))').should == 2
+ end
+
+ describe "inline form" do
+ it "can be inlined" do
+ a = 1/0 rescue 1
+ a.should == 1
+ end
+
+ it "doesn't except rescue expression" do
+ -> {
+ eval <<-ruby
+ a = 1 rescue RuntimeError 2
+ ruby
+ }.should raise_error(SyntaxError)
+ end
+
+ it "rescues only StandardError and its subclasses" do
+ a = raise(StandardError) rescue 1
+ a.should == 1
+
+ -> {
+ a = raise(Exception) rescue 1
+ }.should raise_error(Exception)
+ end
+
+ it "rescues with multiple assignment" do
+
+ a, b = raise rescue [1, 2]
+
+ a.should == 1
+ b.should == 2
end
end
end
diff --git a/spec/ruby/language/retry_spec.rb b/spec/ruby/language/retry_spec.rb
index 96e69b763a..ee5377946f 100644
--- a/spec/ruby/language/retry_spec.rb
+++ b/spec/ruby/language/retry_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The retry statement" do
it "re-executes the closest block" do
@@ -32,7 +32,7 @@ describe "The retry statement" do
end
it "raises a SyntaxError when used outside of a begin statement" do
- lambda { eval 'retry' }.should raise_error(SyntaxError)
+ -> { eval 'retry' }.should raise_error(SyntaxError)
end
end
diff --git a/spec/ruby/language/return_spec.rb b/spec/ruby/language/return_spec.rb
index 6a98fa6d12..a62ed1242d 100644
--- a/spec/ruby/language/return_spec.rb
+++ b/spec/ruby/language/return_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/return', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/return'
describe "The return keyword" do
it "returns any object directly" do
@@ -159,7 +159,7 @@ describe "The return keyword" do
end
it "executes the ensure clause when begin/ensure are inside a lambda" do
- lambda do
+ -> do
begin
return
ensure
@@ -176,15 +176,15 @@ describe "The return keyword" do
end
it "causes lambda to return nil if invoked without any arguments" do
- lambda { return; 456 }.call.should be_nil
+ -> { return; 456 }.call.should be_nil
end
it "causes lambda to return nil if invoked with an empty expression" do
- lambda { return (); 456 }.call.should be_nil
+ -> { return (); 456 }.call.should be_nil
end
it "causes lambda to return the value passed to return" do
- lambda { return 123; 456 }.call.should == 123
+ -> { return 123; 456 }.call.should == 123
end
it "causes the method that lexically encloses the block to return" do
@@ -250,216 +250,240 @@ describe "The return keyword" do
end
end
- ruby_version_is '2.4.2' do
- describe "at top level" do
- before :each do
- @filename = tmp("top_return.rb")
- ScratchPad.record []
- end
+ describe "at top level" do
+ before :each do
+ @filename = tmp("top_return.rb")
+ ScratchPad.record []
+ end
- after do
- rm_r @filename
- end
+ after do
+ rm_r @filename
+ end
- it "stops file execution" do
- ruby_exe(<<-END_OF_CODE).should == "before return\n"
- puts "before return"
- return
+ it "stops file execution" do
+ ruby_exe(<<-END_OF_CODE).should == "before return\n"
+ puts "before return"
+ return
+
+ puts "after return"
+ END_OF_CODE
+
+ $?.exitstatus.should == 0
+ end
+
+ describe "within if" do
+ it "is allowed" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before if"
+ if true
+ return
+ end
- puts "after return"
+ ScratchPad << "after if"
END_OF_CODE
- $?.exitstatus.should == 0
+ load @filename
+ ScratchPad.recorded.should == ["before if"]
end
+ end
- describe "within if" do
- it "is allowed" do
- File.write(@filename, <<-END_OF_CODE)
- ScratchPad << "before if"
- if true
- return
- end
+ describe "within while loop" do
+ it "is allowed" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before while"
+ while true
+ return
+ end
- ScratchPad << "after if"
- END_OF_CODE
+ ScratchPad << "after while"
+ END_OF_CODE
- load @filename
- ScratchPad.recorded.should == ["before if"]
- end
+ load @filename
+ ScratchPad.recorded.should == ["before while"]
end
+ end
- describe "within while loop" do
- it "is allowed" do
- File.write(@filename, <<-END_OF_CODE)
- ScratchPad << "before while"
- while true
- return
- end
+ describe "within a begin" do
+ it "is allowed in begin block" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before begin"
+ begin
+ return
+ end
- ScratchPad << "after while"
- END_OF_CODE
+ ScratchPad << "after begin"
+ END_OF_CODE
- load @filename
- ScratchPad.recorded.should == ["before while"]
- end
+ load @filename
+ ScratchPad.recorded.should == ["before begin"]
end
- describe "within a begin" do
- it "is allowed in begin block" do
- File.write(@filename, <<-END_OF_CODE)
- ScratchPad << "before begin"
- begin
- return
- end
+ it "is allowed in ensure block" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before begin"
+ begin
+ ensure
+ return
+ end
- ScratchPad << "after begin"
- END_OF_CODE
+ ScratchPad << "after begin"
+ END_OF_CODE
- load @filename
- ScratchPad.recorded.should == ["before begin"]
- end
+ load @filename
+ ScratchPad.recorded.should == ["before begin"]
+ end
- it "is allowed in ensure block" do
- File.write(@filename, <<-END_OF_CODE)
- ScratchPad << "before begin"
- begin
- ensure
- return
- end
+ it "is allowed in rescue block" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before begin"
+ begin
+ raise
+ rescue RuntimeError
+ return
+ end
- ScratchPad << "after begin"
- END_OF_CODE
+ ScratchPad << "after begin"
+ END_OF_CODE
- load @filename
- ScratchPad.recorded.should == ["before begin"]
- end
+ load @filename
+ ScratchPad.recorded.should == ["before begin"]
+ end
- it "is allowed in rescue block" do
- File.write(@filename, <<-END_OF_CODE)
- ScratchPad << "before begin"
- begin
- raise
- rescue RuntimeError
- return
- end
+ it "fires ensure block before returning" do
+ ruby_exe(<<-END_OF_CODE).should == "within ensure\n"
+ begin
+ return
+ ensure
+ puts "within ensure"
+ end
- ScratchPad << "after begin"
- END_OF_CODE
+ puts "after begin"
+ END_OF_CODE
+ end
- load @filename
- ScratchPad.recorded.should == ["before begin"]
- end
+ it "fires ensure block before returning while loads file" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before begin"
+ begin
+ return
+ ensure
+ ScratchPad << "within ensure"
+ end
- it "fires ensure block before returning" do
- ruby_exe(<<-END_OF_CODE).should == "within ensure\n"
- begin
- return
- ensure
- puts "within ensure"
- end
+ ScratchPad << "after begin"
+ END_OF_CODE
- puts "after begin"
- END_OF_CODE
- end
+ load @filename
+ ScratchPad.recorded.should == ["before begin", "within ensure"]
+ end
- ruby_bug "#14061", "2.4"..."2.6" do
- it "fires ensure block before returning while loads file" do
- File.write(@filename, <<-END_OF_CODE)
- ScratchPad << "before begin"
- begin
- return
- ensure
- ScratchPad << "within ensure"
- end
-
- ScratchPad << "after begin"
- END_OF_CODE
-
- load @filename
- ScratchPad.recorded.should == ["before begin", "within ensure"]
+ it "swallows exception if returns in ensure block" do
+ File.write(@filename, <<-END_OF_CODE)
+ begin
+ raise
+ ensure
+ ScratchPad << "before return"
+ return
end
- end
+ END_OF_CODE
- it "swallows exception if returns in ensure block" do
- File.write(@filename, <<-END_OF_CODE)
- begin
- raise
- ensure
- ScratchPad << "before return"
- return
- end
- END_OF_CODE
-
- load @filename
- ScratchPad.recorded.should == ["before return"]
- end
+ load @filename
+ ScratchPad.recorded.should == ["before return"]
end
+ end
- describe "within a block" do
- it "is allowed" do
- File.write(@filename, <<-END_OF_CODE)
- ScratchPad << "before call"
- proc { return }.call
+ describe "within a block" do
+ it "is allowed" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before call"
+ proc { return }.call
- ScratchPad << "after call"
- END_OF_CODE
+ ScratchPad << "after call"
+ END_OF_CODE
- load @filename
- ScratchPad.recorded.should == ["before call"]
- end
+ load @filename
+ ScratchPad.recorded.should == ["before call"]
end
+ end
- describe "within a class" do
- it "is allowed" do
- File.write(@filename, <<-END_OF_CODE)
- class A
- ScratchPad << "before return"
- return
+ describe "within a class" do
+ it "raises a SyntaxError" do
+ File.write(@filename, <<-END_OF_CODE)
+ class ReturnSpecs::A
+ ScratchPad << "before return"
+ return
- ScratchPad << "after return"
- end
- END_OF_CODE
+ ScratchPad << "after return"
+ end
+ END_OF_CODE
- load @filename
- ScratchPad.recorded.should == ["before return"]
- end
+ -> { load @filename }.should raise_error(SyntaxError)
end
+ end
- describe "file loading" do
- it "stops file loading and execution" do
- File.write(@filename, <<-END_OF_CODE)
+ describe "within a block within a class" do
+ it "is not allowed" do
+ File.write(@filename, <<-END_OF_CODE)
+ class ReturnSpecs::A
ScratchPad << "before return"
- return
+ 1.times { return }
ScratchPad << "after return"
- END_OF_CODE
+ end
+ END_OF_CODE
- load @filename
- ScratchPad.recorded.should == ["before return"]
- end
+ -> { load @filename }.should raise_error(LocalJumpError)
end
+ end
- describe "file requiring" do
- it "stops file loading and execution" do
- File.write(@filename, <<-END_OF_CODE)
- ScratchPad << "before return"
+ describe "within BEGIN" do
+ it "is allowed" do
+ File.write(@filename, <<-END_OF_CODE)
+ BEGIN {
+ ScratchPad << "before call"
return
- ScratchPad << "after return"
- END_OF_CODE
+ ScratchPad << "after call"
+ }
+ END_OF_CODE
- require @filename
- ScratchPad.recorded.should == ["before return"]
- end
+ load @filename
+ ScratchPad.recorded.should == ["before call"]
end
+ end
- describe "return with argument" do
- # https://bugs.ruby-lang.org/issues/14062
- it "does not affect exit status" do
- ruby_exe(<<-END_OF_CODE).should == ""
- return 10
- END_OF_CODE
+ describe "file loading" do
+ it "stops file loading and execution" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before return"
+ return
+ ScratchPad << "after return"
+ END_OF_CODE
- $?.exitstatus.should == 0
- end
+ load @filename
+ ScratchPad.recorded.should == ["before return"]
+ end
+ end
+
+ describe "file requiring" do
+ it "stops file loading and execution" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before return"
+ return
+ ScratchPad << "after return"
+ END_OF_CODE
+
+ require @filename
+ ScratchPad.recorded.should == ["before return"]
+ end
+ end
+
+ describe "return with argument" do
+ it "warns but does not affect exit status" do
+ err = ruby_exe(<<-END_OF_CODE, args: "2>&1")
+ return 10
+ END_OF_CODE
+ $?.exitstatus.should == 0
+
+ err.should =~ /warning: argument of top-level return is ignored/
end
end
end
diff --git a/spec/ruby/language/safe_navigator_spec.rb b/spec/ruby/language/safe_navigator_spec.rb
index a8b29dc5a3..c3aecff2dd 100644
--- a/spec/ruby/language/safe_navigator_spec.rb
+++ b/spec/ruby/language/safe_navigator_spec.rb
@@ -1,101 +1,99 @@
-require File.expand_path("../../spec_helper", __FILE__)
+require_relative '../spec_helper'
-ruby_version_is "2.3" do
- describe "Safe navigator" do
- it "requires a method name to be provided" do
- lambda { eval("obj&. {}") }.should raise_error(SyntaxError)
- end
+describe "Safe navigator" do
+ it "requires a method name to be provided" do
+ -> { eval("obj&. {}") }.should raise_error(SyntaxError)
+ end
- context "when context is nil" do
- it "always returns nil" do
- eval("nil&.unknown").should == nil
- eval("[][10]&.unknown").should == nil
- end
+ context "when context is nil" do
+ it "always returns nil" do
+ eval("nil&.unknown").should == nil
+ eval("[][10]&.unknown").should == nil
+ end
- it "can be chained" do
- eval("nil&.one&.two&.three").should == nil
- end
+ it "can be chained" do
+ eval("nil&.one&.two&.three").should == nil
+ end
- it "doesn't evaluate arguments" do
- obj = Object.new
- obj.should_not_receive(:m)
- eval("nil&.unknown(obj.m) { obj.m }")
- 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"
+ 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
+ -> { 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"
+ 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
+ -> { 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 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 "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
+ it "allows assignment methods" do
+ klass = Class.new do
+ attr_reader :foo
+ def foo=(val)
+ @foo = val
+ 42
end
- obj = klass.new
+ end
+ obj = klass.new
- eval("obj&.foo = 3").should == 3
- obj.foo.should == 3
+ eval("obj&.foo = 3").should == 3
+ obj.foo.should == 3
- obj = nil
- eval("obj&.foo = 3").should == nil
- end
+ obj = nil
+ eval("obj&.foo = 3").should == nil
+ end
- it "allows assignment operators" do
- klass = Class.new do
- attr_accessor :m
+ it "allows assignment operators" do
+ klass = Class.new do
+ attr_accessor :m
- def initialize
- @m = 0
- end
+ def initialize
+ @m = 0
end
+ end
- obj = klass.new
+ obj = klass.new
- eval("obj&.m += 3")
- obj.m.should == 3
+ eval("obj&.m += 3")
+ obj.m.should == 3
- obj = nil
- eval("obj&.m += 3").should == nil
- end
+ 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
+ it "does not call the operator method lazily with an assignment operator" do
+ klass = Class.new do
+ attr_writer :foo
+ def foo
+ nil
end
- obj = klass.new
-
- lambda {
- eval("obj&.foo += 3")
- }.should raise_error(NoMethodError) { |e|
- e.name.should == :+
- }
end
+ obj = klass.new
+
+ -> {
+ eval("obj&.foo += 3")
+ }.should raise_error(NoMethodError) { |e|
+ e.name.should == :+
+ }
end
end
diff --git a/spec/ruby/language/safe_spec.rb b/spec/ruby/language/safe_spec.rb
new file mode 100644
index 0000000000..ee5c1b3ccc
--- /dev/null
+++ b/spec/ruby/language/safe_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../spec_helper'
+
+describe "The $SAFE variable" do
+ ruby_version_is ""..."3.0" do
+ it "warn when access" do
+ -> {
+ $SAFE
+ }.should complain(/\$SAFE will become a normal global variable in Ruby 3.0/)
+ end
+
+ it "warn when set" do
+ -> {
+ $SAFE = 1
+ }.should complain(/\$SAFE will become a normal global variable in Ruby 3.0/)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "$SAFE is a regular global variable" do
+ $SAFE.should == nil
+ $SAFE = 42
+ $SAFE.should == 42
+ ensure
+ $SAFE = nil
+ end
+ end
+end
diff --git a/spec/ruby/language/send_spec.rb b/spec/ruby/language/send_spec.rb
index 646a700785..5999079d58 100644
--- a/spec/ruby/language/send_spec.rb
+++ b/spec/ruby/language/send_spec.rb
@@ -1,15 +1,15 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/send', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/send'
# Why so many fixed arg tests? JRuby and I assume other Ruby impls have
# separate call paths for simple fixed arity methods. Testing up to five
# will verify special and generic arity code paths for all impls.
#
# Method naming conventions:
-# M - Manditory Args
+# M - Mandatory Args
# O - Optional Arg
# R - Rest Arg
-# Q - Post Manditory Args
+# Q - Post Mandatory Args
specs = LangSendSpecs
@@ -20,7 +20,7 @@ describe "Invoking a method" do
end
it "raises ArgumentError if the method has a positive arity" do
- lambda {
+ -> {
specs.fooM1
}.should raise_error(ArgumentError)
end
@@ -36,7 +36,7 @@ describe "Invoking a method" do
end
it "raises ArgumentError if the methods arity doesn't match" do
- lambda {
+ -> {
specs.fooM1(1,2)
}.should raise_error(ArgumentError)
end
@@ -52,7 +52,7 @@ describe "Invoking a method" do
end
it "raises ArgumentError if extra arguments are passed" do
- lambda {
+ -> {
specs.fooM0O1(2,3)
}.should raise_error(ArgumentError)
end
@@ -64,13 +64,13 @@ describe "Invoking a method" do
end
it "raises an ArgumentError if there are no values for the mandatory args" do
- lambda {
+ -> {
specs.fooM1O1
}.should raise_error(ArgumentError)
end
it "raises an ArgumentError if too many values are passed" do
- lambda {
+ -> {
specs.fooM1O1(1,2,3)
}.should raise_error(ArgumentError)
end
@@ -107,7 +107,7 @@ describe "Invoking a method" do
end
it "raises a SyntaxError with both a literal block and an object as block" do
- lambda {
+ -> {
eval "specs.oneb(10, &l){ 42 }"
}.should raise_error(SyntaxError)
end
@@ -195,12 +195,20 @@ describe "Invoking a method" do
end
it "raises NameError if invoked as a vcall" do
- lambda { no_such_method }.should raise_error NameError
+ -> { no_such_method }.should raise_error NameError
+ end
+
+ it "should omit the method_missing call from the backtrace for NameError" do
+ -> { no_such_method }.should raise_error { |e| e.backtrace.first.should_not include("method_missing") }
end
it "raises NoMethodError if invoked as an unambiguous method call" do
- lambda { no_such_method() }.should raise_error NoMethodError
- lambda { no_such_method(1,2,3) }.should raise_error NoMethodError
+ -> { no_such_method() }.should raise_error NoMethodError
+ -> { no_such_method(1,2,3) }.should raise_error NoMethodError
+ end
+
+ it "should omit the method_missing call from the backtrace for NoMethodError" do
+ -> { no_such_method() }.should raise_error { |e| e.backtrace.first.should_not include("method_missing") }
end
end
@@ -250,10 +258,10 @@ describe "Invoking a private setter method" do
end
describe "Invoking a private getter method" do
- it "does not permit self as a receiver" do
+ it "permits self as a receiver" do
receiver = LangSendSpecs::PrivateGetter.new
- lambda { receiver.call_self_foo }.should raise_error(NoMethodError)
- lambda { receiver.call_self_foo_or_equals(6) }.should raise_error(NoMethodError)
+ receiver.call_self_foo_or_equals(6)
+ receiver.call_self_foo.should == 6
end
end
@@ -403,6 +411,47 @@ describe "Invoking a method" do
specs.rest_len(0,*a,4,*5,6,7,*c,-1).should == 11
end
+ ruby_version_is ""..."3.0" do
+ it "expands the Array elements from the splat after executing the arguments and block if no other arguments follow the splat" do
+ def self.m(*args, &block)
+ [args, block]
+ end
+
+ args = [1, nil]
+ m(*args, &args.pop).should == [[1], nil]
+
+ args = [1, nil]
+ order = []
+ m(*(order << :args; args), &(order << :block; args.pop)).should == [[1], nil]
+ order.should == [:args, :block]
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "expands the Array elements from the splat before applying block argument operations" do
+ def self.m(*args, &block)
+ [args, block]
+ end
+
+ args = [1, nil]
+ m(*args, &args.pop).should == [[1, nil], nil]
+
+ args = [1, nil]
+ order = []
+ m(*(order << :args; args), &(order << :block; args.pop)).should == [[1, nil], nil]
+ order.should == [:args, :block]
+ end
+ end
+
+ it "evaluates the splatted arguments before the block if there are other arguments after the splat" do
+ def self.m(*args, &block)
+ [args, block]
+ end
+
+ args = [1, nil]
+ m(*args, 2, &args.pop).should == [[1, nil, 2], nil]
+ end
+
it "expands an array to arguments grouped in parentheses" do
specs.destructure2([40,2]).should == 42
end
diff --git a/spec/ruby/language/singleton_class_spec.rb b/spec/ruby/language/singleton_class_spec.rb
index 837f479440..c1fb682ea0 100644
--- a/spec/ruby/language/singleton_class_spec.rb
+++ b/spec/ruby/language/singleton_class_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../../fixtures/class', __FILE__)
+require_relative '../spec_helper'
+require_relative '../fixtures/class'
describe "A singleton class" do
it "is TrueClass for true" do
@@ -14,12 +14,12 @@ describe "A singleton class" do
nil.singleton_class.should == NilClass
end
- it "raises a TypeError for Fixnum's" do
- lambda { 1.singleton_class }.should raise_error(TypeError)
+ it "raises a TypeError for Integer's" do
+ -> { 1.singleton_class }.should raise_error(TypeError)
end
it "raises a TypeError for symbols" do
- lambda { :symbol.singleton_class }.should raise_error(TypeError)
+ -> { :symbol.singleton_class }.should raise_error(TypeError)
end
it "is a singleton Class instance" do
@@ -74,7 +74,7 @@ describe "A singleton class" do
end
it "doesn't have singleton class" do
- lambda { bignum_value.singleton_class.superclass.should == Bignum }.should raise_error(TypeError)
+ -> { bignum_value.singleton_class }.should raise_error(TypeError)
end
end
@@ -112,11 +112,11 @@ describe "A constant on a singleton class" do
class << @object
CONST
end
- lambda { CONST }.should raise_error(NameError)
+ -> { CONST }.should raise_error(NameError)
end
it "cannot be accessed via object::CONST" do
- lambda do
+ -> do
@object::CONST
end.should raise_error(TypeError)
end
@@ -127,7 +127,7 @@ describe "A constant on a singleton class" do
CONST = 100
end
- lambda do
+ -> do
@object::CONST
end.should raise_error(NameError)
end
@@ -143,7 +143,7 @@ describe "A constant on a singleton class" do
it "is not preserved when the object is duped" do
@object = @object.dup
- lambda do
+ -> do
class << @object; CONST; end
end.should raise_error(NameError)
end
@@ -280,13 +280,13 @@ 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
diff --git a/spec/ruby/language/source_encoding_spec.rb b/spec/ruby/language/source_encoding_spec.rb
new file mode 100644
index 0000000000..19364fc676
--- /dev/null
+++ b/spec/ruby/language/source_encoding_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../spec_helper'
+
+describe "Source files" do
+
+ describe "encoded in UTF-8 without a BOM" do
+ it "can be parsed" do
+ ruby_exe(fixture(__FILE__, "utf8-nobom.rb"), args: "2>&1").should == "hello\n"
+ end
+ end
+
+ describe "encoded in UTF-8 with a BOM" do
+ it "can be parsed" do
+ ruby_exe(fixture(__FILE__, "utf8-bom.rb"), args: "2>&1").should == "hello\n"
+ end
+ end
+
+ describe "encoded in UTF-16 LE without a BOM" do
+ it "are parsed because empty as they contain a NUL byte before the encoding comment" do
+ ruby_exe(fixture(__FILE__, "utf16-le-nobom.rb"), args: "2>&1").should == ""
+ end
+ end
+
+ describe "encoded in UTF-16 LE with a BOM" do
+ it "are invalid because they contain an invalid UTF-8 sequence before the encoding comment" do
+ bom = "\xFF\xFE".b
+ source = "# encoding: utf-16le\nputs 'hello'\n"
+ source = bom + source.bytes.zip([0]*source.bytesize).flatten.pack('C*')
+ path = tmp("utf16-le-bom.rb")
+
+ touch(path, "wb") { |f| f.write source }
+ begin
+ ruby_exe(path, args: "2>&1", exit_status: 1).should =~ /invalid multibyte char/
+ ensure
+ rm_r path
+ end
+ end
+ end
+
+ describe "encoded in UTF-16 BE without a BOM" do
+ it "are parsed as empty because they contain a NUL byte before the encoding comment" do
+ ruby_exe(fixture(__FILE__, "utf16-be-nobom.rb"), args: "2>&1").should == ""
+ end
+ end
+
+ describe "encoded in UTF-16 BE with a BOM" do
+ it "are invalid because they contain an invalid UTF-8 sequence before the encoding comment" do
+ bom = "\xFE\xFF".b
+ source = "# encoding: utf-16be\nputs 'hello'\n"
+ source = bom + ([0]*source.bytesize).zip(source.bytes).flatten.pack('C*')
+ path = tmp("utf16-be-bom.rb")
+
+ touch(path, "wb") { |f| f.write source }
+ begin
+ ruby_exe(path, args: "2>&1", exit_status: 1).should =~ /invalid multibyte char/
+ ensure
+ rm_r path
+ end
+ end
+ end
+
+end
diff --git a/spec/ruby/language/string_spec.rb b/spec/ruby/language/string_spec.rb
index dbec2652ed..02e3488a1f 100644
--- a/spec/ruby/language/string_spec.rb
+++ b/spec/ruby/language/string_spec.rb
@@ -1,6 +1,6 @@
# -*- encoding: binary -*-
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
# TODO: rewrite these horrid specs. it "are..." seriously?!
@@ -32,6 +32,11 @@ describe "Ruby character strings" do
"#@my_ip".should == 'xxx'
end
+ it "does not interpolate invalid variable names" do
+ "#@".should == '#@'
+ "#$%".should == '#$%'
+ end
+
it "has characters [.(=?!# end simple # interpolation" do
"#@ip[".should == 'xxx['
"#@ip.".should == 'xxx.'
@@ -42,24 +47,13 @@ describe "Ruby character strings" do
"#@ip#@ip".should == 'xxxxxx'
end
- it "taints the result of interpolation when an interpolated value is tainted" do
- "#{"".taint}".tainted?.should be_true
-
- @ip.taint
- "#@ip".tainted?.should be_true
-
- $ip.taint
- "#$ip".tainted?.should be_true
- end
-
- it "untrusts the result of interpolation when an interpolated value is untrusted" do
- "#{"".untrust}".untrusted?.should be_true
-
- @ip.untrust
- "#@ip".untrusted?.should be_true
-
- $ip.untrust
- "#$ip".untrusted?.should be_true
+ it "don't get confused by partial interpolation character sequences" do
+ "#@".should == '#@'
+ "#@ ".should == '#@ '
+ "#@@".should == '#@@'
+ "#@@ ".should == '#@@ '
+ "#$ ".should == '#$ '
+ "#\$".should == '#$'
end
it "allows using non-alnum characters as string delimiters" do
@@ -186,11 +180,11 @@ describe "Ruby character strings" do
# TODO: spec other source encodings
describe "with ASCII_8BIT source encoding" do
it "produces an ASCII string when escaping ASCII characters via \\u" do
- "\u0000".encoding.should == Encoding::ASCII_8BIT
+ "\u0000".encoding.should == Encoding::BINARY
end
it "produces an ASCII string when escaping ASCII characters via \\u{}" do
- "\u{0000}".encoding.should == Encoding::ASCII_8BIT
+ "\u{0000}".encoding.should == Encoding::BINARY
end
it "produces a UTF-8-encoded string when escaping non-ASCII characters via \\u" do
@@ -224,59 +218,82 @@ describe "Ruby String literals" do
long_string_literals.should == "Beautiful is better than ugly.Explicit is better than implicit."
end
- ruby_version_is "2.3" do
- describe "with a magic frozen comment" do
- it "produce the same object each time" do
- ruby_exe(fixture(__FILE__, "freeze_magic_comment_one_literal.rb")).chomp.should == "true"
- end
+ describe "with a magic frozen comment" do
+ it "produce the same object each time" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_one_literal.rb")).chomp.should == "true"
+ end
- it "produce the same object for literals with the same content" do
- ruby_exe(fixture(__FILE__, "freeze_magic_comment_two_literals.rb")).chomp.should == "true"
- end
+ it "produce the same object for literals with the same content" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_two_literals.rb")).chomp.should == "true"
+ end
- it "produce the same object for literals with the same content in different files" do
- ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files.rb")).chomp.should == "true"
- end
+ it "produce the same object for literals with the same content in different files" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files.rb")).chomp.should == "true"
+ end
- it "produce different objects for literals with the same content in different files if the other file doesn't have the comment" do
- 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 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
+ it "produce different objects for literals with the same content in different files if they have different encodings" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_diff_enc.rb")).chomp.should == "true"
end
end
end
-with_feature :encoding do
- describe "Ruby String interpolation" do
- it "creates a String having an Encoding compatible with all components" do
- a = "\u3042"
- b = "abc".encode("ascii-8bit")
+describe "Ruby String interpolation" do
+ it "permits an empty expression" do
+ s = "#{}" # rubocop:disable Lint/EmptyInterpolation
+ s.should.empty?
+ s.should_not.frozen?
+ end
- str = "#{a} x #{b}"
+ it "returns a string with the source encoding by default" do
+ "a#{"b"}c".encoding.should == Encoding::BINARY
+ eval('"a#{"b"}c"'.force_encoding("us-ascii")).encoding.should == Encoding::US_ASCII
+ eval("# coding: US-ASCII \n 'a#{"b"}c'").encoding.should == Encoding::US_ASCII
+ end
- str.should == "\xe3\x81\x82\x20\x78\x20\x61\x62\x63".force_encoding("utf-8")
- str.encoding.should == Encoding::UTF_8
- end
+ it "returns a string with the source encoding, even if the components have another encoding" do
+ a = "abc".force_encoding("euc-jp")
+ "#{a}".encoding.should == Encoding::BINARY
+
+ b = "abc".encode("utf-8")
+ "#{b}".encoding.should == Encoding::BINARY
+ end
- it "creates a String having the Encoding of the components when all are the same Encoding" do
- a = "abc".force_encoding("euc-jp")
- b = "def".force_encoding("euc-jp")
- str = '"#{a} x #{b}"'.force_encoding("euc-jp")
+ it "raises an Encoding::CompatibilityError if the Encodings are not compatible" do
+ a = "\u3042"
+ b = "\xff".force_encoding "binary"
- result = eval(str)
- result.should == "\x61\x62\x63\x20\x78\x20\x64\x65\x66".force_encoding("euc-jp")
- result.encoding.should == Encoding::EUC_JP
- end
+ -> { "#{a} #{b}" }.should raise_error(Encoding::CompatibilityError)
+ end
- it "raises an Encoding::CompatibilityError if the Encodings are not compatible" do
- a = "\u3042"
- b = "\xff".force_encoding "ascii-8bit"
+ it "creates a non-frozen String" do
+ code = <<~'RUBY'
+ "a#{6*7}c"
+ RUBY
+ eval(code).should_not.frozen?
+ end
+
+ ruby_version_is "3.0" do
+ it "creates a non-frozen String when # frozen-string-literal: true is used" do
+ code = <<~'RUBY'
+ # frozen-string-literal: true
+ "a#{6*7}c"
+ RUBY
+ eval(code).should_not.frozen?
+ end
+ end
- lambda { "#{a} #{b}" }.should raise_error(Encoding::CompatibilityError)
+ ruby_version_is ""..."3.0" do
+ it "creates a frozen String when # frozen-string-literal: true is used" do
+ code = <<~'RUBY'
+ # frozen-string-literal: true
+ "a#{6*7}c"
+ RUBY
+ eval(code).should.frozen?
end
end
end
diff --git a/spec/ruby/language/super_spec.rb b/spec/ruby/language/super_spec.rb
index 3d3f5d6f74..1ac5c5e1be 100644
--- a/spec/ruby/language/super_spec.rb
+++ b/spec/ruby/language/super_spec.rb
@@ -1,73 +1,73 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/super', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/super'
describe "The super keyword" do
it "calls the method on the calling class" do
- Super::S1::A.new.foo([]).should == ["A#foo","A#bar"]
- Super::S1::A.new.bar([]).should == ["A#bar"]
- Super::S1::B.new.foo([]).should == ["B#foo","A#foo","B#bar","A#bar"]
- Super::S1::B.new.bar([]).should == ["B#bar","A#bar"]
+ SuperSpecs::S1::A.new.foo([]).should == ["A#foo","A#bar"]
+ SuperSpecs::S1::A.new.bar([]).should == ["A#bar"]
+ SuperSpecs::S1::B.new.foo([]).should == ["B#foo","A#foo","B#bar","A#bar"]
+ SuperSpecs::S1::B.new.bar([]).should == ["B#bar","A#bar"]
end
it "searches the full inheritance chain" do
- Super::S2::B.new.foo([]).should == ["B#foo","A#baz"]
- Super::S2::B.new.baz([]).should == ["A#baz"]
- Super::S2::C.new.foo([]).should == ["B#foo","C#baz","A#baz"]
- Super::S2::C.new.baz([]).should == ["C#baz","A#baz"]
+ SuperSpecs::S2::B.new.foo([]).should == ["B#foo","A#baz"]
+ SuperSpecs::S2::B.new.baz([]).should == ["A#baz"]
+ SuperSpecs::S2::C.new.foo([]).should == ["B#foo","C#baz","A#baz"]
+ SuperSpecs::S2::C.new.baz([]).should == ["C#baz","A#baz"]
end
it "searches class methods" do
- Super::S3::A.new.foo([]).should == ["A#foo"]
- Super::S3::A.foo([]).should == ["A.foo"]
- Super::S3::A.bar([]).should == ["A.bar","A.foo"]
- Super::S3::B.new.foo([]).should == ["A#foo"]
- Super::S3::B.foo([]).should == ["B.foo","A.foo"]
- Super::S3::B.bar([]).should == ["B.bar","A.bar","B.foo","A.foo"]
+ SuperSpecs::S3::A.new.foo([]).should == ["A#foo"]
+ SuperSpecs::S3::A.foo([]).should == ["A.foo"]
+ SuperSpecs::S3::A.bar([]).should == ["A.bar","A.foo"]
+ SuperSpecs::S3::B.new.foo([]).should == ["A#foo"]
+ SuperSpecs::S3::B.foo([]).should == ["B.foo","A.foo"]
+ SuperSpecs::S3::B.bar([]).should == ["B.bar","A.bar","B.foo","A.foo"]
end
it "calls the method on the calling class including modules" do
- Super::MS1::A.new.foo([]).should == ["ModA#foo","ModA#bar"]
- Super::MS1::A.new.bar([]).should == ["ModA#bar"]
- Super::MS1::B.new.foo([]).should == ["B#foo","ModA#foo","ModB#bar","ModA#bar"]
- Super::MS1::B.new.bar([]).should == ["ModB#bar","ModA#bar"]
+ SuperSpecs::MS1::A.new.foo([]).should == ["ModA#foo","ModA#bar"]
+ SuperSpecs::MS1::A.new.bar([]).should == ["ModA#bar"]
+ SuperSpecs::MS1::B.new.foo([]).should == ["B#foo","ModA#foo","ModB#bar","ModA#bar"]
+ SuperSpecs::MS1::B.new.bar([]).should == ["ModB#bar","ModA#bar"]
end
it "searches the full inheritance chain including modules" do
- Super::MS2::B.new.foo([]).should == ["ModB#foo","A#baz"]
- Super::MS2::B.new.baz([]).should == ["A#baz"]
- Super::MS2::C.new.baz([]).should == ["C#baz","A#baz"]
- Super::MS2::C.new.foo([]).should == ["ModB#foo","C#baz","A#baz"]
+ SuperSpecs::MS2::B.new.foo([]).should == ["ModB#foo","A#baz"]
+ SuperSpecs::MS2::B.new.baz([]).should == ["A#baz"]
+ SuperSpecs::MS2::C.new.baz([]).should == ["C#baz","A#baz"]
+ SuperSpecs::MS2::C.new.foo([]).should == ["ModB#foo","C#baz","A#baz"]
end
it "can resolve to different methods in an included module method" do
- Super::MultiSuperTargets::A.new.foo.should == :BaseA
- Super::MultiSuperTargets::B.new.foo.should == :BaseB
+ SuperSpecs::MultiSuperTargets::A.new.foo.should == :BaseA
+ SuperSpecs::MultiSuperTargets::B.new.foo.should == :BaseB
end
it "searches class methods including modules" do
- Super::MS3::A.new.foo([]).should == ["A#foo"]
- Super::MS3::A.foo([]).should == ["ModA#foo"]
- Super::MS3::A.bar([]).should == ["ModA#bar","ModA#foo"]
- Super::MS3::B.new.foo([]).should == ["A#foo"]
- Super::MS3::B.foo([]).should == ["B.foo","ModA#foo"]
- Super::MS3::B.bar([]).should == ["B.bar","ModA#bar","B.foo","ModA#foo"]
+ SuperSpecs::MS3::A.new.foo([]).should == ["A#foo"]
+ SuperSpecs::MS3::A.foo([]).should == ["ModA#foo"]
+ SuperSpecs::MS3::A.bar([]).should == ["ModA#bar","ModA#foo"]
+ SuperSpecs::MS3::B.new.foo([]).should == ["A#foo"]
+ SuperSpecs::MS3::B.foo([]).should == ["B.foo","ModA#foo"]
+ SuperSpecs::MS3::B.bar([]).should == ["B.bar","ModA#bar","B.foo","ModA#foo"]
end
it "searches BasicObject from a module for methods defined there" do
- Super::IncludesFromBasic.new.__send__(:foobar).should == 43
+ SuperSpecs::IncludesFromBasic.new.__send__(:foobar).should == 43
end
it "searches BasicObject through another module for methods defined there" do
- Super::IncludesIntermediate.new.__send__(:foobar).should == 42
+ SuperSpecs::IncludesIntermediate.new.__send__(:foobar).should == 42
end
it "calls the correct method when the method visibility is modified" do
- Super::MS4::A.new.example.should == 5
+ SuperSpecs::MS4::A.new.example.should == 5
end
it "calls the correct method when the superclass argument list is different from the subclass" do
- Super::S4::A.new.foo([]).should == ["A#foo"]
- Super::S4::B.new.foo([],"test").should == ["B#foo(a,test)", "A#foo"]
+ SuperSpecs::S4::A.new.foo([]).should == ["A#foo"]
+ SuperSpecs::S4::B.new.foo([],"test").should == ["B#foo(a,test)", "A#foo"]
end
it "raises an error error when super method does not exist" do
@@ -83,8 +83,8 @@ describe "The super keyword" do
end
end
- lambda {sub_normal.new.foo}.should raise_error(NoMethodError, /super/)
- lambda {sub_zsuper.new.foo}.should raise_error(NoMethodError, /super/)
+ -> {sub_normal.new.foo}.should raise_error(NoMethodError, /super/)
+ -> {sub_zsuper.new.foo}.should raise_error(NoMethodError, /super/)
end
it "uses given block even if arguments are passed explicitly" do
@@ -102,16 +102,66 @@ describe "The super keyword" do
c2.new.m(:dump) { :value }.should == :value
end
+ it "can pass an explicit block" do
+ c1 = Class.new do
+ def m(v)
+ yield(v)
+ end
+ end
+ c2 = Class.new(c1) do
+ def m(v)
+ block = -> w { yield(w + 'b') }
+ super(v, &block)
+ end
+ end
+
+ c2.new.m('a') { |x| x + 'c' }.should == 'abc'
+ end
+
+ it "can pass no block using &nil" do
+ c1 = Class.new do
+ def m(v)
+ block_given?
+ end
+ end
+ c2 = Class.new(c1) do
+ def m(v)
+ super(v, &nil)
+ end
+ end
+
+ c2.new.m('a') { raise }.should be_false
+ end
+
+ it "uses block argument given to method when used in a block" do
+ c1 = Class.new do
+ def m
+ yield
+ end
+ end
+ c2 = Class.new(c1) do
+ def m(v)
+ ary = []
+ 1.times do
+ ary << super()
+ end
+ ary
+ end
+ end
+
+ c2.new.m(:dump) { :value }.should == [ :value ]
+ end
+
it "calls the superclass method when in a block" do
- Super::S6.new.here.should == :good
+ SuperSpecs::S6.new.here.should == :good
end
it "calls the superclass method when initial method is defined_method'd" do
- Super::S7.new.here.should == :good
+ SuperSpecs::S7.new.here.should == :good
end
it "can call through a define_method multiple times (caching check)" do
- obj = Super::S7.new
+ obj = SuperSpecs::S7.new
2.times do
obj.here.should == :good
@@ -150,25 +200,25 @@ describe "The super keyword" do
end
end
- lambda { klass.new.a(:a_called) }.should raise_error(RuntimeError)
+ -> { klass.new.a(:a_called) }.should raise_error(RuntimeError)
end
# Rubinius ticket github#157
it "calls method_missing when a superclass method is not found" do
- Super::MM_B.new.is_a?(Hash).should == false
+ SuperSpecs::MM_B.new.is_a?(Hash).should == false
end
# Rubinius ticket github#180
it "respects the original module a method is aliased from" do
- Super::Alias3.new.name3.should == [:alias2, :alias1]
+ SuperSpecs::Alias3.new.name3.should == [:alias2, :alias1]
end
it "sees the included version of a module a method is alias from" do
- Super::AliasWithSuper::Trigger.foo.should == [:b, :a]
+ SuperSpecs::AliasWithSuper::Trigger.foo.should == [:b, :a]
end
it "find super from a singleton class" do
- obj = Super::SingletonCase::Foo.new
+ obj = SuperSpecs::SingletonCase::Foo.new
def obj.foobar(array)
array << :singleton
super
@@ -177,87 +227,112 @@ describe "The super keyword" do
end
it "finds super on other objects if a singleton class aliased the method" do
- orig_obj = Super::SingletonAliasCase::Foo.new
+ orig_obj = SuperSpecs::SingletonAliasCase::Foo.new
orig_obj.alias_on_singleton
orig_obj.new_foobar([]).should == [:foo, :base]
- Super::SingletonAliasCase::Foo.new.foobar([]).should == [:foo, :base]
+ SuperSpecs::SingletonAliasCase::Foo.new.foobar([]).should == [:foo, :base]
end
it "passes along modified rest args when they weren't originally empty" do
- Super::RestArgsWithSuper::B.new.a("bar").should == ["bar", "foo"]
+ SuperSpecs::RestArgsWithSuper::B.new.a("bar").should == ["bar", "foo"]
end
it "passes along modified rest args when they were originally empty" do
- Super::RestArgsWithSuper::B.new.a.should == ["foo"]
+ SuperSpecs::RestArgsWithSuper::B.new.a.should == ["foo"]
+ end
+
+ # https://bugs.ruby-lang.org/issues/14279
+ it "passes along reassigned rest args" do
+ SuperSpecs::ZSuperWithRestReassigned::B.new.a("bar").should == ["foo"]
+ end
+
+ # https://bugs.ruby-lang.org/issues/14279
+ it "wraps into array and passes along reassigned rest args with non-array scalar value" do
+ SuperSpecs::ZSuperWithRestReassignedWithScalar::B.new.a("bar").should == ["foo"]
end
it "invokes methods from a chain of anonymous modules" do
- Super::AnonymousModuleIncludedTwice.new.a([]).should == ["anon", "anon", "non-anon"]
+ SuperSpecs::AnonymousModuleIncludedTwice.new.a([]).should == ["anon", "anon", "non-anon"]
end
it "without explicit arguments can accept a block but still pass the original arguments" do
- Super::ZSuperWithBlock::B.new.a.should == 14
+ SuperSpecs::ZSuperWithBlock::B.new.a.should == 14
end
it "passes along block via reference to method expecting a reference" do
- Super::ZSuperWithBlock::B.new.b.should == [14, 15]
+ SuperSpecs::ZSuperWithBlock::B.new.b.should == [14, 15]
end
it "passes along a block via reference to a method that yields" do
- Super::ZSuperWithBlock::B.new.c.should == 16
+ SuperSpecs::ZSuperWithBlock::B.new.c.should == 16
end
it "without explicit arguments passes optional arguments that have a default value" do
- Super::ZSuperWithOptional::B.new.m(1, 2).should == 14
+ SuperSpecs::ZSuperWithOptional::B.new.m(1, 2).should == 14
end
it "without explicit arguments passes optional arguments that have a non-default value" do
- Super::ZSuperWithOptional::B.new.m(1, 2, 3).should == 3
+ SuperSpecs::ZSuperWithOptional::B.new.m(1, 2, 3).should == 3
end
it "without explicit arguments passes optional arguments that have a default value but were modified" do
- Super::ZSuperWithOptional::C.new.m(1, 2).should == 100
+ SuperSpecs::ZSuperWithOptional::C.new.m(1, 2).should == 100
end
it "without explicit arguments passes optional arguments that have a non-default value but were modified" do
- Super::ZSuperWithOptional::C.new.m(1, 2, 3).should == 100
+ SuperSpecs::ZSuperWithOptional::C.new.m(1, 2, 3).should == 100
end
it "without explicit arguments passes rest arguments" do
- Super::ZSuperWithRest::B.new.m(1, 2, 3).should == [1, 2, 3]
+ SuperSpecs::ZSuperWithRest::B.new.m(1, 2, 3).should == [1, 2, 3]
end
it "without explicit arguments passes rest arguments including any modifications" do
- Super::ZSuperWithRest::B.new.m_modified(1, 2, 3).should == [1, 14, 3]
+ SuperSpecs::ZSuperWithRest::B.new.m_modified(1, 2, 3).should == [1, 14, 3]
end
it "without explicit arguments passes arguments and rest arguments" do
- Super::ZSuperWithRestAndOthers::B.new.m(1, 2, 3, 4, 5).should == [3, 4, 5]
+ SuperSpecs::ZSuperWithRestAndOthers::B.new.m(1, 2, 3, 4, 5).should == [3, 4, 5]
+ SuperSpecs::ZSuperWithRestAndOthers::B.new.m(1, 2).should == []
+ end
+
+ it "without explicit arguments passes arguments, rest arguments, and post arguments" do
+ SuperSpecs::ZSuperWithRestAndPost::B.new.m(1, 2, 3, 4, 5).should == [1, 2, 3]
+ SuperSpecs::ZSuperWithRestOthersAndPost::B.new.m(1, 2, 3, 4, 5).should == [2, 3, 4]
+ SuperSpecs::ZSuperWithRestAndPost::B.new.m(1, 2).should == []
+ SuperSpecs::ZSuperWithRestOthersAndPost::B.new.m(1, 2).should == []
+ end
+
+ it "without explicit arguments passes arguments, rest arguments including modifications, and post arguments" do
+ SuperSpecs::ZSuperWithRestAndPost::B.new.m_modified(1, 2, 3, 4, 5).should == [1, 14, 3]
+ SuperSpecs::ZSuperWithRestOthersAndPost::B.new.m_modified(1, 2, 3, 4, 5).should == [2, 14, 4]
+ SuperSpecs::ZSuperWithRestAndPost::B.new.m_modified(1, 2).should == [nil, 14]
+ SuperSpecs::ZSuperWithRestOthersAndPost::B.new.m_modified(1, 2).should == [nil, 14]
end
it "without explicit arguments passes arguments and rest arguments including any modifications" do
- Super::ZSuperWithRestAndOthers::B.new.m_modified(1, 2, 3, 4, 5).should == [3, 14, 5]
+ SuperSpecs::ZSuperWithRestAndOthers::B.new.m_modified(1, 2, 3, 4, 5).should == [3, 14, 5]
end
it "without explicit arguments that are '_'" do
- Super::ZSuperWithUnderscores::B.new.m(1, 2).should == [1, 2]
+ SuperSpecs::ZSuperWithUnderscores::B.new.m(1, 2).should == [1, 2]
end
it "without explicit arguments that are '_' including any modifications" do
- Super::ZSuperWithUnderscores::B.new.m_modified(1, 2).should == [14, 2]
+ SuperSpecs::ZSuperWithUnderscores::B.new.m_modified(1, 2).should == [14, 2]
end
describe 'when using keyword arguments' do
before :each do
- @req = Super::Keywords::RequiredArguments.new
- @opts = Super::Keywords::OptionalArguments.new
- @etc = Super::Keywords::PlaceholderArguments.new
+ @req = SuperSpecs::Keywords::RequiredArguments.new
+ @opts = SuperSpecs::Keywords::OptionalArguments.new
+ @etc = SuperSpecs::Keywords::PlaceholderArguments.new
- @req_and_opts = Super::Keywords::RequiredAndOptionalArguments.new
- @req_and_etc = Super::Keywords::RequiredAndPlaceholderArguments.new
- @opts_and_etc = Super::Keywords::OptionalAndPlaceholderArguments.new
+ @req_and_opts = SuperSpecs::Keywords::RequiredAndOptionalArguments.new
+ @req_and_etc = SuperSpecs::Keywords::RequiredAndPlaceholderArguments.new
+ @opts_and_etc = SuperSpecs::Keywords::OptionalAndPlaceholderArguments.new
- @req_and_opts_and_etc = Super::Keywords::RequiredAndOptionalAndPlaceholderArguments.new
+ @req_and_opts_and_etc = SuperSpecs::Keywords::RequiredAndOptionalAndPlaceholderArguments.new
end
it 'does not pass any arguments to the parent when none are given' do
@@ -293,15 +368,15 @@ describe "The super keyword" do
describe 'when using regular and keyword arguments' do
before :each do
- @req = Super::RegularAndKeywords::RequiredArguments.new
- @opts = Super::RegularAndKeywords::OptionalArguments.new
- @etc = Super::RegularAndKeywords::PlaceholderArguments.new
+ @req = SuperSpecs::RegularAndKeywords::RequiredArguments.new
+ @opts = SuperSpecs::RegularAndKeywords::OptionalArguments.new
+ @etc = SuperSpecs::RegularAndKeywords::PlaceholderArguments.new
- @req_and_opts = Super::RegularAndKeywords::RequiredAndOptionalArguments.new
- @req_and_etc = Super::RegularAndKeywords::RequiredAndPlaceholderArguments.new
- @opts_and_etc = Super::RegularAndKeywords::OptionalAndPlaceholderArguments.new
+ @req_and_opts = SuperSpecs::RegularAndKeywords::RequiredAndOptionalArguments.new
+ @req_and_etc = SuperSpecs::RegularAndKeywords::RequiredAndPlaceholderArguments.new
+ @opts_and_etc = SuperSpecs::RegularAndKeywords::OptionalAndPlaceholderArguments.new
- @req_and_opts_and_etc = Super::RegularAndKeywords::RequiredAndOptionalAndPlaceholderArguments.new
+ @req_and_opts_and_etc = SuperSpecs::RegularAndKeywords::RequiredAndOptionalAndPlaceholderArguments.new
end
it 'passes only required regular arguments to the parent when no optional keyword arguments are given' do
@@ -337,7 +412,7 @@ describe "The super keyword" do
describe 'when using splat and keyword arguments' do
before :each do
- @all = Super::SplatAndKeywords::AllArguments.new
+ @all = SuperSpecs::SplatAndKeywords::AllArguments.new
end
it 'does not pass any arguments to the parent when none are given' do
diff --git a/spec/ruby/language/symbol_spec.rb b/spec/ruby/language/symbol_spec.rb
index 90540f7d1d..d6a41d3059 100644
--- a/spec/ruby/language/symbol_spec.rb
+++ b/spec/ruby/language/symbol_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "A Symbol literal" do
it "is a ':' followed by any number of valid characters" do
@@ -38,7 +38,7 @@ describe "A Symbol literal" do
it 'inherits the encoding of the magic comment and can have a binary encoding' do
ruby_exe(fixture(__FILE__, "binary_symbol.rb"))
- .should == "[105, 108, 95, 195, 169, 116, 97, 105, 116]\nASCII-8BIT\n"
+ .should == "[105, 108, 95, 195, 169, 116, 97, 105, 116]\n#{Encoding::BINARY.name}\n"
end
it "may contain '::' in the string" do
diff --git a/spec/ruby/language/throw_spec.rb b/spec/ruby/language/throw_spec.rb
index 92f699350c..d723843688 100644
--- a/spec/ruby/language/throw_spec.rb
+++ b/spec/ruby/language/throw_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The throw keyword" do
it "abandons processing" do
@@ -45,7 +45,7 @@ describe "The throw keyword" do
end
it "does not convert strings to a symbol" do
- lambda { catch(:exit) { throw "exit" } }.should raise_error(ArgumentError)
+ -> { catch(:exit) { throw "exit" } }.should raise_error(ArgumentError)
end
it "unwinds stack from within a method" do
@@ -59,13 +59,13 @@ describe "The throw keyword" do
end
it "unwinds stack from within a lambda" do
- c = lambda { throw :foo, :msg }
+ c = -> { throw :foo, :msg }
catch(:foo) { c.call }.should == :msg
end
it "raises an ArgumentError if outside of scope of a matching catch" do
- lambda { throw :test, 5 }.should raise_error(ArgumentError)
- lambda { catch(:different) { throw :test, 5 } }.should raise_error(ArgumentError)
+ -> { throw :test, 5 }.should raise_error(ArgumentError)
+ -> { catch(:different) { throw :test, 5 } }.should raise_error(ArgumentError)
end
it "raises an UncaughtThrowError if used to exit a thread" do
diff --git a/spec/ruby/language/undef_spec.rb b/spec/ruby/language/undef_spec.rb
index 9e788f2a09..4e473b803f 100644
--- a/spec/ruby/language/undef_spec.rb
+++ b/spec/ruby/language/undef_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The undef keyword" do
describe "undefines a method" do
@@ -14,35 +14,35 @@ describe "The undef keyword" do
@undef_class.class_eval do
undef meth
end
- lambda { @obj.meth(5) }.should raise_error(NoMethodError)
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
end
it "with a simple symbol" do
@undef_class.class_eval do
undef :meth
end
- lambda { @obj.meth(5) }.should raise_error(NoMethodError)
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
end
it "with a single quoted symbol" do
@undef_class.class_eval do
undef :'meth'
end
- lambda { @obj.meth(5) }.should raise_error(NoMethodError)
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
end
it "with a double quoted symbol" do
@undef_class.class_eval do
undef :"meth"
end
- lambda { @obj.meth(5) }.should raise_error(NoMethodError)
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
end
it "with a interpolated symbol" do
@undef_class.class_eval do
undef :"#{'meth'}"
end
- lambda { @obj.meth(5) }.should raise_error(NoMethodError)
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
end
end
@@ -61,7 +61,7 @@ describe "The undef keyword" do
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
diff --git a/spec/ruby/language/unless_spec.rb b/spec/ruby/language/unless_spec.rb
index 681f0adfdd..98acdc083b 100644
--- a/spec/ruby/language/unless_spec.rb
+++ b/spec/ruby/language/unless_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
describe "The unless expression" do
it "evaluates the unless body when the expression is false" do
diff --git a/spec/ruby/language/until_spec.rb b/spec/ruby/language/until_spec.rb
index 08898644ce..78c289ff56 100644
--- a/spec/ruby/language/until_spec.rb
+++ b/spec/ruby/language/until_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
# until bool-expr [do]
# body
@@ -220,7 +220,7 @@ describe "The until modifier with begin .. end block" do
a.should == [0, 1, 2, 4]
end
- it "restart the current iteration without reevaluting condition with redo" do
+ it "restart the current iteration without reevaluating condition with redo" do
a = []
i = 0
j = 0
diff --git a/spec/ruby/language/variables_spec.rb b/spec/ruby/language/variables_spec.rb
index 81ba54840a..c900c03d37 100644
--- a/spec/ruby/language/variables_spec.rb
+++ b/spec/ruby/language/variables_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/variables', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/variables'
describe "Multiple assignment" do
context "with a single RHS value" do
@@ -46,7 +46,7 @@ describe "Multiple assignment" do
x = mock("multi-assign single RHS")
x.should_receive(:to_ary).and_return(1)
- lambda { a, b, c = x }.should raise_error(TypeError)
+ -> { a, b, c = x }.should raise_error(TypeError)
end
it "does not call #to_a to convert an Object RHS when assigning a simple MLHS" do
@@ -127,7 +127,7 @@ describe "Multiple assignment" do
x = mock("multi-assign splat")
x.should_receive(:to_ary).and_return(1)
- lambda { *a = x }.should raise_error(TypeError)
+ -> { *a = x }.should raise_error(TypeError)
end
it "does not call #to_ary on an Array subclass" do
@@ -160,7 +160,7 @@ describe "Multiple assignment" do
x = mock("multi-assign splat")
x.should_receive(:to_ary).and_return(1)
- lambda { a, *b, c = x }.should raise_error(TypeError)
+ -> { a, *b, c = x }.should raise_error(TypeError)
end
it "does not call #to_a to convert an Object RHS with a MLHS" do
@@ -256,7 +256,7 @@ describe "Multiple assignment" do
x = mock("multi-assign attributes")
x.should_receive(:m).and_return(y)
- lambda { a, b = x.m }.should raise_error(TypeError)
+ -> { a, b = x.m }.should raise_error(TypeError)
end
it "assigns values from a RHS method call with receiver and arguments" do
@@ -354,6 +354,16 @@ describe "Multiple assignment" do
a.should be_an_instance_of(Array)
end
+ it "unfreezes the array returned from calling 'to_a' on the splatted value" do
+ obj = Object.new
+ def obj.to_a
+ [1,2].freeze
+ end
+ res = *obj
+ res.should == [1,2]
+ res.should_not.frozen?
+ end
+
it "consumes values for an anonymous splat" do
a = 1
(* = *a).should == [1]
@@ -407,7 +417,7 @@ describe "Multiple assignment" do
x = mock("multi-assign RHS splat")
x.should_receive(:to_a).and_return(1)
- lambda { *a = *x }.should raise_error(TypeError)
+ -> { *a = *x }.should raise_error(TypeError)
end
it "does not call #to_ary to convert an Object RHS with a single splat LHS" do
@@ -453,7 +463,7 @@ describe "Multiple assignment" do
x = mock("multi-assign splat")
x.should_receive(:to_a).and_return(1)
- lambda { a = *x }.should raise_error(TypeError)
+ -> { a = *x }.should raise_error(TypeError)
end
it "calls #to_a to convert an Object splat RHS when assigned to a simple MLHS" do
@@ -468,7 +478,7 @@ describe "Multiple assignment" do
x = mock("multi-assign splat")
x.should_receive(:to_a).and_return(1)
- lambda { a, b, c = *x }.should raise_error(TypeError)
+ -> { a, b, c = *x }.should raise_error(TypeError)
end
it "does not call #to_ary to convert an Object splat RHS when assigned to a simple MLHS" do
@@ -491,7 +501,7 @@ describe "Multiple assignment" do
x = mock("multi-assign splat")
x.should_receive(:to_a).and_return(1)
- lambda { a, *b, c = *x }.should raise_error(TypeError)
+ -> { a, *b, c = *x }.should raise_error(TypeError)
end
it "does not call #to_ary to convert an Object RHS with a MLHS" do
@@ -569,7 +579,7 @@ describe "Multiple assignment" do
x = mock("multi-assign splat MRHS")
x.should_receive(:to_a).and_return(1)
- lambda { a, *b = 1, *x }.should raise_error(TypeError)
+ -> { a, *b = 1, *x }.should raise_error(TypeError)
end
it "does not call #to_ary to convert a splatted Object as part of a MRHS with a splat MRHS" do
@@ -592,7 +602,7 @@ describe "Multiple assignment" do
x = mock("multi-assign splat MRHS")
x.should_receive(:to_a).and_return(1)
- lambda { a, *b = *x, 1 }.should raise_error(TypeError)
+ -> { a, *b = *x, 1 }.should raise_error(TypeError)
end
it "does not call #to_ary to convert a splatted Object with a splat MRHS" do
@@ -641,7 +651,7 @@ describe "Multiple assignment" do
x = mock("multi-assign mixed RHS")
x.should_receive(:to_ary).and_return(x)
- lambda { a, (b, c), d = 1, x, 3, 4 }.should raise_error(TypeError)
+ -> { a, (b, c), d = 1, x, 3, 4 }.should raise_error(TypeError)
end
it "calls #to_a to convert a splatted Object value in a MRHS" do
@@ -665,7 +675,7 @@ describe "Multiple assignment" do
x = mock("multi-assign mixed splatted RHS")
x.should_receive(:to_ary).and_return(x)
- lambda { a, *b, (c, d) = 1, 2, 3, *x }.should raise_error(TypeError)
+ -> { a, *b, (c, d) = 1, 2, 3, *x }.should raise_error(TypeError)
end
it "does not call #to_ary to convert an Object when the position receiving the value is a simple variable" do
@@ -705,6 +715,18 @@ describe "Multiple assignment" do
x.should == [1, 2, 3, 4, 5]
end
+ it "can be used to swap array elements" do
+ a = [1, 2]
+ a[0], a[1] = a[1], a[0]
+ a.should == [2, 1]
+ end
+
+ it "can be used to swap range of array elements" do
+ a = [1, 2, 3, 4]
+ a[0, 2], a[2, 2] = a[2, 2], a[0, 2]
+ a.should == [3, 4, 1, 2]
+ end
+
it "assigns RHS values to LHS constants" do
module VariableSpecs
MRHS_VALUES_1, MRHS_VALUES_2 = 1, 2
@@ -758,3 +780,91 @@ describe "A local variable assigned only within a conditional block" do
end
end
end
+
+describe 'Local variable shadowing' do
+ it "does not warn in verbose mode" do
+ result = nil
+
+ -> do
+ eval <<-CODE
+ a = [1, 2, 3]
+ result = a.map { |a| a = 3 }
+ CODE
+ end.should_not complain(verbose: true)
+
+ result.should == [3, 3, 3]
+ end
+end
+
+describe 'Allowed characters' do
+ it 'allows non-ASCII lowercased characters at the beginning' do
+ result = nil
+
+ eval <<-CODE
+ def test
+ μ = 1
+ end
+
+ result = test
+ CODE
+
+ result.should == 1
+ end
+
+ it 'parses a non-ASCII upcased character as a constant identifier' do
+ -> do
+ eval <<-CODE
+ def test
+ ἍBB = 1
+ end
+ CODE
+ end.should raise_error(SyntaxError, /dynamic constant assignment/)
+ end
+end
+
+describe "Instance variables" do
+ context "when instance variable is uninitialized" do
+ ruby_version_is ""..."3.0" do
+ it "warns about accessing uninitialized instance variable" do
+ obj = Object.new
+ def obj.foobar; a = @a; end
+
+ -> { obj.foobar }.should complain(/warning: instance variable @a not initialized/, verbose: true)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "doesn't warn about accessing uninitialized instance variable" do
+ obj = Object.new
+ def obj.foobar; a = @a; end
+
+ -> { obj.foobar }.should_not complain(verbose: true)
+ end
+ end
+
+ it "doesn't warn at lazy initialization" do
+ obj = Object.new
+ def obj.foobar; @a ||= 42; end
+
+ -> { obj.foobar }.should_not complain(verbose: true)
+ end
+ end
+
+ describe "global variable" do
+ context "when global variable is uninitialized" do
+ it "warns about accessing uninitialized global variable in verbose mode" do
+ obj = Object.new
+ def obj.foobar; a = $specs_uninitialized_global_variable; end
+
+ -> { obj.foobar }.should complain(/warning: global variable `\$specs_uninitialized_global_variable' not initialized/, verbose: true)
+ end
+
+ it "doesn't warn at lazy initialization" do
+ obj = Object.new
+ def obj.foobar; $specs_uninitialized_global_variable_lazy ||= 42; end
+
+ -> { obj.foobar }.should_not complain(verbose: true)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/language/while_spec.rb b/spec/ruby/language/while_spec.rb
index 00e948e41f..e172453ca6 100644
--- a/spec/ruby/language/while_spec.rb
+++ b/spec/ruby/language/while_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../spec_helper', __FILE__)
+require_relative '../spec_helper'
# while bool-expr [do]
# body
@@ -330,7 +330,7 @@ describe "The while modifier with begin .. end block" do
a.should == [0, 1, 2, 4]
end
- it "restarts the current iteration without reevaluting condition with redo" do
+ it "restarts the current iteration without reevaluating condition with redo" do
a = []
i = 0
j = 0
diff --git a/spec/ruby/language/yield_spec.rb b/spec/ruby/language/yield_spec.rb
index 663110cbe6..05d713af70 100644
--- a/spec/ruby/language/yield_spec.rb
+++ b/spec/ruby/language/yield_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/yield', __FILE__)
+require_relative '../spec_helper'
+require_relative 'fixtures/yield'
# Note that these specs use blocks defined as { |*a| ... } to capture the
# arguments with which the block is invoked. This is slightly confusing
@@ -13,18 +13,22 @@ describe "The yield call" do
describe "taking no arguments" do
it "raises a LocalJumpError when the method is not passed a block" do
- lambda { @y.z }.should raise_error(LocalJumpError)
+ -> { @y.z }.should raise_error(LocalJumpError)
end
it "ignores assignment to the explicit block argument and calls the passed block" do
@y.ze { 42 }.should == 42
end
+
+ it "does not pass a named block to the block being yielded to" do
+ @y.z() { |&block| block == nil }.should == true
+ end
end
describe "taking a single argument" do
describe "when no block is given" do
it "raises a LocalJumpError" do
- lambda { @y.s(1) }.should raise_error(LocalJumpError)
+ -> { @y.s(1) }.should raise_error(LocalJumpError)
end
end
@@ -48,40 +52,38 @@ describe "The yield call" do
describe "yielding to a lambda" do
it "passes an empty Array when the argument is an empty Array" do
- @y.s([], &lambda { |*a| a }).should == [[]]
+ @y.s([], &-> *a { a }).should == [[]]
end
it "passes nil as a value" do
- @y.s(nil, &lambda { |*a| a }).should == [nil]
+ @y.s(nil, &-> *a { a }).should == [nil]
end
it "passes a single value" do
- @y.s(1, &lambda { |*a| a }).should == [1]
+ @y.s(1, &-> *a { a }).should == [1]
end
it "passes a single, multi-value Array" do
- @y.s([1, 2, 3], &lambda { |*a| a }).should == [[1, 2, 3]]
+ @y.s([1, 2, 3], &-> *a { a }).should == [[1, 2, 3]]
end
it "raises an ArgumentError if too few arguments are passed" do
- lambda {
- @y.s(1, &lambda { |a,b| [a,b] })
+ -> {
+ @y.s(1, &-> a, b { [a,b] })
}.should raise_error(ArgumentError)
end
- ruby_bug "#12705", "2.2"..."2.5" do
- it "should not destructure an Array into multiple arguments" do
- lambda {
- @y.s([1, 2], &lambda { |a,b| [a,b] })
- }.should raise_error(ArgumentError)
- end
+ it "should not destructure an Array into multiple arguments" do
+ -> {
+ @y.s([1, 2], &-> a, b { [a,b] })
+ }.should raise_error(ArgumentError)
end
end
end
describe "taking multiple arguments" do
it "raises a LocalJumpError when the method is not passed a block" do
- lambda { @y.m(1, 2, 3) }.should raise_error(LocalJumpError)
+ -> { @y.m(1, 2, 3) }.should raise_error(LocalJumpError)
end
it "passes the arguments to the block" do
@@ -93,21 +95,21 @@ describe "The yield call" do
end
it "raises an ArgumentError if too many arguments are passed to a lambda" do
- lambda {
- @y.m(1, 2, 3, &lambda { |a| })
+ -> {
+ @y.m(1, 2, 3, &-> a { })
}.should raise_error(ArgumentError)
end
it "raises an ArgumentError if too few arguments are passed to a lambda" do
- lambda {
- @y.m(1, 2, 3, &lambda { |a,b,c,d| })
+ -> {
+ @y.m(1, 2, 3, &-> a, b, c, d { })
}.should raise_error(ArgumentError)
end
end
describe "taking a single splatted argument" do
it "raises a LocalJumpError when the method is not passed a block" do
- lambda { @y.r(0) }.should raise_error(LocalJumpError)
+ -> { @y.r(0) }.should raise_error(LocalJumpError)
end
it "passes a single value" do
@@ -139,7 +141,7 @@ describe "The yield call" do
describe "taking multiple arguments with a splat" do
it "raises a LocalJumpError when the method is not passed a block" do
- lambda { @y.rs(1, 2, [3, 4]) }.should raise_error(LocalJumpError)
+ -> { @y.rs(1, 2, [3, 4]) }.should raise_error(LocalJumpError)
end
it "passes the arguments to the block" do
@@ -164,7 +166,7 @@ describe "The yield call" do
describe "taking matching arguments with splats and post args" do
it "raises a LocalJumpError when the method is not passed a block" do
- lambda { @y.rs(1, 2, [3, 4]) }.should raise_error(LocalJumpError)
+ -> { @y.rs(1, 2, [3, 4]) }.should raise_error(LocalJumpError)
end
it "passes the arguments to the block" do
@@ -172,8 +174,52 @@ describe "The yield call" do
end
end
+ describe "taking a splat and a keyword argument" do
+ it "passes it as an array of the values and a hash" do
+ @y.k([1, 2]) { |*a| a }.should == [1, 2, {:b=>true}]
+ end
+ end
+
it "uses captured block of a block used in define_method" do
@y.deep(2).should == 4
end
+end
+describe "Using yield in a singleton class literal" do
+ ruby_version_is ""..."3.0" do
+ it 'emits a deprecation warning' do
+ code = <<~RUBY
+ def m
+ class << Object.new
+ yield
+ end
+ end
+ m { :ok }
+ RUBY
+
+ -> { eval(code) }.should complain(/warning: `yield' in class syntax will not be supported from Ruby 3.0/)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it 'raises a SyntaxError' do
+ code = <<~RUBY
+ class << Object.new
+ yield
+ end
+ RUBY
+
+ -> { eval(code) }.should raise_error(SyntaxError, /Invalid yield/)
+ end
+ end
+end
+
+describe "Using yield in non-lambda block" do
+ it 'raises a SyntaxError' do
+ code = <<~RUBY
+ 1.times { yield }
+ RUBY
+
+ -> { eval(code) }.should raise_error(SyntaxError, /Invalid yield/)
+ end
end