gopls/internal/golang/completion: fix the isEmptyInterface predicate
The isEmptyInterface predicate had a TODO noting that it should probably
be considering the underlying type. After looking at usage, I agree that
this was probably simple any oversight, but didn't matter because most
uses of the empty interface were uses of any or interface{}, both of
which were an interface.
But with CL 580355, as the 'any' type is becoming a types.Alias, and
this logic is caused inference scoring to change and gopls tests to
fail. Fix isEmptyInterface to check underlying, and add a bit of
commentary for the future.
For golang/go#66921
Change-Id: I7ba3db1b04d83dda0372d9c39b943965f4d8c955
Reviewed-on: https://go-review.googlesource.com/c/tools/+/582335
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/gopls/internal/golang/completion/completion.go b/gopls/internal/golang/completion/completion.go
index 36d382d..f02f7e2 100644
--- a/gopls/internal/golang/completion/completion.go
+++ b/gopls/internal/golang/completion/completion.go
@@ -2936,6 +2936,13 @@
for _, expType := range expTypes {
if isEmptyInterface(expType) {
+ // If any type matches the expected type, fall back to other
+ // considerations below.
+ //
+ // TODO(rfindley): can this be expressed via scoring, rather than a boolean?
+ // Why is it the case that we break ties for the empty interface, but
+ // not for other expected types that may be satisfied by a lot of
+ // types, such as fmt.Stringer?
continue
}
diff --git a/gopls/internal/golang/completion/util.go b/gopls/internal/golang/completion/util.go
index 1261d41..ad4ee5e 100644
--- a/gopls/internal/golang/completion/util.go
+++ b/gopls/internal/golang/completion/util.go
@@ -142,10 +142,17 @@
// TODO(adonovan): shouldn't this use CoreType(T)?
func isPointer(T types.Type) bool { return is[*types.Pointer](aliases.Unalias(T)) }
+// isEmptyInterface whether T is a (possibly Named or Alias) empty interface
+// type, such that every type is assignable to T.
+//
+// isEmptyInterface returns false for type parameters, since they have
+// different assignability rules.
func isEmptyInterface(T types.Type) bool {
- // TODO(adonovan): shouldn't this use Underlying?
- intf, _ := T.(*types.Interface)
- return intf != nil && intf.NumMethods() == 0 && intf.IsMethodSet()
+ if _, ok := T.(*types.TypeParam); ok {
+ return false
+ }
+ intf, _ := T.Underlying().(*types.Interface)
+ return intf != nil && intf.Empty()
}
func isUntyped(T types.Type) bool {