Skip to content

Go: method QNs ignore the receiver — every same-name method in a package collapses onto one node #1909

Closed

Description

@ilyabrykau-orca

Status: fix open — PR #1913 (receiver-qualified Go method QNs, mirroring the C++ out-of-line block; stacked on #1907). Post-fix: Close methods 15 → 86 nodes, Process 12 → 47, Task 1 → 8; one generated dir recovered 1 → 63 Close methods (source 66; the residual 3 are #1911's twins). Generic receivers verified on real code: 45 methods across 8 generic receivers carry clean receiver-qualified QNs (pkg.Cache.Add for func (c *Cache[K, V]) Add) and zero node QNs contain a [. Changes extracted QNs for Go methods → reindex needed (flagged per CONTRIBUTING).

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 concrete methods get a flat, package-scoped QN (proj.pkg.method — receiver ignored), so every same-name method in a package collides on one QN; cbm_gbuf_upsert_node keeps exactly one node and the losers vanish silently (parse coverage stays clean, so check_index_coverage cannot see it).

Measured (private Go repo): 20 Process/Name declarations across 15 files kept 2 nodes; 9 distinct Task() methods fused into one chimera node carrying all 9 bodies' CALLS edges (26 with line numbers outside the surviving body); 19 structs point DEFINES_METHOD at the same single Process; a _test.go mock Close won the smallest-file-path tie-break over the production Close, so callers of the "production" symbol got filtered as test noise.

Interface members already get proj.pkg.Iface.method; C++ out-of-line methods already get proj.file.Class.method; the extractor already computes the receiver type for parent_class — it just doesn't put it in the QN.

Expected: func (s *Storage) Close() → proj.pkg.Storage.Close, one node per receiver.

Reproduction

// x.go
package m

type Storage struct{}
type Cache struct{}

func (s *Storage) Close() {}
func (c Cache) Close() {}

Actual: one Method node m.Close, both types' DEFINES_METHOD pointing at it. Expected: m.Storage.Close + m.Cache.Close.

Secondary effect this also fixes

resolve_same_module (conf 0.90) is an exact module.name lookup. Flat method QNs occupy that key, so pipeline.Process(event) binds whatever unrelated Process merged into the caller's package — a high-confidence false edge no filter can remove. Receiver-qualified QNs delete the flat key; those calls fall through to the typed/guarded paths.

Fix notes (all consumers already agree)

pxc_build_lsp_def passes def QNs verbatim (LSP registries follow automatically); check_go_class_implements already supports class-qualified method QNs; the call-scope side (compute_func_qn) mirrors the def formula via one shared receiver-type helper.

Related: #1906 (the merge made weak edges land on high-traffic symbols), #1114; 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

  1. added
    bugSomething isn't working
    priority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.
    on Sep 5, 2026
  2. added a commit that references this issue on Sep 15, 2026
    da76194
  3. mauro33 commented on Sep 29, 2026

    @mauro33

    Still reproduces on v0.11.0 (macOS arm64), with a minimal repo:

    package repro
    
    type Postmortem struct{}
    type PostmortemEntry struct{}
    
    func (p *Postmortem) Validate() error     { return nil }
    func (e PostmortemEntry) Validate() error { return nil }
    
    func useA(p *Postmortem)     { _ = p.Validate() }
    func useB(e PostmortemEntry) { _ = e.Validate() }

    index_repository(mode=full) → 10 nodes / 18 edges, then:

    • get_file_outline("repro.go") → one Validate (Method, lines 7-7, qn cbm-repro.Validate); the declaration on line 6 has no node of its own.
    • search_graph(name_pattern="Validate") → 1 row, in=2 out=2.
    • trace_path("Validate", inbound) → callers useA and useB, both attributed to the single merged node — so the callers are not dropped, they are merged, and there is no way to ask for the callers of (*Postmortem).Validate alone.

    At scale (a ~37k-node Go repo, measured on an earlier build): 2263 Method nodes against 2556 func ( declarations, i.e. ~293 methods with no node of their own. Expected: receiver-qualified QNs as gopls / go doc name them (pkg.Postmortem.Validate, pkg.PostmortemEntry.Validate). #2036 already notes that receiver-qualified QNs change the import_map_suffix edge counts, so both probably need to land together.

  4. DeusData commented on Oct 20, 2026

    @DeusData
    Owner

    Fixed on main by 5a4af9c (#1913). Thank you, @ilyabrykau-orca, for the report and the fix. Go methods are now receiver-qualified, so same-name methods in a package no longer collapse onto one node. Existing indexes are rebuilt once after upgrading (index format 3 -> 4).

  5. added a commit that references this issue on Oct 31, 2026
    dbba757
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

    bugSomething isn't workingparsing/qualityGraph extraction bugs, false positives, missing edgespriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions