Issue #21921 has been updated by cohen (Cohen Carlisle). This has not affected any real-world app I use. I noticed this while working on a bidirectional hash gem (bihash@rubygems). However, I don't think the behavior is consistent with the spec as written: The spec merely says that keys are compared with ==, but I believe they are actually compared with eql? (for normal hashes). If keys were just compared with ==, all the above comparisons would return true. I think the spec could also be improved in other ways, assuming there will be no behavioral change: The spec doesn't mention that before comparison, keys must first be found via the supposedly larger hash's lookup method. E.g., In `h1 <= h2`, keys from `h1` are looked up in `h2` using `h2`'s lookup method, so when `h2` is a normal hash it finds the key, but when `h2` is an identity hash it fails. The crux of this issue seems to be that the lookup method differs between identity and normal hashes, which isn't mentioned in the spec. Raising when comparing identity and normal hashes could also work, but I would expand that raise to include < and >, since they suffer the same problems: ``` h1 = {}.compare_by_identity h2 = {} h1["one"] = # h1 = {"one" => 1}.compare_by_identity h2["one"] = 1 h2["two"] = # h2 = {"one", "two" => 2} h1 < h2 # => true h2 > h1 # => true h1 = {} h2 = {}.compare_by_identity h1["one"] = # h1 = {"one" => 1} h2["one"] = 1 h2["two"] = # h2 = {"one", "two" => 2}.compare_by_identity h1 < h2 # => false (surprising, different than above) h2 > h1 # => false (surprising, different than above) ``` I think there are actually three interrelated issues here: 1. When comparing for inclusion, hashes take keys from the maybe smaller hash and look them up in the maybe larger hash, yielding inconsistent results when identity and normal hashes mix (original and newest example). 2. The equality semantics of inclusion are different from Hash#== semantics, leading to further confusion (original example). 3. The inclusion specs don't seem consistent with the current behavior and the method/operator docs could call this out more strongly. ---------------------------------------- Bug #21921: Hash inconsistent ==, >=, <= behavior https://bugs.ruby-lang.org/issues/21921#change-116514 * Author: cohen (Cohen Carlisle) * Status: Open * ruby -v: ruby 4.0.1 (2026-01-13 revision e04267a14b) +PRISM [x86_64-linux] * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- Hash seems to have very inconsistent behavior for `==`, `>=`, and `<=`. Given that below h1 == h2 is `false` and that they have the same number of keys, I would expect `<=` and `>=` to also be `false`. However, surprisingly `h1 <= h2` and `h2 >= h1` are `true`, while all other permutations are `false`. ``` h1 = {}.compare_by_identity.tap { _1["one"] = 1 } # => {"one" => 1} h2 = {"one" => 1} # => {"one" => 1} h1 == h2 # => false h2 == h1 # => false h1 >= h2 # => false h1 <= h2 # => true h2 >= h1 # => true h2 <= h1 # => false ``` -- https://bugs.ruby-lang.org/