refactor/rename: fix renaming of aliases

This change gets the renaming of aliases right when
they are materialized by GODEBUG=gotypesaliases=1.
(Obviously there's no way to get it right without
that feature.) And it adds a (conditional) test.

Updates golang/go#65294

Change-Id: Ie242e3a0a9d012218067bdc61a901f6618fecbb5
Reviewed-on: https://go-review.googlesource.com/c/tools/+/567842
Reviewed-by: Robert Findley <rfindley@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/refactor/rename/check.go b/refactor/rename/check.go
index f2bd880..8350ad7 100644
--- a/refactor/rename/check.go
+++ b/refactor/rename/check.go
@@ -13,7 +13,6 @@
 	"go/types"
 
 	"golang.org/x/tools/go/loader"
-	"golang.org/x/tools/internal/aliases"
 	"golang.org/x/tools/internal/typeparams"
 	"golang.org/x/tools/internal/typesinternal"
 	"golang.org/x/tools/refactor/satisfy"
@@ -438,8 +437,8 @@
 		}
 		i++
 	}
-	if spec, ok := path[i].(*ast.TypeSpec); ok {
-		// This struct is also a named type.
+	if spec, ok := path[i].(*ast.TypeSpec); ok && !spec.Assign.IsValid() {
+		// This struct is also a defined type.
 		// We must check for direct (non-promoted) field/field
 		// and method/field conflicts.
 		named := info.Defs[spec.Name].Type()
@@ -452,7 +451,7 @@
 			return // skip checkSelections to avoid redundant errors
 		}
 	} else {
-		// This struct is not a named type.
+		// This struct is not a defined type. (It may be an alias.)
 		// We need only check for direct (non-promoted) field/field conflicts.
 		T := info.Types[tStruct].Type.Underlying().(*types.Struct)
 		for i := 0; i < T.NumFields(); i++ {
@@ -470,10 +469,7 @@
 	// 	type T int       // this and
 	// 	var s struct {T} // this must change too.
 	if from.Anonymous() {
-		// hasTypeName = {Named,Alias,TypeParam}, though
-		// a TypeParam cannot appear as an anonymous field.
-		// TODO(adonovan): add test of aliases.
-		type hasTypeName interface{ Obj() *types.TypeName }
+		// A TypeParam cannot appear as an anonymous field.
 		if t, ok := typesinternal.Unpointer(from.Type()).(hasTypeName); ok {
 			r.check(t.Obj())
 		}
@@ -483,6 +479,9 @@
 	r.checkSelections(from)
 }
 
+// hasTypeName abstracts the named types, *types.{Named,Alias,TypeParam}.
+type hasTypeName interface{ Obj() *types.TypeName }
+
 // checkSelections checks that all uses and selections that resolve to
 // the specified object would continue to do so after the renaming.
 func (r *renamer) checkSelections(from types.Object) {
@@ -596,7 +595,7 @@
 	// Check for conflict at point of declaration.
 	// Check to ensure preservation of assignability requirements.
 	R := recv(from).Type()
-	if isInterface(R) {
+	if types.IsInterface(R) {
 		// Abstract method
 
 		// declaration
@@ -613,7 +612,7 @@
 		for _, info := range r.packages {
 			// Start with named interface types (better errors)
 			for _, obj := range info.Defs {
-				if obj, ok := obj.(*types.TypeName); ok && isInterface(obj.Type()) {
+				if obj, ok := obj.(*types.TypeName); ok && types.IsInterface(obj.Type()) {
 					f, _, _ := types.LookupFieldOrMethod(
 						obj.Type(), false, from.Pkg(), from.Name())
 					if f == nil {
@@ -685,7 +684,7 @@
 			// yields abstract method I.f.  This can make error
 			// messages less than obvious.
 			//
-			if !isInterface(key.RHS) {
+			if !types.IsInterface(key.RHS) {
 				// The logic below was derived from checkSelections.
 
 				rtosel := rmethods.Lookup(from.Pkg(), r.to)
@@ -760,7 +759,7 @@
 		//
 		for key := range r.satisfy() {
 			// key = (lhs, rhs) where lhs is always an interface.
-			if isInterface(key.RHS) {
+			if types.IsInterface(key.RHS) {
 				continue
 			}
 			rsel := r.msets.MethodSet(key.RHS).Lookup(from.Pkg(), from.Name())
@@ -782,7 +781,7 @@
 				var iface string
 
 				I := recv(imeth).Type()
-				if named, ok := aliases.Unalias(I).(*types.Named); ok {
+				if named, ok := I.(hasTypeName); ok {
 					pos = named.Obj().Pos()
 					iface = "interface " + named.Obj().Name()
 				} else {
@@ -850,7 +849,3 @@
 	}
 	return nil
 }
-
-// -- Plundered from golang.org/x/tools/go/ssa -----------------
-
-func isInterface(T types.Type) bool { return types.IsInterface(T) }
diff --git a/refactor/rename/rename.go b/refactor/rename/rename.go
index 56603cd..a5a59e9 100644
--- a/refactor/rename/rename.go
+++ b/refactor/rename/rename.go
@@ -323,7 +323,7 @@
 	for _, obj := range fromObjects {
 		if obj, ok := obj.(*types.Func); ok {
 			recv := obj.Type().(*types.Signature).Recv()
-			if recv != nil && isInterface(recv.Type().Underlying()) {
+			if recv != nil && types.IsInterface(recv.Type()) {
 				r.changeMethods = true
 				break
 			}
diff --git a/refactor/rename/rename_test.go b/refactor/rename/rename_test.go
index 38c59c9..58b4f5e 100644
--- a/refactor/rename/rename_test.go
+++ b/refactor/rename/rename_test.go
@@ -467,6 +467,7 @@
 		ctxt             *build.Context    // nil => use previous
 		offset, from, to string            // values of the -from/-offset and -to flags
 		want             map[string]string // contents of updated files
+		alias            bool              // requires materialized aliases
 	}{
 		// Elimination of renaming import.
 		{
@@ -767,6 +768,78 @@
 `,
 			},
 		},
+		// Renaming of embedded field alias.
+		{
+			alias: true,
+			ctxt: main(`package main
+
+type T int
+type A = T
+type U struct{ A }
+
+var _ = U{}.A
+var a A
+`),
+			offset: "/go/src/main/0.go:#68", to: "A2", // A in "U{}.A"
+			want: map[string]string{
+				"/go/src/main/0.go": `package main
+
+type T int
+type A2 = T
+type U struct{ A2 }
+
+var _ = U{}.A2
+var a A2
+`,
+			},
+		},
+		// Renaming of embedded field pointer to alias.
+		{
+			alias: true,
+			ctxt: main(`package main
+
+type T int
+type A = T
+type U struct{ *A }
+
+var _ = U{}.A
+var a A
+`),
+			offset: "/go/src/main/0.go:#69", to: "A2", // A in "U{}.A"
+			want: map[string]string{
+				"/go/src/main/0.go": `package main
+
+type T int
+type A2 = T
+type U struct{ *A2 }
+
+var _ = U{}.A2
+var a A2
+`,
+			},
+		},
+		// Renaming of alias
+		{
+			ctxt: main(`package main
+
+type A = int
+
+func _() A {
+	return A(0)
+}
+`),
+			offset: "/go/src/main/0.go:#49", to: "A2", // A in "A(0)"
+			want: map[string]string{
+				"/go/src/main/0.go": `package main
+
+type A2 = int
+
+func _() A2 {
+	return A2(0)
+}
+`,
+			},
+		},
 
 		// Lexical scope tests.
 		{
@@ -1247,6 +1320,14 @@
 			return nil
 		}
 
+		if !test.alias {
+			t.Setenv("GODEBUG", "gotypesalias=0")
+		} else if !strings.Contains(fmt.Sprint(build.Default.ReleaseTags), " go1.22") {
+			t.Skip("skipping test that requires materialized type aliases")
+		} else {
+			t.Setenv("GODEBUG", "gotypesalias=1")
+		}
+
 		err := Main(ctxt, test.offset, test.from, test.to)
 		var prefix string
 		if test.offset == "" {
diff --git a/refactor/rename/spec.go b/refactor/rename/spec.go
index 69198cb..ab7dbc3 100644
--- a/refactor/rename/spec.go
+++ b/refactor/rename/spec.go
@@ -25,7 +25,7 @@
 
 	"golang.org/x/tools/go/buildutil"
 	"golang.org/x/tools/go/loader"
-	"golang.org/x/tools/internal/aliases"
+	"golang.org/x/tools/internal/typesinternal"
 )
 
 // A spec specifies an entity to rename.
@@ -459,17 +459,14 @@
 		// search within named type.
 		obj, _, _ := types.LookupFieldOrMethod(tName.Type(), true, info.Pkg, spec.typeMember)
 		if obj == nil {
-			return nil, fmt.Errorf("cannot find field or method %q of %s %s.%s",
-				spec.typeMember, typeKind(tName.Type()), info.Pkg.Path(), tName.Name())
+			return nil, fmt.Errorf("cannot find field or method %q of %s.%s",
+				spec.typeMember, info.Pkg.Path(), tName.Name())
 		}
 
 		if spec.searchFor == "" {
 			// If it is an embedded field, return the type of the field.
 			if v, ok := obj.(*types.Var); ok && v.Anonymous() {
-				switch t := aliases.Unalias(v.Type()).(type) {
-				case *types.Pointer:
-					return []types.Object{aliases.Unalias(t.Elem()).(*types.Named).Obj()}, nil
-				case *types.Named:
+				if t, ok := typesinternal.Unpointer(v.Type()).(hasTypeName); ok {
 					return []types.Object{t.Obj()}, nil
 				}
 			}
@@ -482,7 +479,7 @@
 				spec.searchFor, objectKind(obj), info.Pkg.Path(), tName.Name(),
 				obj.Name())
 		}
-		if isInterface(tName.Type()) {
+		if types.IsInterface(tName.Type()) {
 			return nil, fmt.Errorf("cannot search for local name %q within abstract method (%s.%s).%s",
 				spec.searchFor, info.Pkg.Path(), tName.Name(), searchFunc.Name())
 		}
diff --git a/refactor/rename/util.go b/refactor/rename/util.go
index 26926a8..bc6dc10 100644
--- a/refactor/rename/util.go
+++ b/refactor/rename/util.go
@@ -22,7 +22,7 @@
 	switch obj := obj.(type) {
 	case *types.PkgName:
 		return "imported package name"
-	case *types.TypeName:
+	case *types.TypeName: // defined type | alias | type parameter
 		return "type"
 	case *types.Var:
 		if obj.IsField() {
@@ -37,10 +37,6 @@
 	return strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types."))
 }
 
-func typeKind(T types.Type) string {
-	return strings.ToLower(strings.TrimPrefix(reflect.TypeOf(T.Underlying()).String(), "*types."))
-}
-
 // NB: for renamings, blank is not considered valid.
 func isValidIdentifier(id string) bool {
 	if id == "" || id == "_" {