@@ -286,32 +286,22 @@ impl<'a> Resolver<'a> {
286286 // `Foo` is undefined). The method is orphaned as a consequence.
287287 continue ;
288288 } ;
289- self . get_or_create_singleton_class ( owner_decl_id)
290- . expect ( "Methods with self receiver should always be inside a namespace" )
289+ let Some ( singleton_id) = self . get_or_create_singleton_class ( owner_decl_id) else {
290+ // The enclosing declaration is a constant that can't be promoted to a namespace
291+ // (e.g., `CONST = 1; module CONST; def self.bar; end; end`). The method is
292+ // orphaned as a consequence.
293+ continue ;
294+ } ;
295+ singleton_id
291296 }
292297 Some ( Receiver :: ConstantReceiver ( name_id) ) => {
293- let mut receiver_decl_id = match self . graph . names ( ) . get ( name_id) . unwrap ( ) {
298+ let receiver_decl_id = match self . graph . names ( ) . get ( name_id) . unwrap ( ) {
294299 NameRef :: Resolved ( resolved) => * resolved. declaration_id ( ) ,
295300 NameRef :: Unresolved ( _) => {
296301 continue ;
297302 }
298303 } ;
299304
300- let receiver_decl = self . graph . declarations ( ) . get ( & receiver_decl_id) . unwrap ( ) ;
301-
302- // If the method receiver is a constant alias, it needs to point to a namespace or else we don't have a place to declare the method
303- if matches ! ( receiver_decl, Declaration :: ConstantAlias ( _) ) {
304- let resolved_ids = self . resolve_alias_chains ( receiver_decl_id) ;
305- let Some ( namespace) = resolved_ids
306- . iter ( )
307- . find ( |id| self . graph . declarations ( ) . get ( id) . unwrap ( ) . as_namespace ( ) . is_some ( ) )
308- else {
309- continue ;
310- } ;
311-
312- receiver_decl_id = * namespace;
313- }
314-
315305 let Some ( singleton_id) = self . get_or_create_singleton_class ( receiver_decl_id) else {
316306 continue ;
317307 } ;
@@ -387,22 +377,7 @@ impl<'a> Resolver<'a> {
387377 continue ;
388378 } ;
389379
390- let mut receiver_decl_id = * resolved. declaration_id ( ) ;
391- let receiver_decl = self . graph . declarations ( ) . get ( & receiver_decl_id) . unwrap ( ) ;
392-
393- // If the method receiver is a constant alias, it needs to point to a namespace or else we don't have a place to declare the method
394- if matches ! ( receiver_decl, Declaration :: ConstantAlias ( _) ) {
395- let resolved_ids = self . resolve_alias_chains ( receiver_decl_id) ;
396- let Some ( namespace) = resolved_ids. iter ( ) . find ( |id| {
397- self . graph . declarations ( ) . get ( id) . unwrap ( ) . as_namespace ( ) . is_some ( )
398- } ) else {
399- continue ;
400- } ;
401-
402- receiver_decl_id = * namespace;
403- }
404-
405- receiver_decl_id
380+ * resolved. declaration_id ( )
406381 }
407382 } ;
408383
@@ -616,6 +591,14 @@ impl<'a> Resolver<'a> {
616591 fn get_or_create_singleton_class ( & mut self , attached_id : DeclarationId ) -> Option < DeclarationId > {
617592 let attached_decl = self . graph . declarations ( ) . get ( & attached_id) . unwrap ( ) ;
618593
594+ // If the attached object is a constant alias, follow the alias chain to find the actual namespace
595+ if matches ! ( attached_decl, Declaration :: ConstantAlias ( _) ) {
596+ return match self . resolve_to_namespace ( attached_id) {
597+ Some ( id) => self . get_or_create_singleton_class ( id) ,
598+ None => None ,
599+ } ;
600+ }
601+
619602 if matches ! ( attached_decl, Declaration :: Constant ( _) ) {
620603 if self . graph . all_definitions_promotable ( attached_decl) {
621604 self . graph . promote_constant_to_namespace ( attached_id, |name, owner_id| {
@@ -852,15 +835,11 @@ impl<'a> Resolver<'a> {
852835 Mixin :: Prepend ( _) => {
853836 match self . graph . names ( ) . get ( constant_reference. name_id ( ) ) . unwrap ( ) {
854837 NameRef :: Resolved ( resolved) => {
855- let declaration_id = resolved. declaration_id ( ) ;
856-
857- // Skip if the mixin is not a namespace (e.g.: dynamically defined class or module that we
858- // misidentified as a constant)
859- if !self . graph . is_namespace ( declaration_id) {
838+ let Some ( module_id) = self . resolve_to_namespace ( * resolved. declaration_id ( ) ) else {
860839 continue ;
861- }
840+ } ;
862841
863- let ids = match self . linearize_ancestors ( * declaration_id , context) {
842+ let ids = match self . linearize_ancestors ( module_id , context) {
864843 Ancestors :: Complete ( ids) => ids,
865844 Ancestors :: Cyclic ( ids) => {
866845 context. cyclic = true ;
@@ -895,15 +874,11 @@ impl<'a> Resolver<'a> {
895874 Mixin :: Include ( _) | Mixin :: Extend ( _) => {
896875 match self . graph . names ( ) . get ( constant_reference. name_id ( ) ) . unwrap ( ) {
897876 NameRef :: Resolved ( resolved) => {
898- let declaration_id = resolved. declaration_id ( ) ;
899-
900- // Skip if the mixin is not a namespace (e.g.: dynamically defined class or module that we
901- // misidentified as a constant)
902- if !self . graph . is_namespace ( declaration_id) {
877+ let Some ( module_id) = self . resolve_to_namespace ( * resolved. declaration_id ( ) ) else {
903878 continue ;
904- }
879+ } ;
905880
906- let mut ids = match self . linearize_ancestors ( * declaration_id , context) {
881+ let mut ids = match self . linearize_ancestors ( module_id , context) {
907882 Ancestors :: Complete ( ids) => ids,
908883 Ancestors :: Cyclic ( ids) => {
909884 context. cyclic = true ;
@@ -1200,6 +1175,20 @@ impl<'a> Resolver<'a> {
12001175 }
12011176 }
12021177
1178+ /// If `declaration_id` is already a namespace, returns it directly. If it's a `ConstantAlias`, follows the alias
1179+ /// chain and returns the first namespace found. Returns `None` for all other declaration types or unresolved alias
1180+ /// chains.
1181+ fn resolve_to_namespace ( & self , declaration_id : DeclarationId ) -> Option < DeclarationId > {
1182+ match self . graph . declarations ( ) . get ( & declaration_id) ? {
1183+ Declaration :: Namespace ( _) => Some ( declaration_id) ,
1184+ Declaration :: ConstantAlias ( _) => self
1185+ . resolve_alias_chains ( declaration_id)
1186+ . into_iter ( )
1187+ . find ( |id| self . graph . is_namespace ( id) ) ,
1188+ _ => None ,
1189+ }
1190+ }
1191+
12031192 /// Resolves an alias chain to get all possible final target declarations.
12041193 /// Returns the original ID if it's not an alias or if the target hasn't been resolved yet.
12051194 ///
@@ -1253,10 +1242,27 @@ impl<'a> Resolver<'a> {
12531242 return scope_outcome;
12541243 }
12551244
1256- // Search inheritance chain
1245+ // Search inheritance chain, following alias chains to find the actual namespace
12571246 let ancestor_outcome = match self . graph . names ( ) . get ( nesting) . unwrap ( ) {
12581247 NameRef :: Resolved ( nesting_name_ref) => {
1259- self . search_ancestors ( * nesting_name_ref. declaration_id ( ) , str_id)
1248+ let resolved_ids = self . resolve_alias_chains ( * nesting_name_ref. declaration_id ( ) ) ;
1249+ let mut result = Outcome :: Unresolved ( None ) ;
1250+
1251+ for & id in & resolved_ids {
1252+ match self . graph . declarations ( ) . get ( & id) {
1253+ Some ( Declaration :: ConstantAlias ( _) ) => {
1254+ result = Outcome :: Retry ( None ) ;
1255+ break ;
1256+ }
1257+ Some ( Declaration :: Namespace ( _) ) => {
1258+ result = self . search_ancestors ( id, str_id) ;
1259+ break ;
1260+ }
1261+ _ => { }
1262+ }
1263+ }
1264+
1265+ result
12601266 }
12611267 NameRef :: Unresolved ( _) => Outcome :: Retry ( None ) ,
12621268 } ;
@@ -1344,23 +1350,11 @@ impl<'a> Resolver<'a> {
13441350 if let NameRef :: Resolved ( nesting_name_ref) = self . graph . names ( ) . get ( nesting_id) . unwrap ( ) {
13451351 let declaration_id = * nesting_name_ref. declaration_id ( ) ;
13461352
1347- match self . graph . declarations ( ) . get ( & declaration_id) {
1348- Some ( Declaration :: Namespace ( namespace) ) => {
1349- if let Some ( member) = namespace. member ( & str_id) {
1350- return Outcome :: Resolved ( * member, None ) ;
1351- }
1352- }
1353- Some ( Declaration :: ConstantAlias ( _) ) => {
1354- for id in & self . resolve_alias_chains ( declaration_id) {
1355- if let Some ( declaration) = self . graph . declarations ( ) . get ( id)
1356- && let Some ( namespace) = declaration. as_namespace ( )
1357- && let Some ( member) = namespace. member ( & str_id)
1358- {
1359- return Outcome :: Resolved ( * member, None ) ;
1360- }
1361- }
1362- }
1363- _ => { }
1353+ if let Some ( namespace_id) = self . resolve_to_namespace ( declaration_id)
1354+ && let Some ( namespace) = self . graph . declarations ( ) . get ( & namespace_id) . unwrap ( ) . as_namespace ( )
1355+ && let Some ( member) = namespace. member ( & str_id)
1356+ {
1357+ return Outcome :: Resolved ( * member, None ) ;
13641358 }
13651359
13661360 current_name = nesting_name_ref. name ( ) ;
@@ -1583,10 +1577,8 @@ impl<'a> Resolver<'a> {
15831577
15841578 match name {
15851579 NameRef :: Resolved ( resolved) => {
1586- let declaration_id = resolved. declaration_id ( ) ;
1587-
1588- if self . graph . is_namespace ( declaration_id) {
1589- explicit_parents. push ( * declaration_id) ;
1580+ if let Some ( parent_id) = self . resolve_to_namespace ( * resolved. declaration_id ( ) ) {
1581+ explicit_parents. push ( parent_id) ;
15901582 }
15911583 }
15921584 NameRef :: Unresolved ( _) => {
@@ -4967,4 +4959,76 @@ mod tests {
49674959 assert_declaration_exists ! ( context, "Baz" ) ;
49684960 assert_constant_reference_to ! ( context, "CONST" , "file:///reopen.rb:5:5-5:10" ) ;
49694961 }
4962+
4963+ #[ test]
4964+ fn constant_alias_reopened_as_class_with_nested_inheritance ( ) {
4965+ let mut context = GraphTest :: new ( ) ;
4966+ context. index_uri ( "file:///a.rb" , {
4967+ r"
4968+ module Foo
4969+ Bar = ::Object
4970+ end
4971+
4972+ module Foo
4973+ class Bar
4974+ class Baz < Something
4975+ end
4976+ end
4977+ end
4978+ "
4979+ } ) ;
4980+ context. resolve ( ) ;
4981+
4982+ assert_declaration_exists ! ( context, "Foo::Bar" ) ;
4983+ }
4984+
4985+ #[ test]
4986+ fn superclass_through_alias ( ) {
4987+ let mut context = GraphTest :: new ( ) ;
4988+ context. index_uri ( "file:///a.rb" , {
4989+ r"
4990+ class Base; end
4991+ AliasedBase = Base
4992+ class Foo < AliasedBase; end
4993+ "
4994+ } ) ;
4995+ context. resolve ( ) ;
4996+ assert_no_diagnostics ! ( & context) ;
4997+ assert_ancestors_eq ! ( context, "Foo" , [ "Foo" , "Base" , "Object" ] ) ;
4998+ }
4999+
5000+ #[ test]
5001+ fn mixin_through_alias ( ) {
5002+ let mut context = GraphTest :: new ( ) ;
5003+ context. index_uri ( "file:///a.rb" , {
5004+ r"
5005+ module M; end
5006+ AliasM = M
5007+ class Foo
5008+ include AliasM
5009+ end
5010+ "
5011+ } ) ;
5012+ context. resolve ( ) ;
5013+ assert_no_diagnostics ! ( & context) ;
5014+ assert_ancestors_eq ! ( context, "Foo" , [ "Foo" , "M" , "Object" ] ) ;
5015+ }
5016+
5017+ #[ test]
5018+ fn self_method_inside_non_promotable_constant ( ) {
5019+ let mut context = GraphTest :: new ( ) ;
5020+ context. index_uri ( "file:///a.rb" , {
5021+ r"
5022+ CONST = 1
5023+ module CONST
5024+ def self.bar
5025+ end
5026+ end
5027+ "
5028+ } ) ;
5029+ // Should not panic when a `def self.` method is inside a constant that can't be promoted to a namespace (e.g.,
5030+ // `CONST = 1` is non-promotable).
5031+ context. resolve ( ) ;
5032+ assert_declaration_exists ! ( context, "CONST" ) ;
5033+ }
49705034}
0 commit comments