From 186053ab2af2ef8fc43f3c1d1150216e89d4aff0 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 9 Oct 2026 16:50:46 +0000 Subject: [PATCH] cmd/compile: record method indices before encoding a named type CL 826105 added explicit method indices to unified export data (V5) so that importers can restore a named type's source method order. But the encoder only recorded a type's method indices after encoding its underlying type, and encoding the underlying type can encode one of the type's own generic methods first: if a field's type has a method whose body calls the generic method, encoding that body encodes the generic method object, which then looks up its index before it was recorded and gets 0. Importers then sort it among the type's first methods, so objectpath again attributes analysis facts to the wrong methods (crashing staticcheck as in golang/go#81188). Record the indices before encoding anything else. This is a cherry-pick of upstream CL 847627, ported to Go 1.27: the test uses cmd/compile/internal/importer and go list -export instead of master's cmd/compile/internal/testimporter, which go1.27 lacks. Updates golang/go#81188 Updates golang/go#82105 Change-Id: Icc8cfdd5620bb6d50b1c0b296f325c7d99ee9cc5 (cherry picked from https://go-review.googlesource.com/c/go/+/847627) --- src/cmd/compile/internal/noder/writer.go | 9 +- .../internal/types2/methodorder_test.go | 86 +++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 src/cmd/compile/internal/types2/methodorder_test.go diff --git a/src/cmd/compile/internal/noder/writer.go b/src/cmd/compile/internal/noder/writer.go index 8ca91973e3d632..2f0df9c6c65cc5 100644 --- a/src/cmd/compile/internal/noder/writer.go +++ b/src/cmd/compile/internal/noder/writer.go @@ -917,6 +917,14 @@ func (w *writer) doObj(wext *writer, obj types2.Object) pkgbits.CodeObj { named := obj.Type().(*types2.Named) assert(named.TypeArgs() == nil) + // Record method indices before encoding anything else, as + // encoding the underlying type may transitively encode one of + // named's generic methods (e.g., via a function body that + // calls it), which needs its index. + for i := range named.NumMethods() { + w.p.methodIdx[named.Method(i)] = i + } + w.pos(obj) w.typeParamNames(named.TypeParams()) wext.typeExt(obj) @@ -926,7 +934,6 @@ func (w *writer) doObj(wext *writer, obj types2.Object) pkgbits.CodeObj { var methods, gmethods []*types2.Func for i := range named.NumMethods() { m := named.Method(i) - w.p.methodIdx[m] = i if isGenericMethod(m.Type()) { gmethods = append(gmethods, m) } else { diff --git a/src/cmd/compile/internal/types2/methodorder_test.go b/src/cmd/compile/internal/types2/methodorder_test.go new file mode 100644 index 00000000000000..054a09027ada2b --- /dev/null +++ b/src/cmd/compile/internal/types2/methodorder_test.go @@ -0,0 +1,86 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2_test + +import ( + "internal/testenv" + "io" + "os" + "path/filepath" + "strings" + "testing" + + gcimporter "cmd/compile/internal/importer" + . "cmd/compile/internal/types2" +) + +// TestImportedMethodOrder checks that methods of types imported from +// export data appear in source order, as required by objectpath +// (go.dev/issue/81188). +func TestImportedMethodOrder(t *testing.T) { + testenv.MustHaveGoBuild(t) + + const src = `package methodorder + +// T mixes generic and non-generic methods. +type T struct{} + +func (T) A() {} +func (T) B[X any]() {} +func (T) C() {} +func (T) D[X, Y any](X, Y) {} +func (T) E() {} + +// U's underlying type refers to V, whose method body calls a generic +// method of U, so encoding U's underlying type encodes U.G before the +// rest of U. +type U struct{ v *V } + +type V struct{} + +func (v *V) M(u *U) int { return u.G[int]() } + +func (*U) A() {} +func (*U) B() {} +func (*U) G[X any]() int { return 0 } +func (*U) Z() {} +` + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module methodorder\n\ngo 1.27\n"), 0666); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "methodorder.go"), []byte(src), 0666); err != nil { + t.Fatal(err) + } + cmd := testenv.Command(t, testenv.GoToolPath(t), "list", "-export", "-f={{.Export}}", ".") + cmd.Dir = dir + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("go list: %v\n%s", err, out) + } + export := strings.TrimSpace(string(out)) + + lookup := func(path string) (io.ReadCloser, error) { return os.Open(export) } + pkg, err := gcimporter.Import(make(map[string]*Package), "methodorder", dir, lookup) + if err != nil { + t.Fatal(err) + } + for _, test := range []struct { + typ string + want string + }{ + {"T", "A B C D E"}, + {"U", "A B G Z"}, + } { + named := pkg.Scope().Lookup(test.typ).Type().(*Named) + var names []string + for i := range named.NumMethods() { + names = append(names, named.Method(i).Name()) + } + if got := strings.Join(names, " "); got != test.want { + t.Errorf("methods of %s = %s; want %s", test.typ, got, test.want) + } + } +}