Skip to content

fix(ruby): a call follows a class's real ancestors, in Ruby's order - #3169

Merged
colbymchenry merged 1 commit into
mainfrom
claude/fx-ruby-ancestors
Nov 1, 2026
Merged

colbymchenry merged 1 commit into
mainfrom
claude/fx-ruby-ancestors

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

Summary

Regression items 3, 4 and 6 of REG (Ruby): a call now follows a class's real ancestors, in Ruby's order. company.has_attribute? reaches the instance method, mapper.get and cookies.signed come back, and sinatra's bad_cookies.empty? no longer links Cookies::Jar.

Root cause

Fix

  • Ancestors as Ruby reads them: superclass and mixins by constant lookup, a class's own body only, the module mixed in last asked first, and for an instance no module it extends.
  • A receiver named after a class takes the class constant lookup finds first, then a namesake.
  • A call on a class reaches a superclass's def self., what it extends and its concerns' ClassMethods, never a mixin's instance method or a mixin's own def self..
  • A bare name that is a method of the class around the call, taking no arguments and handing back what one class makes (@cookie_jar ||= Cookies::CookieJar.build(…), the factory read too), types the call on it, unless the calling method binds that name.
  • RUBY_STD_METHODS: a ? or ! method of Ruby's core classes needs a receiver named after the owner itself. record.new_record? and the rest of fix(ruby): calls to !/? methods and calls on what .new made link their method #3049's gain stay.

Validation

Red to green: 7 of 11 tests in ruby-receiver-class-ancestry.test.ts fail on main; the rest guard the rules beside them.

A/B against 4499d00, all CLI steps rc 0:

Repo Edges Moved Added Lost
rails-src 793d744c 171,738 to 179,247 2,159 8,179 670
sinatra cb22afd7 2,678 to 2,676 0 0 2
mastodon b0555670, Ruby files 23,466 to 23,499 1 34 1
jekyll 97c3bdc8 same edges, 9 guesses now typed
controls gin, flask byte-identical

Classes, 86 sampled entries read against source, 0 false added:

  • Added: 4,315 class calls (Topic.find, Topic.create!, HelloJob.set), 2,034 inherited instance calls (topic.id, topic.reload), 1,715 bare helper calls in view tests (concat, content_tag), now in scope.
  • Moved: to the first method in Ruby's order (save to Suppressor#save, which calls super), off FakeRecord::Base, off Digestor::Node.create.
  • Lost: 442 Node-style false edges, the false ClassMethods and Timestamp#type picks, Core::set on a Concurrent::Event. About 30 were true by luck, like 20 xml.first, whose String#first came through ActiveModel::Type::String read as core String.

REG's PUB entries: lost/20, lost/24, lost/25 and sinatra/added/3 hold. company.has_attribute? 14 of 14 on the instance method. rails-src/moved/24 can't be fixed without harm: table in add_to(table) is a TableDefinition only because its one caller passes self. 1.6.2 got it from a substring of the qualified name, the rule that also sent 429 error.message to WrongEncodingError and 144 response.body to Buffer. Reaching it needs call-site flow, which nothing here has.

Index CPU on rails-src, median of 5 interleaved: 74.9 to 73.2 s. No extraction change, so no kernel parity.

Suite on the top of this stack, 6e22039, on main 3bd9949: 857 files passed, 6 skipped; 10,662 tests passed, 0 failed, 87 skipped (kernel built, 4 workers). A rebuild after the rebase gave the same graphs as the A/B build on sinatra, serilog, eShopOnWeb, drupal-core and bookstack.

Stacked on the PHP PR.

🤖 Generated with Claude Code

Base automatically changed from claude/fx-php-class-ancestry to main November 1, 2026 04:59
@colbymchenry
colbymchenry deleted the claude/fx-ruby-ancestors branch November 1, 2026 05:00
@colbymchenry
colbymchenry restored the claude/fx-ruby-ancestors branch November 1, 2026 05:00
@colbymchenry colbymchenry reopened this Nov 1, 2026
Since #1935 a receiver named after a class took what its supertypes declare,
read by bare names: `class Human < ActiveRecord::Base` asked every class
called `Base`, so rails' `human.name` reached Journey's `Node#name`, and
`Topic.create` Digestor's `Node.create`. A value of a known class walked
`extends` edges the same way, so `company.has_attribute?` took the
`has_attribute?` of a `ClassMethods` module. And `class Mapper; include
HttpHelpers` lost `mapper.get`, which 1.6.2 reached by a substring of the
qualified name.

The ancestors are now read as Ruby reads them: superclass and mixins found
by constant lookup, a class's own body only, the module mixed in last asked
first, and for an instance no module it `extend`s.
- A receiver named after a class takes the class constant lookup finds, and
  past that class's own methods a word picks no namesake.
- A call on a class reaches a superclass's `def self.`, what it `extend`s and
  the `ClassMethods` its concerns bring, never a mixin's instance method or
  a mixin's own `def self.`.
- A bare name that is a method of the class around the call, taking no
  arguments and handing back what one class makes (`@jar ||= Jar.build(…)`),
  types the call on it, so `cookies.signed` reaches `ChainedCookieJars`.
- A `?` or `!` method of Ruby's core classes needs a receiver named after the
  owner itself: sinatra's `bad_cookies.empty?` is no `Cookies::Jar#empty?`.

Validation, base 4499d00 against this change (numbers in the PR):
- rails-src 793d744c: 171,738 to 179,247 edges, 2,159 moved, 8,179 added, 670
  lost. Added: Concern class methods (`Topic.find`), inherited instance methods
  (`topic.id`), helpers on bare calls (`concat`). Moved: to the first method in
  Ruby's order, off `Digestor::Node.create`. 86 sampled entries read: 0 false added.
  Lost: 442 `Node::name`-style false edges of the 670; about 30 were true by luck.
- sinatra cb22afd7: 2 lost, both `bad_cookies.empty?`. mastodon (Ruby only)
  b0555670: 1 moved, 34 added, 1 lost. jekyll 97c3bdc8: same edges, 9 guesses
  now typed. Controls gin and flask byte-identical.
- REG: rails-src 3 lost back; `company.has_attribute?` 14 of 14 on the instance
  method; rails-src/moved/24 (`table.foreign_key`, right by a substring) stays:
  it needs call-site flow, and the substring rule took 429 `error.message`.
- Index CPU on rails-src, median of 5 interleaved: 74.9 to 73.2 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@colbymchenry
colbymchenry force-pushed the claude/fx-ruby-ancestors branch from e8c76e4 to 7889010 Compare November 1, 2026 05:01
@colbymchenry
colbymchenry merged commit 087de4c into main Nov 1, 2026
@colbymchenry
colbymchenry deleted the claude/fx-ruby-ancestors branch November 1, 2026 05:01
bompus added a commit to bompus/codegraph that referenced this pull request Nov 2, 2026
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.

1 participant