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).

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