Skip to content

Commit 186053a

Browse files
committed
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#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#81188 Updates golang#82105 Change-Id: Icc8cfdd5620bb6d50b1c0b296f325c7d99ee9cc5 (cherry picked from https://go-review.googlesource.com/c/go/+/847627)
1 parent 509d157 commit 186053a

2 files changed

Lines changed: 94 additions & 1 deletion

File tree

‎src/cmd/compile/internal/noder/writer.go‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,14 @@ func (w *writer) doObj(wext *writer, obj types2.Object) pkgbits.CodeObj {
917917
named := obj.Type().(*types2.Named)
918918
assert(named.TypeArgs() == nil)
919919

920+
// Record method indices before encoding anything else, as
921+
// encoding the underlying type may transitively encode one of
922+
// named's generic methods (e.g., via a function body that
923+
// calls it), which needs its index.
924+
for i := range named.NumMethods() {
925+
w.p.methodIdx[named.Method(i)] = i
926+
}
927+
920928
w.pos(obj)
921929
w.typeParamNames(named.TypeParams())
922930
wext.typeExt(obj)
@@ -926,7 +934,6 @@ func (w *writer) doObj(wext *writer, obj types2.Object) pkgbits.CodeObj {
926934
var methods, gmethods []*types2.Func
927935
for i := range named.NumMethods() {
928936
m := named.Method(i)
929-
w.p.methodIdx[m] = i
930937
if isGenericMethod(m.Type()) {
931938
gmethods = append(gmethods, m)
932939
} else {
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright 2026 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package types2_test
6+
7+
import (
8+
"internal/testenv"
9+
"io"
10+
"os"
11+
"path/filepath"
12+
"strings"
13+
"testing"
14+
15+
gcimporter "cmd/compile/internal/importer"
16+
. "cmd/compile/internal/types2"
17+
)
18+
19+
// TestImportedMethodOrder checks that methods of types imported from
20+
// export data appear in source order, as required by objectpath
21+
// (go.dev/issue/81188).
22+
func TestImportedMethodOrder(t *testing.T) {
23+
testenv.MustHaveGoBuild(t)
24+
25+
const src = `package methodorder
26+
27+
// T mixes generic and non-generic methods.
28+
type T struct{}
29+
30+
func (T) A() {}
31+
func (T) B[X any]() {}
32+
func (T) C() {}
33+
func (T) D[X, Y any](X, Y) {}
34+
func (T) E() {}
35+
36+
// U's underlying type refers to V, whose method body calls a generic
37+
// method of U, so encoding U's underlying type encodes U.G before the
38+
// rest of U.
39+
type U struct{ v *V }
40+
41+
type V struct{}
42+
43+
func (v *V) M(u *U) int { return u.G[int]() }
44+
45+
func (*U) A() {}
46+
func (*U) B() {}
47+
func (*U) G[X any]() int { return 0 }
48+
func (*U) Z() {}
49+
`
50+
dir := t.TempDir()
51+
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module methodorder\n\ngo 1.27\n"), 0666); err != nil {
52+
t.Fatal(err)
53+
}
54+
if err := os.WriteFile(filepath.Join(dir, "methodorder.go"), []byte(src), 0666); err != nil {
55+
t.Fatal(err)
56+
}
57+
cmd := testenv.Command(t, testenv.GoToolPath(t), "list", "-export", "-f={{.Export}}", ".")
58+
cmd.Dir = dir
59+
out, err := cmd.CombinedOutput()
60+
if err != nil {
61+
t.Fatalf("go list: %v\n%s", err, out)
62+
}
63+
export := strings.TrimSpace(string(out))
64+
65+
lookup := func(path string) (io.ReadCloser, error) { return os.Open(export) }
66+
pkg, err := gcimporter.Import(make(map[string]*Package), "methodorder", dir, lookup)
67+
if err != nil {
68+
t.Fatal(err)
69+
}
70+
for _, test := range []struct {
71+
typ string
72+
want string
73+
}{
74+
{"T", "A B C D E"},
75+
{"U", "A B G Z"},
76+
} {
77+
named := pkg.Scope().Lookup(test.typ).Type().(*Named)
78+
var names []string
79+
for i := range named.NumMethods() {
80+
names = append(names, named.Method(i).Name())
81+
}
82+
if got := strings.Join(names, " "); got != test.want {
83+
t.Errorf("methods of %s = %s; want %s", test.typ, got, test.want)
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)