Skip to content

Commit 97823c7

Browse files
committed
Add receiver to MethodAliasDefinition
alias_method can be called on a specific class, like Foo.alias_method(:new, :old), where the alias belongs to Foo rather than the enclosing scope. To handle this, we now store the receiver on MethodAliasDefinition so the resolver knows which class owns the alias. Since alias_method always creates instance aliases (unlike def Foo.bar which targets the singleton class), constant receivers resolve to the class itself.
1 parent b8324df commit 97823c7

File tree

3 files changed

+293
-80
lines changed

3 files changed

+293
-80
lines changed

rust/rubydex/src/indexing/ruby_indexer.rs

Lines changed: 153 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -361,34 +361,31 @@ impl<'a> RubyIndexer<'a> {
361361
})
362362
}
363363

364-
// Runs the given closure if the given call `node` is invoked directly on `self` for each one of its string or
365-
// symbol arguments
364+
// Runs the given closure for each string or symbol argument in the given call `node`
366365
fn each_string_or_symbol_arg<F>(node: &ruby_prism::CallNode, mut f: F)
367366
where
368367
F: FnMut(String, ruby_prism::Location),
369368
{
370-
let receiver = node.receiver();
369+
let Some(arguments) = node.arguments() else {
370+
return;
371+
};
371372

372-
if (receiver.is_none() || receiver.unwrap().as_self_node().is_some())
373-
&& let Some(arguments) = node.arguments()
374-
{
375-
for argument in &arguments.arguments() {
376-
match argument {
377-
ruby_prism::Node::SymbolNode { .. } => {
378-
let symbol = argument.as_symbol_node().unwrap();
379-
380-
if let Some(value_loc) = symbol.value_loc() {
381-
let name = Self::location_to_string(&value_loc);
382-
f(name, value_loc);
383-
}
384-
}
385-
ruby_prism::Node::StringNode { .. } => {
386-
let string = argument.as_string_node().unwrap();
387-
let name = String::from_utf8_lossy(string.unescaped()).to_string();
388-
f(name, argument.location());
373+
for argument in &arguments.arguments() {
374+
match argument {
375+
ruby_prism::Node::SymbolNode { .. } => {
376+
let symbol = argument.as_symbol_node().unwrap();
377+
378+
if let Some(value_loc) = symbol.value_loc() {
379+
let name = Self::location_to_string(&value_loc);
380+
f(name, value_loc);
389381
}
390-
_ => {}
391382
}
383+
ruby_prism::Node::StringNode { .. } => {
384+
let string = argument.as_string_node().unwrap();
385+
let name = String::from_utf8_lossy(string.unescaped()).to_string();
386+
f(name, argument.location());
387+
}
388+
_ => {}
392389
}
393390
}
394391
}
@@ -1104,11 +1101,22 @@ impl<'a> RubyIndexer<'a> {
11041101
return Some(name_id);
11051102
}
11061103

1104+
let new_name_id = self.make_singleton_name_id(name_id);
1105+
1106+
let location = receiver.map_or(fallback_location, ruby_prism::Node::location);
1107+
let offset = Offset::from_prism_location(&location);
1108+
self.local_graph
1109+
.add_constant_reference(ConstantReference::new(new_name_id, self.uri_id, offset));
1110+
Some(new_name_id)
1111+
}
1112+
1113+
/// Creates a singleton class `NameId` (`<ClassName>`) attached to the given constant `NameId`.
1114+
fn make_singleton_name_id(&mut self, attached_name_id: NameId) -> NameId {
11071115
let singleton_class_name = {
11081116
let name = self
11091117
.local_graph
11101118
.names()
1111-
.get(&name_id)
1119+
.get(&attached_name_id)
11121120
.expect("Indexed constant name should exist");
11131121

11141122
let target_str = self
@@ -1121,15 +1129,8 @@ impl<'a> RubyIndexer<'a> {
11211129
};
11221130

11231131
let string_id = self.local_graph.intern_string(singleton_class_name);
1124-
let new_name_id = self
1125-
.local_graph
1126-
.add_name(Name::new(string_id, ParentScope::Attached(name_id), None));
1127-
1128-
let location = receiver.map_or(fallback_location, ruby_prism::Node::location);
1129-
let offset = Offset::from_prism_location(&location);
11301132
self.local_graph
1131-
.add_constant_reference(ConstantReference::new(new_name_id, self.uri_id, offset));
1132-
Some(new_name_id)
1133+
.add_name(Name::new(string_id, ParentScope::Attached(attached_name_id), None))
11331134
}
11341135
}
11351136

@@ -1540,6 +1541,11 @@ impl Visit<'_> for RubyIndexer<'_> {
15401541
}
15411542

15421543
let mut index_attr = |kind: AttrKind, call: &ruby_prism::CallNode| {
1544+
let receiver = call.receiver();
1545+
if receiver.as_ref().is_some_and(|r| r.as_self_node().is_none()) {
1546+
return;
1547+
}
1548+
15431549
let call_offset = Offset::from_prism_location(&call.location());
15441550

15451551
Self::each_string_or_symbol_arg(call, |name, location| {
@@ -1653,8 +1659,22 @@ impl Visit<'_> for RubyIndexer<'_> {
16531659
let new_name_str_id = self.local_graph.intern_string(format!("{new_name}()"));
16541660
let old_name_str_id = self.local_graph.intern_string(format!("{old_name}()"));
16551661

1656-
let method_receiver = self.method_receiver(node.receiver().as_ref(), node.location());
1657-
let reference = MethodRef::new(old_name_str_id, self.uri_id, old_offset.clone(), method_receiver);
1662+
// Only constant receivers get a Receiver since self.alias_method is the same as bare alias_method
1663+
let recv_node = node.receiver();
1664+
let receiver = recv_node.as_ref().and_then(|recv_node| match recv_node {
1665+
ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. } => self
1666+
.index_constant_reference(recv_node, true)
1667+
.map(Receiver::ConstantReceiver),
1668+
_ => None,
1669+
});
1670+
1671+
// alias_method references instance methods, so we use the class NameId directly
1672+
let method_ref_receiver = match &receiver {
1673+
Some(Receiver::ConstantReceiver(name_id)) => Some(*name_id),
1674+
Some(Receiver::SelfReceiver(_)) | None => self.method_receiver(recv_node.as_ref(), node.location()),
1675+
};
1676+
1677+
let reference = MethodRef::new(old_name_str_id, self.uri_id, old_offset.clone(), method_ref_receiver);
16581678
self.local_graph.add_method_reference(reference);
16591679

16601680
let offset = Offset::from_prism_location(&node.location());
@@ -1668,6 +1688,7 @@ impl Visit<'_> for RubyIndexer<'_> {
16681688
comments,
16691689
flags,
16701690
self.current_nesting_definition_id(),
1691+
receiver,
16711692
)));
16721693

16731694
let definition_id = self.local_graph.add_definition(definition);
@@ -1978,6 +1999,7 @@ impl Visit<'_> for RubyIndexer<'_> {
19781999
comments,
19792000
flags,
19802001
self.current_nesting_definition_id(),
2002+
None,
19812003
)));
19822004

19832005
let definition_id = self.local_graph.add_definition(definition);
@@ -4909,6 +4931,105 @@ mod tests {
49094931
});
49104932
}
49114933

4934+
#[test]
4935+
fn index_alias_method_with_constant_receiver() {
4936+
let context = index_source({
4937+
"
4938+
class Foo
4939+
def bar; end
4940+
end
4941+
4942+
Foo.alias_method :baz, :bar
4943+
"
4944+
});
4945+
4946+
assert_no_local_diagnostics!(&context);
4947+
4948+
assert_definition_at!(&context, "5:1-5:28", MethodAlias, |def| {
4949+
let new_name = context.graph().strings().get(def.new_name_str_id()).unwrap();
4950+
let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
4951+
assert_eq!(new_name.as_str(), "baz()");
4952+
assert_eq!(old_name.as_str(), "bar()");
4953+
4954+
match def.receiver() {
4955+
Some(Receiver::ConstantReceiver(name_id)) => {
4956+
let name = context.graph().names().get(name_id).unwrap();
4957+
let actual_name = context.graph().strings().get(name.str()).unwrap().as_str();
4958+
assert_eq!(actual_name, "Foo");
4959+
}
4960+
other => panic!("Expected ConstantReceiver, got {other:?}"),
4961+
}
4962+
});
4963+
4964+
let method_ref = context
4965+
.graph()
4966+
.method_references()
4967+
.values()
4968+
.find(|m| context.graph().strings().get(m.str()).unwrap().as_str() == "bar()")
4969+
.expect("should have a method reference for bar()");
4970+
4971+
let receiver = context.graph().names().get(&method_ref.receiver().unwrap()).unwrap();
4972+
assert_eq!(context.graph().strings().get(receiver.str()).unwrap().as_str(), "Foo");
4973+
}
4974+
4975+
#[test]
4976+
fn index_alias_method_with_constant_path_receiver() {
4977+
let context = index_source({
4978+
"
4979+
module Outer
4980+
class Inner
4981+
def bar; end
4982+
end
4983+
end
4984+
4985+
Outer::Inner.alias_method :baz, :bar
4986+
"
4987+
});
4988+
4989+
assert_no_local_diagnostics!(&context);
4990+
4991+
assert_definition_at!(&context, "7:1-7:37", MethodAlias, |def| {
4992+
let new_name = context.graph().strings().get(def.new_name_str_id()).unwrap();
4993+
let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
4994+
assert_eq!(new_name.as_str(), "baz()");
4995+
assert_eq!(old_name.as_str(), "bar()");
4996+
4997+
match def.receiver() {
4998+
Some(Receiver::ConstantReceiver(name_id)) => {
4999+
let name = context.graph().names().get(name_id).unwrap();
5000+
let actual_name = context.graph().strings().get(name.str()).unwrap().as_str();
5001+
assert_eq!(actual_name, "Inner");
5002+
}
5003+
other => panic!("Expected ConstantReceiver, got {other:?}"),
5004+
}
5005+
});
5006+
}
5007+
5008+
#[test]
5009+
fn index_alias_method_with_self_receiver_has_no_receiver() {
5010+
let context = index_source({
5011+
"
5012+
class Foo
5013+
self.alias_method :baz, :bar
5014+
end
5015+
"
5016+
});
5017+
5018+
assert_no_local_diagnostics!(&context);
5019+
5020+
assert_definition_at!(&context, "1:1-3:4", Class, |foo_class_def| {
5021+
assert_definition_at!(&context, "2:3-2:31", MethodAlias, |def| {
5022+
let new_name = context.graph().strings().get(def.new_name_str_id()).unwrap();
5023+
let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
5024+
assert_eq!(new_name.as_str(), "baz()");
5025+
assert_eq!(old_name.as_str(), "bar()");
5026+
5027+
assert!(def.receiver().is_none());
5028+
assert_eq!(foo_class_def.id(), def.lexical_nesting_id().unwrap());
5029+
});
5030+
});
5031+
}
5032+
49125033
#[test]
49135034
fn index_alias_global_variables() {
49145035
let context = index_source({

rust/rubydex/src/model/definitions.rs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -724,7 +724,7 @@ pub struct MethodDefinition {
724724
assert_mem_size!(MethodDefinition, 112);
725725

726726
/// The receiver of a singleton method definition.
727-
#[derive(Debug)]
727+
#[derive(Debug, Clone, Copy)]
728728
pub enum Receiver {
729729
/// `def self.foo` - receiver is the enclosing definition (class, module, singleton class or DSL)
730730
SelfReceiver(DefinitionId),
@@ -734,6 +734,16 @@ pub enum Receiver {
734734

735735
assert_mem_size!(Receiver, 16);
736736

737+
impl Receiver {
738+
/// Appends the receiver's inner ID to the given string for `DefinitionId` hashing.
739+
pub fn append_to_id(&self, formatted_id: &mut String) {
740+
match self {
741+
Receiver::SelfReceiver(def_id) => formatted_id.push_str(&def_id.to_string()),
742+
Receiver::ConstantReceiver(name_id) => formatted_id.push_str(&name_id.to_string()),
743+
}
744+
}
745+
}
746+
737747
impl MethodDefinition {
738748
#[allow(clippy::too_many_arguments)]
739749
#[must_use]
@@ -766,12 +776,7 @@ impl MethodDefinition {
766776
let mut formatted_id = format!("{}{}{}", *self.uri_id, self.offset.start(), *self.str_id);
767777

768778
if let Some(receiver) = &self.receiver {
769-
match receiver {
770-
Receiver::SelfReceiver(def_id) => formatted_id.push_str(&def_id.to_string()),
771-
Receiver::ConstantReceiver(name_id) => {
772-
formatted_id.push_str(&name_id.to_string());
773-
}
774-
}
779+
receiver.append_to_id(&mut formatted_id);
775780
}
776781

777782
DefinitionId::from(&formatted_id)
@@ -1332,11 +1337,13 @@ pub struct MethodAliasDefinition {
13321337
flags: DefinitionFlags,
13331338
comments: Vec<Comment>,
13341339
lexical_nesting_id: Option<DefinitionId>,
1340+
receiver: Option<Receiver>,
13351341
}
1336-
assert_mem_size!(MethodAliasDefinition, 80);
1342+
assert_mem_size!(MethodAliasDefinition, 96);
13371343

13381344
impl MethodAliasDefinition {
13391345
#[must_use]
1346+
#[allow(clippy::too_many_arguments)]
13401347
pub const fn new(
13411348
new_name_str_id: StringId,
13421349
old_name_str_id: StringId,
@@ -1345,6 +1352,7 @@ impl MethodAliasDefinition {
13451352
comments: Vec<Comment>,
13461353
flags: DefinitionFlags,
13471354
lexical_nesting_id: Option<DefinitionId>,
1355+
receiver: Option<Receiver>,
13481356
) -> Self {
13491357
Self {
13501358
new_name_str_id,
@@ -1354,18 +1362,25 @@ impl MethodAliasDefinition {
13541362
flags,
13551363
comments,
13561364
lexical_nesting_id,
1365+
receiver,
13571366
}
13581367
}
13591368

13601369
#[must_use]
13611370
pub fn id(&self) -> DefinitionId {
1362-
DefinitionId::from(&format!(
1371+
let mut formatted_id = format!(
13631372
"{}{}{}{}",
13641373
*self.uri_id,
13651374
self.offset.start(),
13661375
*self.new_name_str_id,
13671376
*self.old_name_str_id,
1368-
))
1377+
);
1378+
1379+
if let Some(receiver) = &self.receiver {
1380+
receiver.append_to_id(&mut formatted_id);
1381+
}
1382+
1383+
DefinitionId::from(&formatted_id)
13691384
}
13701385

13711386
#[must_use]
@@ -1402,6 +1417,11 @@ impl MethodAliasDefinition {
14021417
pub fn flags(&self) -> &DefinitionFlags {
14031418
&self.flags
14041419
}
1420+
1421+
#[must_use]
1422+
pub fn receiver(&self) -> &Option<Receiver> {
1423+
&self.receiver
1424+
}
14051425
}
14061426

14071427
#[derive(Debug)]

0 commit comments

Comments
 (0)