diff --git hash.c hash.c index c9d60c7a56..0dfd43ec59 100644 --- hash.c +++ hash.c @@ -2411,6 +2411,57 @@ rb_hash_eql(VALUE hash1, VALUE hash2) return hash_equal(hash1, hash2, TRUE); } + +static int +eqq_i(VALUE key, VALUE val1, VALUE arg) +{ + struct equal_data *data = (struct equal_data *)arg; + st_data_t val2; + + if (!st_lookup(data->tbl, key, &val2)) { + data->result = Qfalse; + return ST_STOP; + } + if (!rb_funcall(val1, idEqq, 1, (VALUE)val2)) { + data->result = Qfalse; + return ST_STOP; + } + return ST_CONTINUE; +} + +static VALUE +recursive_eqq(VALUE hash, VALUE dt, int recur) +{ + struct equal_data *data; + + if (recur) return Qtrue; /* Subtle! */ + data = (struct equal_data*)dt; + data->result = Qtrue; + rb_hash_foreach(hash, eqq_i, dt); + + return data->result; +} + +static VALUE +rb_hash_eqq(VALUE hash1, VALUE hash2) +{ + struct equal_data data; + + if (hash1 == hash2) return Qtrue; + if (!RB_TYPE_P(hash2, T_HASH)) return Qfalse; + if (RHASH_EMPTY_P(hash1)) + return RHASH_EMPTY_P(hash2) ? Qtrue : Qfalse; + if (RHASH_SIZE(hash1) > RHASH_SIZE(hash2)) + return Qfalse; + if (!RHASH(hash1)->ntbl || !RHASH(hash2)->ntbl) + return Qtrue; + if (RHASH(hash1)->ntbl->type != RHASH(hash2)->ntbl->type) + return Qfalse; + + data.tbl = RHASH(hash2)->ntbl; + return rb_exec_recursive_paired(recursive_eqq, hash1, hash2, (VALUE)&data); +} + static int hash_i(VALUE key, VALUE val, VALUE arg) { @@ -4683,6 +4734,7 @@ Init_Hash(void) rb_define_method(rb_cHash, "to_proc", rb_hash_to_proc, 0); rb_define_method(rb_cHash, "==", rb_hash_equal, 1); + rb_define_method(rb_cHash, "===", rb_hash_eqq, 1); rb_define_method(rb_cHash, "[]", rb_hash_aref, 1); rb_define_method(rb_cHash, "hash", rb_hash_hash, 0); rb_define_method(rb_cHash, "eql?", rb_hash_eql, 1); diff --git test/ruby/test_hash.rb test/ruby/test_hash.rb index 088178defa..b6fc2b38c7 100644 --- test/ruby/test_hash.rb +++ test/ruby/test_hash.rb @@ -1107,6 +1107,49 @@ def o.eql?(x); false; end assert_not_send([@cls[], :eql?, o]) end + def test_eqq + user = { id: 1, name: "homu", age: 14 } + assert_operator({ id: 1 }, :===, user) + assert_operator({ id: 1, age: 14 }, :===, user) + assert_operator({ id: Integer }, :===, user) + assert_operator({ name: String, age: Integer }, :===, user) + assert_operator({ name: /m/ }, :===, user) + assert_operator({ number: nil }, :===, { number: nil }) + assert_operator({}, :===, {}) + obj = Object.new + def obj.=== other + true + end + assert_operator({ id: obj }, :===, user) + a = { name: String } + a[:a] = a + assert_operator(a, :===, a) + b = { name: "homu" } + b[:a] = b + assert_operator(a, :===, b) + + assert_not_operator({ id: 2 }, :===, user) + assert_not_operator({ name: Integer }, :===, user) + assert_not_operator({ number: 42 }, :===, user) + assert_not_operator({ number: nil }, :===, {}) + assert_not_operator({ number: 42 }, :===, {}) + assert_not_operator({ number: 42 }, :===, 42) + assert_not_operator({ number: 42 }, :===, []) + assert_not_operator({}, :===, user) + assert_not_operator({}, :===, 42) + assert_not_operator({}, :===, { name: "homu" }) + obj2 = Object.new + def obj2.=== other + false + end + assert_not_operator({ id: obj2 }, :===, user) + a = { name: Integer } + a[:a] = a + b = { name: "homu" } + b[:a] = b + assert_not_operator(a, :===, b) + end + def test_hash2 assert_kind_of(Integer, @cls[].hash) h = @cls[1=>2]