Actions
Bug #22174
openSet operations (&, ^, collect!, flatten, classify, divide) do not preserve compare_by_identity
Bug #22174:
Set operations (&, ^, collect!, flatten, classify, divide) do not preserve compare_by_identity
Status:
Open
Assignee:
-
Target version:
-
ruby -v:
ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +PRISM [arm64-darwin25]
Description
Since Set was transitioned to a C-level core class in Ruby 4.0, several operations that allocate new sets or partition subsets (&, ^, collect!, flatten, classify, and divide) lost the compare_by_identity behavior for the receiver set or its subsets.
- For
&,collect!,flatten,classify, anddivide, the resulting sets/subsets are allocated without propagating thecompare_by_identityflag (returningfalsefor#compare_by_identity?). - For
^(XOR) with a genericEnumerable, the returned set has the flag set (viadup), but it allocates a temporary settmpto hold the RHS elements without propagating thecompare_by_identityflag. This causes the RHS elements to be deduplicated incorrectly using value equality instead of identity equality.
A pull request has been opened with the fix and tests: https://github.com/ruby/ruby/pull/17633
Reproduction¶
require 'set'
# 1. Intersection &
s1 = Set.new.compare_by_identity
s2 = Set.new
s1 << "a"
s2 << "a"
puts "Intersection preserves compare_by_identity: #{(s1 & s2).compare_by_identity?}"
# Expected: true
# Actual: false
# 2. XOR ^ (with duplicate objects by value on RHS)
s_xor = Set.new.compare_by_identity
x1 = +"x"
x2 = +"x"
result = s_xor ^ [x1, x2]
puts "XOR with Enumerable preserves identity comparison: #{result.size == 2}"
# Expected: true (size should be 2, because x1 and x2 are distinct objects)
# Actual: false (size is 1)
# 3. collect! / map!
s_collect = Set.new(["a", "b"]).compare_by_identity
s_collect.collect! { |x| x }
puts "collect! preserves compare_by_identity: #{s_collect.compare_by_identity?}"
# Expected: true
# Actual: false
# 4. flatten
s_flat = Set.new([Set.new([1])]).compare_by_identity
puts "flatten preserves compare_by_identity: #{s_flat.flatten.compare_by_identity?}"
# Expected: true
# Actual: false
# 5. classify
s_classify = Set.new(["a", "b"]).compare_by_identity
classified = s_classify.classify { |x| x }
puts "classify subsets preserve compare_by_identity: #{classified.values.all?(&:compare_by_identity?)}"
# Expected: true
# Actual: false
# 6. divide
s_divide = Set.new(["a", "b"]).compare_by_identity
divided = s_divide.divide { |x| x }
puts "divide subsets preserve compare_by_identity: #{divided.all?(&:compare_by_identity?)}"
# Expected: true
# Actual: false
Updated by jeremyevans0 (Jeremy Evans) 2 days ago
- Backport changed from 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN to 3.3: DONTNEED, 3.4: DONTNEED, 4.0: REQUIRED
Actions