@@ -7,7 +7,7 @@ use crate::model::{
77 declaration:: {
88 Ancestor , Ancestors , ClassDeclaration , ClassVariableDeclaration , ConstantAliasDeclaration , ConstantDeclaration ,
99 Declaration , GlobalVariableDeclaration , InstanceVariableDeclaration , MethodDeclaration , ModuleDeclaration ,
10- Namespace , SingletonClassDeclaration ,
10+ Namespace , SingletonClassDeclaration , TodoDeclaration ,
1111 } ,
1212 definitions:: { Definition , Mixin , Receiver } ,
1313 graph:: { CLASS_ID , Graph , MODULE_ID , OBJECT_ID } ,
@@ -1057,12 +1057,60 @@ impl<'a> Resolver<'a> {
10571057 fn name_owner_id ( & mut self , name_id : NameId ) -> Outcome {
10581058 let name_ref = self . graph . names ( ) . get ( & name_id) . unwrap ( ) ;
10591059
1060- if let Some ( parent_scope) = name_ref. parent_scope ( ) . as_ref ( ) {
1060+ if let Some ( & parent_scope) = name_ref. parent_scope ( ) . as_ref ( ) {
10611061 // If we have `A::B`, the owner of `B` is whatever `A` resolves to.
10621062 // If `A` is an alias, resolve through to get the actual namespace.
1063- match self . resolve_constant_internal ( * parent_scope) {
1063+ // On `Retry`, we don't create a Todo: the parent may still resolve through inheritance once ancestors are
1064+ // linearized. We only create Todos for `Unresolved` outcomes where the parent is genuinely unknown.
1065+ match self . resolve_constant_internal ( parent_scope) {
10641066 Outcome :: Resolved ( id, linearization) => self . resolve_to_primary_namespace ( id, linearization) ,
1065- other => other,
1067+ // Retry or Unresolved(Some(_)) means we might find it later through ancestor linearization
1068+ Outcome :: Retry ( id) => Outcome :: Retry ( id) ,
1069+ Outcome :: Unresolved ( Some ( id) ) => Outcome :: Unresolved ( Some ( id) ) ,
1070+ // Only create a Todo when genuinely unresolvable (no pending linearizations)
1071+ Outcome :: Unresolved ( None ) => {
1072+ let parent_name = self . graph . names ( ) . get ( & parent_scope) . unwrap ( ) ;
1073+ let parent_str_id = * parent_name. str ( ) ;
1074+ let parent_has_explicit_prefix = parent_name. parent_scope ( ) . as_ref ( ) . is_some ( ) ;
1075+ // NLL: borrow of parent_name ends here
1076+
1077+ // For bare names (no explicit `::` prefix), always use OBJECT_ID as the owner.
1078+ // Using nesting here would create "Nesting::Bar" instead of "Bar" for a bare `Bar`
1079+ // reference, which is incorrect: if `Bar` can't be found anywhere, the placeholder
1080+ // should live at the top level so it can be promoted when `module Bar` appears later.
1081+ let parent_owner_id = if parent_has_explicit_prefix {
1082+ match self . name_owner_id ( parent_scope) {
1083+ Outcome :: Resolved ( id, _) => id,
1084+ _ => * OBJECT_ID ,
1085+ }
1086+ } else {
1087+ * OBJECT_ID
1088+ } ;
1089+
1090+ let fully_qualified_name = if parent_owner_id == * OBJECT_ID {
1091+ self . graph . strings ( ) . get ( & parent_str_id) . unwrap ( ) . to_string ( )
1092+ } else {
1093+ format ! (
1094+ "{}::{}" ,
1095+ self . graph. declarations( ) . get( & parent_owner_id) . unwrap( ) . name( ) ,
1096+ self . graph. strings( ) . get( & parent_str_id) . unwrap( ) . as_str( )
1097+ )
1098+ } ;
1099+
1100+ let declaration_id = DeclarationId :: from ( & fully_qualified_name) ;
1101+
1102+ if let std:: collections:: hash_map:: Entry :: Vacant ( e) =
1103+ self . graph . declarations_mut ( ) . entry ( declaration_id)
1104+ {
1105+ e. insert ( Declaration :: Namespace ( Namespace :: Todo ( Box :: new ( TodoDeclaration :: new (
1106+ fully_qualified_name,
1107+ parent_owner_id,
1108+ ) ) ) ) ) ;
1109+ self . graph . add_member ( & parent_owner_id, declaration_id, parent_str_id) ;
1110+ }
1111+
1112+ Outcome :: Resolved ( declaration_id, None )
1113+ }
10661114 }
10671115 } else if let Some ( nesting_id) = name_ref. nesting ( )
10681116 && !name_ref. parent_scope ( ) . is_top_level ( )
@@ -1916,9 +1964,12 @@ mod tests {
19161964
19171965 assert_no_diagnostics ! ( & context) ;
19181966
1919- assert_declaration_does_not_exist ! ( context, "Foo" ) ;
1920- assert_declaration_does_not_exist ! ( context, "Foo::Bar" ) ;
1921- assert_declaration_does_not_exist ! ( context, "Foo::Bar::Baz" ) ;
1967+ assert_declaration_kind_eq ! ( context, "Foo" , "<TODO>" ) ;
1968+
1969+ assert_members_eq ! ( context, "Object" , vec![ "Foo" ] ) ;
1970+ assert_members_eq ! ( context, "Foo" , vec![ "Bar" ] ) ;
1971+ assert_members_eq ! ( context, "Foo::Bar" , vec![ "Baz" ] ) ;
1972+ assert_no_members ! ( context, "Foo::Bar::Baz" ) ;
19221973 }
19231974
19241975 #[ test]
@@ -5261,4 +5312,151 @@ mod tests {
52615312 assert_declaration_does_not_exist ! ( context, "Foo::<Foo>" ) ;
52625313 assert_declaration_does_not_exist ! ( context, "Foo::<Foo>#bar()" ) ;
52635314 }
5315+
5316+ #[ test]
5317+ fn resolve_missing_declaration_to_todo ( ) {
5318+ let mut context = GraphTest :: new ( ) ;
5319+ context. index_uri ( "file:///foo.rb" , {
5320+ r"
5321+ class Foo::Bar
5322+ include Foo::Baz
5323+
5324+ def bar; end
5325+ end
5326+
5327+ module Foo::Baz
5328+ def baz; end
5329+ end
5330+ "
5331+ } ) ;
5332+ context. resolve ( ) ;
5333+
5334+ assert_no_diagnostics ! ( & context) ;
5335+
5336+ assert_declaration_kind_eq ! ( context, "Foo" , "<TODO>" ) ;
5337+
5338+ assert_members_eq ! ( context, "Object" , vec![ "Foo" ] ) ;
5339+ assert_members_eq ! ( context, "Foo" , vec![ "Bar" , "Baz" ] ) ;
5340+ assert_members_eq ! ( context, "Foo::Bar" , vec![ "bar()" ] ) ;
5341+ assert_members_eq ! ( context, "Foo::Baz" , vec![ "baz()" ] ) ;
5342+ }
5343+
5344+ #[ test]
5345+ fn todo_declaration_promoted_to_real_namespace ( ) {
5346+ let mut context = GraphTest :: new ( ) ;
5347+ context. index_uri ( "file:///foo.rb" , {
5348+ r"
5349+ class Foo::Bar
5350+ def bar; end
5351+ end
5352+
5353+ class Foo
5354+ def foo; end
5355+ end
5356+ "
5357+ } ) ;
5358+ context. resolve ( ) ;
5359+
5360+ assert_no_diagnostics ! ( & context) ;
5361+
5362+ // Foo was initially created as a Todo (from class Foo::Bar), then promoted to Class
5363+ assert_declaration_kind_eq ! ( context, "Foo" , "Class" ) ;
5364+
5365+ assert_members_eq ! ( context, "Object" , vec![ "Foo" ] ) ;
5366+ assert_members_eq ! ( context, "Foo" , vec![ "Bar" , "foo()" ] ) ;
5367+ assert_members_eq ! ( context, "Foo::Bar" , vec![ "bar()" ] ) ;
5368+ }
5369+
5370+ #[ test]
5371+ fn todo_declaration_promoted_to_real_namespace_incrementally ( ) {
5372+ let mut context = GraphTest :: new ( ) ;
5373+ context. index_uri ( "file:///bar.rb" , {
5374+ r"
5375+ class Foo::Bar
5376+ def bar; end
5377+ end
5378+ "
5379+ } ) ;
5380+ context. resolve ( ) ;
5381+
5382+ assert_no_diagnostics ! ( & context) ;
5383+ assert_declaration_kind_eq ! ( context, "Foo" , "<TODO>" ) ;
5384+
5385+ context. index_uri ( "file:///foo.rb" , {
5386+ r"
5387+ class Foo
5388+ def foo; end
5389+ end
5390+ "
5391+ } ) ;
5392+ context. resolve ( ) ;
5393+
5394+ assert_no_diagnostics ! ( & context) ;
5395+
5396+ // Foo was promoted from Todo to Class after the second resolution
5397+ assert_declaration_kind_eq ! ( context, "Foo" , "Class" ) ;
5398+
5399+ assert_members_eq ! ( context, "Object" , vec![ "Foo" ] ) ;
5400+ assert_members_eq ! ( context, "Foo" , vec![ "Bar" , "foo()" ] ) ;
5401+ assert_members_eq ! ( context, "Foo::Bar" , vec![ "bar()" ] ) ;
5402+ }
5403+
5404+ #[ test]
5405+ fn qualified_name_inside_nesting_resolves_to_top_level ( ) {
5406+ let mut context = GraphTest :: new ( ) ;
5407+ context. index_uri ( "file:///foo.rb" , {
5408+ r"
5409+ module Foo
5410+ class Bar::Baz
5411+ def qux; end
5412+ end
5413+ end
5414+
5415+ module Bar
5416+ end
5417+ "
5418+ } ) ;
5419+ context. resolve ( ) ;
5420+
5421+ assert_no_diagnostics ! ( & context) ;
5422+ assert_declaration_kind_eq ! ( context, "Bar" , "Module" ) ;
5423+ assert_members_eq ! ( context, "Bar" , vec![ "Baz" ] ) ;
5424+ assert_declaration_exists ! ( context, "Bar::Baz" ) ;
5425+ assert_members_eq ! ( context, "Bar::Baz" , vec![ "qux()" ] ) ;
5426+ assert_declaration_does_not_exist ! ( context, "Foo::Bar" ) ;
5427+ }
5428+
5429+ #[ test]
5430+ fn qualified_name_inside_nesting_resolves_when_discovered_incrementally ( ) {
5431+ let mut context = GraphTest :: new ( ) ;
5432+ context. index_uri ( "file:///baz.rb" , {
5433+ r"
5434+ module Foo
5435+ class Bar::Baz
5436+ def qux; end
5437+ end
5438+ end
5439+ "
5440+ } ) ;
5441+ context. resolve ( ) ;
5442+
5443+ // Bar is unknown — a Todo is created at the top level, not "Foo::Bar"
5444+ assert_declaration_kind_eq ! ( context, "Bar" , "<TODO>" ) ;
5445+ assert_declaration_does_not_exist ! ( context, "Foo::Bar" ) ;
5446+
5447+ context. index_uri ( "file:///bar.rb" , {
5448+ r"
5449+ module Bar
5450+ end
5451+ "
5452+ } ) ;
5453+ context. resolve ( ) ;
5454+
5455+ // After discovering top-level Bar, the Todo should be promoted and Baz re-homed.
5456+ assert_no_diagnostics ! ( & context) ;
5457+ assert_declaration_kind_eq ! ( context, "Bar" , "Module" ) ;
5458+ assert_members_eq ! ( context, "Bar" , vec![ "Baz" ] ) ;
5459+ assert_members_eq ! ( context, "Bar::Baz" , vec![ "qux()" ] ) ;
5460+ assert_declaration_does_not_exist ! ( context, "Foo::Bar" ) ;
5461+ }
52645462}
0 commit comments