summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2020-06-01 12:28:05 -0700
committerJeremy Evans <code@jeremyevans.net>2020-06-19 12:58:25 -0700
commit878af5147def7fed089d3cc388742f0111db58ae (patch)
tree93e6e68b6b40575f7a1f3a641383c4111f05ef08
parentb3aff6a11cbc96e5fc6c615d3f7a7a11fda6f59a (diff)
Implement Proc#== and #eql?
Previously, these were not implemented, and Object#== and #eql? were used. This tries to check the proc internals to make sure that procs created from separate blocks are treated as not equal, but procs created from the same block are treated as equal, even when the lazy proc allocation optimization is used. Implements [Feature #14267]
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/3174
-rw-r--r--proc.c62
-rw-r--r--spec/ruby/core/proc/eql_spec.rb8
-rw-r--r--spec/ruby/core/proc/equal_value_spec.rb8
-rw-r--r--spec/ruby/core/proc/shared/equal.rb14
-rw-r--r--test/ruby/test_proc.rb10
5 files changed, 93 insertions, 9 deletions
diff --git a/proc.c b/proc.c
index 4af42c04bd..82b48dff9f 100644
--- a/proc.c
+++ b/proc.c
@@ -1255,6 +1255,66 @@ rb_proc_get_iseq(VALUE self, int *is_proc)
}
static VALUE
+proc_eq(VALUE self, VALUE other)
+{
+ const rb_proc_t *self_proc, *other_proc;
+ const struct rb_block *self_block, *other_block;
+ const struct rb_captured_block *self_cblock, *other_cblock;
+
+ if (rb_obj_class(self) != rb_obj_class(other)) {
+ return Qfalse;
+ }
+
+ GetProcPtr(self, self_proc);
+ GetProcPtr(other, other_proc);
+
+ if (self_proc->is_from_method != other_proc->is_from_method ||
+ self_proc->is_lambda != other_proc->is_lambda) {
+ return Qfalse;
+ }
+
+ self_block = &self_proc->block;
+ other_block = &other_proc->block;
+
+ if (vm_block_type(self_block) != vm_block_type(other_block)) {
+ return Qfalse;
+ }
+
+ switch (vm_block_type(self_block)) {
+ case block_type_iseq:
+ if (self_block->as.captured.ep != \
+ other_block->as.captured.ep ||
+ self_block->as.captured.code.iseq != \
+ other_block->as.captured.code.iseq) {
+ return Qfalse;
+
+ }
+ break;
+ case block_type_ifunc:
+ if (self_block->as.captured.ep != \
+ other_block->as.captured.ep ||
+ self_block->as.captured.code.ifunc != \
+ other_block->as.captured.code.ifunc) {
+ return Qfalse;
+
+ }
+ break;
+ case block_type_proc:
+ if (self_block->as.proc != other_block->as.proc) {
+ return Qfalse;
+ }
+ break;
+ case block_type_symbol:
+ if (self_block->as.symbol != other_block->as.symbol) {
+ return Qfalse;
+ }
+ break;
+ }
+
+ return Qtrue;
+}
+
+static VALUE
iseq_location(const rb_iseq_t *iseq)
{
VALUE loc[2];
@@ -3970,6 +4030,8 @@ Init_Proc(void)
rb_define_method(rb_cProc, "curry", proc_curry, -1);
rb_define_method(rb_cProc, "<<", proc_compose_to_left, 1);
rb_define_method(rb_cProc, ">>", proc_compose_to_right, 1);
+ rb_define_method(rb_cProc, "==", proc_eq, 1);
+ rb_define_method(rb_cProc, "eql?", proc_eq, 1);
rb_define_method(rb_cProc, "source_location", rb_proc_location, 0);
rb_define_method(rb_cProc, "parameters", rb_proc_parameters, 0);
rb_define_method(rb_cProc, "ruby2_keywords", proc_ruby2_keywords, 0);
diff --git a/spec/ruby/core/proc/eql_spec.rb b/spec/ruby/core/proc/eql_spec.rb
index a3c5a5658d..d95e890c29 100644
--- a/spec/ruby/core/proc/eql_spec.rb
+++ b/spec/ruby/core/proc/eql_spec.rb
@@ -2,5 +2,11 @@ require_relative '../../spec_helper'
require_relative 'shared/equal'
describe "Proc#eql?" do
- it_behaves_like :proc_equal_undefined, :eql?
+ ruby_version_is "0"..."2.8" do
+ it_behaves_like :proc_equal_undefined, :eql?
+ end
+
+ ruby_version_is "2.8" do
+ it_behaves_like :proc_equal, :eql?
+ end
end
diff --git a/spec/ruby/core/proc/equal_value_spec.rb b/spec/ruby/core/proc/equal_value_spec.rb
index 1b6ac792cf..fb465992e9 100644
--- a/spec/ruby/core/proc/equal_value_spec.rb
+++ b/spec/ruby/core/proc/equal_value_spec.rb
@@ -2,5 +2,11 @@ require_relative '../../spec_helper'
require_relative 'shared/equal'
describe "Proc#==" do
- it_behaves_like :proc_equal_undefined, :==
+ ruby_version_is "0"..."2.8" do
+ it_behaves_like :proc_equal_undefined, :==
+ end
+
+ ruby_version_is "2.8" do
+ it_behaves_like :proc_equal, :==
+ end
end
diff --git a/spec/ruby/core/proc/shared/equal.rb b/spec/ruby/core/proc/shared/equal.rb
index 46a1894424..0c0020ca7f 100644
--- a/spec/ruby/core/proc/shared/equal.rb
+++ b/spec/ruby/core/proc/shared/equal.rb
@@ -36,26 +36,26 @@ describe :proc_equal, shared: true do
a.send(@method, b).should be_false
end
- it "returns true if both procs have the same body and environment" do
+ 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_true
+ p.send(@method, p2).should be_false
end
- it "returns true if both lambdas with the same body and environment" do
+ it "returns false if lambdas are distinct but have same body and environment" do
x = -> { :foo }
x2 = -> { :foo }
- x.send(@method, x2).should be_true
+ x.send(@method, x2).should be_false
end
- it "returns true if both different kinds of procs with the same body and env" do
+ 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_true
+ p.send(@method, p2).should be_false
x = proc { :bar }
x2 = -> { :bar }
- x.send(@method, x2).should be_true
+ x.send(@method, x2).should be_false
end
it "returns false if other is not a Proc" do
diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb
index 765b400dbe..0cc5d488c2 100644
--- a/test/ruby/test_proc.rb
+++ b/test/ruby/test_proc.rb
@@ -137,6 +137,14 @@ class TestProc < Test::Unit::TestCase
lambda { x }
end
+ def m_nest0(&block)
+ block
+ end
+
+ def m_nest(&block)
+ [m_nest0(&block), m_nest0(&block)]
+ end
+
def test_eq
a = m(1)
b = m(2)
@@ -148,6 +156,8 @@ class TestProc < Test::Unit::TestCase
a = lambda {|x| lambda {} }.call(1)
b = lambda {}
assert_not_equal(a, b, "[ruby-dev:22601]")
+
+ assert_equal(*m_nest{}, "[ruby-core:84583] Feature #14627")
end
def test_block_par