Repository navigation
fix(archtest): resolve the recursion pin's calls with go/types - #826
Merged
Merged
Conversation
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
This was referenced Oct 12, 2026
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.
7 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
internal/archtest's recursion pin (TestLoweringRecursion_IsOnlyTheKnownCycles) built the lowering call graph fromgo/parseroutput alone, socalleesOf,declOfandtypeNameOfreconstructed 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 edgeprobeFromMapping -> sniffProbe.fillFromis absent from the graph onmain, and a new cycle written in that shape would never be pinned.This loads the four lowering packages with
golang.org/x/tools/go/packagesand takes every edge fromtypes.Info.Uses, which is what #797 proposed. The hand-written resolver and itsTestCalleesOf_*/TestDeclOf_*/TestTypeNameOf_*tests are removed (git diff --stat origin/main -- internal/archtest/recursion_test.goshows how much shorterrecursion_test.gogets). 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
mainand on this branch:fillFromedge 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 onloweringCallGraphkeeps that argument.loweringRecursionssets are reproduced unchanged.go/callgraph/staticandchawere tried too and rejected.staticloses function-value edges (the schema set shrinks from 39 members to 10).chaover-approximates through interfaces and invents a new set.Usesmatches the semantics the pin already had.Cost
internal/archtestpackage goes from about 0.49 s to 0.71 s.go build ./...first): 0.46 s to 0.89 s, about 1 s more wall time including buildingx/toolsinto the test binary.golang.org/x/toolsv0.52.0 becomes a direct requirement. Onlyinternal/archtest's tests import it, andgo list -deps ./...shows no production package depending on it, so norulesentry changes (rulesaudits production files only, andinternal/archtesthas none).x/toolsrequiresx/syncv0.24.0. The bump from v0.23.0 differs only inerrgroup_test.go, so production compiles the sameerrgroupsource.x/modv0.42.0 is new.x/netandx/sysmove to v0.61.0 and v0.49.0 becausex/toolsrequires them;go list -deps -test ./...shows no package in this module compiles either.x/toolsreads 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 throughp.Errors. The Makefile comment aboveGO_VERSIONnow listsx/toolsamong the things to bump together.Closes #797
Test plan
go test ./internal/archtestandgo test -race ./internal/archtestpass, andTestDocComments_StayUnderTheCapis green.go build ./...,go vet ./...andgo mod tidy -diffare clean,gofmt -lis empty, and golangci-lint v2.14.0 (the Makefile pin) reports0 issuesover./...on a fresh cache.types.UnaliasinnodeNamereddens the aliased-receiver case..Origin()incalleesInreddens the generic-method case.TestLoweringRecursion_IsOnlyTheKnownCycles.-runon that test), and a no-op rewrite ofcalleesInsurvives it.