Skip to content

Include CA metadata in Gem::NameTuple#to_a - #181

Merged
jenshenny merged 1 commit into
feature-branch-ca-changes-rubygemsfrom
fix/name-tuple-content-address-hash
Aug 28, 2026
Merged

Include CA metadata in Gem::NameTuple#to_a#181
jenshenny merged 1 commit into
feature-branch-ca-changes-rubygemsfrom
fix/name-tuple-content-address-hash

Conversation

@jenshenny

Copy link
Copy Markdown

Problem

Gem::NameTuple#== compared five fields — name, version, platform, content_address, and ruby_abi — but #hash was to_a.hash, and to_a returned only [name, version, platform]. That's a legal hash (equal objects still hash equal), but it meant every CA variant sharing the same name/version/platform landed in the same hash bucket.

The one place this is exercised today is commands/update_command.rb, where gems_to_update.uniq.sort buckets tuples by hash and disambiguates with eql?; in any Set, Array#uniq, or Hash keyed by tuples, lookups walk that bucket chain field-by-field, turning what should be O(1) into roughly O(n) as the number of variants grows.

Change

to_a now returns the full 5-field tuple [name, version, platform, content_address, ruby_abi], consistent with deconstruct_keys and inspect. Since #hash is to_a.hash, it now tracks == exactly and spreads CA variants across buckets.

The slim 3-element shape is still needed for the marshalled spec index (specs.4.8.gz), so it is now produced explicitly by the two class methods that own that contract, rather than inherited from to_a:

  • Gem::NameTuple.to_basiclist.map { |t| [t.name, t.version, t.platform] } (decoupled from to_a).
  • Gem::NameTuple.from_list — handles NameTuple objects (passed through, CA metadata preserved), 3-element arrays (the wire form), and 5-element arrays (the full form), routing content_address/ruby_abi to the keyword args of new. This also fixes a latent data-loss where the old new(*t) stripped CA metadata when handed a NameTuple whose to_a carried it.

The == Array branch still uses to_a == other, so array equality now expects the full 5-field form.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates Gem::NameTuple hashing to account for content-addressable metadata while preserving the three-field serialized index format.

Changes:

  • Expands to_a, deconstruction, and hashing to five fields.
  • Preserves three-field wire serialization through to_basic.
  • Adds conversion and hashing tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
lib/rubygems/name_tuple.rb Updates tuple conversion, serialization, and hashing.
test/rubygems/test_gem_name_tuple.rb Tests metadata conversion and hash behavior.
Suppressed comments (1)

lib/rubygems/name_tuple.rb:32

  • This branch accepts every array length and silently drops elements after index 4; non-array entries also become nil. from_list processes downloaded spec indexes, and Gem::Source#load_marshal_specs relies on the prior ArgumentError behavior to reject/retry invalid caches. Restrict arrays to the documented 3- or 5-element forms and raise ArgumentError for all other inputs so corrupt rows cannot be accepted or fail later outside that recovery path.
    list.map do |tuple|
      case tuple
      when Gem::NameTuple
        tuple
      when Array
        new(tuple[0], tuple[1], tuple[2], content_address: tuple[3], ruby_abi: tuple[4])

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lib/rubygems/name_tuple.rb Outdated
@jenshenny
jenshenny force-pushed the fix/name-tuple-content-address-hash branch from 9a76819 to b8c592f Compare August 26, 2026 15:07
@jenshenny
jenshenny marked this pull request as ready for review August 26, 2026 15:07

@girachawda girachawda left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense of supporting the legacy shape with to_basic while updating the NameTuple API shape with the 3 or 5 field option

@OughtPuts OughtPuts left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great stuff Jen, thanks for your work ❤️ Had a few thoughts, happy to discuss!

Comment thread lib/rubygems/name_tuple.rb
Comment thread lib/rubygems/name_tuple.rb
Comment thread test/rubygems/test_gem_name_tuple.rb Outdated

refute_equal base.hash, ca1.hash
refute_equal ca1.hash, ca2.hash
assert_equal ca1.hash, ca1_dup.hash

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I'd say it's worth making absolutely sure this is asserting the new behaviour:

Suggested change
assert_equal ca1.hash, ca1_dup.hash
assert_equal [ca1.name, ca1.version, ca1.platform, ca1.content_address, ca1.ruby_abi].hash, ca1.hash

@jenshenny
jenshenny force-pushed the fix/name-tuple-content-address-hash branch 3 times, most recently from 2c8afeb to 9915896 Compare August 27, 2026 18:52
@jenshenny
jenshenny requested a review from OughtPuts August 27, 2026 18:54
@jenshenny
jenshenny force-pushed the fix/name-tuple-content-address-hash branch from 9915896 to 761f341 Compare August 27, 2026 19:05
@jenshenny
jenshenny force-pushed the feature-branch-ca-changes-rubygems branch from a3547fe to 4dd1d3b Compare August 28, 2026 04:29

@OughtPuts OughtPuts left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great!

…iants

Assisted-By: devx/25d3c4be-88ab-425e-a76b-08a00d8e9d71
@jenshenny
jenshenny force-pushed the fix/name-tuple-content-address-hash branch from 761f341 to a16ce85 Compare August 28, 2026 13:48
@jenshenny
jenshenny merged commit f9999af into feature-branch-ca-changes-rubygems Aug 28, 2026
106 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants