Skip to content

Commit 1315d61

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 1315d61

File tree

3 files changed

+294
-80
lines changed

3 files changed

+294
-80
lines changed

rust/rubydex/src/indexing/ruby_indexer.rs

Lines changed: 154 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.create_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 create_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,23 @@ 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+
let receiver_node = node.receiver();
1663+
let receiver = receiver_node.as_ref().and_then(|node| match node {
1664+
ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. } => self
1665+
.index_constant_reference(node, true)
1666+
.map(Receiver::ConstantReceiver),
1667+
_ => None,
1668+
});
1669+
1670+
// alias_method references instance methods, so we use the class NameId directly
1671+
let method_ref_receiver = match &receiver {
1672+
Some(Receiver::ConstantReceiver(name_id)) => Some(*name_id),
1673+
Some(Receiver::SelfReceiver(_)) | None => {
1674+
self.method_receiver(receiver_node.as_ref(), node.location())
1675+
}
1676+
};
1677+
1678+
let reference = MethodRef::new(old_name_str_id, self.uri_id, old_offset.clone(), method_ref_receiver);
16581679
self.local_graph.add_method_reference(reference);
16591680

16601681
let offset = Offset::from_prism_location(&node.location());
@@ -1668,6 +1689,7 @@ impl Visit<'_> for RubyIndexer<'_> {
16681689
comments,
16691690
flags,
16701691
self.current_nesting_definition_id(),
1692+
receiver,
16711693
)));
16721694

16731695
let definition_id = self.local_graph.add_definition(definition);
@@ -1978,6 +2000,7 @@ impl Visit<'_> for RubyIndexer<'_> {
19782000
comments,
19792001
flags,
19802002
self.current_nesting_definition_id(),
2003+
None,
19812004
)));
19822005

19832006
let definition_id = self.local_graph.add_definition(definition);
@@ -4909,6 +4932,105 @@ mod tests {
49094932
});
49104933
}
49114934

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