Repository navigation
fix(extract): Go struct fields are never extracted — 0 Field nodes for ~5000 field declarations (structs miss one level of descent) #1935
Description
fix(extract): Go struct fields are never extracted — find_class_body stops one level above field_declaration_list
Status: fix open — PR #1940 (descend struct_type → field_declaration_list before extract_class_fields() iterates + skip blank-identifier _ padding; stacked on #1937). Measured: 0 → 4533 Go Field nodes, each with its declared type in return_type. Ordering: must land with #1937 (cross-language refs) and #1944 (bare-ref-vs-Field, found while validating this) under it — see below.
What happens
go_field_types[] = {"field_declaration"} is registered in lang_specs.c; the extractor just never reaches those nodes. find_class_body() returns type_spec's type child:
- interface:
interface_typeholds its method specs directly → always worked; - struct:
struct_type's only named child is afield_declaration_list, one level above thefield_declarations the member loop matches → every field silently skipped.parse_partialflags nothing (the file parses clean).
Damage measured (~1150-file Go repo)
| source | graph | |
|---|---|---|
| Go struct field declarations | ~5055 (4846 named, 208 embedded) | 0 |
Field nodes in the whole graph |
— | 850, all C/C++ |
| Go interface method declarations | 342 | present |
No Config.Timeout, no Event.PID; trace_path data-flow stops at the struct; nothing exists for a declared-type resolver (#1927's guard rationale) to consult; func-typed-field call dispatch has no node to reach — probed with type Router struct{ fn func(int) error }: r.fn(4) binds Router.fn once fields exist, and without them the call is dropped or free to be hijacked by an unrelated same-named symbol.
Minimal reproduction
package fxf
type Config struct {
Name string
Timeout int
Nested *Config
}
type Reader interface {
Read(p []byte) (int, error)
Close() error
}Actual: Struct Config, Interface Reader, both interface Methods — no node for Name/Timeout/Nested. After the fix: three Field nodes with return_type string/int/*Config; interface members unchanged.
Detection recipe
SELECT CASE WHEN file_path LIKE '%.go' THEN 'go' ELSE 'other' END, count(*)
FROM nodes WHERE label='Field' GROUP BY 1;Zero go rows on a struct-bearing Go repo is this bug.
Why this must not land alone (measured, twice)
Recovering ~4.5k nodes named err/ctx/path/_ exposes the reference resolvers:
- Cross-language slice — fix(pipeline): USAGE and WRITES edges have no cross-language guard — 31% of WRITES on a Go+C repo cross the Go/C boundary #1928/fix(pipeline): guard USAGE/WRITES/READS against cross-language binds #1937: without the guard, ~5.7k WRITES / ~22k USAGE minted, many across the Go↔C boundary.
- Same-language slice — fix(pipeline): a bare Go reference can bind a struct Field — READS/WRITES/USAGE attach every local err to whichever Field is named err #1942/fix(pipeline): a bare Go reference never binds a struct Field #1944 (found field-validating this stack): even with fix(pipeline): guard USAGE/WRITES/READS against cross-language binds #1937, bare Go locals bound the new fields project-wide — USAGE onto Go fields 21308, WRITES 5191, top target a test struct's field
Twith 3013 edges. A bare identifier can never denote a Go field (selector-only access), so fix(pipeline): a bare Go reference never binds a struct Field #1944 vetoes those binds.
With both guards under it the full stack ends below main's reference totals (USAGE 29198 → 21035, WRITES 2681 → 774) while adding the nodes. Embedded fields (~208) stay unrepresented by construction (no name child) — separate follow-up; lsp_embed_dispatch unaffected. Blank _ padding fields are skipped (55 nodes, 241 collision edges otherwise).
Related: #451 (same feature, Python), #1939 (field-chain dispatch, consumes these nodes); tracked in #1932.
Activity
fix(pipeline): a bare Go reference never binds a struct Field
fix(pipeline): a bare Go reference never binds a struct Field
fix(pipeline): a bare Go reference never binds a struct Field
fix(pipeline): a bare Go reference never binds a struct Field
DeusData commentedon Aug 31, 2026
Fixed on
mainby47116b8e(descend intofield_declaration_list),cb7cb444(skip the Go blank identifier), ande07a12b6(tests), merged as61de19bbd357365fa28244a3213bc00393b9deb6(#1940).Root cause as diagnosed:
find_class_body()returnstype_spec'stypechild, andstruct_type's only named child is afield_declaration_list— one level above thefield_declarationnodes the member loop iterates.go_field_types[] = {"field_declaration"}was already registered inlang_specs.c; the extractor simply never reached them, which is why this failed silently rather than erroring.Landed after #1937 by design: the ~4600 recovered
Fieldnodes namederr/ctx/pathwould otherwise have minted name-collisionWRITES/USAGEedges through the unguarded reference resolver.Known remaining gap, recorded rather than closed over: embedded fields (~208) stay unrepresented because they have no
namechild.test(ci): make the Windows path guard fixture polyglot so CI indexes …