@@ -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 (
@@ -1297,7 +1317,7 @@ impl Visit<'_> for RubyIndexer<'_> {
12971317 if let Some ( target_name_id) = self . index_constant_alias_target ( & node. value ( ) ) {
12981318 self . add_constant_alias_definition ( & node. as_node ( ) , target_name_id, true ) ;
12991319 } else {
1300- self . add_constant_definition ( & node. as_node ( ) , true ) ;
1320+ self . add_constant_definition ( & node. as_node ( ) , true , Self :: is_promotable_value ( & node . value ( ) ) ) ;
13011321 self . visit ( & node. value ( ) ) ;
13021322 }
13031323 }
@@ -1311,7 +1331,7 @@ impl Visit<'_> for RubyIndexer<'_> {
13111331 if let Some ( target_name_id) = self . index_constant_alias_target ( & value) {
13121332 self . add_constant_alias_definition ( & node. as_node ( ) , target_name_id, false ) ;
13131333 } else {
1314- self . add_constant_definition ( & node. as_node ( ) , false ) ;
1334+ self . add_constant_definition ( & node. as_node ( ) , false , Self :: is_promotable_value ( & value ) ) ;
13151335 self . visit ( & value) ;
13161336 }
13171337 }
@@ -1330,7 +1350,7 @@ impl Visit<'_> for RubyIndexer<'_> {
13301350 if let Some ( target_name_id) = self . index_constant_alias_target ( & node. value ( ) ) {
13311351 self . add_constant_alias_definition ( & node. target ( ) . as_node ( ) , target_name_id, true ) ;
13321352 } else {
1333- self . add_constant_definition ( & node. target ( ) . as_node ( ) , true ) ;
1353+ self . add_constant_definition ( & node. target ( ) . as_node ( ) , true , Self :: is_promotable_value ( & node . value ( ) ) ) ;
13341354 self . visit ( & node. value ( ) ) ;
13351355 }
13361356 }
@@ -1344,7 +1364,7 @@ impl Visit<'_> for RubyIndexer<'_> {
13441364 if let Some ( target_name_id) = self . index_constant_alias_target ( & value) {
13451365 self . add_constant_alias_definition ( & node. target ( ) . as_node ( ) , target_name_id, false ) ;
13461366 } else {
1347- self . add_constant_definition ( & node. target ( ) . as_node ( ) , false ) ;
1367+ self . add_constant_definition ( & node. target ( ) . as_node ( ) , false , Self :: is_promotable_value ( & value ) ) ;
13481368 self . visit ( & value) ;
13491369 }
13501370 }
@@ -1361,7 +1381,10 @@ impl Visit<'_> for RubyIndexer<'_> {
13611381 for left in & node. lefts ( ) {
13621382 match left {
13631383 ruby_prism:: Node :: ConstantTargetNode { .. } | ruby_prism:: Node :: ConstantPathTargetNode { .. } => {
1364- self . add_constant_definition ( & left, false ) ;
1384+ // Individual values aren't available in multi-write, so we default to
1385+ // promotable because multi-assignment often comes from meta-programming
1386+ // (e.g., `A, B = create_classes`).
1387+ self . add_constant_definition ( & left, false , true ) ;
13651388 }
13661389 ruby_prism:: Node :: GlobalVariableTargetNode { .. } => {
13671390 self . add_definition_from_location (
@@ -5887,4 +5910,52 @@ mod tests {
58875910 // We expect two constant references for `Foo` and `<Foo>` on each singleton method call
58885911 assert_eq ! ( 6 , context. graph( ) . constant_references( ) . len( ) ) ;
58895912 }
5913+
5914+ #[ test]
5915+ fn constant_with_call_value_is_promotable ( ) {
5916+ let context = index_source ( "Foo = some_call" ) ;
5917+
5918+ assert_definition_at ! ( & context, "1:1-1:4" , Constant , |def| {
5919+ assert!(
5920+ def. flags( ) . is_promotable( ) ,
5921+ "Expected PROMOTABLE flag to be set for call value"
5922+ ) ;
5923+ } ) ;
5924+ }
5925+
5926+ #[ test]
5927+ fn constant_with_literal_value_is_not_promotable ( ) {
5928+ let context = index_source ( "FOO = 42" ) ;
5929+
5930+ assert_definition_at ! ( & context, "1:1-1:4" , Constant , |def| {
5931+ assert!(
5932+ !def. flags( ) . is_promotable( ) ,
5933+ "Expected PROMOTABLE flag to NOT be set for literal value"
5934+ ) ;
5935+ } ) ;
5936+ }
5937+
5938+ #[ test]
5939+ fn constant_with_operator_call_is_not_promotable ( ) {
5940+ let context = index_source ( "FOO = 1 + 2" ) ;
5941+
5942+ assert_definition_at ! ( & context, "1:1-1:4" , Constant , |def| {
5943+ assert!(
5944+ !def. flags( ) . is_promotable( ) ,
5945+ "Expected PROMOTABLE flag to NOT be set for operator call"
5946+ ) ;
5947+ } ) ;
5948+ }
5949+
5950+ #[ test]
5951+ fn constant_with_dot_call_is_promotable ( ) {
5952+ let context = index_source ( "Foo = Bar.new" ) ;
5953+
5954+ assert_definition_at ! ( & context, "1:1-1:4" , Constant , |def| {
5955+ assert!(
5956+ def. flags( ) . is_promotable( ) ,
5957+ "Expected PROMOTABLE flag to be set for dot call"
5958+ ) ;
5959+ } ) ;
5960+ }
58905961}
0 commit comments