@@ -622,15 +622,63 @@ func (f *file) importToSymbol(imp ir.Import) *symbol {
622622 }
623623}
624624
625+ // messageToSymbols takes an [ir.MessageValue] and returns the symbols parsed from it.
625626func (f * file ) messageToSymbols (msg ir.MessageValue ) []* symbol {
627+ return f .messageToSymbolsHelper (msg , 0 , nil )
628+ }
629+
630+ // messageToSymbolsHelper is a recursive helper for extracting the symbols from a [ir.MessageValue].
631+ func (f * file ) messageToSymbolsHelper (msg ir.MessageValue , index int , parents []* symbol ) []* symbol {
626632 var symbols []* symbol
627633 for field := range msg .Fields () {
628634 if field .ValueAST ().IsZero () {
629635 continue
630636 }
637+ // There are a couple of different ways options can be structured, e.g.
638+ //
639+ // [(option).message = {
640+ // field_a: 2
641+ // field_b: 100
642+ // }]
643+ //
644+ // Or:
645+ //
646+ // [
647+ // (option).message.field_a = 2
648+ // (option).message.field_b = 100
649+ // ]
650+ //
651+ // For both examples, we would want to create a separate symbol for each referenceable
652+ // part of each path, e.g. (option), .message, .field_a, etc.
653+ //
654+ // An option is represented as a [ir.MessageValue] that is accessed recursively, so in
655+ // both examples above, we have:
656+ //
657+ // (option) -> (option).message -> (option).message.field_a / field_a
658+ // -> (option).message.field_b / field_b
659+ //
660+ // As we walk the message recursively, we set a symbol for each message/field along the
661+ // way. Because we are accessing each message from the top-level message, we need to
662+ // make sure that we capture a symbol for each corresponding path span along the way.
663+ //
664+ // In the example, in the second definition, (option) and .message for field_b has a
665+ // separate span from (option) and .message for field_a, but when we walk the mesasge
666+ // tree, we get the span for (option) and .mesage for the first field. So we check the
667+ // symbols we've collected so far in parents and make sure we have captured a symbol for
668+ // each path component.
631669 for element := range seq .Values (field .Elements ()) {
632- span := element .Value ().KeyASTs ().At (element .ValueNodeIndex ()).Span ()
633- elem := & symbol {
670+ key := field .KeyASTs ().At (element .ValueNodeIndex ())
671+ components := slices .Collect (key .AsPath ().Components )
672+ var span report.Span
673+ // This covers the first case in the example above where the path is relative,
674+ // e.g. field_a is a relative path within { } for (option).message.
675+ if index > len (components )- 1 {
676+ span = components [len (components )- 1 ].Span ()
677+ } else {
678+ // Otherwise, we get the component for the corresponding index.
679+ span = components [index ].Span ()
680+ }
681+ sym := & symbol {
634682 // NOTE: no [ir.Symbol] for option elements
635683 file : f ,
636684 span : span ,
@@ -640,9 +688,48 @@ func (f *file) messageToSymbols(msg ir.MessageValue) []*symbol {
640688 },
641689 isOption : true ,
642690 }
643- symbols = append (symbols , elem )
691+ symbols = append (symbols , sym )
644692 if ! element .AsMessage ().IsZero () {
645- symbols = append (symbols , f .messageToSymbols (element .AsMessage ())... )
693+ // For message value elements, we use the Type AST.
694+ sym .kind = & reference {
695+ def : element .Type ().AST (),
696+ fullName : element .Type ().FullName (),
697+ }
698+ symbols = append (symbols , f .messageToSymbolsHelper (element .AsMessage (), index + 1 , symbols )... )
699+ }
700+
701+ // We check back along the path to make sure that we have a symbol for each component.
702+ //
703+ // We need to ensure that (option) for (option).message.field_b has a symbol defined
704+ // among the parent symbols.
705+ if len (components ) > 1 {
706+ parentType := element .Value ().Container ()
707+ for _ , component := range slices .Backward (components ) {
708+ if component .IsLast () {
709+ continue
710+ }
711+ found := false
712+ for _ , parent := range parents {
713+ if parent .span == component .Span () {
714+ found = true
715+ break
716+ }
717+ }
718+ if ! found {
719+ sym := & symbol {
720+ // NOTE: no [ir.Symbol] for option elements
721+ file : f ,
722+ span : component .Span (),
723+ kind : & reference {
724+ def : parentType .Type ().AST (),
725+ fullName : parentType .Type ().FullName (),
726+ },
727+ isOption : true ,
728+ }
729+ symbols = append (symbols , sym )
730+ }
731+ parentType = parentType .AsValue ().Container ()
732+ }
646733 }
647734 }
648735 }
0 commit comments