@@ -571,7 +571,24 @@ impl<'a> RubyIndexer<'a> {
571571 definition_id
572572 }
573573
574- fn add_constant_definition ( & mut self , node : & ruby_prism:: Node , also_add_reference : bool ) -> Option < DefinitionId > {
574+ /// Returns whether a value node represents a method call that could produce a class or module.
575+ /// Only regular method calls (bare calls or dot/safe-nav calls) are considered promotable.
576+ /// Operator calls like `1 + 2` are `CallNode`s in Prism but should not be promotable.
577+ fn is_promotable_value ( value : & ruby_prism:: Node ) -> bool {
578+ value. as_call_node ( ) . is_some_and ( |call| {
579+ // Bare calls (no receiver): `some_factory_call`
580+ // Dot/safe-nav/scope calls: `Struct.new(...)`, `foo&.bar`, `Struct::new`
581+ // Excluded: operator calls like `1 + 2` which have a receiver but no call operator
582+ call. receiver ( ) . is_none ( ) || call. call_operator_loc ( ) . is_some ( )
583+ } )
584+ }
585+
586+ fn add_constant_definition (
587+ & mut self ,
588+ node : & ruby_prism:: Node ,
589+ also_add_reference : bool ,
590+ promotable : bool ,
591+ ) -> Option < DefinitionId > {
575592 let name_id = self . index_constant_reference ( node, also_add_reference) ?;
576593
577594 // Get the location for the constant name/path only (not including the value)
@@ -583,7 +600,10 @@ impl<'a> RubyIndexer<'a> {
583600 } ;
584601
585602 let offset = Offset :: from_prism_location ( & location) ;
586- let ( comments, flags) = self . find_comments_for ( offset. start ( ) ) ;
603+ let ( comments, mut flags) = self . find_comments_for ( offset. start ( ) ) ;
604+ if promotable {
605+ flags |= DefinitionFlags :: PROMOTABLE ;
606+ }
587607 let lexical_nesting_id = self . parent_lexical_scope_id ( ) ;
588608
589609 let definition = Definition :: Constant ( Box :: new ( ConstantDefinition :: new (
@@ -1289,7 +1309,7 @@ impl Visit<'_> for RubyIndexer<'_> {
12891309 if let Some ( target_name_id) = self . index_constant_alias_target ( & node. value ( ) ) {
12901310 self . add_constant_alias_definition ( & node. as_node ( ) , target_name_id, true ) ;
12911311 } else {
1292- self . add_constant_definition ( & node. as_node ( ) , true ) ;
1312+ self . add_constant_definition ( & node. as_node ( ) , true , Self :: is_promotable_value ( & node . value ( ) ) ) ;
12931313 self . visit ( & node. value ( ) ) ;
12941314 }
12951315 }
@@ -1303,7 +1323,7 @@ impl Visit<'_> for RubyIndexer<'_> {
13031323 if let Some ( target_name_id) = self . index_constant_alias_target ( & value) {
13041324 self . add_constant_alias_definition ( & node. as_node ( ) , target_name_id, false ) ;
13051325 } else {
1306- self . add_constant_definition ( & node. as_node ( ) , false ) ;
1326+ self . add_constant_definition ( & node. as_node ( ) , false , Self :: is_promotable_value ( & value ) ) ;
13071327 self . visit ( & value) ;
13081328 }
13091329 }
@@ -1322,7 +1342,7 @@ impl Visit<'_> for RubyIndexer<'_> {
13221342 if let Some ( target_name_id) = self . index_constant_alias_target ( & node. value ( ) ) {
13231343 self . add_constant_alias_definition ( & node. target ( ) . as_node ( ) , target_name_id, true ) ;
13241344 } else {
1325- self . add_constant_definition ( & node. target ( ) . as_node ( ) , true ) ;
1345+ self . add_constant_definition ( & node. target ( ) . as_node ( ) , true , Self :: is_promotable_value ( & node . value ( ) ) ) ;
13261346 self . visit ( & node. value ( ) ) ;
13271347 }
13281348 }
@@ -1336,7 +1356,7 @@ impl Visit<'_> for RubyIndexer<'_> {
13361356 if let Some ( target_name_id) = self . index_constant_alias_target ( & value) {
13371357 self . add_constant_alias_definition ( & node. target ( ) . as_node ( ) , target_name_id, false ) ;
13381358 } else {
1339- self . add_constant_definition ( & node. target ( ) . as_node ( ) , false ) ;
1359+ self . add_constant_definition ( & node. target ( ) . as_node ( ) , false , Self :: is_promotable_value ( & value ) ) ;
13401360 self . visit ( & value) ;
13411361 }
13421362 }
@@ -1353,7 +1373,10 @@ impl Visit<'_> for RubyIndexer<'_> {
13531373 for left in & node. lefts ( ) {
13541374 match left {
13551375 ruby_prism:: Node :: ConstantTargetNode { .. } | ruby_prism:: Node :: ConstantPathTargetNode { .. } => {
1356- self . add_constant_definition ( & left, false ) ;
1376+ // Individual values aren't available in multi-write, so we default to
1377+ // promotable because multi-assignment often comes from meta-programming
1378+ // (e.g., `A, B = create_classes`).
1379+ self . add_constant_definition ( & left, false , true ) ;
13571380 }
13581381 ruby_prism:: Node :: GlobalVariableTargetNode { .. } => {
13591382 self . add_definition_from_location (
@@ -6037,4 +6060,52 @@ mod tests {
60376060 // We expect two constant references for `Foo` and `<Foo>` on each singleton method call
60386061 assert_eq ! ( 6 , context. graph( ) . constant_references( ) . len( ) ) ;
60396062 }
6063+
6064+ #[ test]
6065+ fn constant_with_call_value_is_promotable ( ) {
6066+ let context = index_source ( "Foo = some_call" ) ;
6067+
6068+ assert_definition_at ! ( & context, "1:1-1:4" , Constant , |def| {
6069+ assert!(
6070+ def. flags( ) . is_promotable( ) ,
6071+ "Expected PROMOTABLE flag to be set for call value"
6072+ ) ;
6073+ } ) ;
6074+ }
6075+
6076+ #[ test]
6077+ fn constant_with_literal_value_is_not_promotable ( ) {
6078+ let context = index_source ( "FOO = 42" ) ;
6079+
6080+ assert_definition_at ! ( & context, "1:1-1:4" , Constant , |def| {
6081+ assert!(
6082+ !def. flags( ) . is_promotable( ) ,
6083+ "Expected PROMOTABLE flag to NOT be set for literal value"
6084+ ) ;
6085+ } ) ;
6086+ }
6087+
6088+ #[ test]
6089+ fn constant_with_operator_call_is_not_promotable ( ) {
6090+ let context = index_source ( "FOO = 1 + 2" ) ;
6091+
6092+ assert_definition_at ! ( & context, "1:1-1:4" , Constant , |def| {
6093+ assert!(
6094+ !def. flags( ) . is_promotable( ) ,
6095+ "Expected PROMOTABLE flag to NOT be set for operator call"
6096+ ) ;
6097+ } ) ;
6098+ }
6099+
6100+ #[ test]
6101+ fn constant_with_dot_call_is_promotable ( ) {
6102+ let context = index_source ( "Foo = Bar.new" ) ;
6103+
6104+ assert_definition_at ! ( & context, "1:1-1:4" , Constant , |def| {
6105+ assert!(
6106+ def. flags( ) . is_promotable( ) ,
6107+ "Expected PROMOTABLE flag to be set for dot call"
6108+ ) ;
6109+ } ) ;
6110+ }
60406111}
0 commit comments