Rubydex ignores Zeitwerk implicit namespaces during constant resolution
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 355
- Forks
- 24
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 17
Description
Summary
Rubydex does not model namespaces that Zeitwerk autovivifies from directories.
If a subclass has an implicit namespace whose name is already defined by its parent class, Rubydex resolves the namespace through inheritance. It then merges methods from two distinct runtime classes.
This causes false positives in RuboCop 1.89’s project-index-backed Lint/DuplicateMethods.
Reproduction
# repro.rb
require "fileutils"
require "rubydex"
require "tmpdir"
require "zeitwerk"
Dir.mktmpdir do |directory|
root = File.join(directory, "lib")
files = {
"parent.rb" => <<~RUBY,
class Parent
end
RUBY
"parent/tasks.rb" => <<~RUBY,
class Parent::Tasks
end
RUBY
"child.rb" => <<~RUBY,
class Child < Parent
end
RUBY
"parent/tasks/worker.rb" => <<~RUBY,
class Parent::Tasks::Worker
def run
end
end
RUBY
"child/tasks/worker.rb" => <<~RUBY,
class Child::Tasks::Worker
def run
end
end
RUBY
}
files.each do |path, source|
full_path = File.join(root, path)
FileUtils.mkdir_p(File.dirname(full_path))
File.write(full_path, source)
end
loader = Zeitwerk::Loader.new
loader.push_dir(root)
loader.setup
puts "Zeitwerk:"
puts Child::Tasks.name
puts Child::Tasks::Worker.name
graph = Rubydex::Graph.new
graph.index_all([root])
graph.resolve
puts "Rubydex:"
%w[
Parent::Tasks::Worker#run()
Child::Tasks::Worker#run()
].each do |name|
declaration = graph[name]
paths = declaration&.definitions&.map do |definition|
definition.location.to_file_path
end || []
puts "#{name}: #{paths.empty? ? "<missing>" : paths.join(", ")}"
end
end
Run with:
bundle add rubydex --version 0.3.0
bundle add zeitwerk
bundle exec ruby repro.rb
Expected result
Zeitwerk creates Child::Tasks from the child/tasks directory. It is independent from Parent::Tasks, despite Child < Parent.
Zeitwerk:
Child::Tasks
Child::Tasks::Worker
Rubydex:
Parent::Tasks::Worker#run():
.../parent/tasks/worker.rb
Child::Tasks::Worker#run():
.../child/tasks/worker.rb
Actual result
Zeitwerk resolves both classes correctly, but Rubydex indexes Child::Tasks::Worker#run as Parent::Tasks::Worker#run:
Zeitwerk:
Child::Tasks
Child::Tasks::Worker
Rubydex:
Parent::Tasks::Worker#run():
.../child/tasks/worker.rb, .../parent/tasks/worker.rb
Child::Tasks::Worker#run():
<missing>
Environment
- Rubydex 0.3.0
- Ruby 3.4.10
- Linux
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the repro.rb flow that builds Rubydex::Graph, calls index_all([root]), and then calls resolve; inspect how graph[name] returns declarations and definitions. Reproduce the Zeitwerk comparison first, then trace namespace and inheritance resolution. Done means Child::Tasks::Worker#run maps only to child/tasks/worker.rb while Parent::Tasks::Worker#run remains separate.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ruby
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100