go/types: ObjectString: remove only 1 Alias for "type A = RHS"

As we migrate towards materialized Alias types, the ObjectString
for a type A such as
   type A = B
   type B = int
should be "type A = B", removing exactly one Alias constructor
from the type of A. (The previous behavior was "type A = int".)

I suspect the existing Alias.{Unalias,Underlying} API is
inadequate and that we will need an Alias.RHS accessor that
removes exactly one Alias. Other clients such as the import/
export packages will need it, because aliases are not
isomorphic to defined types, in which, given
   type A B
   type B int
the Underlying of A is indeed int. See #66559.

Change-Id: I11a4aacbe6dbeeafc3aee31b3c096296b5970cd8
Reviewed-on: https://go-review.googlesource.com/c/go/+/574716
Auto-Submit: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Alan Donovan 2024-03-27 09:40:24 -04:00 committed by Gopher Robot
parent 6ffbcd8249
commit 61a3ee5441
4 changed files with 116 additions and 100 deletions

View file

@ -542,10 +542,14 @@ func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) {
}
if tname.IsAlias() {
buf.WriteString(" =")
if alias, ok := typ.(*Alias); ok { // materialized? (gotypesalias=1)
typ = alias.fromRHS
}
} else if t, _ := typ.(*TypeParam); t != nil {
typ = t.bound
} else {
// TODO(gri) should this be fromRHS for *Named?
// (See discussion in #66559.)
typ = under(typ)
}
}

View file

@ -5,6 +5,7 @@
package types2_test
import (
"fmt"
"internal/testenv"
"strings"
"testing"
@ -79,69 +80,72 @@ func TestEmbeddedMethod(t *testing.T) {
}
var testObjects = []struct {
src string
obj string
want string
src string
obj string
want string
alias bool // needs materialized aliases
}{
{"import \"io\"; var r io.Reader", "r", "var p.r io.Reader"},
{"import \"io\"; var r io.Reader", "r", "var p.r io.Reader", false},
{"const c = 1.2", "c", "const p.c untyped float"},
{"const c float64 = 3.14", "c", "const p.c float64"},
{"const c = 1.2", "c", "const p.c untyped float", false},
{"const c float64 = 3.14", "c", "const p.c float64", false},
{"type t struct{f int}", "t", "type p.t struct{f int}"},
{"type t func(int)", "t", "type p.t func(int)"},
{"type t[P any] struct{f P}", "t", "type p.t[P any] struct{f P}"},
{"type t[P any] struct{f P}", "t.P", "type parameter P any"},
{"type C interface{m()}; type t[P C] struct{}", "t.P", "type parameter P p.C"},
{"type t struct{f int}", "t", "type p.t struct{f int}", false},
{"type t func(int)", "t", "type p.t func(int)", false},
{"type t[P any] struct{f P}", "t", "type p.t[P any] struct{f P}", false},
{"type t[P any] struct{f P}", "t.P", "type parameter P any", false},
{"type C interface{m()}; type t[P C] struct{}", "t.P", "type parameter P p.C", false},
{"type t = struct{f int}", "t", "type p.t = struct{f int}"},
{"type t = func(int)", "t", "type p.t = func(int)"},
{"type t = struct{f int}", "t", "type p.t = struct{f int}", false},
{"type t = func(int)", "t", "type p.t = func(int)", false},
{"type A = B; type B = int", "A", "type p.A = p.B", true},
{"var v int", "v", "var p.v int"},
{"var v int", "v", "var p.v int", false},
{"func f(int) string", "f", "func p.f(int) string"},
{"func g[P any](x P){}", "g", "func p.g[P any](x P)"},
{"func g[P interface{~int}](x P){}", "g.P", "type parameter P interface{~int}"},
{"", "any", "type any = interface{}"},
{"func f(int) string", "f", "func p.f(int) string", false},
{"func g[P any](x P){}", "g", "func p.g[P any](x P)", false},
{"func g[P interface{~int}](x P){}", "g.P", "type parameter P interface{~int}", false},
{"", "any", "type any = interface{}", false},
}
func TestObjectString(t *testing.T) {
testenv.MustHaveGoBuild(t)
for _, test := range testObjects {
src := "package p; " + test.src
pkg, err := typecheck(src, nil, nil)
if err != nil {
t.Errorf("%s: %s", src, err)
continue
}
names := strings.Split(test.obj, ".")
if len(names) != 1 && len(names) != 2 {
t.Errorf("%s: invalid object path %s", test.src, test.obj)
continue
}
_, obj := pkg.Scope().LookupParent(names[0], nopos)
if obj == nil {
t.Errorf("%s: %s not found", test.src, names[0])
continue
}
if len(names) == 2 {
if typ, ok := obj.Type().(interface{ TypeParams() *TypeParamList }); ok {
obj = lookupTypeParamObj(typ.TypeParams(), names[1])
if obj == nil {
t.Errorf("%s: %s not found", test.src, test.obj)
continue
}
} else {
t.Errorf("%s: %s has no type parameters", test.src, names[0])
continue
for i, test := range testObjects {
t.Run(fmt.Sprint(i), func(t *testing.T) {
if test.alias {
t.Setenv("GODEBUG", "gotypesalias=1")
}
}
if got := obj.String(); got != test.want {
t.Errorf("%s: got %s, want %s", test.src, got, test.want)
}
src := "package p; " + test.src
pkg, err := typecheck(src, nil, nil)
if err != nil {
t.Fatalf("%s: %s", src, err)
}
names := strings.Split(test.obj, ".")
if len(names) != 1 && len(names) != 2 {
t.Fatalf("%s: invalid object path %s", test.src, test.obj)
}
_, obj := pkg.Scope().LookupParent(names[0], nopos)
if obj == nil {
t.Fatalf("%s: %s not found", test.src, names[0])
}
if len(names) == 2 {
if typ, ok := obj.Type().(interface{ TypeParams() *TypeParamList }); ok {
obj = lookupTypeParamObj(typ.TypeParams(), names[1])
if obj == nil {
t.Fatalf("%s: %s not found", test.src, test.obj)
}
} else {
t.Fatalf("%s: %s has no type parameters", test.src, names[0])
}
}
if got := obj.String(); got != test.want {
t.Errorf("%s: got %s, want %s", test.src, got, test.want)
}
})
}
}

View file

@ -545,10 +545,14 @@ func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) {
}
if tname.IsAlias() {
buf.WriteString(" =")
if alias, ok := typ.(*Alias); ok { // materialized? (gotypesalias=1)
typ = alias.fromRHS
}
} else if t, _ := typ.(*TypeParam); t != nil {
typ = t.bound
} else {
// TODO(gri) should this be fromRHS for *Named?
// (See discussion in #66559.)
typ = under(typ)
}
}

View file

@ -8,6 +8,7 @@
package types_test
import (
"fmt"
"internal/testenv"
"strings"
"testing"
@ -82,69 +83,72 @@ func TestEmbeddedMethod(t *testing.T) {
}
var testObjects = []struct {
src string
obj string
want string
src string
obj string
want string
alias bool // needs materialized aliases
}{
{"import \"io\"; var r io.Reader", "r", "var p.r io.Reader"},
{"import \"io\"; var r io.Reader", "r", "var p.r io.Reader", false},
{"const c = 1.2", "c", "const p.c untyped float"},
{"const c float64 = 3.14", "c", "const p.c float64"},
{"const c = 1.2", "c", "const p.c untyped float", false},
{"const c float64 = 3.14", "c", "const p.c float64", false},
{"type t struct{f int}", "t", "type p.t struct{f int}"},
{"type t func(int)", "t", "type p.t func(int)"},
{"type t[P any] struct{f P}", "t", "type p.t[P any] struct{f P}"},
{"type t[P any] struct{f P}", "t.P", "type parameter P any"},
{"type C interface{m()}; type t[P C] struct{}", "t.P", "type parameter P p.C"},
{"type t struct{f int}", "t", "type p.t struct{f int}", false},
{"type t func(int)", "t", "type p.t func(int)", false},
{"type t[P any] struct{f P}", "t", "type p.t[P any] struct{f P}", false},
{"type t[P any] struct{f P}", "t.P", "type parameter P any", false},
{"type C interface{m()}; type t[P C] struct{}", "t.P", "type parameter P p.C", false},
{"type t = struct{f int}", "t", "type p.t = struct{f int}"},
{"type t = func(int)", "t", "type p.t = func(int)"},
{"type t = struct{f int}", "t", "type p.t = struct{f int}", false},
{"type t = func(int)", "t", "type p.t = func(int)", false},
{"type A = B; type B = int", "A", "type p.A = p.B", true},
{"var v int", "v", "var p.v int"},
{"var v int", "v", "var p.v int", false},
{"func f(int) string", "f", "func p.f(int) string"},
{"func g[P any](x P){}", "g", "func p.g[P any](x P)"},
{"func g[P interface{~int}](x P){}", "g.P", "type parameter P interface{~int}"},
{"", "any", "type any = interface{}"},
{"func f(int) string", "f", "func p.f(int) string", false},
{"func g[P any](x P){}", "g", "func p.g[P any](x P)", false},
{"func g[P interface{~int}](x P){}", "g.P", "type parameter P interface{~int}", false},
{"", "any", "type any = interface{}", false},
}
func TestObjectString(t *testing.T) {
testenv.MustHaveGoBuild(t)
for _, test := range testObjects {
src := "package p; " + test.src
pkg, err := typecheck(src, nil, nil)
if err != nil {
t.Errorf("%s: %s", src, err)
continue
}
names := strings.Split(test.obj, ".")
if len(names) != 1 && len(names) != 2 {
t.Errorf("%s: invalid object path %s", test.src, test.obj)
continue
}
_, obj := pkg.Scope().LookupParent(names[0], nopos)
if obj == nil {
t.Errorf("%s: %s not found", test.src, names[0])
continue
}
if len(names) == 2 {
if typ, ok := obj.Type().(interface{ TypeParams() *TypeParamList }); ok {
obj = lookupTypeParamObj(typ.TypeParams(), names[1])
if obj == nil {
t.Errorf("%s: %s not found", test.src, test.obj)
continue
}
} else {
t.Errorf("%s: %s has no type parameters", test.src, names[0])
continue
for i, test := range testObjects {
t.Run(fmt.Sprint(i), func(t *testing.T) {
if test.alias {
t.Setenv("GODEBUG", "gotypesalias=1")
}
}
if got := obj.String(); got != test.want {
t.Errorf("%s: got %s, want %s", test.src, got, test.want)
}
src := "package p; " + test.src
pkg, err := typecheck(src, nil, nil)
if err != nil {
t.Fatalf("%s: %s", src, err)
}
names := strings.Split(test.obj, ".")
if len(names) != 1 && len(names) != 2 {
t.Fatalf("%s: invalid object path %s", test.src, test.obj)
}
_, obj := pkg.Scope().LookupParent(names[0], nopos)
if obj == nil {
t.Fatalf("%s: %s not found", test.src, names[0])
}
if len(names) == 2 {
if typ, ok := obj.Type().(interface{ TypeParams() *TypeParamList }); ok {
obj = lookupTypeParamObj(typ.TypeParams(), names[1])
if obj == nil {
t.Fatalf("%s: %s not found", test.src, test.obj)
}
} else {
t.Fatalf("%s: %s has no type parameters", test.src, names[0])
}
}
if got := obj.String(); got != test.want {
t.Errorf("%s: got %s, want %s", test.src, got, test.want)
}
})
}
}