go/ssa: types.Unalias() cleanup.

Removes a few instances of unneeded Unalias calls, and
adds additional documentation about aliases.

Change-Id: I02045ef74d6d9b80189caf6a9831bded2bae5ad4
Reviewed-on: https://go-review.googlesource.com/c/tools/+/568176
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Tim King <taking@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/go/ssa/builder.go b/go/ssa/builder.go
index 79b9edd..9b3753c 100644
--- a/go/ssa/builder.go
+++ b/go/ssa/builder.go
@@ -1253,7 +1253,7 @@
 	case *types.Array, *types.Slice:
 		var at *types.Array
 		var array Value
-		switch t := aliases.Unalias(t).(type) {
+		switch t := t.(type) {
 		case *types.Slice:
 			at = types.NewArray(t.Elem(), b.arrayLen(fn, e.Elts))
 			array = emitNew(fn, at, e.Lbrace, "slicelit")
diff --git a/go/ssa/const.go b/go/ssa/const.go
index e0d79f5..2a4e0dd 100644
--- a/go/ssa/const.go
+++ b/go/ssa/const.go
@@ -46,9 +46,9 @@
 	// Candidates (perhaps all) are eliminated during the type-set
 	// iteration, which executes at least once.
 	state := types.IsBoolean | types.IsInteger | types.IsString
-	underIs(typeSetOf(typ), func(t types.Type) bool {
+	underIs(typeSetOf(typ), func(ut types.Type) bool {
 		var c types.BasicInfo
-		if t, ok := aliases.Unalias(t).(*types.Basic); ok {
+		if t, ok := ut.(*types.Basic); ok {
 			c = t.Info()
 		}
 		if c&types.IsNumeric != 0 { // int/float/complex
diff --git a/go/ssa/coretype.go b/go/ssa/coretype.go
index 3a51283..8c218f9 100644
--- a/go/ssa/coretype.go
+++ b/go/ssa/coretype.go
@@ -50,6 +50,7 @@
 	// This is a adaptation of x/exp/typeparams.NormalTerms which x/tools cannot depend on.
 	var terms []*types.Term
 	var err error
+	// typeSetOf(t) == typeSetOf(Unalias(t))
 	switch typ := aliases.Unalias(typ).(type) {
 	case *types.TypeParam:
 		terms, err = typeparams.StructuralTerms(typ)
diff --git a/go/ssa/emit.go b/go/ssa/emit.go
index 9d2c18a..97c0b59 100644
--- a/go/ssa/emit.go
+++ b/go/ssa/emit.go
@@ -12,7 +12,6 @@
 	"go/token"
 	"go/types"
 
-	"golang.org/x/tools/internal/aliases"
 	"golang.org/x/tools/internal/typeparams"
 )
 
@@ -277,18 +276,20 @@
 		sliceTo0ArrayPtr
 		convert
 	)
-	classify := func(s, d types.Type) conversionCase {
+	// classify the conversion case of a source type us to a destination type ud.
+	// us and ud are underlying types (not *Named or *Alias)
+	classify := func(us, ud types.Type) conversionCase {
 		// Just a change of type, but not value or representation?
-		if isValuePreserving(s, d) {
+		if isValuePreserving(us, ud) {
 			return changeType
 		}
 
 		// Conversion from slice to array or slice to array pointer?
-		if slice, ok := aliases.Unalias(s).(*types.Slice); ok {
+		if slice, ok := us.(*types.Slice); ok {
 			var arr *types.Array
 			var ptr bool
 			// Conversion from slice to array pointer?
-			switch d := aliases.Unalias(d).(type) {
+			switch d := ud.(type) {
 			case *types.Array:
 				arr = d
 			case *types.Pointer:
@@ -313,8 +314,8 @@
 
 		// The only remaining case in well-typed code is a representation-
 		// changing conversion of basic types (possibly with []byte/[]rune).
-		if !isBasic(s) && !isBasic(d) {
-			panic(fmt.Sprintf("in %s: cannot convert term %s (%s [within %s]) to type %s [within %s]", f, val, val.Type(), s, typ, d))
+		if !isBasic(us) && !isBasic(ud) {
+			panic(fmt.Sprintf("in %s: cannot convert term %s (%s [within %s]) to type %s [within %s]", f, val, val.Type(), us, typ, ud))
 		}
 		return convert
 	}
diff --git a/go/ssa/func.go b/go/ssa/func.go
index d4a21c7..f645fa1 100644
--- a/go/ssa/func.go
+++ b/go/ssa/func.go
@@ -37,7 +37,8 @@
 	panic(fmt.Sprintf("no type for %T @ %s", e, f.Prog.Fset.Position(e.Pos())))
 }
 
-// typ is the locally instantiated type of T. T==typ(T) if f is not an instantiation.
+// typ is the locally instantiated type of T.
+// If f is not an instantiation, then f.typ(T)==T.
 func (f *Function) typ(T types.Type) types.Type {
 	return f.subst.typ(T)
 }
diff --git a/go/ssa/interp/ops.go b/go/ssa/interp/ops.go
index 62b635c..668fcae 100644
--- a/go/ssa/interp/ops.go
+++ b/go/ssa/interp/ops.go
@@ -173,7 +173,7 @@
 
 // zero returns a new "zero" value of the specified type.
 func zero(t types.Type) value {
-	switch t := aliases.Unalias(t).(type) {
+	switch t := t.(type) {
 	case *types.Basic:
 		if t.Kind() == types.UntypedNil {
 			panic("untyped nil has no zero value")
@@ -235,6 +235,8 @@
 		return a
 	case *types.Named:
 		return zero(t.Underlying())
+	case *aliases.Alias:
+		return zero(aliases.Unalias(t))
 	case *types.Interface:
 		return iface{} // nil type, methodset and value
 	case *types.Slice:
diff --git a/go/ssa/interp/reflect.go b/go/ssa/interp/reflect.go
index 7df3ea2..d713256 100644
--- a/go/ssa/interp/reflect.go
+++ b/go/ssa/interp/reflect.go
@@ -179,8 +179,8 @@
 }
 
 func reflectKind(t types.Type) reflect.Kind {
-	switch t := aliases.Unalias(t).(type) {
-	case *types.Named:
+	switch t := t.(type) {
+	case *types.Named, *aliases.Alias:
 		return reflectKind(t.Underlying())
 	case *types.Basic:
 		switch t.Kind() {
diff --git a/go/ssa/methods.go b/go/ssa/methods.go
index 60711dd..197b9e7 100644
--- a/go/ssa/methods.go
+++ b/go/ssa/methods.go
@@ -210,7 +210,7 @@
 
 		switch T := T.(type) {
 		case *aliases.Alias:
-			visit(aliases.Unalias(T), false)
+			visit(aliases.Unalias(T), skip) // emulates the pre-Alias behavior
 
 		case *types.Basic:
 			// nop
diff --git a/go/ssa/subst.go b/go/ssa/subst.go
index 9f2f2f3..e1b8e19 100644
--- a/go/ssa/subst.go
+++ b/go/ssa/subst.go
@@ -80,11 +80,7 @@
 		subst.cache[t] = res
 	}()
 
-	// fall through if result r will be identical to t, types.Identical(r, t).
 	switch t := t.(type) {
-	case *aliases.Alias:
-		return subst.typ(aliases.Unalias(t))
-
 	case *types.TypeParam:
 		r := subst.replacements[t]
 		assert(r != nil, "type param without replacement encountered")
@@ -140,6 +136,9 @@
 	case *types.Interface:
 		return subst.interface_(t)
 
+	case *aliases.Alias:
+		return subst.alias(t)
+
 	case *types.Named:
 		return subst.named(t)
 
@@ -307,6 +306,18 @@
 	return types.NewInterfaceType(methods, embeds).Complete()
 }
 
+func (subst *subster) alias(t *aliases.Alias) types.Type {
+	// TODO(go.dev/issues/46477): support TypeParameters once these are available from go/types.
+	u := aliases.Unalias(t)
+	if s := subst.typ(u); s != u {
+		// If there is any change, do not create a new alias.
+		return s
+	}
+	// If there is no change, t did not reach any type parameter.
+	// Keep the Alias.
+	return t
+}
+
 func (subst *subster) named(t *types.Named) types.Type {
 	// A named type may be:
 	// (1) ordinary named type (non-local scope, no type parameters, no type arguments),
diff --git a/go/ssa/util.go b/go/ssa/util.go
index 4d65259..462e275 100644
--- a/go/ssa/util.go
+++ b/go/ssa/util.go
@@ -51,8 +51,9 @@
 }
 
 // isBasic reports whether t is a basic type.
+// t is assumed to be an Underlying type (not Named or Alias).
 func isBasic(t types.Type) bool {
-	_, ok := aliases.Unalias(t).(*types.Basic)
+	_, ok := t.(*types.Basic)
 	return ok
 }
 
@@ -263,13 +264,40 @@
 		return nil
 	}
 
+	unaliasAll := func(ts []types.Type) []types.Type {
+		// Is there some top level alias?
+		var found bool
+		for _, t := range ts {
+			if _, ok := t.(*aliases.Alias); ok {
+				found = true
+				break
+			}
+		}
+		if !found {
+			return ts // no top level alias
+		}
+
+		cp := make([]types.Type, len(ts)) // copy with top level aliases removed.
+		for i, t := range ts {
+			cp[i] = aliases.Unalias(t)
+		}
+		return cp
+	}
+	l := unaliasAll(ts)
+
 	c.mu.Lock()
 	defer c.mu.Unlock()
-	return c.lists.rep(ts)
+	return c.lists.rep(l)
 }
 
 // Type returns a canonical representative of type T.
+// Removes top-level aliases.
+//
+// For performance, reasons the canonical instance is order-dependent,
+// and may contain deeply nested aliases.
 func (c *canonizer) Type(T types.Type) types.Type {
+	T = aliases.Unalias(T) // remove the top level alias.
+
 	c.mu.Lock()
 	defer c.mu.Unlock()