Skip to content

Go: build-tag twin files collide on one QN — the stub can shadow the real implementation (Rust cfg got #495) #1911

Description

@ilyabrykau-orca

Status: draft implementation-proposal open — PR #1946 (marked ready on maintainer design ack). Minimal scope as proposed here: per-file τ folded into func/method QNs, #495 #-suffix style; τ-aware resolution is the follow-up. Sole-constrained-variant callers keep their edges; twin-pair callers from unconstrained files resolve as ambiguous (honest) instead of binding the lexicographically-smallest stub. Measured: 2177 τ-suffixed func/method nodes across 372 constrained files; swallowed-twin files 319 → 281; CALLS total +62 (recovered targets outweigh fail-closed ambiguity). Receiver qualification (#1913) does not separate twins — generated-twin evidence below.

Version

codebase-memory-mcp 0.10.8 (also reproduced on a source build @ d706c33)

Platform

macOS (Apple Silicon)

Install channel

Built from source

Binary variant

standard

What happened, and what did you expect?

Go build-constrained twin files (//go:build unix / //go:build !unix, GOOS/GOARCH filename suffixes) legally define the same symbols in one package. The indexer ignores the constraints; the upsert keeps one node per QN by the deterministic smallest-file-path rule. Measured:

  • hostmirror_other.go (2-line stub) beat hostmirror_unix.go (63-line real implementation) because o < u: get_code_snippet returns the stub and all 25 inbound CALLS landed on the stub;
  • same for a nofollow_unix.go/nofollow_other.go constant pair;
  • one generated GOARCH twin pair loses 39 of 40 nodes — 100% of the losing file;
  • the dropped twin leaves no trace: both files parse clean, so index_status/check_index_coverage report nothing.

Expected: both build variants exist as nodes — exactly what #495 already does for Rust #[cfg(...)] twins.

Reproduction

// mirror_unix.go
//go:build unix

package mirror

func MirrorConfig(path string) (string, error) {
	dst := path + ".bak"
	// ... real work ...
	return dst, nil
}
// mirror_other.go
//go:build !unix

package mirror

func MirrorConfig(path string) (string, error) { return "", nil }

Actual: one node, file_path = mirror_other.go (stub wins); the real implementation has no node. Expected: two nodes, one per constraint.

Proposed design (Go analog of #495)

Per-file τ, resolved once: the compacted //go:build expression when present, else the GOOS/GOARCH filename suffix. Fold τ into that file's def QNs (proj.pkg.MirrorConfig#unix), mirrored on the call-scope side. Same-config cross-file calls stay exact by trying module.name#τ (caller's τ) before the plain key; calls from unconstrained files degrade to the multi-candidate path — the honest answer, and strictly better than today's silent bind-everyone-to-the-stub.

Generated-twin evidence (bpf2go, measured with #1913/#1915 applied)

bpf2go emits one Go file per (program, target) pair, gated by //go:build GOARCH/endianness lines. Two files in the measured repo are GOARCH twins with an identical set of 21 top-level declarations: the arm64 twin kept 43 nodes, the x86 twin kept 1 (its __file__ node) — 39 symbol nodes lost, 100% of the losing file, invisible to parse_partial. Directory-level: 66 Close methods in source, 63 in the graph — the missing 3 are exactly the x86 twin's, and receiver-qualified QNs cannot separate them because the twins are twins all the way down; only a build-constraint discriminator in the QN can. Scale note: adding a second build target — a routine change — multiplies the loss by the number of programs, which argues for a cheap, automatic discriminator rather than an opt-in.

Related: #495, #1909, #1910 (same silent-upsert-collapse family); tracked in #1932.

Confirmations

  • I searched existing issues and this is not a duplicate.
  • My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.

Activity

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 edges

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions