diff --git a/src/cmd/compile/internal/importer/ureader.go b/src/cmd/compile/internal/importer/ureader.go index f5c2f41069d..3488f131488 100644 --- a/src/cmd/compile/internal/importer/ureader.go +++ b/src/cmd/compile/internal/importer/ureader.go @@ -9,6 +9,7 @@ import ( "cmd/compile/internal/syntax" "cmd/compile/internal/types2" "cmd/internal/src" + "internal/godebug" "internal/pkgbits" ) @@ -409,7 +410,7 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types2.Package, string) { case pkgbits.ObjAlias: pos := r.pos() typ := r.typ() - return types2.NewTypeName(pos, objPkg, objName, typ) + return newAliasTypeName(pos, objPkg, objName, typ) case pkgbits.ObjConst: pos := r.pos() @@ -533,3 +534,18 @@ func (r *reader) ident(marker pkgbits.SyncMarker) (*types2.Package, string) { r.Sync(marker) return r.pkg(), r.String() } + +// newAliasTypeName returns a new TypeName, with a materialized *types2.Alias if supported. +func newAliasTypeName(pos syntax.Pos, pkg *types2.Package, name string, rhs types2.Type) *types2.TypeName { + // Copied from x/tools/internal/aliases.NewAlias via + // GOROOT/src/go/internal/gcimporter/ureader.go. + if gotypesalias.Value() == "1" { + tname := types2.NewTypeName(pos, pkg, name, nil) + _ = types2.NewAlias(tname, rhs) // form TypeName -> Alias cycle + return tname + } + return types2.NewTypeName(pos, pkg, name, rhs) +} + +// gotypesalias controls the use of Alias types. +var gotypesalias = godebug.New("#gotypesalias") diff --git a/src/cmd/compile/internal/noder/writer.go b/src/cmd/compile/internal/noder/writer.go index 71606a915f6..785176b3b57 100644 --- a/src/cmd/compile/internal/noder/writer.go +++ b/src/cmd/compile/internal/noder/writer.go @@ -831,7 +831,11 @@ func (w *writer) doObj(wext *writer, obj types2.Object) pkgbits.CodeObj { case *types2.TypeName: if obj.IsAlias() { w.pos(obj) - w.typ(types2.Unalias(obj.Type())) + t := obj.Type() + if alias, ok := t.(*types2.Alias); ok { // materialized alias + t = alias.Rhs() + } + w.typ(t) return pkgbits.ObjAlias } diff --git a/src/cmd/compile/internal/types2/alias.go b/src/cmd/compile/internal/types2/alias.go index 149cd3b265d..7bc0e5a9f94 100644 --- a/src/cmd/compile/internal/types2/alias.go +++ b/src/cmd/compile/internal/types2/alias.go @@ -32,6 +32,12 @@ func (a *Alias) Obj() *TypeName { return a.obj } func (a *Alias) Underlying() Type { return unalias(a).Underlying() } func (a *Alias) String() string { return TypeString(a, nil) } +// TODO(adonovan): uncomment when proposal #66559 is accepted. +// +// // Rhs returns the type R on the right-hand side of an alias +// // declaration "type A = R", which may be another alias. +// func (a *Alias) Rhs() Type { return a.fromRHS } + // Unalias returns t if it is not an alias type; // otherwise it follows t's alias chain until it // reaches a non-alias type which is then returned. diff --git a/src/cmd/compile/internal/types2/api.go b/src/cmd/compile/internal/types2/api.go index bb02d9198e6..0b44d4ff38b 100644 --- a/src/cmd/compile/internal/types2/api.go +++ b/src/cmd/compile/internal/types2/api.go @@ -469,3 +469,10 @@ func (conf *Config) Check(path string, files []*syntax.File, info *Info) (*Packa pkg := NewPackage(path, "") return pkg, NewChecker(conf, pkg, info).Files(files) } + +// Rhs returns the type R on the right-hand side of an alias +// declaration "type A = R", which may be another alias. +// +// TODO(adonovan): move to alias.go (common with go/types) once +// proposal #66559 is accepted. +func (a *Alias) Rhs() Type { return a.fromRHS } diff --git a/src/go/internal/gcimporter/ureader.go b/src/go/internal/gcimporter/ureader.go index 5397a2796f7..b7d7b6c8617 100644 --- a/src/go/internal/gcimporter/ureader.go +++ b/src/go/internal/gcimporter/ureader.go @@ -7,6 +7,7 @@ package gcimporter import ( "go/token" "go/types" + "internal/godebug" "internal/pkgbits" "sort" ) @@ -479,7 +480,7 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) { case pkgbits.ObjAlias: pos := r.pos() typ := r.typ() - declare(types.NewTypeName(pos, objPkg, objName, typ)) + declare(newAliasTypeName(pos, objPkg, objName, typ)) case pkgbits.ObjConst: pos := r.pos() @@ -655,3 +656,15 @@ func pkgScope(pkg *types.Package) *types.Scope { } return types.Universe } + +// newAliasTypeName returns a new TypeName, with a materialized *types.Alias if supported. +func newAliasTypeName(pos token.Pos, pkg *types.Package, name string, rhs types.Type) *types.TypeName { + // When GODEBUG=gotypesalias=1, the Type() of the return value is a + // *types.Alias. Copied from x/tools/internal/aliases.NewAlias. + if godebug.New("gotypesalias").Value() == "1" { + tname := types.NewTypeName(pos, pkg, name, nil) + _ = types.NewAlias(tname, rhs) // form TypeName -> Alias cycle + return tname + } + return types.NewTypeName(pos, pkg, name, rhs) +} diff --git a/src/go/types/alias.go b/src/go/types/alias.go index e32ddbcb331..3490d26c203 100644 --- a/src/go/types/alias.go +++ b/src/go/types/alias.go @@ -35,6 +35,12 @@ func (a *Alias) Obj() *TypeName { return a.obj } func (a *Alias) Underlying() Type { return unalias(a).Underlying() } func (a *Alias) String() string { return TypeString(a, nil) } +// TODO(adonovan): uncomment when proposal #66559 is accepted. +// +// // Rhs returns the type R on the right-hand side of an alias +// // declaration "type A = R", which may be another alias. +// func (a *Alias) Rhs() Type { return a.fromRHS } + // Unalias returns t if it is not an alias type; // otherwise it follows t's alias chain until it // reaches a non-alias type which is then returned. diff --git a/src/go/types/eval_test.go b/src/go/types/eval_test.go index 1521f2fe419..c0ac8225acb 100644 --- a/src/go/types/eval_test.go +++ b/src/go/types/eval_test.go @@ -13,6 +13,7 @@ import ( "go/parser" "go/token" "go/types" + "internal/godebug" "internal/testenv" "strings" "testing" @@ -173,6 +174,14 @@ func TestEvalPos(t *testing.T) { if err != nil { t.Fatalf("could not parse file %d: %s", i, err) } + + // Materialized aliases give a different (better) + // result for the final test, so skip it for now. + // TODO(adonovan): reenable when gotypesalias=1 is the default. + if gotypesalias.Value() == "1" && strings.Contains(src, "interface{R}.Read") { + continue + } + files = append(files, file) } @@ -196,6 +205,9 @@ func TestEvalPos(t *testing.T) { } } +// gotypesalias controls the use of Alias types. +var gotypesalias = godebug.New("#gotypesalias") + // split splits string s at the first occurrence of s, trimming spaces. func split(s, sep string) (string, string) { before, after, _ := strings.Cut(s, sep) diff --git a/src/go/types/named_test.go b/src/go/types/named_test.go index d930874f120..effeeb6f5c4 100644 --- a/src/go/types/named_test.go +++ b/src/go/types/named_test.go @@ -117,7 +117,7 @@ type Inst = *Tree[int] return n.Underlying().(*Struct).Field(0).Type().(*Pointer).Elem().(*Named) } - Inst := pkg.Scope().Lookup("Inst").Type().(*Pointer).Elem().(*Named) + Inst := Unalias(pkg.Scope().Lookup("Inst").Type()).(*Pointer).Elem().(*Named) Node := firstFieldType(Inst) Tree := firstFieldType(Node) if !Identical(Inst, Tree) { diff --git a/test/typeparam/struct.go b/test/typeparam/struct.go index 2dad9087bc9..fee38eccc34 100644 --- a/test/typeparam/struct.go +++ b/test/typeparam/struct.go @@ -21,6 +21,7 @@ type S1 struct { type Eint = E[int] type Ebool = E[bool] +type Eint2 = Eint type S2 struct { Eint