gopls: rationalize "deref" helpers
This change (part of the types.Alias audit) defines two
helper functions for dealing with pointer types:
1. internal/typeparams.MustDeref (formerly go/ssa.mustDeref)
is the type equivalent of a LOAD instruction.
2. internal/typesinternal.Unpointer strips off a
Pointer constructor (possibly wrapped in an Alias).
The golang.Deref function, which recursively strips off
Pointer and Named constructors, is a meaningless
operation. It has been replaced in all cases by
Unpointer + Unalias.
There are far too many functions called 'deref' with
subtle variations in their behavior. I plan to
standardize x/tools on few common idioms.
(There is more to do.)
Updates golang/go#65294
Change-Id: I502bab95e8d954715784b7e35ec801f4be4bc959
Reviewed-on: https://go-review.googlesource.com/c/tools/+/565476
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Tim King <taking@google.com>
diff --git a/go/ssa/builder.go b/go/ssa/builder.go
index 8622dfc..ecbc3e4 100644
--- a/go/ssa/builder.go
+++ b/go/ssa/builder.go
@@ -328,7 +328,7 @@
}
case "new":
- return emitNew(fn, mustDeref(typ), pos, "new")
+ return emitNew(fn, typeparams.MustDeref(typ), pos, "new")
case "len", "cap":
// Special case: len or cap of an array or *array is
@@ -419,7 +419,7 @@
wantAddr := true
v := b.receiver(fn, e.X, wantAddr, escaping, sel)
index := sel.index[len(sel.index)-1]
- fld := fieldOf(mustDeref(v.Type()), index) // v is an addr.
+ fld := fieldOf(typeparams.MustDeref(v.Type()), index) // v is an addr.
// Due to the two phases of resolving AssignStmt, a panic from x.f = p()
// when x is nil is required to come after the side-effects of
@@ -468,7 +468,7 @@
v.setType(et)
return fn.emit(v)
}
- return &lazyAddress{addr: emit, t: mustDeref(et), pos: e.Lbrack, expr: e}
+ return &lazyAddress{addr: emit, t: typeparams.MustDeref(et), pos: e.Lbrack, expr: e}
case *ast.StarExpr:
return &address{addr: b.expr(fn, e.X), pos: e.Star, expr: e}
diff --git a/go/ssa/create.go b/go/ssa/create.go
index c4da35d..4545c17 100644
--- a/go/ssa/create.go
+++ b/go/ssa/create.go
@@ -259,6 +259,7 @@
obj := scope.Lookup(name)
memberFromObject(p, obj, nil, "")
if obj, ok := obj.(*types.TypeName); ok {
+ // No Unalias: aliases should not duplicate methods.
if named, ok := obj.Type().(*types.Named); ok {
for i, n := 0, named.NumMethods(); i < n; i++ {
memberFromObject(p, named.Method(i), nil, "")
diff --git a/go/ssa/emit.go b/go/ssa/emit.go
index d77b440..2a26c94 100644
--- a/go/ssa/emit.go
+++ b/go/ssa/emit.go
@@ -11,6 +11,8 @@
"go/ast"
"go/token"
"go/types"
+
+ "golang.org/x/tools/internal/typeparams"
)
// emitAlloc emits to f a new Alloc instruction allocating a variable
@@ -64,7 +66,7 @@
// new temporary, and returns the value so defined.
func emitLoad(f *Function, addr Value) *UnOp {
v := &UnOp{Op: token.MUL, X: addr}
- v.setType(mustDeref(addr.Type()))
+ v.setType(typeparams.MustDeref(addr.Type()))
f.emit(v)
return v
}
@@ -414,7 +416,7 @@
// emitStore emits to f an instruction to store value val at location
// addr, applying implicit conversions as required by assignability rules.
func emitStore(f *Function, addr, val Value, pos token.Pos) *Store {
- typ := mustDeref(addr.Type())
+ typ := typeparams.MustDeref(addr.Type())
s := &Store{
Addr: addr,
Val: emitConv(f, val, typ),
diff --git a/go/ssa/func.go b/go/ssa/func.go
index 0ac2204..4d3e391 100644
--- a/go/ssa/func.go
+++ b/go/ssa/func.go
@@ -14,6 +14,8 @@
"io"
"os"
"strings"
+
+ "golang.org/x/tools/internal/typeparams"
)
// Like ObjectOf, but panics instead of returning nil.
@@ -531,7 +533,7 @@
if len(f.Locals) > 0 {
buf.WriteString("# Locals:\n")
for i, l := range f.Locals {
- fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, l.Name(), relType(mustDeref(l.Type()), from))
+ fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, l.Name(), relType(typeparams.MustDeref(l.Type()), from))
}
}
writeSignature(buf, from, f.Name(), f.Signature)
diff --git a/go/ssa/lift.go b/go/ssa/lift.go
index da49fe9..8bb1949 100644
--- a/go/ssa/lift.go
+++ b/go/ssa/lift.go
@@ -43,6 +43,8 @@
"go/token"
"math/big"
"os"
+
+ "golang.org/x/tools/internal/typeparams"
)
// If true, show diagnostic information at each step of lifting.
@@ -465,7 +467,7 @@
*fresh++
phi.pos = alloc.Pos()
- phi.setType(mustDeref(alloc.Type()))
+ phi.setType(typeparams.MustDeref(alloc.Type()))
phi.block = v
if debugLifting {
fmt.Fprintf(os.Stderr, "\tplace %s = %s at block %s\n", phi.Name(), phi, v)
@@ -510,7 +512,7 @@
func renamed(renaming []Value, alloc *Alloc) Value {
v := renaming[alloc.index]
if v == nil {
- v = zeroConst(mustDeref(alloc.Type()))
+ v = zeroConst(typeparams.MustDeref(alloc.Type()))
renaming[alloc.index] = v
}
return v
diff --git a/go/ssa/lvalue.go b/go/ssa/lvalue.go
index 186cfca..eede307 100644
--- a/go/ssa/lvalue.go
+++ b/go/ssa/lvalue.go
@@ -11,6 +11,8 @@
"go/ast"
"go/token"
"go/types"
+
+ "golang.org/x/tools/internal/typeparams"
)
// An lvalue represents an assignable location that may appear on the
@@ -52,7 +54,7 @@
}
func (a *address) typ() types.Type {
- return mustDeref(a.addr.Type())
+ return typeparams.MustDeref(a.addr.Type())
}
// An element is an lvalue represented by m[k], the location of an
diff --git a/go/ssa/print.go b/go/ssa/print.go
index 727a735..38d8404 100644
--- a/go/ssa/print.go
+++ b/go/ssa/print.go
@@ -17,6 +17,7 @@
"strings"
"golang.org/x/tools/go/types/typeutil"
+ "golang.org/x/tools/internal/typeparams"
)
// relName returns the name of v relative to i.
@@ -94,7 +95,7 @@
op = "new"
}
from := v.Parent().relPkg()
- return fmt.Sprintf("%s %s (%s)", op, relType(mustDeref(v.Type()), from), v.Comment)
+ return fmt.Sprintf("%s %s (%s)", op, relType(typeparams.MustDeref(v.Type()), from), v.Comment)
}
func (v *Phi) String() string {
@@ -260,7 +261,7 @@
func (v *FieldAddr) String() string {
// Be robust against a bad index.
name := "?"
- if fld := fieldOf(mustDeref(v.X.Type()), v.Field); fld != nil {
+ if fld := fieldOf(typeparams.MustDeref(v.X.Type()), v.Field); fld != nil {
name = fld.Name()
}
return fmt.Sprintf("&%s.%s [#%d]", relName(v.X, v), name, v.Field)
@@ -449,7 +450,7 @@
case *Global:
fmt.Fprintf(buf, " var %-*s %s\n",
- maxname, name, relType(mustDeref(mem.Type()), from))
+ maxname, name, relType(typeparams.MustDeref(mem.Type()), from))
}
}
diff --git a/go/ssa/util.go b/go/ssa/util.go
index 729a4e5..915b4e2 100644
--- a/go/ssa/util.go
+++ b/go/ssa/util.go
@@ -115,15 +115,6 @@
return typ, false
}
-// mustDeref returns the element type of a type with a pointer core type.
-// Panics on failure.
-func mustDeref(typ types.Type) types.Type {
- if et, ok := deref(typ); ok {
- return et
- }
- panic("cannot dereference type " + typ.String())
-}
-
// recvType returns the receiver type of method obj.
func recvType(obj *types.Func) types.Type {
return obj.Type().(*types.Signature).Recv().Type()
diff --git a/gopls/internal/golang/completion/completion.go b/gopls/internal/golang/completion/completion.go
index b796de4..8ff7f0b 100644
--- a/gopls/internal/golang/completion/completion.go
+++ b/gopls/internal/golang/completion/completion.go
@@ -37,10 +37,12 @@
goplsastutil "golang.org/x/tools/gopls/internal/util/astutil"
"golang.org/x/tools/gopls/internal/util/safetoken"
"golang.org/x/tools/gopls/internal/util/typesutil"
+ "golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/fuzzy"
"golang.org/x/tools/internal/imports"
"golang.org/x/tools/internal/typeparams"
+ "golang.org/x/tools/internal/typesinternal"
)
// A CompletionItem represents a possible completion suggested by the algorithm.
@@ -1585,7 +1587,7 @@
}
if c.inference.objType != nil {
- if named, _ := golang.Deref(c.inference.objType).(*types.Named); named != nil {
+ if named, ok := aliases.Unalias(typesinternal.Unpointer(c.inference.objType)).(*types.Named); ok {
// If we expected a named type, check the type's package for
// completion items. This is useful when the current file hasn't
// imported the type's package yet.
@@ -1651,14 +1653,14 @@
return
}
- t = golang.Deref(t)
+ t = typesinternal.Unpointer(t)
// If we have an expected type and it is _not_ a named type, handle
// it specially. Non-named types like "[]int" will never be
// considered via a lexical search, so we need to directly inject
// them. Also allow generic types since lexical search does not
// infer instantiated versions of them.
- if named, _ := t.(*types.Named); named == nil || named.TypeParams().Len() > 0 {
+ if named, ok := aliases.Unalias(t).(*types.Named); !ok || named.TypeParams().Len() > 0 {
// If our expected type is "[]int", this will add a literal
// candidate of "[]int{}".
c.literal(ctx, t, nil)
@@ -1896,7 +1898,7 @@
clInfo := compLitInfo{
cl: n,
- clType: golang.Deref(tv.Type).Underlying(),
+ clType: typesinternal.Unpointer(tv.Type).Underlying(),
}
var (
diff --git a/gopls/internal/golang/completion/literal.go b/gopls/internal/golang/completion/literal.go
index 45f772d..dc4fc0d 100644
--- a/gopls/internal/golang/completion/literal.go
+++ b/gopls/internal/golang/completion/literal.go
@@ -14,7 +14,9 @@
"golang.org/x/tools/gopls/internal/golang"
"golang.org/x/tools/gopls/internal/golang/completion/snippet"
"golang.org/x/tools/gopls/internal/protocol"
+ "golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/event"
+ "golang.org/x/tools/internal/typesinternal"
)
// literal generates composite literal, function literal, and make()
@@ -49,10 +51,11 @@
//
// don't offer "mySlice{}" since we have already added a candidate
// of "[]int{}".
- if _, named := literalType.(*types.Named); named && expType != nil {
- if _, named := golang.Deref(expType).(*types.Named); !named {
- return
- }
+ if is[*types.Named](aliases.Unalias(literalType)) &&
+ expType != nil &&
+ !is[*types.Named](aliases.Unalias(typesinternal.Unpointer(expType))) {
+
+ return
}
// Check if an object of type literalType would match our expected type.
@@ -589,3 +592,8 @@
_, foundObj := scope.LookupParent(obj.Name(), c.pos)
return obj == foundObj
}
+
+func is[T any](x any) bool {
+ _, ok := x.(T)
+ return ok
+}
diff --git a/gopls/internal/golang/completion/postfix_snippets.go b/gopls/internal/golang/completion/postfix_snippets.go
index 252d2e7..fad8f78 100644
--- a/gopls/internal/golang/completion/postfix_snippets.go
+++ b/gopls/internal/golang/completion/postfix_snippets.go
@@ -21,8 +21,10 @@
"golang.org/x/tools/gopls/internal/golang/completion/snippet"
"golang.org/x/tools/gopls/internal/protocol"
"golang.org/x/tools/gopls/internal/util/safetoken"
+ "golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/imports"
+ "golang.org/x/tools/internal/typesinternal"
)
// Postfix snippets are artificial methods that allow the user to
@@ -465,7 +467,7 @@
// go/types predicates are undefined on types.Typ[types.Invalid].
if !types.Identical(t, types.Typ[types.Invalid]) && types.Implements(t, errorIntf) {
name = "err"
- } else if _, isNamed := golang.Deref(t).(*types.Named); !isNamed {
+ } else if !is[*types.Named](aliases.Unalias(typesinternal.Unpointer(t))) {
name = nonNamedDefault
}
diff --git a/gopls/internal/golang/completion/util.go b/gopls/internal/golang/completion/util.go
index 65d1b4d..8ac7f16 100644
--- a/gopls/internal/golang/completion/util.go
+++ b/gopls/internal/golang/completion/util.go
@@ -14,6 +14,7 @@
"golang.org/x/tools/gopls/internal/protocol"
"golang.org/x/tools/gopls/internal/util/safetoken"
"golang.org/x/tools/internal/diff"
+ "golang.org/x/tools/internal/typesinternal"
)
// exprAtPos returns the index of the expression containing pos.
@@ -38,7 +39,12 @@
var visit func(T types.Type)
visit = func(T types.Type) {
- if T, ok := golang.Deref(T).Underlying().(*types.Struct); ok {
+ // T may be a Struct, optionally Named, with an optional
+ // Pointer (with optional Aliases at every step!):
+ // Consider: type T *struct{ f int }; _ = T(nil).f
+ //
+ // TODO(adonovan): use typeparams.Deref in next CL.
+ if T, ok := typesinternal.Unpointer(T.Underlying()).Underlying().(*types.Struct); ok {
if seen.At(T) != nil {
return
}
diff --git a/gopls/internal/golang/hover.go b/gopls/internal/golang/hover.go
index 15bd97a..e7b903d 100644
--- a/gopls/internal/golang/hover.go
+++ b/gopls/internal/golang/hover.go
@@ -1226,7 +1226,7 @@
var fields []promotedField
var visit func(t types.Type, stack []*types.Named)
visit = func(t types.Type, stack []*types.Named) {
- tStruct, ok := Deref(t).Underlying().(*types.Struct)
+ tStruct, ok := typesinternal.Unpointer(t).Underlying().(*types.Struct)
if !ok {
return
}
diff --git a/gopls/internal/golang/identifier.go b/gopls/internal/golang/identifier.go
index 28f8975..994010f 100644
--- a/gopls/internal/golang/identifier.go
+++ b/gopls/internal/golang/identifier.go
@@ -8,6 +8,9 @@
"errors"
"go/ast"
"go/types"
+
+ "golang.org/x/tools/internal/aliases"
+ "golang.org/x/tools/internal/typesinternal"
)
// ErrNoIdentFound is error returned when no identifier is found at a particular position
@@ -23,24 +26,30 @@
return sig
}
+// searchForEnclosing returns, given the AST path to a SelectorExpr,
+// the exported named type of the innermost implicit field selection.
+//
+// For example, given "new(A).d" where this is (due to embedding) a
+// shorthand for "new(A).b.c.d", it returns the named type of c,
+// if it is exported, otherwise the type of b, or A.
func searchForEnclosing(info *types.Info, path []ast.Node) *types.TypeName {
for _, n := range path {
switch n := n.(type) {
case *ast.SelectorExpr:
if sel, ok := info.Selections[n]; ok {
- recv := Deref(sel.Recv())
+ recv := typesinternal.Unpointer(sel.Recv())
// Keep track of the last exported type seen.
var exported *types.TypeName
- if named, ok := recv.(*types.Named); ok && named.Obj().Exported() {
+ if named, ok := aliases.Unalias(recv).(*types.Named); ok && named.Obj().Exported() {
exported = named.Obj()
}
// We don't want the last element, as that's the field or
// method itself.
for _, index := range sel.Index()[:len(sel.Index())-1] {
if r, ok := recv.Underlying().(*types.Struct); ok {
- recv = Deref(r.Field(index).Type())
- if named, ok := recv.(*types.Named); ok && named.Obj().Exported() {
+ recv = typesinternal.Unpointer(r.Field(index).Type())
+ if named, ok := aliases.Unalias(recv).(*types.Named); ok && named.Obj().Exported() {
exported = named.Obj()
}
}
diff --git a/gopls/internal/golang/rename_check.go b/gopls/internal/golang/rename_check.go
index 11b154c..ffaff30 100644
--- a/gopls/internal/golang/rename_check.go
+++ b/gopls/internal/golang/rename_check.go
@@ -45,6 +45,8 @@
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/gopls/internal/cache"
"golang.org/x/tools/gopls/internal/util/safetoken"
+ "golang.org/x/tools/internal/aliases"
+ "golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/refactor/satisfy"
)
@@ -371,7 +373,7 @@
return visit(nil) // pop stack, don't descend
}
// TODO(adonovan): fix: for generics, should be T.core not T.underlying.
- if _, ok := Deref(tv.Type).Underlying().(*types.Struct); ok {
+ if _, ok := typesinternal.Unpointer(tv.Type).Underlying().(*types.Struct); ok {
if n.Type != nil {
ast.Inspect(n.Type, visit)
}
@@ -501,7 +503,7 @@
if from.Anonymous() {
if named, ok := from.Type().(*types.Named); ok {
r.check(named.Obj())
- } else if named, ok := Deref(from.Type()).(*types.Named); ok {
+ } else if named, ok := aliases.Unalias(typesinternal.Unpointer(from.Type())).(*types.Named); ok {
r.check(named.Obj())
}
}
diff --git a/gopls/internal/golang/util.go b/gopls/internal/golang/util.go
index 5283a5b..c2f5d50 100644
--- a/gopls/internal/golang/util.go
+++ b/gopls/internal/golang/util.go
@@ -113,30 +113,6 @@
return FormatNode(fset, n)
}
-// Deref returns a pointer's element type, traversing as many levels as needed.
-// Otherwise it returns typ.
-//
-// It can return a pointer type for cyclic types (see golang/go#45510).
-func Deref(typ types.Type) types.Type {
- var seen map[types.Type]struct{}
- for {
- p, ok := typ.Underlying().(*types.Pointer)
- if !ok {
- return typ
- }
- if _, ok := seen[p.Elem()]; ok {
- return typ
- }
-
- typ = p.Elem()
-
- if seen == nil {
- seen = make(map[types.Type]struct{})
- }
- seen[typ] = struct{}{}
- }
-}
-
// findFileInDeps finds package metadata containing URI in the transitive
// dependencies of m. When using the Go command, the answer is unique.
func findFileInDeps(s metadata.Source, mp *metadata.Package, uri protocol.DocumentURI) *metadata.Package {
diff --git a/internal/typeparams/coretype.go b/internal/typeparams/coretype.go
index 7ea8840..048e216 100644
--- a/internal/typeparams/coretype.go
+++ b/internal/typeparams/coretype.go
@@ -5,6 +5,7 @@
package typeparams
import (
+ "fmt"
"go/types"
)
@@ -120,3 +121,15 @@
return []*types.Term{types.NewTerm(false, typ)}, nil
}
}
+
+// MustDeref returns the type of the variable pointed to by t.
+// It panics if t's core type is not a pointer.
+//
+// TODO(adonovan): ideally this would live in typesinternal, but that
+// creates an import cycle. Move there when we melt this package down.
+func MustDeref(t types.Type) types.Type {
+ if ptr, ok := CoreType(t).(*types.Pointer); ok {
+ return ptr.Elem()
+ }
+ panic(fmt.Sprintf("%v is not a pointer", t))
+}
diff --git a/internal/typesinternal/recv.go b/internal/typesinternal/recv.go
index 1a7b726..fea7c8b 100644
--- a/internal/typesinternal/recv.go
+++ b/internal/typesinternal/recv.go
@@ -22,3 +22,22 @@
named, _ = aliases.Unalias(t).(*types.Named)
return
}
+
+// Unpointer returns T given *T or an alias thereof.
+// For all other types it is the identity function.
+// It does not look at underlying types.
+// The result may be an alias.
+//
+// Use this function to strip off the optional pointer on a receiver
+// in a field or method selection, without losing the named type
+// (which is needed to compute the method set).
+//
+// See also [typeparams.MustDeref], which removes one level of
+// indirection from the type, regardless of named types (analogous to
+// a LOAD instruction).
+func Unpointer(t types.Type) types.Type {
+ if ptr, ok := aliases.Unalias(t).(*types.Pointer); ok {
+ return ptr.Elem()
+ }
+ return t
+}