Skip to content

Commit 4f2c3c2

Browse files
committed
Fix corner cases of re-opening aliases
1 parent 2c4dcbb commit 4f2c3c2

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())
@@ -4965,4 +4998,125 @@ mod tests {
49654998
context.resolve();
49664999
assert_declaration_exists!(context, "CONST");
49675000
}
5001+
5002+
#[test]
5003+
fn including_unresolved_alias() {
5004+
let mut context = GraphTest::new();
5005+
context.index_uri("file:///a.rb", {
5006+
r"
5007+
module Foo; end
5008+
Foo::Bar = Bar
5009+
5010+
module Baz
5011+
include Foo::Bar
5012+
end
5013+
"
5014+
});
5015+
5016+
context.resolve();
5017+
assert_ancestors_eq!(context, "Baz", ["Baz"]);
5018+
}
5019+
5020+
#[test]
5021+
fn prepending_unresolved_alias() {
5022+
let mut context = GraphTest::new();
5023+
context.index_uri("file:///a.rb", {
5024+
r"
5025+
module Foo; end
5026+
Foo::Bar = Bar
5027+
5028+
module Baz
5029+
prepend Foo::Bar
5030+
end
5031+
"
5032+
});
5033+
5034+
context.resolve();
5035+
assert_ancestors_eq!(context, "Baz", ["Baz"]);
5036+
}
5037+
5038+
#[test]
5039+
fn inheriting_unresolved_alias() {
5040+
let mut context = GraphTest::new();
5041+
context.index_uri("file:///a.rb", {
5042+
r"
5043+
module Foo; end
5044+
Foo::Bar = Bar
5045+
5046+
class Baz < Foo::Bar
5047+
end
5048+
"
5049+
});
5050+
5051+
context.resolve();
5052+
assert_ancestors_eq!(context, "Baz", ["Baz", "Object"]);
5053+
}
5054+
5055+
#[test]
5056+
fn re_opening_unresolved_alias() {
5057+
let mut context = GraphTest::new();
5058+
context.index_uri("file:///a.rb", {
5059+
r"
5060+
module Foo; end
5061+
Foo::Bar = Bar
5062+
5063+
module Foo::Bar
5064+
CONST = 123
5065+
@class_ivar = 123
5066+
@@class_var = 789
5067+
5068+
attr_reader :some_attr
5069+
5070+
def self.class_method; end
5071+
5072+
def initialize
5073+
@instance_ivar = 456
5074+
end
5075+
end
5076+
"
5077+
});
5078+
5079+
context.resolve();
5080+
assert_declaration_does_not_exist!(context, "Foo::Bar::CONST");
5081+
assert_declaration_does_not_exist!(context, "Foo::Bar::<Bar>#@class_ivar");
5082+
assert_declaration_does_not_exist!(context, "Foo::Bar#@instance_ivar");
5083+
assert_declaration_does_not_exist!(context, "Foo::Bar#@@class_var");
5084+
assert_declaration_does_not_exist!(context, "Foo::Bar#some_attr()");
5085+
assert_declaration_does_not_exist!(context, "Foo::Bar::<Bar>#class_method()");
5086+
assert_declaration_does_not_exist!(context, "Foo::Bar#initialize()");
5087+
}
5088+
5089+
#[test]
5090+
fn re_opening_namespace_alias() {
5091+
let mut context = GraphTest::new();
5092+
context.index_uri("file:///a.rb", {
5093+
r"
5094+
module Foo; end
5095+
ALIAS = Foo
5096+
5097+
module ALIAS
5098+
CONST = 123
5099+
@class_ivar = 123
5100+
@@class_var = 789
5101+
5102+
attr_reader :some_attr
5103+
5104+
def self.class_method; end
5105+
5106+
def initialize
5107+
@instance_ivar = 456
5108+
end
5109+
end
5110+
"
5111+
});
5112+
5113+
context.resolve();
5114+
assert_declaration_exists!(context, "Foo::CONST");
5115+
assert_declaration_exists!(context, "Foo::<Foo>#@class_ivar");
5116+
assert_declaration_exists!(context, "Foo#@instance_ivar");
5117+
assert_declaration_exists!(context, "Foo#@@class_var");
5118+
assert_declaration_exists!(context, "Foo#some_attr()");
5119+
assert_declaration_exists!(context, "Foo::<Foo>#class_method()");
5120+
assert_declaration_exists!(context, "Foo#initialize()");
5121+
}
49685122
}

0 commit comments

Comments
 (0)