internal/aliases: add type parameters argument to NewAliases

Adds a type parameters argument to NewAliases and updates all
usage locations. Also adds a unit test that creates a type
parameterized alias.

Updates golang/go#68778

Change-Id: I5e3e76a5f597cf658faa9036319eded33eeb9286
Reviewed-on: https://go-review.googlesource.com/c/tools/+/607535
Reviewed-by: Robert Findley <rfindley@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/go/ssa/subst.go b/go/ssa/subst.go
index 4dcb871..6315158 100644
--- a/go/ssa/subst.go
+++ b/go/ssa/subst.go
@@ -365,19 +365,19 @@
 		rhs := subst.typ(aliases.Rhs(t))
 
 		// Create the fresh alias.
-		obj := aliases.NewAlias(true, tname.Pos(), tname.Pkg(), tname.Name(), rhs)
-		fresh := obj.Type()
-		if fresh, ok := fresh.(*aliases.Alias); ok {
-			// TODO: assume ok when aliases are always materialized (go1.27).
-			aliases.SetTypeParams(fresh, newTParams)
-		}
+		//
+		// Until 1.27, the result of aliases.NewAlias(...).Type() cannot guarantee it is a *types.Alias.
+		// However, as t is an *alias.Alias and t is well-typed, then aliases must have been enabled.
+		// Follow this decision, and always enable aliases here.
+		const enabled = true
+		obj := aliases.NewAlias(enabled, tname.Pos(), tname.Pkg(), tname.Name(), rhs, newTParams)
 
 		// Substitute into all of the constraints after they are created.
 		for i, ntp := range newTParams {
 			bound := tparams.At(i).Constraint()
 			ntp.SetConstraint(subst.typ(bound))
 		}
-		return fresh
+		return obj.Type()
 	}
 
 	// t is declared within the function origin and has type arguments.
diff --git a/internal/aliases/aliases.go b/internal/aliases/aliases.go
index c24c2ee..f7798e3 100644
--- a/internal/aliases/aliases.go
+++ b/internal/aliases/aliases.go
@@ -22,11 +22,17 @@
 // GODEBUG=gotypesalias=... by invoking the type checker. The Enabled
 // function is expensive and should be called once per task (e.g.
 // package import), not once per call to NewAlias.
-func NewAlias(enabled bool, pos token.Pos, pkg *types.Package, name string, rhs types.Type) *types.TypeName {
+//
+// Precondition: enabled || len(tparams)==0.
+// If materialized aliases are disabled, there must not be any type parameters.
+func NewAlias(enabled bool, pos token.Pos, pkg *types.Package, name string, rhs types.Type, tparams []*types.TypeParam) *types.TypeName {
 	if enabled {
 		tname := types.NewTypeName(pos, pkg, name, nil)
-		newAlias(tname, rhs)
+		newAlias(tname, rhs, tparams)
 		return tname
 	}
+	if len(tparams) > 0 {
+		panic("cannot create an alias with type parameters when gotypesalias is not enabled")
+	}
 	return types.NewTypeName(pos, pkg, name, rhs)
 }
diff --git a/internal/aliases/aliases_go121.go b/internal/aliases/aliases_go121.go
index 6652f7d..a775fcc 100644
--- a/internal/aliases/aliases_go121.go
+++ b/internal/aliases/aliases_go121.go
@@ -27,7 +27,9 @@
 // Unalias returns the type t for go <=1.21.
 func Unalias(t types.Type) types.Type { return t }
 
-func newAlias(name *types.TypeName, rhs types.Type) *Alias { panic("unreachable") }
+func newAlias(name *types.TypeName, rhs types.Type, tparams []*types.TypeParam) *Alias {
+	panic("unreachable")
+}
 
 // Enabled reports whether [NewAlias] should create [types.Alias] types.
 //
diff --git a/internal/aliases/aliases_go122.go b/internal/aliases/aliases_go122.go
index 3ef1afe..31c159e 100644
--- a/internal/aliases/aliases_go122.go
+++ b/internal/aliases/aliases_go122.go
@@ -70,10 +70,9 @@
 // newAlias is an internal alias around types.NewAlias.
 // Direct usage is discouraged as the moment.
 // Try to use NewAlias instead.
-func newAlias(tname *types.TypeName, rhs types.Type) *Alias {
+func newAlias(tname *types.TypeName, rhs types.Type, tparams []*types.TypeParam) *Alias {
 	a := types.NewAlias(tname, rhs)
-	// TODO(go.dev/issue/65455): Remove kludgy workaround to set a.actual as a side-effect.
-	Unalias(a)
+	SetTypeParams(a, tparams)
 	return a
 }
 
diff --git a/internal/aliases/aliases_test.go b/internal/aliases/aliases_test.go
index d27fd6d..d19afcc 100644
--- a/internal/aliases/aliases_test.go
+++ b/internal/aliases/aliases_test.go
@@ -24,7 +24,7 @@
 // be an *aliases.Alias.
 func TestNewAlias(t *testing.T) {
 	const source = `
-	package P
+	package p
 
 	type Named int
 	`
@@ -35,7 +35,7 @@
 	}
 
 	var conf types.Config
-	pkg, err := conf.Check("P", fset, []*ast.File{f}, nil)
+	pkg, err := conf.Check("p", fset, []*ast.File{f}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -47,15 +47,18 @@
 	}
 
 	for _, godebug := range []string{
-		// "", // The default is in transition; suppress this case for now
+		// The default gotypesalias value follows the x/tools/go.mod version
+		// The go.mod is at 1.19 so the default is gotypesalias=0.
+		// "", // Use the default GODEBUG value.
 		"gotypesalias=0",
-		"gotypesalias=1"} {
+		"gotypesalias=1",
+	} {
 		t.Run(godebug, func(t *testing.T) {
 			t.Setenv("GODEBUG", godebug)
 
 			enabled := aliases.Enabled()
 
-			A := aliases.NewAlias(enabled, token.NoPos, pkg, "A", tv.Type)
+			A := aliases.NewAlias(enabled, token.NoPos, pkg, "A", tv.Type, nil)
 			if got, want := A.Name(), "A"; got != want {
 				t.Errorf("Expected A.Name()==%q. got %q", want, got)
 			}
@@ -75,3 +78,79 @@
 		})
 	}
 }
+
+// TestNewAlias tests that alias.NewAlias can create a parameterized alias
+// A[T] of a type whose underlying and Unaliased type is *T. The test then
+// instantiates A[Named] and checks that the underlying and Unaliased type
+// of A[Named] is *Named.
+//
+// Requires gotypesalias GODEBUG and aliastypeparams GOEXPERIMENT.
+func TestNewParameterizedAlias(t *testing.T) {
+	testenv.NeedsGoExperiment(t, "aliastypeparams")
+
+	t.Setenv("GODEBUG", "gotypesalias=1") // needed until gotypesalias is removed (1.27).
+	enabled := aliases.Enabled()
+	if !enabled {
+		t.Fatal("Need materialized aliases enabled")
+	}
+
+	const source = `
+	package p
+
+	type Named int
+	`
+	fset := token.NewFileSet()
+	f, err := parser.ParseFile(fset, "hello.go", source, 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var conf types.Config
+	pkg, err := conf.Check("p", fset, []*ast.File{f}, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// type A[T ~int] = *T
+	tparam := types.NewTypeParam(
+		types.NewTypeName(token.NoPos, pkg, "T", nil),
+		types.NewUnion([]*types.Term{types.NewTerm(true, types.Typ[types.Int])}),
+	)
+	ptrT := types.NewPointer(tparam)
+	A := aliases.NewAlias(enabled, token.NoPos, pkg, "A", ptrT, []*types.TypeParam{tparam})
+	if got, want := A.Name(), "A"; got != want {
+		t.Errorf("NewAlias: got %q, want %q", got, want)
+	}
+
+	if got, want := A.Type().Underlying(), ptrT; !types.Identical(got, want) {
+		t.Errorf("A.Type().Underlying (%q) is not identical to %q", got, want)
+	}
+	if got, want := aliases.Unalias(A.Type()), ptrT; !types.Identical(got, want) {
+		t.Errorf("Unalias(A)==%q is not identical to %q", got, want)
+	}
+
+	if _, ok := A.Type().(*aliases.Alias); !ok {
+		t.Errorf("Expected A.Type() to be a types.Alias(). got %q", A.Type())
+	}
+
+	pkg.Scope().Insert(A) // Add A to pkg so it is available to types.Eval.
+
+	named, ok := pkg.Scope().Lookup("Named").(*types.TypeName)
+	if !ok {
+		t.Fatalf("Failed to Lookup(%q) in package %s", "Named", pkg)
+	}
+	ptrNamed := types.NewPointer(named.Type())
+
+	const expr = `A[Named]`
+	tv, err := types.Eval(fset, pkg, 0, expr)
+	if err != nil {
+		t.Fatalf("Eval(%s) failed: %v", expr, err)
+	}
+
+	if got, want := tv.Type.Underlying(), ptrNamed; !types.Identical(got, want) {
+		t.Errorf("A[Named].Type().Underlying (%q) is not identical to %q", got, want)
+	}
+	if got, want := aliases.Unalias(tv.Type), ptrNamed; !types.Identical(got, want) {
+		t.Errorf("Unalias(A[Named])==%q is not identical to %q", got, want)
+	}
+}
diff --git a/internal/gcimporter/iimport.go b/internal/gcimporter/iimport.go
index 01c4023..1c8cf5a 100644
--- a/internal/gcimporter/iimport.go
+++ b/internal/gcimporter/iimport.go
@@ -569,7 +569,8 @@
 		// 	tparams := r.tparamList()
 		// 	alias.SetTypeParams(tparams)
 		// }
-		r.declare(aliases.NewAlias(r.p.aliases, pos, r.currPkg, name, typ))
+		var tparams []*types.TypeParam
+		r.declare(aliases.NewAlias(r.p.aliases, pos, r.currPkg, name, typ, tparams))
 
 	case constTag:
 		typ, val := r.value()
diff --git a/internal/gcimporter/ureader_yes.go b/internal/gcimporter/ureader_yes.go
index 2c07706..50b4a37 100644
--- a/internal/gcimporter/ureader_yes.go
+++ b/internal/gcimporter/ureader_yes.go
@@ -526,7 +526,8 @@
 		case pkgbits.ObjAlias:
 			pos := r.pos()
 			typ := r.typ()
-			declare(aliases.NewAlias(r.p.aliases, pos, objPkg, objName, typ))
+			var tparams []*types.TypeParam // TODO(#68778): read type params once pkgbits.V2 is available.
+			declare(aliases.NewAlias(r.p.aliases, pos, objPkg, objName, typ, tparams))
 
 		case pkgbits.ObjConst:
 			pos := r.pos()