From: "knu (Akinori MUSHA) via ruby-core" Date: 2026-09-15T09:08:11+00:00 Subject: [ruby-core:126728] [Ruby Bug#22174] Set operations (&, ^, collect!, flatten, classify, divide) do not preserve compare_by_identity Issue #22174 has been updated by knu (Akinori MUSHA). I'd leave Ruby 3.4 and the pure Ruby implementation just as they are. As for Ruby 4.x I'd expect: - `collect!`/`map!` retain the identity flag of the receiver. - `divide`/`classify` generate subsets inheriting the flag of the receiver. - `&`/`^` return a set inheriting the flag of the LHS set. - `flatten` returns a set inheriting the flag of the receiver. And on these points, I would prefer consistency with what users who explicitly choose to use the flag would expect from this feature over backward compatibility. ---------------------------------------- Bug #22174: Set operations (&, ^, collect!, flatten, classify, divide) do not preserve compare_by_identity https://bugs.ruby-lang.org/issues/22174#change-119025 * Author: gil.desmarais (Gil Desmarais) * Status: Open * ruby -v: ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +PRISM [arm64-darwin25] * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- 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`, and `divide`, the resulting sets/subsets are allocated without propagating the `compare_by_identity` flag (returning `false` for `#compare_by_identity?`). * For `^` (XOR) with a generic `Enumerable`, the returned set has the flag set (via `dup`), but it allocates a temporary set `tmp` to hold the RHS elements without propagating the `compare_by_identity` flag. 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 ```ruby 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 -- 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/