summaryrefslogtreecommitdiff
path: root/spec/ruby/language/for_spec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/language/for_spec.rb')
-rw-r--r--spec/ruby/language/for_spec.rb380
1 files changed, 380 insertions, 0 deletions
diff --git a/spec/ruby/language/for_spec.rb b/spec/ruby/language/for_spec.rb
new file mode 100644
index 0000000000..b0f3aef405
--- /dev/null
+++ b/spec/ruby/language/for_spec.rb
@@ -0,0 +1,380 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/for_scope'
+
+# for name[, name]... in expr [do]
+# body
+# end
+describe "The for expression" do
+ it "iterates over an Enumerable passing each element to the block" do
+ j = 0
+ for i in 1..3
+ j += i
+ end
+ j.should == 6
+ end
+
+ it "iterates over a list of arrays and destructures with empty comma" do
+ for i, in [[1,2]]
+ i.should == 1
+ end
+ end
+
+ it "iterates over a list of arrays and destructures with an empty splat" do
+ for i, * in [[1,2]]
+ i.should == 1
+ end
+ end
+
+ it "iterates over a list of arrays and destructures with a splat" do
+ for i, *j in [[1,2]]
+ i.should == 1
+ j.should == [2]
+ end
+ end
+
+ it "iterates over a list of arrays and destructures with a splat and additional targets" do
+ for i, *j, k in [[1,2,3,4]]
+ i.should == 1
+ j.should == [2,3]
+ k.should == 4
+ end
+ end
+
+ it "iterates over an Hash passing each key-value pair to the block" do
+ k = 0
+ l = 0
+
+ for i, j in { 1 => 10, 2 => 20 }
+ k += i
+ l += j
+ end
+
+ k.should == 3
+ l.should == 30
+ end
+
+ it "iterates over any object responding to 'each'" do
+ obj = Object.new
+ def obj.each
+ (0..10).each { |i| yield i }
+ end
+
+ j = 0
+ for i in obj
+ j += i
+ end
+ j.should == 55
+ end
+
+ it "allows an instance variable as an iterator name" do
+ m = [1,2,3]
+ n = 0
+ for @var in m
+ n += 1
+ end
+ @var.should == 3
+ n.should == 3
+ end
+
+ it "allows a class variable as an iterator name" do
+ class OFor
+ m = [1,2,3]
+ n = 0
+ for @@var in m
+ n += 1
+ end
+ @@var.should == 3
+ n.should == 3
+ end
+ end
+
+ it "allows a constant as an iterator name" do
+ class OFor
+ m = [1,2,3]
+ n = 0
+ -> {
+ for CONST in m
+ n += 1
+ end
+ }.should complain(/already initialized constant/)
+ CONST.should == 3
+ n.should == 3
+ end
+ end
+
+ it "allows a global variable as an iterator name" do
+ old_global_var = $var
+ m = [1,2,3]
+ n = 0
+ for $var in m
+ n += 1
+ end
+ $var.should == 3
+ n.should == 3
+ $var = old_global_var
+ end
+
+ it "allows an attribute as an iterator name" do
+ class OFor
+ attr_accessor :target
+ end
+
+ ofor = OFor.new
+ m = [1,2,3]
+ n = 0
+ for ofor.target in m
+ n += 1
+ end
+ ofor.target.should == 3
+ n.should == 3
+ end
+
+ it "allows an attribute with safe navigation as an iterator name" do
+ class OFor
+ attr_accessor :target
+ end
+
+ ofor = OFor.new
+ m = [1,2,3]
+ n = 0
+ eval <<~RUBY
+ for ofor&.target in m
+ n += 1
+ end
+ RUBY
+ ofor.target.should == 3
+ n.should == 3
+ end
+
+ it "allows an attribute with safe navigation on a nil base as an iterator name" do
+ ofor = nil
+ m = [1,2,3]
+ n = 0
+ eval <<~RUBY
+ for ofor&.target in m
+ n += 1
+ end
+ RUBY
+ ofor.should == nil
+ n.should == 3
+ end
+
+ it "allows an array index writer as an iterator name" do
+ arr = [:a, :b, :c]
+ m = [1,2,3]
+ n = 0
+ for arr[1] in m
+ n += 1
+ end
+ arr.should == [:a, 3, :c]
+ n.should == 3
+ end
+
+ it "allows a hash index writer as an iterator name" do
+ hash = { a: 10, b: 20, c: 30 }
+ m = [1,2,3]
+ n = 0
+ for hash[:b] in m
+ n += 1
+ end
+ hash.should == { a: 10, b: 3, c: 30 }
+ n.should == 3
+ end
+
+ # 1.9 behaviour verified by nobu in
+ # http://redmine.ruby-lang.org/issues/show/2053
+ it "yields only as many values as there are arguments" do
+ class OFor
+ def each
+ [[1,2,3], [4,5,6]].each do |a|
+ yield(a[0],a[1],a[2])
+ end
+ end
+ end
+ o = OFor.new
+ qs = []
+ for q in o
+ qs << q
+ end
+ qs.should == [1, 4]
+ q.should == 4
+ end
+
+ it "optionally takes a 'do' after the expression" do
+ j = 0
+ for i in 1..3 do
+ j += i
+ end
+ j.should == 6
+ end
+
+ it "allows body begin on the same line if do is used" do
+ j = 0
+ for i in 1..3 do j += i
+ end
+ j.should == 6
+ end
+
+ it "declares iteration variables in the surrounding variable scope" do
+ for a, b in [[1,2]]
+ end
+
+ a.should == 1
+ b.should == 2
+ end
+
+ it "declares variables in the body in the surrounding variable scope" do
+ for i in 1..2
+ a = 123
+ end
+
+ a.should == 123
+ end
+
+ it "declares variables in the body in the surrounding variable scope with 'do'" do
+ for i in 1..2 do
+ a = 123
+ end
+
+ a.should == 123
+ end
+
+ it "declares variables inside a block as normal" do
+ for i in 1..2 do
+ proc {
+ inside_proc = 42
+ }.call
+ end
+ local_variables.should == [:i]
+ end
+
+ it "declares variables inside a lambda as normal" do
+ for i in 1..2 do
+ -> {
+ inside_proc = 42
+ }.call
+ end
+ local_variables.should == [:i]
+ end
+
+ it "can be nested" do
+ for a in [6]
+ for b in [7]
+ c = a * b
+ end
+ end
+ local_variables.sort.should == [:a, :b, :c]
+ c.should == 42
+ end
+
+ it "can be nested with blocks in between" do
+ # This is an edge case spec for Ruby implementations which have
+ # their own runtime scope per for loop body (like YARV and TruffleRuby)
+ for a in [1]
+ a1 = a
+ a1.should == a
+ for b in [2]
+ b1 = b
+ a1.should == a
+ b1.should == b
+ proc {
+ inside_proc = 42
+
+ a1.should == a
+ b1.should == b
+ inside_proc.should == 42
+
+ for c in [3].map { |enum_var|
+ a1.should == a
+ b1.should == b
+ inside_proc.should == 42
+ enum_var
+ }
+ c1 = c
+
+ a1.should == a
+ b1.should == b
+ c1.should == c
+ inside_proc.should == 42
+
+ for d in [4]
+ d1 = d
+
+ a1.should == a
+ b1.should == b
+ c1.should == c
+ d1.should == d
+ inside_proc.should == 42
+ end
+ end
+ local_variables.sort.should == [:a, :a1, :b, :b1, :c, :c1, :d, :d1, :inside_proc]
+ }.call
+ end
+ end
+ local_variables.sort.should == [:a, :a1, :b, :b1]
+ end
+
+ it "can be nested with forward arguments" do
+ def bar(*args)
+ args
+ end
+
+ def foo(...)
+ for a in [1]
+ r = bar(...)
+ end
+ r
+ end
+
+ foo(2, 3).should == [2, 3]
+ end
+
+ it "does not try to access variables outside the method" do
+ ForSpecs::ForInClassMethod.foo.should == [:bar, :baz]
+ ForSpecs::ForInClassMethod::READER.call.should == :same_variable_set_outside
+ end
+
+ it "returns expr" do
+ for i in 1..3; end.should == (1..3)
+ for i,j in { 1 => 10, 2 => 20 }; end.should == { 1 => 10, 2 => 20 }
+ end
+
+ it "breaks out of a loop upon 'break', returning nil" do
+ j = 0
+ for i in 1..3
+ j += i
+
+ break if i == 2
+ end.should == nil
+
+ j.should == 3
+ end
+
+ it "allows 'break' to have an argument which becomes the value of the for expression" do
+ for i in 1..3
+ break 10 if i == 2
+ end.should == 10
+ end
+
+ it "starts the next iteration with 'next'" do
+ j = 0
+ for i in 1..5
+ next if i == 2
+
+ j += i
+ end
+
+ j.should == 13
+ end
+
+ it "repeats current iteration with 'redo'" do
+ j = 0
+ for i in 1..3
+ j += i
+
+ redo if i == 2 && j < 4
+ end
+
+ j.should == 8
+ end
+end