diff options
Diffstat (limited to 'spec/ruby/core/proc')
33 files changed, 2275 insertions, 0 deletions
diff --git a/spec/ruby/core/proc/allocate_spec.rb b/spec/ruby/core/proc/allocate_spec.rb new file mode 100644 index 0000000000..54e1b69df9 --- /dev/null +++ b/spec/ruby/core/proc/allocate_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' + +describe "Proc.allocate" do + it "raises a TypeError" do + -> { + Proc.allocate + }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/core/proc/arity_spec.rb b/spec/ruby/core/proc/arity_spec.rb new file mode 100644 index 0000000000..5c7728cb30 --- /dev/null +++ b/spec/ruby/core/proc/arity_spec.rb @@ -0,0 +1,656 @@ +require_relative '../../spec_helper' + +describe "Proc#arity" do + SpecEvaluate.desc = "for definition" + + context "for instances created with -> () { }" do + context "returns zero" do + evaluate <<-ruby do + @a = -> () {} + ruby + + @a.arity.should == 0 + end + + evaluate <<-ruby do + @a = -> (&b) {} + ruby + + @a.arity.should == 0 + end + end + + context "returns positive values" do + evaluate <<-ruby do + @a = -> (a) { } + @b = -> (a, b) { } + @c = -> (a, b, c) { } + @d = -> (a, b, c, d) { } + ruby + + @a.arity.should == 1 + @b.arity.should == 2 + @c.arity.should == 3 + @d.arity.should == 4 + end + + evaluate <<-ruby do + @a = -> (a:) { } + @b = -> (a:, b:) { } + @c = -> (a: 1, b:, c:, d: 2) { } + ruby + + @a.arity.should == 1 + @b.arity.should == 1 + @c.arity.should == 1 + end + + evaluate <<-ruby do + @a = -> (a, b:) { } + @b = -> (a, b:, &l) { } + ruby + + @a.arity.should == 2 + @b.arity.should == 2 + end + + evaluate <<-ruby do + @a = -> (a, b, c:, d: 1) { } + @b = -> (a, b, c:, d: 1, **k, &l) { } + ruby + + @a.arity.should == 3 + @b.arity.should == 3 + end + + evaluate <<-ruby do + @a = -> ((a, (*b, c))) { } + @b = -> (a, (*b, c), d, (*e), (*)) { } + ruby + + @a.arity.should == 1 + @b.arity.should == 5 + end + end + + context "returns negative values" do + evaluate <<-ruby do + @a = -> (a=1) { } + @b = -> (a=1, b=2) { } + ruby + + @a.arity.should == -1 + @b.arity.should == -1 + end + + evaluate <<-ruby do + @a = -> (a, b=1) { } + @b = -> (a, b, c=1, d=2) { } + ruby + + @a.arity.should == -2 + @b.arity.should == -3 + end + + evaluate <<-ruby do + @a = -> (a=1, *b) { } + @b = -> (a=1, b=2, *c) { } + ruby + + @a.arity.should == -1 + @b.arity.should == -1 + end + + evaluate <<-ruby do + @a = -> (*) { } + @b = -> (*a) { } + ruby + + @a.arity.should == -1 + @b.arity.should == -1 + end + + evaluate <<-ruby do + @a = -> (a, *) { } + @b = -> (a, *b) { } + @c = -> (a, b, *c) { } + @d = -> (a, b, c, *d) { } + ruby + + @a.arity.should == -2 + @b.arity.should == -2 + @c.arity.should == -3 + @d.arity.should == -4 + end + + evaluate <<-ruby do + @a = -> (*a, b) { } + @b = -> (*a, b, c) { } + @c = -> (*a, b, c, d) { } + ruby + + @a.arity.should == -2 + @b.arity.should == -3 + @c.arity.should == -4 + end + + evaluate <<-ruby do + @a = -> (a, *b, c) { } + @b = -> (a, b, *c, d, e) { } + ruby + + @a.arity.should == -3 + @b.arity.should == -5 + end + + evaluate <<-ruby do + @a = -> (a, b=1, c=2, *d, e, f) { } + @b = -> (a, b, c=1, *d, e, f, g) { } + ruby + + @a.arity.should == -4 + @b.arity.should == -6 + end + + evaluate <<-ruby do + @a = -> (a: 1) { } + @b = -> (a: 1, b: 2) { } + ruby + + @a.arity.should == -1 + @b.arity.should == -1 + end + + evaluate <<-ruby do + @a = -> (a=1, b: 2) { } + @b = -> (*a, b: 1) { } + @c = -> (a=1, b: 2) { } + @d = -> (a=1, *b, c: 2, &l) { } + ruby + + @a.arity.should == -1 + @b.arity.should == -1 + @c.arity.should == -1 + @d.arity.should == -1 + end + + evaluate <<-ruby do + @a = -> (**k, &l) { } + @b= -> (*a, **k) { } + @c = ->(a: 1, b: 2, **k) { } + ruby + + @a.arity.should == -1 + @b.arity.should == -1 + @c.arity.should == -1 + end + + evaluate <<-ruby do + @a = -> (a=1, *b, c:, d: 2, **k, &l) { } + ruby + + @a.arity.should == -2 + end + + evaluate <<-ruby do + @a = -> (a, b=1, *c, d, e:, f: 2, **k, &l) { } + @b = -> (a, b=1, *c, d:, e:, f: 2, **k, &l) { } + @c = -> (a=0, b=1, *c, d, e:, f: 2, **k, &l) { } + @d = -> (a=0, b=1, *c, d:, e:, f: 2, **k, &l) { } + ruby + + @a.arity.should == -4 + @b.arity.should == -3 + @c.arity.should == -3 + @d.arity.should == -2 + end + end + end + + context "for instances created with lambda { || }" do + context "returns zero" do + evaluate <<-ruby do + @a = lambda { } + @b = lambda { || } + ruby + + @a.arity.should == 0 + @b.arity.should == 0 + end + + evaluate <<-ruby do + @a = lambda { |&b| } + ruby + + @a.arity.should == 0 + end + end + + context "returns positive values" do + evaluate <<-ruby do + @a = lambda { |a| } + @b = lambda { |a, b| } + @c = lambda { |a, b, c| } + @d = lambda { |a, b, c, d| } + ruby + + @a.arity.should == 1 + @b.arity.should == 2 + @c.arity.should == 3 + @d.arity.should == 4 + end + + evaluate <<-ruby do + @a = lambda { |a:| } + @b = lambda { |a:, b:| } + @c = lambda { |a: 1, b:, c:, d: 2| } + ruby + + @a.arity.should == 1 + @b.arity.should == 1 + @c.arity.should == 1 + end + + evaluate <<-ruby do + @a = lambda { |a, b:| } + @b = lambda { |a, b:, &l| } + ruby + + @a.arity.should == 2 + @b.arity.should == 2 + end + + evaluate <<-ruby do + @a = lambda { |a, b, c:, d: 1| } + @b = lambda { |a, b, c:, d: 1, **k, &l| } + ruby + + @a.arity.should == 3 + @b.arity.should == 3 + end + + # implicit rest + evaluate <<-ruby do + @a = lambda { |a, | } + ruby + + @a.arity.should == 1 + end + end + + context "returns negative values" do + evaluate <<-ruby do + @a = lambda { |a=1| } + @b = lambda { |a=1, b=2| } + ruby + + @a.arity.should == -1 + @b.arity.should == -1 + end + + evaluate <<-ruby do + @a = lambda { |a, b=1| } + @b = lambda { |a, b, c=1, d=2| } + ruby + + @a.arity.should == -2 + @b.arity.should == -3 + end + + evaluate <<-ruby do + @a = lambda { |a=1, *b| } + @b = lambda { |a=1, b=2, *c| } + ruby + + @a.arity.should == -1 + @b.arity.should == -1 + end + + evaluate <<-ruby do + @a = lambda { |*| } + @b = lambda { |*a| } + ruby + + @a.arity.should == -1 + @b.arity.should == -1 + end + + evaluate <<-ruby do + @a = lambda { |a, *| } + @b = lambda { |a, *b| } + @c = lambda { |a, b, *c| } + @d = lambda { |a, b, c, *d| } + ruby + + @a.arity.should == -2 + @b.arity.should == -2 + @c.arity.should == -3 + @d.arity.should == -4 + end + + evaluate <<-ruby do + @a = lambda { |*a, b| } + @b = lambda { |*a, b, c| } + @c = lambda { |*a, b, c, d| } + ruby + + @a.arity.should == -2 + @b.arity.should == -3 + @c.arity.should == -4 + end + + evaluate <<-ruby do + @a = lambda { |a, *b, c| } + @b = lambda { |a, b, *c, d, e| } + ruby + + @a.arity.should == -3 + @b.arity.should == -5 + end + + evaluate <<-ruby do + @a = lambda { |a, b=1, c=2, *d, e, f| } + @b = lambda { |a, b, c=1, *d, e, f, g| } + ruby + + @a.arity.should == -4 + @b.arity.should == -6 + end + + evaluate <<-ruby do + @a = lambda { |a: 1| } + @b = lambda { |a: 1, b: 2| } + ruby + + @a.arity.should == -1 + @b.arity.should == -1 + end + + evaluate <<-ruby do + @a = lambda { |a=1, b: 2| } + @b = lambda { |*a, b: 1| } + @c = lambda { |a=1, b: 2| } + @d = lambda { |a=1, *b, c: 2, &l| } + ruby + + @a.arity.should == -1 + @b.arity.should == -1 + @c.arity.should == -1 + @d.arity.should == -1 + end + + evaluate <<-ruby do + @a = lambda { |**k, &l| } + @b = lambda { |*a, **k| } + @c = lambda { |a: 1, b: 2, **k| } + ruby + + @a.arity.should == -1 + @b.arity.should == -1 + @c.arity.should == -1 + end + + evaluate <<-ruby do + @a = lambda { |a=1, *b, c:, d: 2, **k, &l| } + ruby + + @a.arity.should == -2 + end + + evaluate <<-ruby do + @a = lambda { |(a, (*b, c)), d=1| } + @b = lambda { |a, (*b, c), d, (*e), (*), **k| } + @c = lambda { |a, (b, c), *, d:, e: 2, **| } + ruby + + @a.arity.should == -2 + @b.arity.should == -6 + @c.arity.should == -4 + end + + evaluate <<-ruby do + @a = lambda { |a, b=1, *c, d, e:, f: 2, **k, &l| } + @b = lambda { |a, b=1, *c, d:, e:, f: 2, **k, &l| } + @c = lambda { |a=0, b=1, *c, d, e:, f: 2, **k, &l| } + @d = lambda { |a=0, b=1, *c, d:, e:, f: 2, **k, &l| } + ruby + + @a.arity.should == -4 + @b.arity.should == -3 + @c.arity.should == -3 + @d.arity.should == -2 + end + end + end + + context "for instances created with proc { || }" do + context "returns zero" do + evaluate <<-ruby do + @a = proc { } + @b = proc { || } + ruby + + @a.arity.should == 0 + @b.arity.should == 0 + end + + evaluate <<-ruby do + @a = proc { |&b| } + ruby + + @a.arity.should == 0 + end + + evaluate <<-ruby do + @a = proc { |a=1| } + @b = proc { |a=1, b=2| } + ruby + + @a.arity.should == 0 + @b.arity.should == 0 + end + + evaluate <<-ruby do + @a = proc { |a: 1| } + @b = proc { |a: 1, b: 2| } + ruby + + @a.arity.should == 0 + @b.arity.should == 0 + end + + evaluate <<-ruby do + @a = proc { |**k, &l| } + @b = proc { |a: 1, b: 2, **k| } + ruby + + @a.arity.should == 0 + @b.arity.should == 0 + end + + evaluate <<-ruby do + @a = proc { |a=1, b: 2| } + @b = proc { |a=1, b: 2| } + ruby + + @a.arity.should == 0 + @b.arity.should == 0 + end + end + + context "returns positive values" do + evaluate <<-ruby do + @a = proc { |a| } + @b = proc { |a, b| } + @c = proc { |a, b, c| } + @d = proc { |a, b, c, d| } + ruby + + @a.arity.should == 1 + @b.arity.should == 2 + @c.arity.should == 3 + @d.arity.should == 4 + end + + evaluate <<-ruby do + @a = proc { |a, b=1| } + @b = proc { |a, b, c=1, d=2| } + ruby + + @a.arity.should == 1 + @b.arity.should == 2 + end + + evaluate <<-ruby do + @a = lambda { |a:| } + @b = lambda { |a:, b:| } + @c = lambda { |a: 1, b:, c:, d: 2| } + ruby + + @a.arity.should == 1 + @b.arity.should == 1 + @c.arity.should == 1 + end + + evaluate <<-ruby do + @a = proc { |a, b:| } + @b = proc { |a, b:, &l| } + ruby + + @a.arity.should == 2 + @b.arity.should == 2 + end + + evaluate <<-ruby do + @a = proc { |a, b, c:, d: 1| } + @b = proc { |a, b, c:, d: 1, **k, &l| } + ruby + + @a.arity.should == 3 + @b.arity.should == 3 + end + + evaluate <<-ruby do + @a = proc { |(a, (*b, c)), d=1| } + @b = proc { |a, (*b, c), d, (*e), (*), **k| } + ruby + + @a.arity.should == 1 + @b.arity.should == 5 + end + + # implicit rest + evaluate <<-ruby do + @a = proc { |a, | } + ruby + + @a.arity.should == 1 + end + end + + context "returns negative values" do + evaluate <<-ruby do + @a = proc { |a=1, *b| } + @b = proc { |a=1, b=2, *c| } + ruby + + @a.arity.should == -1 + @b.arity.should == -1 + end + + evaluate <<-ruby do + @a = proc { |*| } + @b = proc { |*a| } + ruby + + @a.arity.should == -1 + @b.arity.should == -1 + end + + evaluate <<-ruby do + @a = proc { |a, *| } + @b = proc { |a, *b| } + @c = proc { |a, b, *c| } + @d = proc { |a, b, c, *d| } + ruby + + @a.arity.should == -2 + @b.arity.should == -2 + @c.arity.should == -3 + @d.arity.should == -4 + end + + evaluate <<-ruby do + @a = proc { |*a, b| } + @b = proc { |*a, b, c| } + @c = proc { |*a, b, c, d| } + ruby + + @a.arity.should == -2 + @b.arity.should == -3 + @c.arity.should == -4 + end + + evaluate <<-ruby do + @a = proc { |a, *b, c| } + @b = proc { |a, b, *c, d, e| } + ruby + + @a.arity.should == -3 + @b.arity.should == -5 + end + + evaluate <<-ruby do + @a = proc { |a, b=1, c=2, *d, e, f| } + @b = proc { |a, b, c=1, *d, e, f, g| } + ruby + + @a.arity.should == -4 + @b.arity.should == -6 + end + + evaluate <<-ruby do + @a = proc { |*a, b: 1| } + @b = proc { |a=1, *b, c: 2, &l| } + ruby + + @a.arity.should == -1 + @b.arity.should == -1 + end + + evaluate <<-ruby do + @a = proc { |*a, **k| } + ruby + + @a.arity.should == -1 + end + + evaluate <<-ruby do + @a = proc { |a=1, *b, c:, d: 2, **k, &l| } + ruby + + @a.arity.should == -2 + end + + evaluate <<-ruby do + @a = proc { |a, (b, c), *, d:, e: 2, **| } + ruby + + @a.arity.should == -4 + end + + evaluate <<-ruby do + @a = proc { |a, b=1, *c, d, e:, f: 2, **k, &l| } + @b = proc { |a, b=1, *c, d:, e:, f: 2, **k, &l| } + @c = proc { |a=0, b=1, *c, d, e:, f: 2, **k, &l| } + @d = proc { |a=0, b=1, *c, d:, e:, f: 2, **k, &l| } + ruby + + @a.arity.should == -4 + @b.arity.should == -3 + @c.arity.should == -3 + @d.arity.should == -2 + end + end + end +end diff --git a/spec/ruby/core/proc/binding_spec.rb b/spec/ruby/core/proc/binding_spec.rb new file mode 100644 index 0000000000..86ab6bd400 --- /dev/null +++ b/spec/ruby/core/proc/binding_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../spec_helper' + +describe "Proc#binding" do + it "returns a Binding instance" do + [Proc.new{}, -> {}, proc {}].each { |p| + p.binding.should be_kind_of(Binding) + } + end + + it "returns the binding associated with self" do + obj = mock('binding') + def obj.test_binding(some, params) + -> {} + end + + lambdas_binding = obj.test_binding(1, 2).binding + + eval("some", lambdas_binding).should == 1 + eval("params", lambdas_binding).should == 2 + end +end diff --git a/spec/ruby/core/proc/block_pass_spec.rb b/spec/ruby/core/proc/block_pass_spec.rb new file mode 100644 index 0000000000..411c0bf3db --- /dev/null +++ b/spec/ruby/core/proc/block_pass_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../spec_helper' + +describe "Proc as a block pass argument" do + def revivify(&b) + b + end + + it "remains the same object if re-vivified by the target method" do + p = Proc.new {} + p2 = revivify(&p) + p.should equal p2 + p.should == p2 + end + + it "remains the same object if reconstructed with Proc.new" do + p = Proc.new {} + p2 = Proc.new(&p) + p.should equal p2 + p.should == p2 + end +end diff --git a/spec/ruby/core/proc/call_spec.rb b/spec/ruby/core/proc/call_spec.rb new file mode 100644 index 0000000000..6ec2fc8682 --- /dev/null +++ b/spec/ruby/core/proc/call_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../spec_helper' +require_relative 'shared/call' +require_relative 'shared/call_arguments' + +describe "Proc#call" do + it_behaves_like :proc_call, :call + it_behaves_like :proc_call_block_args, :call +end + +describe "Proc#call on a Proc created with Proc.new" do + it_behaves_like :proc_call_on_proc_new, :call +end + +describe "Proc#call on a Proc created with Kernel#lambda or Kernel#proc" do + it_behaves_like :proc_call_on_proc_or_lambda, :call +end diff --git a/spec/ruby/core/proc/case_compare_spec.rb b/spec/ruby/core/proc/case_compare_spec.rb new file mode 100644 index 0000000000..f11513cdb9 --- /dev/null +++ b/spec/ruby/core/proc/case_compare_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../spec_helper' +require_relative 'shared/call' +require_relative 'shared/call_arguments' + +describe "Proc#===" do + it_behaves_like :proc_call, :=== + it_behaves_like :proc_call_block_args, :=== +end + +describe "Proc#=== on a Proc created with Proc.new" do + it_behaves_like :proc_call_on_proc_new, :=== +end + +describe "Proc#=== on a Proc created with Kernel#lambda or Kernel#proc" do + it_behaves_like :proc_call_on_proc_or_lambda, :=== +end diff --git a/spec/ruby/core/proc/clone_spec.rb b/spec/ruby/core/proc/clone_spec.rb new file mode 100644 index 0000000000..730dc421a8 --- /dev/null +++ b/spec/ruby/core/proc/clone_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/dup' + +describe "Proc#clone" do + it_behaves_like :proc_dup, :clone + + ruby_bug "cloning a frozen proc is broken on Ruby 3.3", "3.3"..."3.4" do + it "preserves frozen status" do + proc = Proc.new { } + proc.freeze + proc.frozen?.should == true + proc.clone.frozen?.should == true + end + end + + ruby_version_is "3.3" do + it "calls #initialize_clone on subclass" do + obj = ProcSpecs::MyProc2.new(:a, 2) { } + dup = obj.clone + + dup.should_not equal(obj) + dup.class.should == ProcSpecs::MyProc2 + + dup.first.should == :a + dup.second.should == 2 + dup.initializer.should == :clone + end + end +end diff --git a/spec/ruby/core/proc/compose_spec.rb b/spec/ruby/core/proc/compose_spec.rb new file mode 100644 index 0000000000..9e9b57e06f --- /dev/null +++ b/spec/ruby/core/proc/compose_spec.rb @@ -0,0 +1,142 @@ +require_relative '../../spec_helper' +require_relative 'shared/compose' + +describe "Proc#<<" do + it "returns a Proc that is the composition of self and the passed Proc" do + upcase = proc { |s| s.upcase } + succ = proc { |s| s.succ } + + (succ << upcase).call('Ruby').should == "RUBZ" + end + + it "calls passed Proc with arguments and then calls self with result" do + f = proc { |x| x * x } + g = proc { |x| x + x } + + (f << g).call(2).should == 16 + (g << f).call(2).should == 8 + end + + it "accepts any callable object" do + inc = proc { |n| n + 1 } + + double = Object.new + def double.call(n); n * 2; end + + (inc << double).call(3).should == 7 + end + + it_behaves_like :proc_compose, :<<, -> { proc { |s| s.upcase } } + + describe "composition" do + it "is a Proc" do + f = proc { |x| x * x } + g = proc { |x| x + x } + + (f << g).is_a?(Proc).should == true + (f << g).should_not.lambda? + end + + it "is a lambda when parameter is lambda" do + f = -> x { x * x } + g = proc { |x| x + x } + lambda_proc = -> x { x } + + # lambda << proc + (f << g).is_a?(Proc).should == true + (f << g).should_not.lambda? + + # lambda << lambda + (f << lambda_proc).is_a?(Proc).should == true + (f << lambda_proc).should.lambda? + + # proc << lambda + (g << f).is_a?(Proc).should == true + (g << f).should.lambda? + end + + it "may accept multiple arguments" do + inc = proc { |n| n + 1 } + mul = proc { |n, m| n * m } + + (inc << mul).call(2, 3).should == 7 + end + + it "passes blocks to the second proc" do + ScratchPad.record [] + one = proc { |&arg| arg.call :one if arg } + two = proc { |&arg| arg.call :two if arg } + (one << two).call { |x| ScratchPad << x } + ScratchPad.recorded.should == [:two] + end + end +end + +describe "Proc#>>" do + it "returns a Proc that is the composition of self and the passed Proc" do + upcase = proc { |s| s.upcase } + succ = proc { |s| s.succ } + + (succ >> upcase).call('Ruby').should == "RUBZ" + end + + it "calls passed Proc with arguments and then calls self with result" do + f = proc { |x| x * x } + g = proc { |x| x + x } + + (f >> g).call(2).should == 8 + (g >> f).call(2).should == 16 + end + + it "accepts any callable object" do + inc = proc { |n| n + 1 } + + double = Object.new + def double.call(n); n * 2; end + + (inc >> double).call(3).should == 8 + end + + it_behaves_like :proc_compose, :>>, -> { proc { |s| s.upcase } } + + describe "composition" do + it "is a Proc" do + f = proc { |x| x * x } + g = proc { |x| x + x } + + (f >> g).is_a?(Proc).should == true + (f >> g).should_not.lambda? + end + + it "is a Proc when other is lambda" do + f = proc { |x| x * x } + g = -> x { x + x } + + (f >> g).is_a?(Proc).should == true + (f >> g).should_not.lambda? + end + + it "is a lambda when self is lambda" do + f = -> x { x * x } + g = proc { |x| x + x } + + (f >> g).is_a?(Proc).should == true + (f >> g).should.lambda? + end + + it "may accept multiple arguments" do + inc = proc { |n| n + 1 } + mul = proc { |n, m| n * m } + + (mul >> inc).call(2, 3).should == 7 + end + + it "passes blocks to the first proc" do + ScratchPad.record [] + one = proc { |&arg| arg.call :one if arg } + two = proc { |&arg| arg.call :two if arg } + (one >> two).call { |x| ScratchPad << x } + ScratchPad.recorded.should == [:one] + end + end +end diff --git a/spec/ruby/core/proc/curry_spec.rb b/spec/ruby/core/proc/curry_spec.rb new file mode 100644 index 0000000000..6daabe0ee1 --- /dev/null +++ b/spec/ruby/core/proc/curry_spec.rb @@ -0,0 +1,179 @@ +require_relative '../../spec_helper' + +describe "Proc#curry" do + before :each do + @proc_add = Proc.new {|x,y,z| (x||0) + (y||0) + (z||0) } + @lambda_add = -> x, y, z { (x||0) + (y||0) + (z||0) } + end + + it "returns a Proc when called on a proc" do + p = proc { true } + p.curry.should be_an_instance_of(Proc) + end + + it "returns a Proc when called on a lambda" do + p = -> { true } + p.curry.should be_an_instance_of(Proc) + end + + it "calls the curried proc with the arguments if sufficient arguments have been given" do + @proc_add.curry[1][2][3].should == 6 + @lambda_add.curry[1][2][3].should == 6 + end + + it "returns a Proc that consumes the remainder of the arguments unless sufficient arguments have been given" do + proc2 = @proc_add.curry[1][2] + proc2.should be_an_instance_of(Proc) + proc2.call(3).should == 6 + + lambda2 = @lambda_add.curry[1][2] + lambda2.should be_an_instance_of(Proc) + lambda2.call(3).should == 6 + + @proc_add.curry.call(1,2,3).should == 6 + @lambda_add.curry.call(1,2,3).should == 6 + end + + it "can be called multiple times on the same Proc" do + @proc_add.curry + -> { @proc_add.curry }.should_not raise_error + + @lambda_add.curry + -> { @lambda_add.curry }.should_not raise_error + end + + it "can be passed superfluous arguments if created from a proc" do + @proc_add.curry[1,2,3,4].should == 6 + + @proc_add.curry[1,2].curry[3,4,5,6].should == 6 + end + + it "raises an ArgumentError if passed superfluous arguments when created from a lambda" do + -> { @lambda_add.curry[1,2,3,4] }.should raise_error(ArgumentError) + -> { @lambda_add.curry[1,2].curry[3,4,5,6] }.should raise_error(ArgumentError) + end + + it "returns Procs with arities of -1" do + @proc_add.curry.arity.should == -1 + @lambda_add.curry.arity.should == -1 + l = -> *a { } + l.curry.arity.should == -1 + end + + it "produces Procs that raise ArgumentError for #binding" do + -> do + @proc_add.curry.binding + end.should raise_error(ArgumentError) + end + + it "produces Procs that return [[:rest]] for #parameters" do + @proc_add.curry.parameters.should == [[:rest]] + end + + it "produces Procs that return nil for #source_location" do + @proc_add.curry.source_location.should == nil + end + + it "produces Procs that can be passed as the block for instance_exec" do + curried = @proc_add.curry.call(1, 2) + + instance_exec(3, &curried).should == 6 + end + + it "combines arguments and calculates incoming arity accurately for successively currying" do + l = -> a, b, c { a+b+c } + l1 = l.curry.call(1) + # the l1 currying seems unnecessary, but it triggered the original issue + l2 = l1.curry.call(2) + + l2.curry.call(3).should == 6 + l1.curry.call(2,3).should == 6 + end +end + +describe "Proc#curry with arity argument" do + before :each do + @proc_add = proc { |x,y,z| (x||0) + (y||0) + (z||0) } + @lambda_add = -> x, y, z { (x||0) + (y||0) + (z||0) } + end + + it "accepts an optional Integer argument for the arity" do + -> { @proc_add.curry(3) }.should_not raise_error + -> { @lambda_add.curry(3) }.should_not raise_error + end + + it "returns a Proc when called on a proc" do + @proc_add.curry(3).should be_an_instance_of(Proc) + end + + it "returns a Proc when called on a lambda" do + @lambda_add.curry(3).should be_an_instance_of(Proc) + end + + # [ruby-core:24127] + it "retains the lambda-ness of the Proc on which its called" do + @lambda_add.curry(3).lambda?.should be_true + @proc_add.curry(3).lambda?.should be_false + end + + it "raises an ArgumentError if called on a lambda that requires more than _arity_ arguments" do + -> { @lambda_add.curry(2) }.should raise_error(ArgumentError) + -> { -> x, y, z, *more{}.curry(2) }.should raise_error(ArgumentError) + end + + it 'returns a Proc if called on a lambda that requires fewer than _arity_ arguments but may take more' do + -> a, b, c, d=nil, e=nil {}.curry(4).should be_an_instance_of(Proc) + -> a, b, c, d=nil, *e {}.curry(4).should be_an_instance_of(Proc) + -> a, b, c, *d {}.curry(4).should be_an_instance_of(Proc) + end + + it "raises an ArgumentError if called on a lambda that requires fewer than _arity_ arguments" do + -> { @lambda_add.curry(4) }.should raise_error(ArgumentError) + -> { -> { true }.curry(1) }.should raise_error(ArgumentError) + -> { -> a, b=nil {}.curry(5) }.should raise_error(ArgumentError) + -> { -> a, &b {}.curry(2) }.should raise_error(ArgumentError) + -> { -> a, b=nil, &c {}.curry(3) }.should raise_error(ArgumentError) + end + + it "calls the curried proc with the arguments if _arity_ arguments have been given" do + @proc_add.curry(3)[1][2][3].should == 6 + @lambda_add.curry(3)[1][2][3].should == 6 + end + + it "returns a Proc that consumes the remainder of the arguments when fewer than _arity_ arguments are given" do + proc2 = @proc_add.curry(3)[1][2] + proc2.should be_an_instance_of(Proc) + proc2.call(3).should == 6 + + lambda2 = @lambda_add.curry(3)[1][2] + lambda2.should be_an_instance_of(Proc) + lambda2.call(3).should == 6 + end + + it "can be specified multiple times on the same Proc" do + @proc_add.curry(2) + -> { @proc_add.curry(1) }.should_not raise_error + + @lambda_add.curry(3) + -> { @lambda_add.curry(3) }.should_not raise_error + end + + it "can be passed more than _arity_ arguments if created from a proc" do + @proc_add.curry(3)[1,2,3,4].should == 6 + + @proc_add.curry(3)[1,2].curry(3)[3,4,5,6].should == 6 + end + + it "raises an ArgumentError if passed more than _arity_ arguments when created from a lambda" do + -> { @lambda_add.curry(3)[1,2,3,4] }.should raise_error(ArgumentError) + -> { @lambda_add.curry(3)[1,2].curry(3)[3,4,5,6] }.should raise_error(ArgumentError) + end + + it "returns Procs with arities of -1 regardless of the value of _arity_" do + @proc_add.curry(1).arity.should == -1 + @proc_add.curry(2).arity.should == -1 + @lambda_add.curry(3).arity.should == -1 + l = -> *a { } + l.curry(3).arity.should == -1 + end +end diff --git a/spec/ruby/core/proc/dup_spec.rb b/spec/ruby/core/proc/dup_spec.rb new file mode 100644 index 0000000000..716357d1f0 --- /dev/null +++ b/spec/ruby/core/proc/dup_spec.rb @@ -0,0 +1,28 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/dup' + +describe "Proc#dup" do + it_behaves_like :proc_dup, :dup + + it "resets frozen status" do + proc = Proc.new { } + proc.freeze + proc.frozen?.should == true + proc.dup.frozen?.should == false + end + + ruby_version_is "3.3" do + it "calls #initialize_dup on subclass" do + obj = ProcSpecs::MyProc2.new(:a, 2) { } + dup = obj.dup + + dup.should_not equal(obj) + dup.class.should == ProcSpecs::MyProc2 + + dup.first.should == :a + dup.second.should == 2 + dup.initializer.should == :dup + end + end +end diff --git a/spec/ruby/core/proc/element_reference_spec.rb b/spec/ruby/core/proc/element_reference_spec.rb new file mode 100644 index 0000000000..81ceb91af5 --- /dev/null +++ b/spec/ruby/core/proc/element_reference_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../spec_helper' +require_relative 'shared/call' +require_relative 'shared/call_arguments' +require_relative 'fixtures/proc_aref' +require_relative 'fixtures/proc_aref_frozen' + +describe "Proc#[]" do + it_behaves_like :proc_call, :[] + it_behaves_like :proc_call_block_args, :[] +end + +describe "Proc#call on a Proc created with Proc.new" do + it_behaves_like :proc_call_on_proc_new, :call +end + +describe "Proc#call on a Proc created with Kernel#lambda or Kernel#proc" do + it_behaves_like :proc_call_on_proc_or_lambda, :call +end + +describe "Proc#[] with frozen_string_literal: true/false" do + it "doesn't duplicate frozen strings" do + ProcArefSpecs.aref.frozen?.should be_false + ProcArefSpecs.aref_freeze.frozen?.should be_true + ProcArefFrozenSpecs.aref.frozen?.should be_true + ProcArefFrozenSpecs.aref_freeze.frozen?.should be_true + end +end diff --git a/spec/ruby/core/proc/eql_spec.rb b/spec/ruby/core/proc/eql_spec.rb new file mode 100644 index 0000000000..ad8f6749fc --- /dev/null +++ b/spec/ruby/core/proc/eql_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/equal' + +describe "Proc#eql?" do + it_behaves_like :proc_equal, :eql? +end diff --git a/spec/ruby/core/proc/equal_value_spec.rb b/spec/ruby/core/proc/equal_value_spec.rb new file mode 100644 index 0000000000..ec7f274732 --- /dev/null +++ b/spec/ruby/core/proc/equal_value_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/equal' + +describe "Proc#==" do + it_behaves_like :proc_equal, :== +end diff --git a/spec/ruby/core/proc/fixtures/common.rb b/spec/ruby/core/proc/fixtures/common.rb new file mode 100644 index 0000000000..dfe67d7ba8 --- /dev/null +++ b/spec/ruby/core/proc/fixtures/common.rb @@ -0,0 +1,72 @@ +module ProcSpecs + class ToAryAsNil + def to_ary + nil + end + end + def self.new_proc_in_method + Proc.new + end + + def self.new_proc_from_amp(&block) + block + end + + def self.proc_for_1 + proc { 1 } + end + + class ProcSubclass < Proc + end + + def self.new_proc_subclass_in_method + ProcSubclass.new + end + + class MyProc < Proc + end + + class MyProc2 < Proc + def initialize(a, b) + @first = a + @second = b + end + + attr_reader :first, :second, :initializer + + def initialize_copy(other) + super + @initializer = :copy + @first = other.first + @second = other.second + end + + def initialize_dup(other) + super + @initializer = :dup + @first = other.first + @second = other.second + end + + def initialize_clone(other, **options) + super + @initializer = :clone + @first = other.first + @second = other.second + end + end + + class Arity + def arity_check(&block) + pn = Proc.new(&block).arity + pr = proc(&block).arity + lm = lambda(&block).arity + + if pn == pr and pr == lm + return pn + else + return :arity_check_failed + end + end + end +end diff --git a/spec/ruby/core/proc/fixtures/proc_aref.rb b/spec/ruby/core/proc/fixtures/proc_aref.rb new file mode 100644 index 0000000000..8ee355b14c --- /dev/null +++ b/spec/ruby/core/proc/fixtures/proc_aref.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: false +module ProcArefSpecs + def self.aref + proc {|a| a }["sometext"] + end + + def self.aref_freeze + proc {|a| a }["sometext".freeze] + end +end diff --git a/spec/ruby/core/proc/fixtures/proc_aref_frozen.rb b/spec/ruby/core/proc/fixtures/proc_aref_frozen.rb new file mode 100644 index 0000000000..50a330ba4f --- /dev/null +++ b/spec/ruby/core/proc/fixtures/proc_aref_frozen.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +module ProcArefFrozenSpecs + def self.aref + proc {|a| a }["sometext"] + end + + def self.aref_freeze + proc {|a| a }["sometext".freeze] + end +end diff --git a/spec/ruby/core/proc/fixtures/source_location.rb b/spec/ruby/core/proc/fixtures/source_location.rb new file mode 100644 index 0000000000..5572094630 --- /dev/null +++ b/spec/ruby/core/proc/fixtures/source_location.rb @@ -0,0 +1,55 @@ +module ProcSpecs + class SourceLocation + def self.my_proc + proc { true } + end + + def self.my_lambda + -> { true } + end + + def self.my_proc_new + Proc.new { true } + end + + def self.my_method + method(__method__).to_proc + end + + def self.my_multiline_proc + proc do + 'a'.upcase + 1 + 22 + end + end + + def self.my_multiline_lambda + -> do + 'a'.upcase + 1 + 22 + end + end + + def self.my_multiline_proc_new + Proc.new do + 'a'.upcase + 1 + 22 + end + end + + def self.my_detached_proc + body = proc { true } + proc(&body) + end + + def self.my_detached_lambda + body = -> { true } + suppress_warning {lambda(&body)} + end + + def self.my_detached_proc_new + body = Proc.new { true } + Proc.new(&body) + end + end +end diff --git a/spec/ruby/core/proc/hash_spec.rb b/spec/ruby/core/proc/hash_spec.rb new file mode 100644 index 0000000000..ebe0fde1a0 --- /dev/null +++ b/spec/ruby/core/proc/hash_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../spec_helper' + +describe "Proc#hash" do + it "is provided" do + proc {}.respond_to?(:hash).should be_true + -> {}.respond_to?(:hash).should be_true + end + + it "returns an Integer" do + proc { 1 + 489 }.hash.should be_kind_of(Integer) + end + + it "is stable" do + body = proc { :foo } + proc(&body).hash.should == proc(&body).hash + end +end diff --git a/spec/ruby/core/proc/inspect_spec.rb b/spec/ruby/core/proc/inspect_spec.rb new file mode 100644 index 0000000000..f53d34116f --- /dev/null +++ b/spec/ruby/core/proc/inspect_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/to_s' + +describe "Proc#inspect" do + it_behaves_like :proc_to_s, :inspect +end diff --git a/spec/ruby/core/proc/lambda_spec.rb b/spec/ruby/core/proc/lambda_spec.rb new file mode 100644 index 0000000000..5c3c38fc2a --- /dev/null +++ b/spec/ruby/core/proc/lambda_spec.rb @@ -0,0 +1,62 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +describe "Proc#lambda?" do + it "returns true if the Proc was created from a block with the lambda keyword" do + -> {}.lambda?.should be_true + end + + it "returns false if the Proc was created from a block with the proc keyword" do + proc {}.lambda?.should be_false + end + + it "returns false if the Proc was created from a block with Proc.new" do + Proc.new {}.lambda?.should be_false + end + + ruby_version_is ""..."3.3" do + it "is preserved when passing a Proc with & to the lambda keyword" do + suppress_warning {lambda(&->{})}.lambda?.should be_true + suppress_warning {lambda(&proc{})}.lambda?.should be_false + end + end + + it "is preserved when passing a Proc with & to the proc keyword" do + proc(&->{}).lambda?.should be_true + proc(&proc{}).lambda?.should be_false + end + + it "is preserved when passing a Proc with & to Proc.new" do + Proc.new(&->{}).lambda?.should be_true + Proc.new(&proc{}).lambda?.should be_false + end + + it "returns false if the Proc was created from a block with &" do + ProcSpecs.new_proc_from_amp{}.lambda?.should be_false + end + + it "is preserved when the Proc was passed using &" do + ProcSpecs.new_proc_from_amp(&->{}).lambda?.should be_true + ProcSpecs.new_proc_from_amp(&proc{}).lambda?.should be_false + ProcSpecs.new_proc_from_amp(&Proc.new{}).lambda?.should be_false + end + + it "returns true for a Method converted to a Proc" do + m = :foo.method(:to_s) + m.to_proc.lambda?.should be_true + ProcSpecs.new_proc_from_amp(&m).lambda?.should be_true + end + + # [ruby-core:24127] + it "is preserved when a Proc is curried" do + ->{}.curry.lambda?.should be_true + proc{}.curry.lambda?.should be_false + Proc.new{}.curry.lambda?.should be_false + end + + it "is preserved when a curried Proc is called without enough arguments" do + -> x, y{}.curry.call(42).lambda?.should be_true + proc{|x,y|}.curry.call(42).lambda?.should be_false + Proc.new{|x,y|}.curry.call(42).lambda?.should be_false + end +end diff --git a/spec/ruby/core/proc/new_spec.rb b/spec/ruby/core/proc/new_spec.rb new file mode 100644 index 0000000000..b2b7387756 --- /dev/null +++ b/spec/ruby/core/proc/new_spec.rb @@ -0,0 +1,178 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +describe "Proc.new with an associated block" do + it "returns a proc that represents the block" do + Proc.new { }.call.should == nil + Proc.new { "hello" }.call.should == "hello" + end + + describe "called on a subclass of Proc" do + before :each do + @subclass = Class.new(Proc) do + attr_reader :ok + def initialize + @ok = true + super + end + end + end + + it "returns an instance of the subclass" do + proc = @subclass.new {"hello"} + + proc.class.should == @subclass + proc.call.should == "hello" + proc.ok.should == true + end + + # JRUBY-5026 + describe "using a reified block parameter" do + it "returns an instance of the subclass" do + cls = Class.new do + def self.subclass=(subclass) + @subclass = subclass + end + def self.foo(&block) + @subclass.new(&block) + end + end + cls.subclass = @subclass + proc = cls.foo {"hello"} + + proc.class.should == @subclass + proc.call.should == "hello" + proc.ok.should == true + end + end + end + + # JRUBY-5261; Proc sets up the block during .new, not in #initialize + describe "called on a subclass of Proc that does not 'super' in 'initialize'" do + before :each do + @subclass = Class.new(Proc) do + attr_reader :ok + def initialize + @ok = true + end + end + end + + it "still constructs a functional proc" do + proc = @subclass.new {'ok'} + proc.call.should == 'ok' + proc.ok.should == true + end + end + + it "raises a LocalJumpError when context of the block no longer exists" do + def some_method + Proc.new { return } + end + res = some_method() + + -> { res.call }.should raise_error(LocalJumpError) + end + + it "returns from within enclosing method when 'return' is used in the block" do + # we essentially verify that the created instance behaves like proc, + # not like lambda. + def some_method + Proc.new { return :proc_return_value }.call + :method_return_value + end + some_method.should == :proc_return_value + end + + it "returns a subclass of Proc" do + obj = ProcSpecs::MyProc.new { } + obj.should be_kind_of(ProcSpecs::MyProc) + end + + it "calls initialize on the Proc object" do + obj = ProcSpecs::MyProc2.new(:a, 2) { } + obj.first.should == :a + obj.second.should == 2 + end +end + +describe "Proc.new with a block argument" do + it "returns the passed proc created from a block" do + passed_prc = Proc.new { "hello".size } + prc = Proc.new(&passed_prc) + + prc.should equal(passed_prc) + prc.call.should == 5 + end + + it "returns the passed proc created from a method" do + method = "hello".method(:size) + passed_prc = Proc.new(&method) + prc = Proc.new(&passed_prc) + + prc.should equal(passed_prc) + prc.call.should == 5 + end + + it "returns the passed proc created from a symbol" do + passed_prc = Proc.new(&:size) + prc = Proc.new(&passed_prc) + + prc.should equal(passed_prc) + prc.call("hello").should == 5 + end +end + +describe "Proc.new with a block argument called indirectly from a subclass" do + it "returns the passed proc created from a block" do + passed_prc = ProcSpecs::MyProc.new { "hello".size } + passed_prc.class.should == ProcSpecs::MyProc + prc = ProcSpecs::MyProc.new(&passed_prc) + + prc.should equal(passed_prc) + prc.call.should == 5 + end + + it "returns the passed proc created from a method" do + method = "hello".method(:size) + passed_prc = ProcSpecs::MyProc.new(&method) + passed_prc.class.should == ProcSpecs::MyProc + prc = ProcSpecs::MyProc.new(&passed_prc) + + prc.should equal(passed_prc) + prc.call.should == 5 + end + + it "returns the passed proc created from a symbol" do + passed_prc = ProcSpecs::MyProc.new(&:size) + passed_prc.class.should == ProcSpecs::MyProc + prc = ProcSpecs::MyProc.new(&passed_prc) + + prc.should equal(passed_prc) + prc.call("hello").should == 5 + end +end + +describe "Proc.new without a block" do + it "raises an ArgumentError" do + -> { Proc.new }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if invoked from within a method with no block" do + -> { ProcSpecs.new_proc_in_method }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if invoked on a subclass from within a method with no block" do + -> { ProcSpecs.new_proc_subclass_in_method }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when passed no block" do + def some_method + Proc.new + end + + -> { ProcSpecs.new_proc_in_method { "hello" } }.should raise_error(ArgumentError, 'tried to create Proc object without a block') + -> { ProcSpecs.new_proc_subclass_in_method { "hello" } }.should raise_error(ArgumentError, 'tried to create Proc object without a block') + -> { some_method { "hello" } }.should raise_error(ArgumentError, 'tried to create Proc object without a block') + end +end diff --git a/spec/ruby/core/proc/parameters_spec.rb b/spec/ruby/core/proc/parameters_spec.rb new file mode 100644 index 0000000000..cf8a8f5b12 --- /dev/null +++ b/spec/ruby/core/proc/parameters_spec.rb @@ -0,0 +1,175 @@ +require_relative '../../spec_helper' + +describe "Proc#parameters" do + it "returns an empty Array for a proc expecting no parameters" do + proc {}.parameters.should == [] + end + + it "returns an Array of Arrays for a proc expecting parameters" do + p = proc {|x| } + p.parameters.should be_an_instance_of(Array) + p.parameters.first.should be_an_instance_of(Array) + end + + it "sets the first element of each sub-Array to :opt for optional arguments" do + proc {|x| }.parameters.first.first.should == :opt + proc {|y,*x| }.parameters.first.first.should == :opt + end + + it "regards named parameters in procs as optional" do + proc {|x| }.parameters.first.first.should == :opt + end + + it "sets the first element of each sub-Array to :req for required argument if lambda keyword used" do + proc {|x| }.parameters(lambda: true).first.first.should == :req + proc {|y,*x| }.parameters(lambda: true).first.first.should == :req + end + + it "regards named parameters in procs as required if lambda keyword used" do + proc {|x| }.parameters(lambda: true).first.first.should == :req + end + + it "regards named parameters in lambda as optional if lambda: false keyword used" do + -> x { }.parameters(lambda: false).first.first.should == :opt + end + + it "regards named parameters in procs and lambdas as required if lambda keyword is truthy" do + proc {|x| }.parameters(lambda: 123).first.first.should == :req + -> x { }.parameters(lambda: 123).first.first.should == :req + end + + it "ignores the lambda keyword if it is nil" do + proc {|x|}.parameters(lambda: nil).first.first.should == :opt + -> x { }.parameters(lambda: nil).first.first.should == :req + end + + it "regards optional keyword parameters in procs as optional" do + proc {|x: :y| }.parameters.first.first.should == :key + end + + it "regards parameters with default values as optional" do + -> x=1 { }.parameters.first.first.should == :opt + proc {|x=1| }.parameters.first.first.should == :opt + end + + it "sets the first element of each sub-Array to :req for required arguments" do + -> x, y=[] { }.parameters.first.first.should == :req + -> y, *x { }.parameters.first.first.should == :req + end + + it "regards named parameters in lambdas as required" do + -> x { }.parameters.first.first.should == :req + end + + it "regards keyword parameters in lambdas as required" do + -> x: { }.parameters.first.first.should == :keyreq + end + + it "sets the first element of each sub-Array to :rest for parameters prefixed with asterisks" do + -> *x { }.parameters.first.first.should == :rest + -> x, *y { }.parameters.last.first.should == :rest + proc {|*x| }.parameters.first.first.should == :rest + proc {|x,*y| }.parameters.last.first.should == :rest + end + + it "sets the first element of each sub-Array to :keyrest for parameters prefixed with double asterisks" do + -> **x { }.parameters.first.first.should == :keyrest + -> x, **y { }.parameters.last.first.should == :keyrest + proc {|**x| }.parameters.first.first.should == :keyrest + proc {|x,**y| }.parameters.last.first.should == :keyrest + end + + it "sets the first element of each sub-Array to :block for parameters prefixed with ampersands" do + -> &x { }.parameters.first.first.should == :block + -> x, &y { }.parameters.last.first.should == :block + proc {|&x| }.parameters.first.first.should == :block + proc {|x,&y| }.parameters.last.first.should == :block + end + + it "sets the second element of each sub-Array to the name of the argument" do + -> x { }.parameters.first.last.should == :x + -> x=Math::PI { }.parameters.first.last.should == :x + -> an_argument, glark, &foo { }.parameters[1].last.should == :glark + -> *rest { }.parameters.first.last.should == :rest + -> &block { }.parameters.first.last.should == :block + proc {|x| }.parameters.first.last.should == :x + proc {|x=Math::PI| }.parameters.first.last.should == :x + proc {|an_argument, glark, &foo| }.parameters[1].last.should == :glark + proc {|*rest| }.parameters.first.last.should == :rest + proc {|&block| }.parameters.first.last.should == :block + end + + it "ignores unnamed rest arguments" do + -> x {}.parameters.should == [[:req, :x]] + end + + it "ignores implicit rest arguments" do + proc { |x, | }.parameters.should == [[:opt, :x]] + -> x { }.parameters.should == [[:req, :x]] + end + + it "adds rest arg with name * for \"star\" argument" do + -> * {}.parameters.should == [[:rest, :*]] + end + + it "adds keyrest arg with ** as a name for \"double star\" argument" do + -> ** {}.parameters.should == [[:keyrest, :**]] + end + + it "adds block arg with name & for anonymous block argument" do + -> & {}.parameters.should == [[:block, :&]] + end + + it "does not add locals as block options with a block and splat" do + -> *args, &blk do + local_is_not_parameter = {} + end.parameters.should == [[:rest, :args], [:block, :blk]] + proc do |*args, &blk| + local_is_not_parameter = {} + end.parameters.should == [[:rest, :args], [:block, :blk]] + end + + it "returns all parameters defined with the name _ as _" do + proc = proc {|_, _, _ = 1, *_, _:, _: 2, **_, &_| } + proc.parameters.should == [ + [:opt, :_], + [:opt, :_], + [:opt, :_], + [:rest, :_], + [:keyreq, :_], + [:key, :_], + [:keyrest, :_], + [:block, :_] + ] + + lambda = -> _, _, _ = 1, *_, _:, _: 2, **_, &_ {} + lambda.parameters.should == [ + [:req, :_], + [:req, :_], + [:opt, :_], + [:rest, :_], + [:keyreq, :_], + [:key, :_], + [:keyrest, :_], + [:block, :_] + ] + end + + it "returns :nokey for **nil parameter" do + proc { |**nil| }.parameters.should == [[:nokey]] + end + + ruby_version_is "3.4"..."4.0" do + it "handles the usage of `it` as a parameter" do + eval("proc { it }").parameters.should == [[:opt, nil]] + eval("lambda { it }").parameters.should == [[:req]] + end + end + + ruby_version_is "4.0" do + it "handles the usage of `it` as a parameter" do + eval("proc { it }").parameters.should == [[:opt]] + eval("lambda { it }").parameters.should == [[:req]] + end + end +end diff --git a/spec/ruby/core/proc/ruby2_keywords_spec.rb b/spec/ruby/core/proc/ruby2_keywords_spec.rb new file mode 100644 index 0000000000..d7f8f592e1 --- /dev/null +++ b/spec/ruby/core/proc/ruby2_keywords_spec.rb @@ -0,0 +1,66 @@ +require_relative '../../spec_helper' + +describe "Proc#ruby2_keywords" do + it "marks the final hash argument as keyword hash" do + f = -> *a { a.last } + f.ruby2_keywords + + last = f.call(1, 2, a: "a") + Hash.ruby2_keywords_hash?(last).should == true + end + + it "applies to the underlying method and applies across duplication" do + f1 = -> *a { a.last } + f1.ruby2_keywords + f2 = f1.dup + + Hash.ruby2_keywords_hash?(f1.call(1, 2, a: "a")).should == true + Hash.ruby2_keywords_hash?(f2.call(1, 2, a: "a")).should == true + + f3 = -> *a { a.last } + f4 = f3.dup + f3.ruby2_keywords + + Hash.ruby2_keywords_hash?(f3.call(1, 2, a: "a")).should == true + Hash.ruby2_keywords_hash?(f4.call(1, 2, a: "a")).should == true + end + + it "returns self" do + f = -> *a { } + f.ruby2_keywords.should equal f + end + + it "prints warning when a proc does not accept argument splat" do + f = -> a, b, c { } + + -> { + f.ruby2_keywords + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + + it "prints warning when a proc accepts keywords" do + f = -> *a, b: { } + + -> { + f.ruby2_keywords + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + + it "prints warning when a proc accepts keyword splat" do + f = -> *a, **b { } + + -> { + f.ruby2_keywords + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + + ruby_version_is "4.0" do + it "prints warning when a proc accepts post arguments" do + f = -> *a, b { } + + -> { + f.ruby2_keywords + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + end +end diff --git a/spec/ruby/core/proc/shared/call.rb b/spec/ruby/core/proc/shared/call.rb new file mode 100644 index 0000000000..dbec34df4b --- /dev/null +++ b/spec/ruby/core/proc/shared/call.rb @@ -0,0 +1,99 @@ +require_relative '../fixtures/common' + +describe :proc_call, shared: true do + it "invokes self" do + Proc.new { "test!" }.send(@method).should == "test!" + -> { "test!" }.send(@method).should == "test!" + proc { "test!" }.send(@method).should == "test!" + end + + it "sets self's parameters to the given values" do + Proc.new { |a, b| a + b }.send(@method, 1, 2).should == 3 + Proc.new { |*args| args }.send(@method, 1, 2, 3, 4).should == [1, 2, 3, 4] + Proc.new { |_, *args| args }.send(@method, 1, 2, 3).should == [2, 3] + + -> a, b { a + b }.send(@method, 1, 2).should == 3 + -> *args { args }.send(@method, 1, 2, 3, 4).should == [1, 2, 3, 4] + -> _, *args { args }.send(@method, 1, 2, 3).should == [2, 3] + + proc { |a, b| a + b }.send(@method, 1, 2).should == 3 + proc { |*args| args }.send(@method, 1, 2, 3, 4).should == [1, 2, 3, 4] + proc { |_, *args| args }.send(@method, 1, 2, 3).should == [2, 3] + end +end + + +describe :proc_call_on_proc_new, shared: true do + it "replaces missing arguments with nil" do + Proc.new { |a, b| [a, b] }.send(@method).should == [nil, nil] + Proc.new { |a, b| [a, b] }.send(@method, 1).should == [1, nil] + end + + it "silently ignores extra arguments" do + Proc.new { |a, b| a + b }.send(@method, 1, 2, 5).should == 3 + end + + it "auto-explodes a single Array argument" do + p = Proc.new { |a, b| [a, b] } + p.send(@method, 1, 2).should == [1, 2] + p.send(@method, [1, 2]).should == [1, 2] + p.send(@method, [1, 2, 3]).should == [1, 2] + p.send(@method, [1, 2, 3], 4).should == [[1, 2, 3], 4] + end +end + +describe :proc_call_on_proc_or_lambda, shared: true do + it "ignores excess arguments when self is a proc" do + a = proc {|x| x}.send(@method, 1, 2) + a.should == 1 + + a = proc {|x| x}.send(@method, 1, 2, 3) + a.should == 1 + + a = proc {|x:| x}.send(@method, 2, x: 1) + a.should == 1 + end + + it "will call #to_ary on argument and return self if return is nil" do + argument = ProcSpecs::ToAryAsNil.new + result = proc { |x, _| x }.send(@method, argument) + result.should == argument + end + + it "substitutes nil for missing arguments when self is a proc" do + proc {|x,y| [x,y]}.send(@method).should == [nil,nil] + + a = proc {|x,y| [x, y]}.send(@method, 1) + a.should == [1,nil] + end + + it "raises an ArgumentError on excess arguments when self is a lambda" do + -> { + -> x { x }.send(@method, 1, 2) + }.should raise_error(ArgumentError) + + -> { + -> x { x }.send(@method, 1, 2, 3) + }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError on missing arguments when self is a lambda" do + -> { + -> x { x }.send(@method) + }.should raise_error(ArgumentError) + + -> { + -> x, y { [x,y] }.send(@method, 1) + }.should raise_error(ArgumentError) + end + + it "treats a single Array argument as a single argument when self is a lambda" do + -> a { a }.send(@method, [1, 2]).should == [1, 2] + -> a, b { [a, b] }.send(@method, [1, 2], 3).should == [[1,2], 3] + end + + it "treats a single Array argument as a single argument when self is a proc" do + proc { |a| a }.send(@method, [1, 2]).should == [1, 2] + proc { |a, b| [a, b] }.send(@method, [1, 2], 3).should == [[1,2], 3] + end +end diff --git a/spec/ruby/core/proc/shared/call_arguments.rb b/spec/ruby/core/proc/shared/call_arguments.rb new file mode 100644 index 0000000000..91ada3439e --- /dev/null +++ b/spec/ruby/core/proc/shared/call_arguments.rb @@ -0,0 +1,29 @@ +describe :proc_call_block_args, shared: true do + it "can receive block arguments" do + Proc.new {|&b| b.send(@method)}.send(@method) {1 + 1}.should == 2 + -> &b { b.send(@method)}.send(@method) {1 + 1}.should == 2 + proc {|&b| b.send(@method)}.send(@method) {1 + 1}.should == 2 + end + + it "yields to the block given at declaration and not to the block argument" do + proc_creator = Object.new + def proc_creator.create + Proc.new do |&b| + yield + end + end + a_proc = proc_creator.create { 7 } + a_proc.send(@method) { 3 }.should == 7 + end + + it "can call its block argument declared with a block argument" do + proc_creator = Object.new + def proc_creator.create(method_name) + Proc.new do |&b| + yield + b.send(method_name) + end + end + a_proc = proc_creator.create(@method) { 7 } + a_proc.call { 3 }.should == 10 + end +end diff --git a/spec/ruby/core/proc/shared/compose.rb b/spec/ruby/core/proc/shared/compose.rb new file mode 100644 index 0000000000..3d3f3b310d --- /dev/null +++ b/spec/ruby/core/proc/shared/compose.rb @@ -0,0 +1,22 @@ +describe :proc_compose, shared: true do + it "raises TypeError if passed not callable object" do + lhs = @object.call + not_callable = Object.new + + -> { + lhs.send(@method, not_callable) + }.should raise_error(TypeError, "callable object is expected") + + end + + it "does not try to coerce argument with #to_proc" do + lhs = @object.call + + succ = Object.new + def succ.to_proc(s); s.succ; end + + -> { + lhs.send(@method, succ) + }.should raise_error(TypeError, "callable object is expected") + end +end diff --git a/spec/ruby/core/proc/shared/dup.rb b/spec/ruby/core/proc/shared/dup.rb new file mode 100644 index 0000000000..1266337f94 --- /dev/null +++ b/spec/ruby/core/proc/shared/dup.rb @@ -0,0 +1,39 @@ +describe :proc_dup, shared: true do + it "returns a copy of self" do + a = -> { "hello" } + b = a.send(@method) + + a.should_not equal(b) + + a.call.should == b.call + end + + it "returns an instance of subclass" do + cl = Class.new(Proc) + + cl.new{}.send(@method).class.should == cl + end + + ruby_version_is "3.4" do + it "copies instance variables" do + proc = -> { "hello" } + proc.instance_variable_set(:@ivar, 1) + cl = proc.send(@method) + cl.instance_variables.should == [:@ivar] + end + + it "copies the finalizer" do + code = <<-'RUBY' + obj = Proc.new { } + + ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized\n" }) + + obj.clone + + exit 0 + RUBY + + ruby_exe(code).lines.sort.should == ["finalized\n", "finalized\n"] + end + end +end diff --git a/spec/ruby/core/proc/shared/equal.rb b/spec/ruby/core/proc/shared/equal.rb new file mode 100644 index 0000000000..d0503fb064 --- /dev/null +++ b/spec/ruby/core/proc/shared/equal.rb @@ -0,0 +1,83 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/common' + +describe :proc_equal, shared: true do + it "is a public method" do + Proc.should have_public_instance_method(@method, false) + end + + it "returns true if self and other are the same object" do + p = proc { :foo } + p.send(@method, p).should be_true + + p = Proc.new { :foo } + p.send(@method, p).should be_true + + p = -> { :foo } + p.send(@method, p).should be_true + end + + it "returns true if other is a dup of the original" do + p = proc { :foo } + p.send(@method, p.dup).should be_true + + p = Proc.new { :foo } + p.send(@method, p.dup).should be_true + + p = -> { :foo } + p.send(@method, p.dup).should be_true + end + + # identical here means the same method invocation. + it "returns false when bodies are the same but capture env is not identical" do + a = ProcSpecs.proc_for_1 + b = ProcSpecs.proc_for_1 + + a.send(@method, b).should be_false + end + + it "returns false if procs are distinct but have the same body and environment" do + p = proc { :foo } + p2 = proc { :foo } + p.send(@method, p2).should be_false + end + + it "returns false if lambdas are distinct but have same body and environment" do + x = -> { :foo } + x2 = -> { :foo } + x.send(@method, x2).should be_false + end + + it "returns false if using comparing lambda to proc, even with the same body and env" do + p = -> { :foo } + p2 = proc { :foo } + p.send(@method, p2).should be_false + + x = proc { :bar } + x2 = -> { :bar } + x.send(@method, x2).should be_false + end + + it "returns false if other is not a Proc" do + p = proc { :foo } + p.send(@method, []).should be_false + + p = Proc.new { :foo } + p.send(@method, Object.new).should be_false + + p = -> { :foo } + p.send(@method, :foo).should be_false + end + + it "returns false if self and other are both procs but have different bodies" do + p = proc { :bar } + p2 = proc { :foo } + p.send(@method, p2).should be_false + end + + it "returns false if self and other are both lambdas but have different bodies" do + p = -> { :foo } + p2 = -> { :bar } + p.send(@method, p2).should be_false + end +end diff --git a/spec/ruby/core/proc/shared/to_s.rb b/spec/ruby/core/proc/shared/to_s.rb new file mode 100644 index 0000000000..a52688a89f --- /dev/null +++ b/spec/ruby/core/proc/shared/to_s.rb @@ -0,0 +1,60 @@ +describe :proc_to_s, shared: true do + describe "for a proc created with Proc.new" do + it "returns a description including file and line number" do + Proc.new { "hello" }.send(@method).should =~ /^#<Proc:([^ ]*?) #{Regexp.escape __FILE__}:#{__LINE__ }>$/ + end + + it "has a binary encoding" do + Proc.new { "hello" }.send(@method).encoding.should == Encoding::BINARY + end + end + + describe "for a proc created with lambda" do + it "returns a description including '(lambda)' and including file and line number" do + -> { "hello" }.send(@method).should =~ /^#<Proc:([^ ]*?) #{Regexp.escape __FILE__}:#{__LINE__ } \(lambda\)>$/ + end + + it "has a binary encoding" do + -> { "hello" }.send(@method).encoding.should == Encoding::BINARY + end + end + + describe "for a proc created with proc" do + it "returns a description including file and line number" do + proc { "hello" }.send(@method).should =~ /^#<Proc:([^ ]*?) #{Regexp.escape __FILE__}:#{__LINE__ }>$/ + end + + it "has a binary encoding" do + proc { "hello" }.send(@method).encoding.should == Encoding::BINARY + end + end + + describe "for a proc created with UnboundMethod#to_proc" do + it "returns a description including '(lambda)' and optionally including file and line number" do + def hello; end + s = method("hello").to_proc.send(@method) + if s.include? __FILE__ + s.should =~ /^#<Proc:([^ ]*?) #{Regexp.escape __FILE__}:#{__LINE__ - 3} \(lambda\)>$/ + else + s.should =~ /^#<Proc:([^ ]*?) \(lambda\)>$/ + end + end + + it "has a binary encoding" do + def hello; end + method("hello").to_proc.send(@method).encoding.should == Encoding::BINARY + end + end + + describe "for a proc created with Symbol#to_proc" do + it "returns a description including '(&:symbol)'" do + proc = :foobar.to_proc + proc.send(@method).should.include?('(&:foobar)') + end + + it "has a binary encoding" do + proc = :foobar.to_proc + proc.send(@method).encoding.should == Encoding::BINARY + end + end +end diff --git a/spec/ruby/core/proc/source_location_spec.rb b/spec/ruby/core/proc/source_location_spec.rb new file mode 100644 index 0000000000..fd33f21a26 --- /dev/null +++ b/spec/ruby/core/proc/source_location_spec.rb @@ -0,0 +1,104 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/source_location' + +describe "Proc#source_location" do + before :each do + @proc = ProcSpecs::SourceLocation.my_proc + @lambda = ProcSpecs::SourceLocation.my_lambda + @proc_new = ProcSpecs::SourceLocation.my_proc_new + @method = ProcSpecs::SourceLocation.my_method + end + + it "returns an Array" do + @proc.source_location.should be_an_instance_of(Array) + @proc_new.source_location.should be_an_instance_of(Array) + @lambda.source_location.should be_an_instance_of(Array) + @method.source_location.should be_an_instance_of(Array) + end + + it "sets the first value to the path of the file in which the proc was defined" do + file = @proc.source_location[0] + file.should be_an_instance_of(String) + file.should == File.realpath('fixtures/source_location.rb', __dir__) + + file = @proc_new.source_location[0] + file.should be_an_instance_of(String) + file.should == File.realpath('fixtures/source_location.rb', __dir__) + + file = @lambda.source_location[0] + file.should be_an_instance_of(String) + file.should == File.realpath('fixtures/source_location.rb', __dir__) + + file = @method.source_location[0] + file.should be_an_instance_of(String) + file.should == File.realpath('fixtures/source_location.rb', __dir__) + end + + it "sets the second value to an Integer representing the line on which the proc was defined" do + line = @proc.source_location[1] + line.should be_an_instance_of(Integer) + line.should == 4 + + line = @proc_new.source_location[1] + line.should be_an_instance_of(Integer) + line.should == 12 + + line = @lambda.source_location[1] + line.should be_an_instance_of(Integer) + line.should == 8 + + line = @method.source_location[1] + line.should be_an_instance_of(Integer) + line.should == 15 + end + + it "works even if the proc was created on the same line" do + ruby_version_is(""..."4.1") do + proc { true }.source_location.should == [__FILE__, __LINE__] + Proc.new { true }.source_location.should == [__FILE__, __LINE__] + -> { true }.source_location.should == [__FILE__, __LINE__] + end + ruby_version_is("4.1") do + proc { true }.source_location.should == [__FILE__, __LINE__, 11, __LINE__, 19] + Proc.new { true }.source_location.should == [__FILE__, __LINE__, 15, __LINE__, 23] + -> { true }.source_location.should == [__FILE__, __LINE__, 6, __LINE__, 17] + end + end + + it "returns the first line of a multi-line proc (i.e. the line containing 'proc do')" do + ProcSpecs::SourceLocation.my_multiline_proc.source_location[1].should == 20 + ProcSpecs::SourceLocation.my_multiline_proc_new.source_location[1].should == 34 + ProcSpecs::SourceLocation.my_multiline_lambda.source_location[1].should == 27 + end + + it "returns the location of the proc's body; not necessarily the proc itself" do + ProcSpecs::SourceLocation.my_detached_proc.source_location[1].should == 41 + ProcSpecs::SourceLocation.my_detached_proc_new.source_location[1].should == 51 + ProcSpecs::SourceLocation.my_detached_lambda.source_location[1].should == 46 + end + + it "returns the same value for a proc-ified method as the method reports" do + method = ProcSpecs::SourceLocation.method(:my_proc) + proc = method.to_proc + + method.source_location.should == proc.source_location + end + + it "returns nil for a core method that has been proc-ified" do + method = [].method(:<<) + proc = method.to_proc + + proc.source_location.should == nil + end + + it "works for eval with a given line" do + proc = eval('-> {}', nil, "foo", 100) + location = proc.source_location + ruby_version_is(""..."4.1") do + location.should == ["foo", 100] + end + ruby_version_is("4.1") do + location.should == ["foo", 100, 0, 100, 5] + end + end +end diff --git a/spec/ruby/core/proc/to_proc_spec.rb b/spec/ruby/core/proc/to_proc_spec.rb new file mode 100644 index 0000000000..ffaa34929b --- /dev/null +++ b/spec/ruby/core/proc/to_proc_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' + +describe "Proc#to_proc" do + it "returns self" do + [Proc.new {}, -> {}, proc {}].each { |p| + p.to_proc.should equal(p) + } + end +end diff --git a/spec/ruby/core/proc/to_s_spec.rb b/spec/ruby/core/proc/to_s_spec.rb new file mode 100644 index 0000000000..5e9c46b6b8 --- /dev/null +++ b/spec/ruby/core/proc/to_s_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/to_s' + +describe "Proc#to_s" do + it_behaves_like :proc_to_s, :to_s +end diff --git a/spec/ruby/core/proc/yield_spec.rb b/spec/ruby/core/proc/yield_spec.rb new file mode 100644 index 0000000000..365d5b04bd --- /dev/null +++ b/spec/ruby/core/proc/yield_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../spec_helper' +require_relative 'shared/call' +require_relative 'shared/call_arguments' + +describe "Proc#yield" do + it_behaves_like :proc_call, :yield + it_behaves_like :proc_call_block_args, :yield +end + +describe "Proc#yield on a Proc created with Proc.new" do + it_behaves_like :proc_call_on_proc_new, :yield +end + +describe "Proc#yield on a Proc created with Kernel#lambda or Kernel#proc" do + it_behaves_like :proc_call_on_proc_or_lambda, :yield +end |
