Skip to content

fix(extract): Go struct fields are never extracted — 0 Field nodes for ~5000 field declarations (structs miss one level of descent) #1935

Closed

Description

@ilyabrykau-orca

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_type holds its method specs directly → always worked;
  • struct: struct_type's only named child is a field_declaration_list, one level above the field_declarations the member loop matches → every field silently skipped. parse_partial flags 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:

  1. 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.
  2. 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 T with 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

  1. DeusData commented on Aug 31, 2026

    @DeusData
    Owner

    Fixed on main by 47116b8e (descend into field_declaration_list), cb7cb444 (skip the Go blank identifier), and e07a12b6 (tests), merged as 61de19bbd357365fa28244a3213bc00393b9deb6 (#1940).

    Root cause as diagnosed: find_class_body() returns type_spec's type child, and struct_type's only named child is a field_declaration_list — one level above the field_declaration nodes the member loop iterates. go_field_types[] = {"field_declaration"} was already registered in lang_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 Field nodes named err/ctx/path would otherwise have minted name-collision WRITES/USAGE edges through the unguarded reference resolver.

    Known remaining gap, recorded rather than closed over: embedded fields (~208) stay unrepresented because they have no name child.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    parsing/qualityGraph extraction bugs, false positives, missing edgesstability/performanceServer crashes, OOM, hangs, high CPU/memory

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions