Capture DSL definitions and support Class|Module.new#515
Draft
Capture DSL definitions and support Class|Module.new#515
Class|Module.new#515Conversation
e4b579c to
8af5455
Compare
49426c2 to
1604685
Compare
st0012
commented
Feb 4, 2026
| /// `def self.foo` - receiver is the enclosing definition (class, module, or DSL) | ||
| SelfReceiver(DefinitionId), | ||
| /// `def Foo.bar` - receiver is an explicit constant that needs resolution | ||
| ConstantReceiver(NameId), |
Member
Author
There was a problem hiding this comment.
Currently we eagerly retrieve nesting's name id as the receiver's name id:
class Foo
def self.bar; end # uses `NameId(Foo)`
endBut this doesn't work in anonymous class/module's case with dsl processing:
module Namespace
# Foo may or may not be a class, depending on whether Namespace defines a Class class or not
# So at indexing we only know this is a DslDefinition that's assigned to a constant Foo
Foo = Class.new
def self.bar; end # self can't be resolved to a NameId just yet. we need to associate it with the DslDefinition directly
end
end| ruby_prism::Node::ConstantReadNode { .. } | ruby_prism::Node::ConstantPathNode { .. } => { | ||
| // Index the constant reference and store the ReferenceId | ||
| if let Some(ref_id) = self.index_constant_reference_for_dsl(node) { | ||
| DslValue::Reference(ref_id) |
Member
Author
There was a problem hiding this comment.
This allows us to index Class.new(Foo) and have the Foo reference resolved for DSL processing.
| mixins: Vec<Mixin>, | ||
| /// Reference to the `ConstantDefinition` if this DSL is assigned to a constant. | ||
| /// E.g., for `Foo = Class.new`, this points to the `ConstantDefinition` for `Foo`. | ||
| assigned_to: Option<DefinitionId>, |
Member
Author
There was a problem hiding this comment.
Ideally, DSL definition should know nothing about what it's assigned to. But having this field makes Class.new and Module.new's processing much more efficient.
vinistock
reviewed
Feb 5, 2026
| assert_no_diagnostics!(&context); | ||
| assert_members_eq!(context, "Foo", vec!["aliased()", "original()"]); | ||
| } | ||
|
|
Member
There was a problem hiding this comment.
Some other examples worth testing:
module Foo
Bar = Class.new do
def Foo.my_method; end
end
endmodule Foo
module Bar; end
end
module Baz
include Foo
Bar::Qux = Class.new do
end
endFoo = Class.new do
attr_reader :bar
endmodule Foo; end
Bar = Module.new do
extend Foo
endFoo = Class.new do
end
class Bar < Foo
endFoo = Module.new do
end
class Bar
prepend Foo
end
This was referenced Feb 5, 2026
st0012
added a commit
that referenced
this pull request
Feb 9, 2026
When used, rubydex would list orphan definitions under `/tmp/rubydex-orphan-report.txt` (can be changed by supplying arg to the flag). Example output: ``` Constant ::Rack::Cache::MetaStore::RAILS /Users/hung-wulo/src/github.com/rails/rails/actionpack/lib/action_dispatch/http/rack_cache.rb:29:31-29:35 Constant ::Rack::Cache::EntityStore::RAILS /Users/hung-wulo/src/github.com/rails/rails/actionpack/lib/action_dispatch/http/rack_cache.rb:61:33-61:37 InstanceVariable @adapter /Users/hung-wulo/src/github.com/rails/rails/activejob/test/helper.rb:8:1-8:9 SingletonClass Benchmark::<Benchmark> /Users/hung-wulo/src/github.com/rails/rails/activesupport/lib/active_support/core_ext/benchmark.rb:5:1-5:13 ``` This feature is helpful for investigating issues. ~~For example, some definitions in Core become orphan due to `NameId` collision and that's shown in the report (I've shown that to @vinistock in pairing).~~ (This is fixed now) Another example is that this helps me investigate incorrect resolution in #515. However, it's also worth noting that detecting orphans don't necessarily mean there's a but in the code or rubydex. For example, due to the lack of `<main>` representation, top level instance variables would be considered orphans now.
Draft
st0012
added a commit
that referenced
this pull request
Feb 26, 2026
…ers (#573) ## Summary Prerequisite for #515 (anonymous Class/Module). Anonymous classes have no names, so `def self.foo` inside them can't be represented as `Option<NameId>`. `SelfReceiver(DefinitionId)` points directly to the enclosing definition without needing a name. - Replaces `Option<NameId>` on `MethodDefinition.receiver` with `Receiver::SelfReceiver(DefinitionId) | ConstantReceiver(NameId)`, making the distinction between `def self.foo` and `def Foo.foo` explicit at the type level — previously both were `NameId` and indistinguishable by the time resolution runs. - SelfReceiver enables a direct `definition_id_to_declaration_id` lookup instead of going through name resolution. - Fixed a panic where `def self.baz` inside an unresolvable class (e.g., `class Foo::Bar` where `Foo` is undefined) would crash on `.expect()` — now silently skips orphaned methods.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DSL Support for Class.new and Module.new
1. Overview
Supported Ruby Patterns
This implementation adds support for dynamically created classes and modules:
High-Level Approach
2. Class.new and Module.new
Named vs Anonymous
Superclass and Mixins
Class.newis captured as superclass referenceinclude/prepend/extendcalls inside the block are attached to the DSLDynamicClassDefinitionclass Foo < ParentNested Dynamic Classes
3. Elements Inside DSL Blocks
Methods
Foo)def self.x): owned by the class's singleton class (Foo::<Foo>)selfinside the block resolves to the constant name being assignedInstance Variables
Constants
Constants inside
Class.new/Module.newblocks follow lexical scope, not the dynamic class:Singleton Classes
4. Implementation Details
DSL Indexing
When the indexer encounters a
.newcall that matches a DSL target:Resolution Loop
Resolution processes definitions, references, ancestors, and DSLs in a unified loop:
Example: Processing
Foo = Class.new { def bar; end }Key Functions Reference
try_index_dsl_callhandle_dsl_unithandle_class_newhandle_module_newextract_dsl_contextis_parent_dsl_processedget_dsl_namespace_declarationmaybe_handle_constant_inside_dslresolve_lexical_ownerDefinition Type Summary
.newcall)Foo = ...)Benchmark Diff
Benchmark diff against Core compared to
main