Skip to content

Commit 48da4ca

Browse files
committed
Index method aliases with RBS indexer
Handle alias and alias self.x self.y declarations in RBS files. Instance aliases use the enclosing class as owner, while singleton aliases (self. prefix) use SelfReceiver to route through the singleton class during resolution
1 parent 5975a19 commit 48da4ca

File tree

2 files changed

+149
-4
lines changed

2 files changed

+149
-4
lines changed

rust/rubydex/src/indexing/rbs_indexer.rs

Lines changed: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
//! Visit the RBS AST and create type definitions.
22
33
use ruby_rbs::node::{
4-
self, ClassNode, CommentNode, ConstantNode, ExtendNode, GlobalNode, IncludeNode, ModuleNode, Node, NodeList,
5-
PrependNode, TypeNameNode, Visit,
4+
self, AliasKind, ClassNode, CommentNode, ConstantNode, ExtendNode, GlobalNode, IncludeNode, ModuleNode, Node,
5+
NodeList, PrependNode, TypeNameNode, Visit,
66
};
77

88
use crate::diagnostic::Rule;
99
use crate::indexing::local_graph::LocalGraph;
1010
use crate::model::comment::Comment;
1111
use crate::model::definitions::{
1212
ClassDefinition, ConstantDefinition, Definition, DefinitionFlags, ExtendDefinition, GlobalVariableDefinition,
13-
IncludeDefinition, Mixin, ModuleDefinition, PrependDefinition,
13+
IncludeDefinition, MethodAliasDefinition, Mixin, ModuleDefinition, PrependDefinition, Receiver,
1414
};
1515
use crate::model::document::Document;
1616
use crate::model::ids::{DefinitionId, NameId, ReferenceId, UriId};
@@ -324,6 +324,37 @@ impl Visit for RBSIndexer<'_> {
324324
Mixin::Extend(ExtendDefinition::new(ref_id))
325325
});
326326
}
327+
328+
fn visit_alias_node(&mut self, alias_node: &node::AliasNode) {
329+
let lexical_nesting_id = self.parent_lexical_scope_id();
330+
331+
let receiver = match alias_node.kind() {
332+
AliasKind::Instance => None,
333+
AliasKind::Singleton => lexical_nesting_id.map(Receiver::SelfReceiver),
334+
};
335+
336+
let new_name = Self::bytes_to_string(alias_node.new_name().name());
337+
let old_name = Self::bytes_to_string(alias_node.old_name().name());
338+
339+
let new_name_str_id = self.local_graph.intern_string(format!("{new_name}()"));
340+
let old_name_str_id = self.local_graph.intern_string(format!("{old_name}()"));
341+
342+
let offset = Offset::from_rbs_location(&alias_node.location());
343+
let comments = Self::collect_comments(alias_node.comment());
344+
345+
let definition = Definition::MethodAlias(Box::new(MethodAliasDefinition::new(
346+
new_name_str_id,
347+
old_name_str_id,
348+
self.uri_id,
349+
offset,
350+
comments,
351+
Self::flags(&alias_node.annotations()),
352+
lexical_nesting_id,
353+
receiver,
354+
)));
355+
356+
self.register_definition(definition, lexical_nesting_id);
357+
}
327358
}
328359

329360
#[cfg(test)]
@@ -336,6 +367,7 @@ mod tests {
336367
use crate::{
337368
assert_def_comments_eq, assert_def_mixins_eq, assert_def_name_eq, assert_def_name_offset_eq, assert_def_str_eq,
338369
assert_def_superclass_ref_eq, assert_definition_at, assert_local_diagnostics_eq, assert_no_local_diagnostics,
370+
assert_string_eq,
339371
};
340372

341373
fn index_source(source: &str) -> LocalGraphTest {
@@ -714,4 +746,73 @@ mod tests {
714746
assert!(def.flags().contains(DefinitionFlags::DEPRECATED));
715747
});
716748
}
749+
750+
#[test]
751+
fn index_alias_node() {
752+
let context = index_source({
753+
"
754+
class Foo
755+
# Some documentation
756+
alias bar baz
757+
alias qux quux
758+
end
759+
"
760+
});
761+
762+
assert_no_local_diagnostics!(&context);
763+
764+
assert_definition_at!(&context, "1:1-5:4", Class, |class_def| {
765+
assert_eq!(2, class_def.members().len());
766+
767+
assert_definition_at!(&context, "3:3-3:16", MethodAlias, |def| {
768+
assert_string_eq!(&context, def.new_name_str_id(), "bar()");
769+
assert_string_eq!(&context, def.old_name_str_id(), "baz()");
770+
assert_def_comments_eq!(&context, def, ["Some documentation\n"]);
771+
assert_eq!(class_def.id(), def.lexical_nesting_id().unwrap());
772+
});
773+
774+
assert_definition_at!(&context, "4:3-4:17", MethodAlias, |def| {
775+
assert_string_eq!(&context, def.new_name_str_id(), "qux()");
776+
assert_string_eq!(&context, def.old_name_str_id(), "quux()");
777+
});
778+
});
779+
}
780+
781+
#[test]
782+
fn index_alias_node_with_deprecation() {
783+
let context = index_source({
784+
"
785+
class Foo
786+
%a{deprecated}
787+
alias bar baz
788+
end
789+
"
790+
});
791+
792+
assert_no_local_diagnostics!(&context);
793+
794+
assert_definition_at!(&context, "3:3-3:16", MethodAlias, |def| {
795+
assert!(def.flags().contains(DefinitionFlags::DEPRECATED));
796+
});
797+
}
798+
799+
#[test]
800+
fn index_alias_node_singleton() {
801+
let context = index_source({
802+
"
803+
class Foo
804+
alias self.bar self.baz
805+
end
806+
"
807+
});
808+
809+
assert_no_local_diagnostics!(&context);
810+
assert_eq!(context.graph().definitions().len(), 2);
811+
812+
assert_definition_at!(&context, "2:3-2:26", MethodAlias, |def| {
813+
assert_string_eq!(&context, def.new_name_str_id(), "bar()");
814+
assert_string_eq!(&context, def.old_name_str_id(), "baz()");
815+
assert!(def.receiver().is_some());
816+
});
817+
}
717818
}

rust/rubydex/src/resolution.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,15 @@ impl<'a> Resolver<'a> {
489489
NameRef::Resolved(resolved) => *resolved.declaration_id(),
490490
NameRef::Unresolved(_) => continue,
491491
},
492-
Some(Receiver::SelfReceiver(_)) | None => {
492+
// RBS `alias self.x self.y` creates singleton aliases
493+
Some(Receiver::SelfReceiver(_)) => {
494+
let Some(owner_id) = self.resolve_singleton_owner(receiver.as_ref(), lexical_nesting_id)
495+
else {
496+
continue;
497+
};
498+
owner_id
499+
}
500+
None => {
493501
let Some(resolved) = self.resolve_lexical_owner(lexical_nesting_id) else {
494502
continue;
495503
};
@@ -4722,6 +4730,42 @@ mod tests {
47224730
assert_ancestors_eq!(context, "Foo", ["Foo", "Baz", "Bar", "Object"]);
47234731
}
47244732

4733+
#[test]
4734+
fn rbs_method_alias_resolution() {
4735+
let mut context = GraphTest::new();
4736+
context.index_uri("file:///foo.rb", {
4737+
r"
4738+
class Foo
4739+
def bar; end
4740+
def self.class_method; end
4741+
end
4742+
4743+
module Baz
4744+
def original; end
4745+
end
4746+
"
4747+
});
4748+
context.index_rbs_uri("file:///test.rbs", {
4749+
r"
4750+
class Foo
4751+
alias qux bar
4752+
alias self.class_alias self.class_method
4753+
end
4754+
4755+
module Baz
4756+
alias copy original
4757+
end
4758+
"
4759+
});
4760+
context.resolve();
4761+
4762+
assert_no_diagnostics!(&context);
4763+
4764+
assert_members_eq!(context, "Foo", ["bar()", "qux()"]);
4765+
assert_members_eq!(context, "Foo::<Foo>", ["class_alias()", "class_method()"]);
4766+
assert_members_eq!(context, "Baz", ["copy()", "original()"]);
4767+
}
4768+
47254769
#[test]
47264770
fn resolving_meta_programming_class_reopened() {
47274771
// It's often not possible to provide first-class support to meta-programming constructs, but we have to prevent

0 commit comments

Comments
 (0)