Skip to content

fix(archtest): resolve the recursion pin's calls with go/types - #826

Merged
OmarAlJarrah merged 4 commits into
mainfrom
fix/archtest-typed-call-graph
Oct 12, 2026
Merged

OmarAlJarrah merged 4 commits into
mainfrom
fix/archtest-typed-call-graph

Conversation

@OmarAlJarrah

@OmarAlJarrah OmarAlJarrah commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Summary

internal/archtest's recursion pin (TestLoweringRecursion_IsOnlyTheKnownCycles) built the lowering call graph from go/parser output alone, so calleesOf, declOf and typeNameOf reconstructed by hand what each name refers to. That resolver had been extended twice for missed edges (#210, #323) and still missed one class: a method called on a value bound by a multi-result assignment (probe, merges, err := …; probe.fillFrom(…)). The edge probeFromMapping -> sniffProbe.fillFrom is absent from the graph on main, and a new cycle written in that shape would never be pinned.

This loads the four lowering packages with golang.org/x/tools/go/packages and takes every edge from types.Info.Uses, which is what #797 proposed. The hand-written resolver and its TestCalleesOf_* / TestDeclOf_* / TestTypeNameOf_* tests are removed (git diff --stat origin/main -- internal/archtest/recursion_test.go shows how much shorter recursion_test.go gets). One table of planted sources, TestLoweringCallGraph_FollowsWhatTheTypeCheckerResolves, covers the shapes a parser-only resolver gets wrong or can only guess at: multi-result binds, function values and method values, shadowing locals, closure bodies, methods on instantiated generic types, receivers spelled through an alias, promoted methods, range-variable and type-switch bindings, and interface calls (which still record nothing). It also holds what must stay out of the graph: a field, or a composite-literal key, that shares a node's name.

Result on the current tree

Both graphs were dumped edge by edge, on main and on this branch:

  • The node set is identical, and no edge was removed.
  • 39 edges were added: the fillFrom edge above, plus 38 calls between the packages read (run -> LowerService, fillParamType -> CarriedRef, …). The old graph skipped cross-package calls on purpose, since none can close a cycle: Go refuses the import that would let the callee's package call back. They come along with type resolution but are not a fix, and the doc on loweringCallGraph keeps that argument.
  • All seven loweringRecursions sets are reproduced unchanged.

go/callgraph/static and cha were tried too and rejected. static loses function-value edges (the schema set shrinks from 39 members to 10). cha over-approximates through interfaces and invents a new set. Uses matches the semantics the pin already had.

Cost

  • Warm cache: the whole internal/archtest package goes from about 0.49 s to 0.71 s.
  • Cold cache, in gate order (go build ./... first): 0.46 s to 0.89 s, about 1 s more wall time including building x/tools into the test binary.
  • Dependency: golang.org/x/tools v0.52.0 becomes a direct requirement. Only internal/archtest's tests import it, and go list -deps ./... shows no production package depending on it, so no rules entry changes (rules audits production files only, and internal/archtest has none). x/tools requires x/sync v0.24.0. The bump from v0.23.0 differs only in errgroup_test.go, so production compiles the same errgroup source. x/mod v0.42.0 is new. x/net and x/sys move to v0.61.0 and v0.49.0 because x/tools requires them; go list -deps -test ./... shows no package in this module compiles either.
  • Toolchain coupling: x/tools reads the toolchain's export data, and v0.49.0 cannot read Go 1.27.2's ("export data version 5 is greater than maximum supported version 4"). A future toolchain bump that outruns it fails this test loudly through p.Errors. The Makefile comment above GO_VERSION now lists x/tools among the things to bump together.

Closes #797

Test plan

  • go test ./internal/archtest and go test -race ./internal/archtest pass, and TestDocComments_StayUnderTheCap is green.
  • go build ./..., go vet ./... and go mod tidy -diff are clean, gofmt -l is empty, and golangci-lint v2.14.0 (the Makefile pin) reports 0 issues over ./... on a fresh cache.
  • Mutations, each built before running, with a no-op control that survived:
    • Dropping either types.Unalias in nodeName reddens the aliased-receiver case.
    • Dropping .Origin() in calleesIn reddens the generic-method case.
    • Keying methods by bare name reddens every graph test.
    • Skipping exported callees reddens TestLoweringRecursion_IsOnlyTheKnownCycles.
    • Skipping closure bodies, counting a field or any variable as an edge, recording calls only, dropping key-value pairs, refusing promoted methods, and refusing range-variable and type-switch bindings each redden the planted table on its own (run with -run on that test), and a no-op rewrite of calleesIn survives it.
  • Edge-by-edge diff of the old and new graphs on the same tree, with the numbers given above.
  • Full gate (coverage, fuzz, bench smoke) is left to CI.

The recursion pin built its call graph from syntax alone, so calleesOf,
declOf and typeNameOf reconstructed by hand what each name refers to.
That resolver had already been extended twice for missed edges (#210,
#323) and still missed a method called on a value bound by a
multi-result assignment: probeFromMapping -> sniffProbe.fillFrom is
absent on main, and a new cycle written that way would never be pinned.

Load the lowering packages with golang.org/x/tools/go/packages and take
each edge from types.Info.Uses instead. The hand-written resolver and
its tests go; planted sources now cover the shapes it got wrong
(multi-result binds, function values, shadowing locals, instantiated
generic methods, aliased receivers, interface calls).

On the current tree the new graph has the same nodes and every edge the
old one had. It adds the fillFrom edge and 38 calls between packages,
which the old graph skipped on purpose; none can close a cycle because
Go refuses the import that would call back. All seven pinned sets are
reproduced unchanged.

x/tools reads the toolchain's export data, so it joins the list of
things the Makefile says to bump with GO_VERSION. It is imported only
by internal/archtest's tests; no production package depends on it.

Closes #797
The resolver tests removed with the hand-written resolver held two
things the planted table did not: which identifiers must stay out of
the graph (a field or a composite-literal key that shares a node's
name) and which selectors the graph must resolve (a method value, a
promoted method, a range variable, a type switch's binding).

After the rewrite only the real tree held several of them, and by a
single assertion. Skipping closure bodies, or counting a field as an
edge, reddened TestLoweringRecursion_IsOnlyTheKnownCycles and nothing
else, so a tree that stopped containing such a field would have let
the regression through silently.

Add a planted row for each, including the key-and-value spelled the
same case that keeps a skip from dropping the whole pair. Each of
seven mutations of calleesIn (skip closure bodies, count a field, count
any variable, record calls only, drop key-value pairs, refuse promoted
methods, refuse range and type-switch bindings) now reddens the planted
table on its own, and a no-op rewrite of calleesIn survives it.
The table was named for what a parser-only resolver could not do, but
several of its rows are controls (a function value, a call through an
interface) rather than syntax failures. Name it for the rule every row
shares: edges follow what the type checker resolves a name to.

Also drop a sentence in TestLoweringCallGraph_ResolvesAMethodOnAValue
that explained a failure mode of the removed resolver, a lookup by
trailing name in a flat namespace, which the graph can no longer have.
The multi-result bind is now listed among the shapes that test covers.
@OmarAlJarrah
OmarAlJarrah merged commit 482d434 into main Oct 12, 2026
1 check passed
@OmarAlJarrah
OmarAlJarrah deleted the fix/archtest-typed-call-graph branch October 12, 2026 16:22
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.

archtest: resolve the recursion pin's callees with type information

1 participant