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) + } + } +}