Skip to content

Commit 5ea4e22

Browse files
committed
Fix corner cases of re-opening aliases
1 parent adc0cd9 commit 5ea4e22

1 file changed

Lines changed: 170 additions & 16 deletions

File tree

rust/rubydex/src/resolution.rs

Lines changed: 170 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -308,29 +308,41 @@ impl<'a> Resolver<'a> {
308308

309309
singleton_id
310310
}
311-
None => self.resolve_lexical_owner(*method_definition.lexical_nesting_id()),
311+
None => {
312+
let Some(resolved) = self.resolve_lexical_owner(*method_definition.lexical_nesting_id())
313+
else {
314+
continue;
315+
};
316+
resolved
317+
}
312318
};
313319

314320
self.create_declaration(str_id, id, owner_id, |name| {
315321
Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id)))
316322
});
317323
}
318324
Definition::AttrAccessor(attr) => {
319-
let owner_id = self.resolve_lexical_owner(*attr.lexical_nesting_id());
325+
let Some(owner_id) = self.resolve_lexical_owner(*attr.lexical_nesting_id()) else {
326+
continue;
327+
};
320328

321329
self.create_declaration(*attr.str_id(), id, owner_id, |name| {
322330
Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id)))
323331
});
324332
}
325333
Definition::AttrReader(attr) => {
326-
let owner_id = self.resolve_lexical_owner(*attr.lexical_nesting_id());
334+
let Some(owner_id) = self.resolve_lexical_owner(*attr.lexical_nesting_id()) else {
335+
continue;
336+
};
327337

328338
self.create_declaration(*attr.str_id(), id, owner_id, |name| {
329339
Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id)))
330340
});
331341
}
332342
Definition::AttrWriter(attr) => {
333-
let owner_id = self.resolve_lexical_owner(*attr.lexical_nesting_id());
343+
let Some(owner_id) = self.resolve_lexical_owner(*attr.lexical_nesting_id()) else {
344+
continue;
345+
};
334346

335347
self.create_declaration(*attr.str_id(), id, owner_id, |name| {
336348
Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id)))
@@ -403,7 +415,9 @@ impl<'a> Resolver<'a> {
403415
}
404416

405417
// If the method has no explicit receiver, we resolve the owner based on the lexical nesting
406-
let method_owner_id = self.resolve_lexical_owner(*method.lexical_nesting_id());
418+
let Some(method_owner_id) = self.resolve_lexical_owner(*method.lexical_nesting_id()) else {
419+
continue;
420+
};
407421

408422
// If the method is in a singleton class, the instance variable belongs to the class object
409423
// Like `class << Foo; def bar; @bar = 1; end; end`, where `@bar` is owned by `Foo::<Foo>`
@@ -436,9 +450,10 @@ impl<'a> Resolver<'a> {
436450
.definition_id_to_declaration_id(nesting_id)
437451
.copied()
438452
.unwrap_or(*OBJECT_ID);
439-
let owner_id = self
440-
.get_or_create_singleton_class(nesting_decl_id)
441-
.expect("class/module nesting should always be a namespace");
453+
454+
let Some(owner_id) = self.get_or_create_singleton_class(nesting_decl_id) else {
455+
continue;
456+
};
442457
{
443458
debug_assert!(
444459
matches!(
@@ -494,7 +509,9 @@ impl<'a> Resolver<'a> {
494509
}
495510
}
496511
Definition::MethodAlias(alias) => {
497-
let owner_id = self.resolve_lexical_owner(*alias.lexical_nesting_id());
512+
let Some(owner_id) = self.resolve_lexical_owner(*alias.lexical_nesting_id()) else {
513+
continue;
514+
};
498515

499516
self.create_declaration(*alias.new_name_str_id(), id, owner_id, |name| {
500517
Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id)))
@@ -537,7 +554,8 @@ impl<'a> Resolver<'a> {
537554
self.graph.add_member(&owner_id, declaration_id, str_id);
538555
}
539556

540-
/// Resolves owner for class variables, bypassing singleton classes.
557+
/// Resolves owner for class variables, bypassing singleton classes. Returns `None` if the owner can't be
558+
/// determined (e.g., unresolved constant alias).
541559
fn resolve_class_variable_owner(&self, lexical_nesting_id: Option<DefinitionId>) -> Option<DeclarationId> {
542560
let mut current_nesting = lexical_nesting_id;
543561
while let Some(nesting_id) = current_nesting {
@@ -549,13 +567,24 @@ impl<'a> Resolver<'a> {
549567
break;
550568
}
551569
}
552-
current_nesting.and_then(|id| self.graph.definition_id_to_declaration_id(id).copied())
570+
let declaration_id = current_nesting.and_then(|id| self.graph.definition_id_to_declaration_id(id).copied())?;
571+
572+
// If the declaration is a constant alias, follow the alias chain to find the
573+
// target namespace. Returns None if the alias target is unresolved.
574+
if matches!(
575+
self.graph.declarations().get(&declaration_id),
576+
Some(Declaration::ConstantAlias(_))
577+
) {
578+
self.resolve_to_namespace(declaration_id)
579+
} else {
580+
Some(declaration_id)
581+
}
553582
}
554583

555584
/// Resolves owner from lexical nesting.
556-
fn resolve_lexical_owner(&self, lexical_nesting_id: Option<DefinitionId>) -> DeclarationId {
585+
fn resolve_lexical_owner(&self, lexical_nesting_id: Option<DefinitionId>) -> Option<DeclarationId> {
557586
let Some(id) = lexical_nesting_id else {
558-
return *OBJECT_ID;
587+
return Some(*OBJECT_ID);
559588
};
560589

561590
// If no declaration exists yet for this definition, walk up the lexical chain.
@@ -566,16 +595,20 @@ impl<'a> Resolver<'a> {
566595
return self.resolve_lexical_owner(*definition.lexical_nesting_id());
567596
};
568597

569-
let declarations = self.graph.declarations();
598+
let decl = self.graph.declarations().get(declaration_id).unwrap();
570599

571600
// If the associated declaration is a namespace that can own things, we found the right owner. Otherwise, we might
572601
// have found something nested inside something else (like a method), in which case we have to recurse until we find
573602
// the appropriate owner
574603
if matches!(
575-
declarations.get(declaration_id).unwrap(),
604+
decl,
576605
Declaration::Namespace(Namespace::Class(_) | Namespace::Module(_) | Namespace::SingletonClass(_))
577606
) {
578-
*declaration_id
607+
Some(*declaration_id)
608+
} else if matches!(decl, Declaration::ConstantAlias(_)) {
609+
// Follow the alias chain to find the target namespace. If the alias is unresolved,
610+
// the definition cannot be properly owned and should be skipped by the caller.
611+
self.resolve_to_namespace(*declaration_id)
579612
} else {
580613
let definition = self.graph.definitions().get(&id).unwrap();
581614
self.resolve_lexical_owner(*definition.lexical_nesting_id())
@@ -5031,4 +5064,125 @@ mod tests {
50315064
context.resolve();
50325065
assert_declaration_exists!(context, "CONST");
50335066
}
5067+
5068+
#[test]
5069+
fn including_unresolved_alias() {
5070+
let mut context = GraphTest::new();
5071+
context.index_uri("file:///a.rb", {
5072+
r"
5073+
module Foo; end
5074+
Foo::Bar = Bar
5075+
5076+
module Baz
5077+
include Foo::Bar
5078+
end
5079+
"
5080+
});
5081+
5082+
context.resolve();
5083+
assert_ancestors_eq!(context, "Baz", ["Baz"]);
5084+
}
5085+
5086+
#[test]
5087+
fn prepending_unresolved_alias() {
5088+
let mut context = GraphTest::new();
5089+
context.index_uri("file:///a.rb", {
5090+
r"
5091+
module Foo; end
5092+
Foo::Bar = Bar
5093+
5094+
module Baz
5095+
prepend Foo::Bar
5096+
end
5097+
"
5098+
});
5099+
5100+
context.resolve();
5101+
assert_ancestors_eq!(context, "Baz", ["Baz"]);
5102+
}
5103+
5104+
#[test]
5105+
fn inheriting_unresolved_alias() {
5106+
let mut context = GraphTest::new();
5107+
context.index_uri("file:///a.rb", {
5108+
r"
5109+
module Foo; end
5110+
Foo::Bar = Bar
5111+
5112+
class Baz < Foo::Bar
5113+
end
5114+
"
5115+
});
5116+
5117+
context.resolve();
5118+
assert_ancestors_eq!(context, "Baz", ["Baz", "Object"]);
5119+
}
5120+
5121+
#[test]
5122+
fn re_opening_unresolved_alias() {
5123+
let mut context = GraphTest::new();
5124+
context.index_uri("file:///a.rb", {
5125+
r"
5126+
module Foo; end
5127+
Foo::Bar = Bar
5128+
5129+
module Foo::Bar
5130+
CONST = 123
5131+
@class_ivar = 123
5132+
@@class_var = 789
5133+
5134+
attr_reader :some_attr
5135+
5136+
def self.class_method; end
5137+
5138+
def initialize
5139+
@instance_ivar = 456
5140+
end
5141+
end
5142+
"
5143+
});
5144+
5145+
context.resolve();
5146+
assert_declaration_does_not_exist!(context, "Foo::Bar::CONST");
5147+
assert_declaration_does_not_exist!(context, "Foo::Bar::<Bar>#@class_ivar");
5148+
assert_declaration_does_not_exist!(context, "Foo::Bar#@instance_ivar");
5149+
assert_declaration_does_not_exist!(context, "Foo::Bar#@@class_var");
5150+
assert_declaration_does_not_exist!(context, "Foo::Bar#some_attr()");
5151+
assert_declaration_does_not_exist!(context, "Foo::Bar::<Bar>#class_method()");
5152+
assert_declaration_does_not_exist!(context, "Foo::Bar#initialize()");
5153+
}
5154+
5155+
#[test]
5156+
fn re_opening_namespace_alias() {
5157+
let mut context = GraphTest::new();
5158+
context.index_uri("file:///a.rb", {
5159+
r"
5160+
module Foo; end
5161+
ALIAS = Foo
5162+
5163+
module ALIAS
5164+
CONST = 123
5165+
@class_ivar = 123
5166+
@@class_var = 789
5167+
5168+
attr_reader :some_attr
5169+
5170+
def self.class_method; end
5171+
5172+
def initialize
5173+
@instance_ivar = 456
5174+
end
5175+
end
5176+
"
5177+
});
5178+
5179+
context.resolve();
5180+
assert_declaration_exists!(context, "Foo::CONST");
5181+
assert_declaration_exists!(context, "Foo::<Foo>#@class_ivar");
5182+
assert_declaration_exists!(context, "Foo#@instance_ivar");
5183+
assert_declaration_exists!(context, "Foo#@@class_var");
5184+
assert_declaration_exists!(context, "Foo#some_attr()");
5185+
assert_declaration_exists!(context, "Foo::<Foo>#class_method()");
5186+
assert_declaration_exists!(context, "Foo#initialize()");
5187+
}
50345188
}

0 commit comments

Comments
 (0)