* [ruby-core:124857] [Ruby Bug#21921] Hash inconsistent ==, >=, <= behavior
@ 2026-02-21 2:31 cohen (Cohen Carlisle) via ruby-core
2026-02-21 3:49 ` [ruby-core:124858] " mame (Yusuke Endoh) via ruby-core
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: cohen (Cohen Carlisle) via ruby-core @ 2026-02-21 2:31 UTC (permalink / raw)
To: ruby-core; +Cc: cohen (Cohen Carlisle)
Issue #21921 has been reported by cohen (Cohen Carlisle).
----------------------------------------
Bug #21921: Hash inconsistent ==, >=, <= behavior
https://bugs.ruby-lang.org/issues/21921
* 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/
______________________________________________
ruby-core mailing list -- ruby-core@ml.ruby-lang.org
To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/
^ permalink raw reply [flat|nested] 7+ messages in thread
* [ruby-core:124858] [Ruby Bug#21921] Hash inconsistent ==, >=, <= behavior
2026-02-21 2:31 [ruby-core:124857] [Ruby Bug#21921] Hash inconsistent ==, >=, <= behavior cohen (Cohen Carlisle) via ruby-core
@ 2026-02-21 3:49 ` mame (Yusuke Endoh) via ruby-core
2026-02-21 15:02 ` [ruby-core:124860] " cohen (Cohen Carlisle) via ruby-core
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: mame (Yusuke Endoh) via ruby-core @ 2026-02-21 3:49 UTC (permalink / raw)
To: ruby-core; +Cc: mame (Yusuke Endoh)
Issue #21921 has been updated by mame (Yusuke Endoh).
The current specification of `Hash#<=` is to check if all elements of the supposedly smaller Hash are included in the supposedly larger Hash. I think the reported behaviors are actually working as specified.
https://github.com/ruby/ruby/blob/master/doc/language/hash_inclusion.rdoc
Did this behavior cause any issues in a real-world application? If you just noticed the inconsistency, I think it's fine to keep the status quo.
If we really must fix it, raising an exception when comparing a `compare_by_identity` Hash with a normal Hash via `<=` or `>=` (leaving `Hash#==` as is) seems reasonable.
----------------------------------------
Bug #21921: Hash inconsistent ==, >=, <= behavior
https://bugs.ruby-lang.org/issues/21921#change-116512
* 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/
______________________________________________
ruby-core mailing list -- ruby-core@ml.ruby-lang.org
To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/
^ permalink raw reply [flat|nested] 7+ messages in thread
* [ruby-core:124860] [Ruby Bug#21921] Hash inconsistent ==, >=, <= behavior
2026-02-21 2:31 [ruby-core:124857] [Ruby Bug#21921] Hash inconsistent ==, >=, <= behavior cohen (Cohen Carlisle) via ruby-core
2026-02-21 3:49 ` [ruby-core:124858] " mame (Yusuke Endoh) via ruby-core
@ 2026-02-21 15:02 ` cohen (Cohen Carlisle) via ruby-core
2026-02-21 17:56 ` [ruby-core:124862] " mame (Yusuke Endoh) via ruby-core
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: cohen (Cohen Carlisle) via ruby-core @ 2026-02-21 15:02 UTC (permalink / raw)
To: ruby-core; +Cc: cohen (Cohen Carlisle)
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/
______________________________________________
ruby-core mailing list -- ruby-core@ml.ruby-lang.org
To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/
^ permalink raw reply [flat|nested] 7+ messages in thread
* [ruby-core:124862] [Ruby Bug#21921] Hash inconsistent ==, >=, <= behavior
2026-02-21 2:31 [ruby-core:124857] [Ruby Bug#21921] Hash inconsistent ==, >=, <= behavior cohen (Cohen Carlisle) via ruby-core
2026-02-21 3:49 ` [ruby-core:124858] " mame (Yusuke Endoh) via ruby-core
2026-02-21 15:02 ` [ruby-core:124860] " cohen (Cohen Carlisle) via ruby-core
@ 2026-02-21 17:56 ` mame (Yusuke Endoh) via ruby-core
2026-02-21 18:13 ` [ruby-core:124863] " mame (Yusuke Endoh) via ruby-core
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: mame (Yusuke Endoh) via ruby-core @ 2026-02-21 17:56 UTC (permalink / raw)
To: ruby-core; +Cc: mame (Yusuke Endoh)
Issue #21921 has been updated by mame (Yusuke Endoh).
The documentation for Hash inclusion is a bit hard to understand due to its mathematical phrasing.
> The spec merely says that keys are compared with `==`, but I believe they are actually compared with `eql?` (for normal hashes).
The documentation isn't actually saying such a thing. I assume you misread the following sentence:
> An entry `h0[k0]` in one hash is equal to an entry `h1[k1]` in another hash if and only if the two keys are equal (`k0 == k1`) and their two values are equal (`h0[k0] == h1[h1]`).
This sentence is merely defining the phrase "is equal to". It does not describe the hash lookup mechanism itself.
> * Subset (included in or equal to another):
> * Hash `h0` is a subset of hash `h1` (see Hash#<=) if each entry in `h0` is equal to an entry in `h1`.
This defines the spec of `Hash#<=`. It says that `h0` is a subset of `h1` when every entry (key-value pair) included in `h0` *is equal to* some entry included in `h1`.
While the lookup method is not explicitly stated here either, I think it's quite natural to use `h1`'s lookup method when trying to find if those entries exist in `h1`.
> 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:
That makes sense. However, I don't really think we should change the behavior at the risk of breaking existing working code.
----------------------------------------
Bug #21921: Hash inconsistent ==, >=, <= behavior
https://bugs.ruby-lang.org/issues/21921#change-116516
* 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/
______________________________________________
ruby-core mailing list -- ruby-core@ml.ruby-lang.org
To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/
^ permalink raw reply [flat|nested] 7+ messages in thread
* [ruby-core:124863] [Ruby Bug#21921] Hash inconsistent ==, >=, <= behavior
2026-02-21 2:31 [ruby-core:124857] [Ruby Bug#21921] Hash inconsistent ==, >=, <= behavior cohen (Cohen Carlisle) via ruby-core
` (2 preceding siblings ...)
2026-02-21 17:56 ` [ruby-core:124862] " mame (Yusuke Endoh) via ruby-core
@ 2026-02-21 18:13 ` mame (Yusuke Endoh) via ruby-core
2026-02-23 16:56 ` [ruby-core:124870] " Dan0042 (Daniel DeLorme) via ruby-core
2026-02-24 5:07 ` [ruby-core:124874] " maxfelsher (Max Felsher) via ruby-core
5 siblings, 0 replies; 7+ messages in thread
From: mame (Yusuke Endoh) via ruby-core @ 2026-02-21 18:13 UTC (permalink / raw)
To: ruby-core; +Cc: mame (Yusuke Endoh)
Issue #21921 has been updated by mame (Yusuke Endoh).
By the way, I noticed the following behavior, which feels like it might not satisfy the specification:
```ruby
h1 = {}.compare_by_identity
h1["one"] = true
h1["one"] = true
h2 = { "one" => true }
h1 <= h2 # current: false, expected(?): true
```
The two `"one" => true` entries in `h1` can both be considered to be included in `h2`, so the spec seems to imply this should return `true`. However, I'm torn on whether we should explicitly describe such implementation details in the documentation.
----------------------------------------
Bug #21921: Hash inconsistent ==, >=, <= behavior
https://bugs.ruby-lang.org/issues/21921#change-116517
* 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/
______________________________________________
ruby-core mailing list -- ruby-core@ml.ruby-lang.org
To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/
^ permalink raw reply [flat|nested] 7+ messages in thread
* [ruby-core:124870] [Ruby Bug#21921] Hash inconsistent ==, >=, <= behavior
2026-02-21 2:31 [ruby-core:124857] [Ruby Bug#21921] Hash inconsistent ==, >=, <= behavior cohen (Cohen Carlisle) via ruby-core
` (3 preceding siblings ...)
2026-02-21 18:13 ` [ruby-core:124863] " mame (Yusuke Endoh) via ruby-core
@ 2026-02-23 16:56 ` Dan0042 (Daniel DeLorme) via ruby-core
2026-02-24 5:07 ` [ruby-core:124874] " maxfelsher (Max Felsher) via ruby-core
5 siblings, 0 replies; 7+ messages in thread
From: Dan0042 (Daniel DeLorme) via ruby-core @ 2026-02-23 16:56 UTC (permalink / raw)
To: ruby-core; +Cc: Dan0042 (Daniel DeLorme)
Issue #21921 has been updated by Dan0042 (Daniel DeLorme).
> > An entry `h0[k0]` in one hash is equal to an entry `h1[k1]` in another hash if and only if the two keys are equal (`k0 == k1`) and their two values are equal (`h0[k0] == h1[h1]`).
>
> This sentence is merely defining the phrase "is equal to". It does not describe the hash lookup mechanism itself.
It doesn't just say "equal", it specifically mentions `k0 == k1`; might be worth disambiguating to "if the two keys are equal (either `k0.eql? k1` or `k0.equal? k1`)"
But what has me puzzled is the relationship of == vs <= vs <
I am fine with the notion that h1 (compare_by_identity) is a strict subset of h2, so `h1==h2 => false` is fine and `h1<=h2 => true` is fine, but in that case it should mean that `h1<h2 => true` but that is not the case. Isn't that a bug?
----------------------------------------
Bug #21921: Hash inconsistent ==, >=, <= behavior
https://bugs.ruby-lang.org/issues/21921#change-116525
* 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/
______________________________________________
ruby-core mailing list -- ruby-core@ml.ruby-lang.org
To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/
^ permalink raw reply [flat|nested] 7+ messages in thread
* [ruby-core:124874] [Ruby Bug#21921] Hash inconsistent ==, >=, <= behavior
2026-02-21 2:31 [ruby-core:124857] [Ruby Bug#21921] Hash inconsistent ==, >=, <= behavior cohen (Cohen Carlisle) via ruby-core
` (4 preceding siblings ...)
2026-02-23 16:56 ` [ruby-core:124870] " Dan0042 (Daniel DeLorme) via ruby-core
@ 2026-02-24 5:07 ` maxfelsher (Max Felsher) via ruby-core
5 siblings, 0 replies; 7+ messages in thread
From: maxfelsher (Max Felsher) via ruby-core @ 2026-02-24 5:07 UTC (permalink / raw)
To: ruby-core; +Cc: maxfelsher (Max Felsher)
Issue #21921 has been updated by maxfelsher (Max Felsher).
> > > An entry `h0[k0]` in one hash is equal to an entry `h1[k1]` in another hash if and only if the two keys are equal (`k0 == k1`) and their two values are equal (`h0[k0] == h1[h1]`).
> >
> > This sentence is merely defining the phrase "is equal to". It does not describe the hash lookup mechanism itself.
>
> It doesn't just say "equal", it specifically mentions `k0 == k1`; might be worth disambiguating to "if the two keys are equal (either `k0.eql? k1` or `k0.equal? k1`)"
>
Yes, I read the spec as saying "Given two hashes h0 and h1, and two objects k0 and k1 that are keys in h0 and h1 respectively, the h0[k0] entry and h1[k1] entry are equal if the following Ruby code evaluates to true: `k0 == k1 && h0[k0] == h1[k1]`." And therefore the spec's implementation of `<=` would be equivalent to the following (for every entry in h0, there is at least one equal entry in h1):
``` ruby
def subset?(h0, h1)
h0.all? do |k0, _v0|
h1.any? do |k1, _v1|
k0 == k1 && h0[k0] == h1[k1]
end
end
end
```
Given such a specification, a hash being compare-by-identity wouldn't make a difference in the logic, since everything is being compared by `==`. I assume that this implementation scales pretty poorly, though.
I think the current implementation is closer to this:
``` ruby
def subset?(h0, h1)
h0.all? do |k0, _v0|
h1.key?(k0) && h0[k0] == h1[k0] # Note the use of k0 throughout
end
end
```
In this one, compare-by-identity does matter, since you're using the keys from h0 to look up values in h1. It seems like either the specification or the implementation should change to be consistent with each other?
**Side note:** the specification actually says `h0[k0] == h1[h1]`, but I've assumed that there's a typo in the last variable name and it should be `h0[k0] == h1[k1]`. If that assumption is correct, it seems like at the very least that typo should be corrected?
----------------------------------------
Bug #21921: Hash inconsistent ==, >=, <= behavior
https://bugs.ruby-lang.org/issues/21921#change-116533
* 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/
______________________________________________
ruby-core mailing list -- ruby-core@ml.ruby-lang.org
To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-02-24 5:08 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-21 2:31 [ruby-core:124857] [Ruby Bug#21921] Hash inconsistent ==, >=, <= behavior cohen (Cohen Carlisle) via ruby-core
2026-02-21 3:49 ` [ruby-core:124858] " mame (Yusuke Endoh) via ruby-core
2026-02-21 15:02 ` [ruby-core:124860] " cohen (Cohen Carlisle) via ruby-core
2026-02-21 17:56 ` [ruby-core:124862] " mame (Yusuke Endoh) via ruby-core
2026-02-21 18:13 ` [ruby-core:124863] " mame (Yusuke Endoh) via ruby-core
2026-02-23 16:56 ` [ruby-core:124870] " Dan0042 (Daniel DeLorme) via ruby-core
2026-02-24 5:07 ` [ruby-core:124874] " maxfelsher (Max Felsher) via ruby-core
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).