Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 174 additions & 14 deletions rust/rubydex/src/indexing/ruby_indexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,17 +361,12 @@ impl<'a> RubyIndexer<'a> {
})
}

// Runs the given closure if the given call `node` is invoked directly on `self` for each one of its string or
// symbol arguments
// Runs the given closure for each string or symbol argument of a call node.
fn each_string_or_symbol_arg<F>(node: &ruby_prism::CallNode, mut f: F)
where
F: FnMut(String, ruby_prism::Location),
{
let receiver = node.receiver();

if (receiver.is_none() || receiver.unwrap().as_self_node().is_some())
&& let Some(arguments) = node.arguments()
{
Comment on lines -370 to -374
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the receiver guard out of each_string_or_symbol_arg and into index_attr as it was blocking Foo.alias_method(:bar, :baz) from being indexed. Now constant receivers produce a ConstantReceiver on the definition

if let Some(arguments) = node.arguments() {
for argument in &arguments.arguments() {
match argument {
ruby_prism::Node::SymbolNode { .. } => {
Expand Down Expand Up @@ -1549,6 +1544,11 @@ impl Visit<'_> for RubyIndexer<'_> {
}

let mut index_attr = |kind: AttrKind, call: &ruby_prism::CallNode| {
let receiver = call.receiver();
if receiver.is_some() && receiver.unwrap().as_self_node().is_none() {
return;
}

let call_offset = Offset::from_prism_location(&call.location());

Self::each_string_or_symbol_arg(call, |name, location| {
Expand Down Expand Up @@ -1645,6 +1645,21 @@ impl Visit<'_> for RubyIndexer<'_> {
}
}
"alias_method" => {
let recv_node = node.receiver();
let recv_ref = recv_node.as_ref();
if recv_ref.is_some_and(|recv| {
!matches!(
recv,
ruby_prism::Node::SelfNode { .. }
| ruby_prism::Node::ConstantReadNode { .. }
| ruby_prism::Node::ConstantPathNode { .. }
)
}) {
// TODO: Add a diagnostic for dynamic receivers
self.visit_call_node_parts(node);
return;
}

let mut names: Vec<(String, Offset)> = Vec::new();

Self::each_string_or_symbol_arg(node, |name, location| {
Expand All @@ -1662,7 +1677,15 @@ impl Visit<'_> for RubyIndexer<'_> {
let new_name_str_id = self.local_graph.intern_string(format!("{new_name}()"));
let old_name_str_id = self.local_graph.intern_string(format!("{old_name}()"));

let method_receiver = self.method_receiver(node.receiver().as_ref(), node.location());
let (receiver, method_receiver) = match recv_ref {
Some(
recv @ (ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. }),
) => {
let name_id = self.index_constant_reference(recv, true);
(name_id.map(Receiver::ConstantReceiver), name_id)
}
_ => (None, self.method_receiver(recv_ref, node.location())),
};
let reference = MethodRef::new(old_name_str_id, self.uri_id, old_offset.clone(), method_receiver);
self.local_graph.add_method_reference(reference);

Expand All @@ -1677,6 +1700,7 @@ impl Visit<'_> for RubyIndexer<'_> {
comments,
flags,
self.current_nesting_definition_id(),
receiver,
)));

let definition_id = self.local_graph.add_definition(definition);
Expand Down Expand Up @@ -1987,6 +2011,7 @@ impl Visit<'_> for RubyIndexer<'_> {
comments,
flags,
self.current_nesting_definition_id(),
None,
)));

let definition_id = self.local_graph.add_definition(definition);
Expand Down Expand Up @@ -4553,6 +4578,7 @@ mod tests {

assert_definition_at!(&context, "1:1-5:4", Class, |foo| {
assert_definition_at!(&context, "3:5-3:24", MethodAlias, |alias_method| {
assert!(alias_method.receiver().is_none());
assert_eq!(foo.id(), alias_method.lexical_nesting_id().unwrap());
});
});
Expand Down Expand Up @@ -4940,7 +4966,7 @@ mod tests {
let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
assert_eq!(new_name.as_str(), "foo()");
assert_eq!(old_name.as_str(), "bar()");

assert!(def.receiver().is_none());
assert_eq!(foo_class_def.id(), def.lexical_nesting_id().unwrap());
});

Expand All @@ -4949,7 +4975,7 @@ mod tests {
let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
assert_eq!(new_name.as_str(), "baz()");
assert_eq!(old_name.as_str(), "qux()");

assert!(def.receiver().is_none());
assert_eq!(foo_class_def.id(), def.lexical_nesting_id().unwrap());
});
});
Expand All @@ -4971,7 +4997,7 @@ mod tests {
let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
assert_eq!(new_name.as_str(), "foo()");
assert_eq!(old_name.as_str(), "bar()");

assert!(def.receiver().is_none());
assert!(def.lexical_nesting_id().is_none());
});

Expand Down Expand Up @@ -5009,7 +5035,7 @@ mod tests {
let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
assert_eq!(new_name.as_str(), "foo_symbol()");
assert_eq!(old_name.as_str(), "bar_symbol()");

assert!(def.receiver().is_none());
assert!(def.lexical_nesting_id().is_none());
});

Expand All @@ -5018,7 +5044,7 @@ mod tests {
let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
assert_eq!(new_name.as_str(), "foo_string()");
assert_eq!(old_name.as_str(), "bar_string()");

assert!(def.receiver().is_none());
assert!(def.lexical_nesting_id().is_none());
});

Expand All @@ -5028,12 +5054,146 @@ mod tests {
let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
assert_eq!(new_name.as_str(), "baz()");
assert_eq!(old_name.as_str(), "qux()");

assert!(def.receiver().is_none());
assert_eq!(foo_class_def.id(), def.lexical_nesting_id().unwrap());
});
});
}

#[test]
fn index_alias_method_with_self_receiver_maps_to_none() {
let context = index_source({
"
class Foo
self.alias_method :bar, :baz
end
"
});

assert_no_local_diagnostics!(&context);

assert_definition_at!(&context, "2:3-2:31", MethodAlias, |def| {
assert!(def.receiver().is_none());
});
}

#[test]
fn index_alias_method_with_constant_receiver() {
let context = index_source({
"
class Foo; end
Foo.alias_method :bar, :baz
"
});

assert_no_local_diagnostics!(&context);

assert_definition_at!(&context, "2:1-2:28", MethodAlias, |def| {
assert_string_eq!(&context, def.new_name_str_id(), "bar()");
assert_string_eq!(&context, def.old_name_str_id(), "baz()");
assert_method_has_receiver!(&context, def, "Foo");
});
}

#[test]
fn index_alias_method_in_singleton_class_has_no_receiver() {
let context = index_source({
"
class Foo
def self.find; end

class << self
alias_method :find_old, :find
end
end
"
});

assert_no_local_diagnostics!(&context);

assert_definition_at!(&context, "1:1-7:4", Class, |_foo| {
assert_definition_at!(&context, "4:3-6:6", SingletonClass, |singleton| {
assert_definition_at!(&context, "5:5-5:34", MethodAlias, |def| {
assert_string_eq!(&context, def.new_name_str_id(), "find_old()");
assert_string_eq!(&context, def.old_name_str_id(), "find()");
assert!(def.receiver().is_none());
assert_eq!(singleton.id(), def.lexical_nesting_id().unwrap());
});
});
});
}

#[test]
fn index_alias_keyword_in_singleton_class_has_no_receiver() {
// Same as above: `alias` inside `class << self` has no receiver.
let context = index_source({
"
class Foo
def self.find; end

class << self
alias find_old find
end
end
"
});

assert_no_local_diagnostics!(&context);

assert_definition_at!(&context, "4:3-6:6", SingletonClass, |singleton| {
assert_definition_at!(&context, "5:5-5:24", MethodAlias, |def| {
assert!(def.receiver().is_none());
assert_eq!(singleton.id(), def.lexical_nesting_id().unwrap());
});
});
}

#[test]
fn index_alias_method_with_nested_constant_receiver() {
let context = index_source({
"
module A
class B
def original; end
end
end

A::B.alias_method :new_name, :original
"
});

assert_no_local_diagnostics!(&context);

assert_definition_at!(&context, "7:1-7:39", MethodAlias, |def| {
assert_string_eq!(&context, def.new_name_str_id(), "new_name()");
assert_method_has_receiver!(&context, def, "B");
assert!(def.lexical_nesting_id().is_none());
});
}

#[test]
fn index_alias_method_with_dynamic_receiver_not_indexed() {
let context = index_source({
"
class Foo
def original; end
end

foo.alias_method :new_name, :original
"
});

assert_no_local_diagnostics!(&context);

let alias_count = context
.graph()
.definitions()
.values()
.filter(|def| matches!(def, Definition::MethodAlias(_)))
.count();
assert_eq!(0, alias_count);
}

#[test]
fn index_alias_global_variables() {
let context = index_source({
Expand Down
11 changes: 10 additions & 1 deletion rust/rubydex/src/model/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1361,10 +1361,12 @@ pub struct MethodAliasDefinition {
flags: DefinitionFlags,
comments: Box<[Comment]>,
lexical_nesting_id: Option<DefinitionId>,
receiver: Option<Receiver>,
}
assert_mem_size!(MethodAliasDefinition, 72);
assert_mem_size!(MethodAliasDefinition, 88);

impl MethodAliasDefinition {
#[allow(clippy::too_many_arguments)]
#[must_use]
pub const fn new(
new_name_str_id: StringId,
Expand All @@ -1374,6 +1376,7 @@ impl MethodAliasDefinition {
comments: Box<[Comment]>,
flags: DefinitionFlags,
lexical_nesting_id: Option<DefinitionId>,
receiver: Option<Receiver>,
) -> Self {
Self {
new_name_str_id,
Expand All @@ -1383,6 +1386,7 @@ impl MethodAliasDefinition {
flags,
comments,
lexical_nesting_id,
receiver,
}
}

Expand Down Expand Up @@ -1427,6 +1431,11 @@ impl MethodAliasDefinition {
&self.lexical_nesting_id
}

#[must_use]
pub fn receiver(&self) -> &Option<Receiver> {
&self.receiver
}

#[must_use]
pub fn flags(&self) -> &DefinitionFlags {
&self.flags
Expand Down
Loading
Loading