Skip to content

fix(go): variables declared in a var ( … ) block are indexed - #2682

Merged
colbymchenry merged 3 commits into
mainfrom
claude/go-var-groups
Oct 19, 2026
Merged

colbymchenry merged 3 commits into
mainfrom
claude/go-var-groups

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

Last follow-up from #2678, found while doing #2680. Variables declared in a Go var ( … ) block were not indexed at all. tree-sitter-go wraps a var group's specs in a var_spec_list node, unlike a const ( … ) group's, which sit directly in the declaration. Both extractors read only the specs directly inside a declaration, so etcd, prometheus and gin had no node for any grouped variable. What followed from that:

  • the calls and composite literals in their values were never recorded, so a struct used only there looked unused (gin's bsonBinding in var ( BSON BindingBody = bsonBinding{} … ));
  • code reading them had nothing to reference;
  • a call through one, like prometheus's fPointPool.Get() or etcd's slowApplies.Inc(), was matched by its method name alone. fix(go): a call on a package-level variable or constant links through the type it is declared with #2667 reads a package-level variable's declared type, but only for variables that have a node.

Change

  • In the Go branch of variable extraction (extractVariable in TS, extract_variable in the kernel), the specs inside a var_spec_list are read like a declaration's own. Each member becomes a variable the file contains, with its initializer walked under it, as a variable declared on its own already is.
  • Docs follow fix(go): a constant in a const ( … ) group keeps its own doc comment #2680: a member's own doc comment, or the group's when it has none.
  • Unchanged, as for ungrouped declarations: a spec naming several variables (a, b = 1, 2) still makes a node for the first name only. That limit is shared with const and ungrouped var and is not touched here.

Verification

A/B on real repos

main 6e68a11 vs this branch. #2681, merged in by the sync, changes only TypeScript-family file discovery. Repos: etcd 6782f522, prometheus 0b57789d, gin 0f09c3a9.

Kernel and wasm indexes are byte-identical on both arms (nodes, edges, unresolved refs, files, docstrings). No node is removed; every new node is a grouped variable.

etcd prometheus gin
new variable nodes (+ contains edges) 1,080 334 78
same-file value references to them 1,105 468 54
edges from their values (calls, instantiates, value/function refs) 612 214 62
calls now landing on a function-valued variable 134 0 0
calls resolved for the first time 7 0 1
calls re-resolved 0 8 0
calls no longer linked 38 19 0
function refs no longer linked 1 0 0

How each row was checked:

  • Value references (1,276 / 527 / 55, those from new nodes included): every one is same-file, and the reading function's body names the variable.

  • Calls landing on variables are calls of a function-valued variable, e.g. grpc-gateway's generated forward_KV_Range_0(…) (= runtime.ForwardResponseMessage) and etcd tests' clusterTestCases(). Main already links calls to ungrouped function-valued variables the same way (9 in etcd).

  • Resolved for the first time:

    • linuxTryLockFile(…) and openWALFilesFn(…) reach the function their variable is set to (= flockTryLockFile, = openWALFiles);
    • Meta.Name() reaches backend.Bucket.Name;
    • s.Write(…) with var ( … s mvcc.KV ) reaches mvcc.KV.Write;
    • gin's MsgPack.Bind(…) reaches Binding.Bind.
  • Re-resolved, all from wrong to right:

    • prometheus fPointPool.Get()/.Put() (×6): a zeropool.New(…) value, from chunkenc.Pool to zeropool.Pool;
    • testHistogram.Copy() (×2): from FloatHistogram.Copy to Histogram.Copy.
  • No longer linked, all wrong before (each a call through a grouped variable whose value or type comes from outside the project, which fix(go): a call on a package-level variable or constant links through the type it is declared with #2667 now reads):

    • etcd: 33 Prometheus counter/gauge .Inc() calls linked to v2store's Stats.Inc; a sync.Once's .Do linked to the client KV.Do; 3 cobra/text/template .Execute linked to migrationPlan.Execute; a regexp's .Match linked to Prefix.Match;
    • prometheus: 9 regexp .MatchString linked to FastRegexMatcher.MatchString; 9 Prometheus counter .Inc()/.Dec() linked to noopMetric; fgprof.Handler()'s .ServeHTTP linked to readHandler.ServeHTTP.
  • The function ref tt.rangeLimit in an etcd test, a struct field, had been linked to the txn package's rangeLimit function by name. A grouped rangeLimit variable in tools/benchmark now shares that name, so the name-only guess no longer picks one.

  • Edges from the new variables' values follow the resolver's existing rules. Composite literals instantiate their types, and calls reach boolPtr, getGoGC, MustNewRegexp, the prompb converters and others. A few inherit existing name guesses, which main already makes for the same calls in ordinary functions of the same files:

    • prometheus testParser.ParseExpr(…) goes to a test type's ParseExpr, as main's three uses in rules/recording_test.go do;
    • a kingpin a.Flag(…).String() goes to rwProtoMsgFlagParser.String, as 18 of main's do;
    • etcd's txn.End() on a kv.Read(…) result goes to lease's TxnDelete.End, as main's three uses in kv_test.go do.

    Open PR fix(go): type receivers from package-qualified parameters and call results; no guess for outside types #1954 is on call-result typing, the gap behind these.

  • Dead code: the report (default kinds, and with exported symbols) is identical. Underneath, 21 / 3 / 11 symbols that had no incoming edge now have one from a grouped variable's value: gin's bsonBinding, formBinding, headerBinding and the other binding structs, timeCodec; etcd's NewIntegrationRunner/NewUnitRunner, GlobalFlags; prometheus's getGoGC. So impact on them now reaches the variables and their readers. The report's own text check had kept them off it.

  • Kernel parity (kernel-parity.mjs --lang go): 0 diffs on all three repos. etcd 1065/1105, the same 40 erroring files deferred as on main; prometheus 746/746; gin 99/99.

  • Cost: indexing time unchanged within noise (etcd 10→11 s, prometheus 12→14 s, gin 2→3 s on this box).

🤖 Generated with Claude Code

colbymchenry and others added 3 commits October 19, 2026 16:05
tree-sitter-go wraps the specs of a var group in a var_spec_list node,
unlike a const group's, which sit directly in the declaration. Both
extractors read only the specs directly inside a declaration, so every
variable declared in a var ( ... ) block was missing from the graph: no
node, no call or construction recorded from its initializer, and nothing
for code that reads it, or for a call made through it, to point at.

Each member of a var group is now a variable, as one declared on its
own already was, with its own doc comment or the group's, and its
initializer walked under it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant