@@ -72,10 +72,11 @@ type astWalker struct {
7272 out []facts.Fact
7373 impls []implPair
7474
75- // ownerStack[len-1] points at the symbol fact currently being constructed.
76- // Calls/instantiations discovered while walking that symbol's body are
77- // appended to its Relations slice.
78- ownerStack []* facts.Fact
75+ // ownerStack[len-1] indexes into out for the symbol fact currently being
76+ // constructed. Stored as an index, not a *facts.Fact: a nested item (a
77+ // local `fn` inside a function body) appends to out too, which can
78+ // reallocate its backing array and strand a raw pointer.
79+ ownerStack []int
7980
8081 // modStack/typeStack hold the enclosing inline-`mod { }` and
8182 // impl/trait-block names, so a nested declaration's canonical name is
@@ -145,13 +146,13 @@ func (w *astWalker) qualifyMod(name string) string {
145146 return strings .Join (parts , "." )
146147}
147148
148- func (w * astWalker ) pushOwner (f * facts. Fact ) { w .ownerStack = append (w .ownerStack , f ) }
149- func (w * astWalker ) popOwner () { w .ownerStack = w .ownerStack [:len (w .ownerStack )- 1 ] }
149+ func (w * astWalker ) pushOwner (idx int ) { w .ownerStack = append (w .ownerStack , idx ) }
150+ func (w * astWalker ) popOwner () { w .ownerStack = w .ownerStack [:len (w .ownerStack )- 1 ] }
150151func (w * astWalker ) currentOwner () * facts.Fact {
151152 if len (w .ownerStack ) == 0 {
152153 return nil
153154 }
154- return w . ownerStack [len (w .ownerStack )- 1 ]
155+ return & w . out [ w . ownerStack [len (w .ownerStack )- 1 ] ]
155156}
156157
157158func (w * astWalker ) pushType (name string , methods map [string ]bool ) {
@@ -177,11 +178,16 @@ func (w *astWalker) walkSourceFile(root *sitter.Node) {
177178 w .modFnStack = w .modFnStack [:len (w .modFnStack )- 1 ]
178179}
179180
180- func (w * astWalker ) currentModFns () map [string ]bool {
181- if len (w .modFnStack ) == 0 {
182- return nil
181+ // isKnownFn checks every enclosing scope (function-local, then outward
182+ // through mod/file scope), not just the innermost, so a value-reference
183+ // inside a function body still resolves to an outer mod-level sibling.
184+ func (w * astWalker ) isKnownFn (name string ) bool {
185+ for i := len (w .modFnStack ) - 1 ; i >= 0 ; i -- {
186+ if w.modFnStack [i ][name ] {
187+ return true
188+ }
183189 }
184- return w . modFnStack [ len ( w . modFnStack ) - 1 ]
190+ return false
185191}
186192
187193// walkItemsTrackingAttrs iterates parent's children like walkChild, but first
@@ -344,8 +350,8 @@ func (w *astWalker) handleStruct(node *sitter.Node) {
344350 Relations : []facts.Relation {{Kind : facts .RelDeclares , Target : w .dir }},
345351 })
346352 ownerIdx := len (w .out ) - 1
347- w .pushOwner (& w . out [ ownerIdx ] )
348- w .scanSerdeAttributeRefs (node .ChildByFieldName ("body" ))
353+ w .pushOwner (ownerIdx )
354+ w .scanAttributeFnRefs (node .ChildByFieldName ("body" ))
349355 w .popOwner ()
350356}
351357
@@ -368,8 +374,8 @@ func (w *astWalker) handleEnum(node *sitter.Node) {
368374 Relations : []facts.Relation {{Kind : facts .RelDeclares , Target : w .dir }},
369375 })
370376 ownerIdx := len (w .out ) - 1
371- w .pushOwner (& w . out [ ownerIdx ] )
372- w .scanSerdeAttributeRefs (node .ChildByFieldName ("body" ))
377+ w .pushOwner (ownerIdx )
378+ w .scanAttributeFnRefs (node .ChildByFieldName ("body" ))
373379 w .popOwner ()
374380}
375381
@@ -460,12 +466,14 @@ func (w *astWalker) handleFunction(node *sitter.Node) {
460466
461467 w .out = append (w .out , f )
462468 ownerIdx := len (w .out ) - 1
463- w .pushOwner (& w . out [ ownerIdx ] )
469+ w .pushOwner (ownerIdx )
464470
465471 savedDecisions := w .decisions
466472 w .decisions = 0
467473 if body := node .ChildByFieldName ("body" ); body != nil {
474+ w .modFnStack = append (w .modFnStack , collectFnNames (body , w .src ))
468475 w .walkForCalls (body )
476+ w .modFnStack = w .modFnStack [:len (w .modFnStack )- 1 ]
469477 }
470478 w .out [ownerIdx ].Props ["cyclomatic" ] = 1 + w .decisions
471479 w .decisions = savedDecisions
@@ -544,8 +552,7 @@ func (w *astWalker) handleConstOrStatic(node *sitter.Node, symbolKind string) {
544552 Relations : []facts.Relation {{Kind : facts .RelDeclares , Target : w .dir }},
545553 }
546554 w .out = append (w .out , f )
547- owner := & w .out [len (w .out )- 1 ]
548- w .pushOwner (owner )
555+ w .pushOwner (len (w .out ) - 1 )
549556 if valueNode := node .ChildByFieldName ("value" ); valueNode != nil {
550557 w .walkForCalls (valueNode )
551558 }
@@ -712,25 +719,45 @@ func (w *astWalker) walkForCalls(node *sitter.Node) {
712719// `identifier` immediately followed by a parenthesized token_tree sibling.
713720func (w * astWalker ) scanTokenTreeCalls (node * sitter.Node ) {
714721 n := node .ChildCount ()
715- for i := uint (0 ); i + 1 < n ; i ++ {
722+ for i := uint (0 ); i < n ; i ++ {
716723 c := node .Child (i )
717724 if c .Kind () != "identifier" {
718725 continue
719726 }
720- next := node . Child ( i + 1 )
721- if next . Kind () != "token_tree" || next . ChildCount () == 0 || next . Child ( 0 ). Kind () != "(" {
722- continue
727+ var next * sitter. Node
728+ if i + 1 < n {
729+ next = node . Child ( i + 1 )
723730 }
724- name := nodeText (c , w .src )
725- if isCapitalized (name ) {
726- w .emitEdge (facts .RelInstantiates , name )
731+ if next != nil && next .Kind () == "token_tree" && next .ChildCount () > 0 && next .Child (0 ).Kind () == "(" {
732+ name := nodeText (c , w .src )
733+ if isCapitalized (name ) {
734+ w .emitEdge (facts .RelInstantiates , name )
735+ continue
736+ }
737+ if i > 0 && node .Child (i - 1 ).Kind () == "." {
738+ w .emitEdge (facts .RelCalls , name )
739+ continue
740+ }
741+ if target := w .resolveCall (name ); target != "" {
742+ w .emitEdge (facts .RelCalls , target )
743+ }
727744 continue
728745 }
729- if i > 0 && node .Child (i - 1 ).Kind () == "." {
730- w .emitEdge (facts .RelCalls , name )
731- continue
746+ // Not applied as a call: may still be a function passed by name as a
747+ // value nested inside a macro's own argument, e.g. Box::new(f) inside
748+ // vec![...]. Skip path segments (preceded/followed by "."/"::") and
749+ // attribute-style `key = value` pairs, already handled by scanAttribute.
750+ if i > 0 {
751+ if pk := node .Child (i - 1 ).Kind (); pk == "." || pk == "::" {
752+ continue
753+ }
732754 }
733- if target := w .resolveCall (name ); target != "" {
755+ if next != nil {
756+ if nk := next .Kind (); nk == "::" || nk == "=" {
757+ continue
758+ }
759+ }
760+ if target := w .resolveValueReference (nodeText (c , w .src )); target != "" {
734761 w .emitEdge (facts .RelCalls , target )
735762 }
736763 }
@@ -833,23 +860,28 @@ func (w *astWalker) emitEdge(kind, target string) {
833860 owner .Relations = append (owner .Relations , facts.Relation {Kind : kind , Target : target })
834861}
835862
836- // serdeAttrFnKeys: serde options whose value is a string naming a function,
837- // e.g. #[serde(default = "some_fn")] — resolved by serde's derive macro.
838- var serdeAttrFnKeys = map [string ]bool {
863+ // attrFnRefKeys: field-attribute options whose value names a function —
864+ // serde's #[serde(default = "some_fn")] (string) or clap's
865+ // #[arg(value_parser = some_fn)] (bare path) — resolved by that macro.
866+ var attrFnRefKeys = map [string ]bool {
839867 "default" : true , "skip_serializing_if" : true ,
840868 "serialize_with" : true , "deserialize_with" : true , "with" : true ,
869+ "value_parser" : true ,
841870}
842871
843- // scanSerdeAttributeRefs walks a struct/enum body for #[serde(...)]
872+ // attrFnRefMacros: attribute macro names worth scanning for attrFnRefKeys.
873+ var attrFnRefMacros = map [string ]bool {"serde" : true , "arg" : true }
874+
875+ // scanAttributeFnRefs walks a struct/enum body for #[serde(...)]/#[arg(...)]
844876// attributes referencing a function by name.
845- func (w * astWalker ) scanSerdeAttributeRefs (body * sitter.Node ) {
877+ func (w * astWalker ) scanAttributeFnRefs (body * sitter.Node ) {
846878 if body == nil {
847879 return
848880 }
849881 var walk func (n * sitter.Node )
850882 walk = func (n * sitter.Node ) {
851883 if n .Kind () == "attribute" {
852- w .scanSerdeAttribute (n )
884+ w .scanAttribute (n )
853885 return
854886 }
855887 for i := uint (0 ); i < uint (n .ChildCount ()); i ++ {
@@ -859,10 +891,10 @@ func (w *astWalker) scanSerdeAttributeRefs(body *sitter.Node) {
859891 walk (body )
860892}
861893
862- // scanSerdeAttribute scans one `serde(...)` attribute's token_tree for
863- // `key = " value"` pairs keyed by serdeAttrFnKeys .
864- func (w * astWalker ) scanSerdeAttribute (attr * sitter.Node ) {
865- if attr .NamedChildCount () == 0 || nodeText (attr .NamedChild (0 ), w .src ) != "serde" {
894+ // scanAttribute scans one attribute's token_tree for `key = value` pairs
895+ // keyed by attrFnRefKeys, where value is a string literal or a bare path .
896+ func (w * astWalker ) scanAttribute (attr * sitter.Node ) {
897+ if attr .NamedChildCount () == 0 || ! attrFnRefMacros [ nodeText (attr .NamedChild (0 ), w .src )] {
866898 return
867899 }
868900 tree := findChildByKind (attr , "token_tree" )
@@ -871,18 +903,21 @@ func (w *astWalker) scanSerdeAttribute(attr *sitter.Node) {
871903 }
872904 n := tree .ChildCount ()
873905 for i := uint (0 ); i + 2 < n ; i ++ {
874- if ! serdeAttrFnKeys [nodeText (tree .Child (i ), w .src )] || tree .Child (i + 1 ).Kind () != "=" {
906+ if ! attrFnRefKeys [nodeText (tree .Child (i ), w .src )] || tree .Child (i + 1 ).Kind () != "=" {
875907 continue
876908 }
877- lit := tree .Child (i + 2 )
878- if lit .Kind () != "string_literal" {
879- continue
909+ name := ""
910+ switch v := tree .Child (i + 2 ); v .Kind () {
911+ case "string_literal" :
912+ if content := findChildByKind (v , "string_content" ); content != nil {
913+ name = nodeText (content , w .src )
914+ }
915+ case "identifier" , "scoped_identifier" :
916+ name = nodeText (v , w .src )
880917 }
881- content := findChildByKind (lit , "string_content" )
882- if content == nil {
918+ if name == "" {
883919 continue
884920 }
885- name := nodeText (content , w .src )
886921 if idx := strings .LastIndex (name , "::" ); idx >= 0 {
887922 name = name [idx + 2 :]
888923 }
@@ -914,7 +949,7 @@ func (w *astWalker) resolveValueReference(name string) string {
914949 if methods := w .currentMethods (); methods [name ] {
915950 return w .dir + "." + w .qualify (name )
916951 }
917- if fns := w . currentModFns (); fns [ name ] {
952+ if w . isKnownFn ( name ) {
918953 return w .dir + "." + w .qualifyMod (name )
919954 }
920955 if target , ok := w .importMap [name ]; ok {
0 commit comments