Skip to content

fix(ruby): emit mixes_in for include/prepend inside class << self#1816

Open
Synvoya wants to merge 1 commit into
Graphify-Labs:v8from
Synvoya:fix/ruby-singleton-class-mixin
Open

fix(ruby): emit mixes_in for include/prepend inside class << self#1816
Synvoya wants to merge 1 commit into
Graphify-Labs:v8from
Synvoya:fix/ruby-singleton-class-mixin

Conversation

@Synvoya

@Synvoya Synvoya commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Bug

An include/prepend inside a class << self block injects the module's instance methods as the enclosing class's own singleton (class) methods — the exact mixin relationship that extend <Mod> expresses in the class body, which the extractor already captures as a mixes_in edge (#1668).

The Ruby mixin scan in _extract_generic only walked the class/module body's direct children, so an include/prepend nested one level down inside a class << self singleton-class block was silently skipped and produced no edge. This drops the heritage link for the canonical "add class methods via a module" idiom.

Minimal repro

# concern.rb
module Announceable
  def announce; end
end

# broadcaster.rb
class Broadcaster
  class << self
    include Announceable   # -> Broadcaster gains `announce` as a CLASS method
  end
end
  • Before: no mixes_in edge — _mixes_in(graph) == set()
  • After: ("Broadcaster", "Announceable") in mixes_in, identical to the semantically-equivalent extend Announceable

singleton_class was previously unreferenced anywhere in the codebase, so this form had no handling at all.

Fix

Scan class << self blocks in addition to the class/module body, reusing the existing constant-argument filter and the single-owner resolver guard — no new resolution path. The inner statement loop is unchanged; only the set of statements it iterates is widened.

Guard: only self counts. class << other opens a different object's singleton class, not the enclosing class's, so an include there stays unlinked (covered by a negative test).

Tests

Added to tests/test_ruby_resolution.py (alongside the #1668 mixin specs):

  • test_include_in_singleton_class_emits_mixes_in — the fix; fails before, passes after.
  • test_include_in_other_object_singleton_class_emits_no_mixin — locks the class << other guard (no over-emission).

tests/test_ruby_resolution.py + tests/test_languages.py: 338 passed, 13 skipped (skips are the optional tree-sitter-dm grammar). No cross-language impact — the changed block is inside the config.ts_module == "tree_sitter_ruby" guard.

An `include`/`prepend` inside a `class << self` block injects the
module's instance methods as the enclosing class's own singleton
(class) methods — the very mixin relationship `extend <Mod>` expresses
in the class body, which the extractor already captures (Graphify-Labs#1668). The
mixin scan only walked the class/module body's direct children, so the
`class << self` form was silently dropped and emitted no `mixes_in`
edge.

Scan `class << self` blocks as well, reusing the existing
constant-argument filter and the single-owner resolver guard. Only
`self` counts: `class << other` opens a different object's singleton,
not the enclosing class's, so an include there stays unlinked.
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