Skip to content

fix(go): resolve methods promoted through embedded types - #814

Open
kunalgoesbyken wants to merge 1 commit into
trailhq:mainfrom
kunalgoesbyken:fix/go-embedded-struct-method-promotion
Open

kunalgoesbyken wants to merge 1 commit into
trailhq:mainfrom
kunalgoesbyken:fix/go-embedded-struct-method-promotion

Conversation

@kunalgoesbyken

Copy link
Copy Markdown
Contributor

Summary

type Server struct{ Base } promotes Base's methods, but the walk never recorded the embedding, so s.Hello() found no Server.Hello and had no parent chain to climb. Calls to a promoted method got no edge.

Changes

  • extract.ts: a Go struct or interface emits an extends edge for each unqualified embedded type: Base, *Base, Base[T], and an interface embedded in an interface. A package-qualified embed (sync.Mutex) names another package and is skipped, as are constraint unions and ~int elements. A named field (base Base) is not an embed.
  • resolve.ts: for Go, extends may target a struct, and the edge only binds to a type in the embedder's own directory. The existing typed-member walk then finds the promoted method, with the embedder's own method winning over the embedded one.
  • resolve.ts, resolveTypedMember: for Go, a method found on two embedded types at the same depth is an ambiguous selector (the compiler rejects the call), so it resolves to neither.
$ graft callers Base.Hello      # before
Hello · method · server.go:L5-L5
  no indexed callers

$ graft callers Base.Hello      # after
Hello · method · server.go:L5-L5
  calls ← run (server.go:L14-L17)
      16: s.Hello()
  calls ← Start (server.go:L12-L12)

When two packages both define Base.Hello, the owner index is keyed by type name only, so the call drops rather than land on the other package's method. That limit is unchanged.

Testing

On Windows 11 with Node 26:

  • test/graph-go-embedded.test.ts (new): struct, pointer and interface embedding, the outer type's own method winning, a same-named Base in another package not being linked, the same method promoted from two embedded types resolving to neither, a shallower promotion winning over a deeper one, and a named field and sync.Mutex producing no extends edge. Both tests fail on main.
  • test/graph*, blast*, ask*, generic*, depth*, resolve* and covers* suites: 674 tests, 1 fail. The one failure is discoverWorkspaceChildren follows a symlinked child repo, a symlink permission error on Windows that fails the same way on main.
  • tsc --noEmit passes.

On seedfall, 7 calls and 20 extends edges are added and none removed. For example cmd/seedfall/explore.go#cmdExplore -> cmd/seedfall/configflags.go#configArgs.register, through exploreArgs embedding configArgs.

Fixes #813

…face

`type Server struct{ Base }` promotes Base's methods to Server, but the
walk never recorded the embedding, so `s.Hello()` found no `Server.Hello`
and had no parent chain to climb. Calls to a promoted method, from outside
or through the receiver, got no edge.

Emit an `extends` edge for each same-package embedded type (`Base`, `*Base`,
`Base[T]`, and an interface embedded in an interface) so the existing
typed-member resolution walks up to it. A package-qualified embed and a
constraint union are not recorded, and the edge only binds to a type in the
embedder's own directory.

The same method promoted from two embedded types at one depth is an
ambiguous selector in Go, so that call resolves to neither.
@trailhq-graft

trailhq-graft Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

🌱 graft blast radius

1 area changed → 3 areas can be affected. 5 dependent symbols, depth 2.
Tests: Graph Resolution has tests the diff did not touch.
Tag: @anirudhkumar-nanonets — 4 of 4 areas · @shhdwi — Graph Resolution, Graph Building

flowchart TB
  A0(("Graph Building<br/>3 symbols"))
  A1(("Engine<br/>1 symbol"))
  A2(("Pull Request Review<br/>1 symbol"))
  classDef reached fill:#D9EDF3,stroke:#3AA7C9,stroke-width:1.5px,color:#0E313C;
  class A0,A1,A2 reached;
Loading
Can be affected Symbols Nearest hop Reached from
Graph Building 3 src/graph/build.ts:L217-L492 buildGraph — calls, depth 1 Graph Resolution
Engine 1 src/engine.ts:L104-L115 graph — calls, depth 2 Graph Resolution
Pull Request Review 1 src/app/review.ts:L45-L99 reviewPullRequest — calls, depth 2 Graph Resolution
Who knows this code — 2 people across 4 areas
Area Who knows it
Graph Resolution · changed @shhdwi — 14 commits, last 13d ago · @anirudhkumar-nanonets — 10 commits, last 2d ago
Graph Building · affected @anirudhkumar-nanonets — 21 commits, last 7d ago · @shhdwi — 7 commits, last 13d ago
Engine · affected @anirudhkumar-nanonets — 17 commits, last 27d ago
Pull Request Review · affected @anirudhkumar-nanonets — 3 commits, last 2mo ago

Ownership is git history over each area's own files, weighted towards recent work (120-day half-life). Merge commits and bots are dropped, and you are dropped from your own PR. A name with no @ has no GitHub handle in its commit email — tag them by hand, or add a .mailmap entry. A suggestion from history, not a CODEOWNERS rule.

All 5 dependent symbols, grouped by area

Graph Building — 3 symbols in 3 files

  • src/graph/build.ts:L217-L492 — buildGraph (calls, depth 1)
    368: const edges = resolveEdges(nodes, rawEdges, { goModules: readGoModules(root, repoFiles) });
  • src/graph/refresh-run.ts:L18-L21 — main (calls, depth 2)
  • src/graph/refresh.ts:L202-L225 — rebuild (calls, depth 2)

Engine — 1 symbol in 1 file

  • src/engine.ts:L104-L115 — graph (calls, depth 2)

Pull Request Review — 1 symbol in 1 file

  • src/app/review.ts:L45-L99 — reviewPullRequest (calls, depth 2)
Test signal per changed area — 1 ⚠

Reached = a node under a test path has a resolved edge into the changed symbol. It undercounts anything called indirectly — through a CLI, a spawned process or a dynamic import — so read a low ratio as “look here”, never as a coverage gate.

  • ⚠ Graph Resolution — 1 of 5 reached · 4 test files reach it, none changed here
    • not reached: walk, nameOf, heritageEdges, resolveTypedMember
51 test suites also reference this code

54 symbols, kept out of the diagram and the table so they cannot crowd out the areas a reviewer has to look at.

  • test/ask-index.test.ts
  • test/ask.test.ts
  • test/claude-hooks.test.ts
  • test/claude-prompt-private.test.ts
  • test/container-extract.test.ts
  • test/context-only-dir.test.ts
  • test/context.test.ts
  • test/covers.test.ts
  • test/depth-parity.ts
  • test/generic-extract.test.ts
  • test/graph-bash.test.ts
  • test/graph-cross-language.test.ts
  • test/graph-elixir.test.ts
  • test/graph-extensions.test.ts
  • test/graph-go.test.ts
  • test/graph-incremental.test.ts
  • test/graph-invariants.test.ts
  • test/graph-java.test.ts
  • test/graph-jsx.test.ts
  • test/graph-kotlin.test.ts
  • …31 more

graft blast · origin/main...HEAD · depth 2 · 3 changed files

Open the interactive graph → — click an area to see its dependent symbols at file:line.

github-actions Bot added a commit that referenced this pull request Nov 10, 2026
@kunalgoesbyken

Copy link
Copy Markdown
Contributor Author

Same as #806: only test (windows-latest) is red and the sibling PRs passed it. Could someone with access re-run that job? Thanks.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Go: methods promoted through an embedded struct or interface produce no call edge

1 participant