internal/typeparams: work around LookupFieldOrMethod inconsistency
This change adds to x/tools a workaround for a bug in go/types
that causes LookupFieldOrMethod and NewTypeSet to be inconsistent
wrt an ill-typed method (*T).f where T itself is a pointer.
The workaround is that, if Lookup fails, we walk the MethodSet.
Updates golang/go#60634
Fixes golang/go#60628
Change-Id: I87caa2ae077e5cdfa40b65a2f52e261384c91167
Reviewed-on: https://go-review.googlesource.com/c/tools/+/501197
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
Run-TryBot: Alan Donovan <adonovan@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
diff --git a/internal/typeparams/common.go b/internal/typeparams/common.go
index cfba818..b9e87c6 100644
--- a/internal/typeparams/common.go
+++ b/internal/typeparams/common.go
@@ -105,6 +105,26 @@
}
orig := NamedTypeOrigin(named)
gfn, _, _ := types.LookupFieldOrMethod(orig, true, fn.Pkg(), fn.Name())
+
+ // This is a fix for a gopls crash (#60628) due to a go/types bug (#60634). In:
+ // package p
+ // type T *int
+ // func (*T) f() {}
+ // LookupFieldOrMethod(T, true, p, f)=nil, but NewMethodSet(*T)={(*T).f}.
+ // Here we make them consistent by force.
+ // (The go/types bug is general, but this workaround is reached only
+ // for generic T thanks to the early return above.)
+ if gfn == nil {
+ mset := types.NewMethodSet(types.NewPointer(orig))
+ for i := 0; i < mset.Len(); i++ {
+ m := mset.At(i)
+ if m.Obj().Id() == fn.Id() {
+ gfn = m.Obj()
+ break
+ }
+ }
+ }
+
return gfn.(*types.Func)
}
diff --git a/internal/typeparams/common_test.go b/internal/typeparams/common_test.go
index 68ef6c6..d1f13fa 100644
--- a/internal/typeparams/common_test.go
+++ b/internal/typeparams/common_test.go
@@ -140,10 +140,12 @@
t.Fatal(err)
}
+ // Look up func T.m.
T := pkg.Scope().Lookup("T").Type()
obj, _, _ := types.LookupFieldOrMethod(T, true, pkg, "m")
m := obj.(*types.Func)
+ // Assert that the origin of each t.m() call is p.T.m.
ast.Inspect(f, func(n ast.Node) bool {
if call, ok := n.(*ast.CallExpr); ok {
sel := call.Fun.(*ast.SelectorExpr)
@@ -158,6 +160,54 @@
}
}
+// Issue #60628 was a crash in gopls caused by inconsistency (#60634) between
+// LookupFieldOrMethod and NewFileSet for methods with an illegal
+// *T receiver type, where T itself is a pointer.
+// This is a regression test for the workaround in OriginMethod.
+func TestOriginMethod60628(t *testing.T) {
+ const src = `package p; type T[P any] *int; func (r *T[A]) f() {}`
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p.go", src, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Expect type error: "invalid receiver type T[A] (pointer or interface type)".
+ info := types.Info{
+ Uses: make(map[*ast.Ident]types.Object),
+ }
+ var conf types.Config
+ pkg, _ := conf.Check("p", fset, []*ast.File{f}, &info) // error expected
+ if pkg == nil {
+ t.Fatal("no package")
+ }
+
+ // Look up methodset of *T.
+ T := pkg.Scope().Lookup("T").Type()
+ mset := types.NewMethodSet(types.NewPointer(T))
+ if mset.Len() == 0 {
+ t.Errorf("NewMethodSet(*T) is empty")
+ }
+ for i := 0; i < mset.Len(); i++ {
+ sel := mset.At(i)
+ m := sel.Obj().(*types.Func)
+
+ // TODO(adonovan): check the consistency property required to fix #60634.
+ if false {
+ m2, _, _ := types.LookupFieldOrMethod(T, true, m.Pkg(), m.Name())
+ if m2 != m {
+ t.Errorf("LookupFieldOrMethod(%v, indirect=true, %v) = %v, want %v",
+ T, m, m2, m)
+ }
+ }
+
+ // Check the workaround.
+ if OriginMethod(m) == nil {
+ t.Errorf("OriginMethod(%v) = nil", m)
+ }
+ }
+}
+
func TestGenericAssignableTo(t *testing.T) {
testenv.NeedsGo1Point(t, 18)