From 61a3ee54411086d4bf996b65dd3bc2b7432f9b51 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Wed, 27 Mar 2024 09:40:24 -0400 Subject: [PATCH] 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 LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley --- src/cmd/compile/internal/types2/object.go | 4 + .../compile/internal/types2/object_test.go | 100 +++++++++--------- src/go/types/object.go | 4 + src/go/types/object_test.go | 100 +++++++++--------- 4 files changed, 112 insertions(+), 96 deletions(-) diff --git a/src/cmd/compile/internal/types2/object.go b/src/cmd/compile/internal/types2/object.go index e48a4895a77b0..776986f1112ea 100644 --- a/src/cmd/compile/internal/types2/object.go +++ b/src/cmd/compile/internal/types2/object.go @@ -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) } } diff --git a/src/cmd/compile/internal/types2/object_test.go b/src/cmd/compile/internal/types2/object_test.go index ef1a864ec98bf..20a9a5fd0c856 100644 --- a/src/cmd/compile/internal/types2/object_test.go +++ b/src/cmd/compile/internal/types2/object_test.go @@ -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 - } + for i, test := range testObjects { + t.Run(fmt.Sprint(i), func(t *testing.T) { + if test.alias { + t.Setenv("GODEBUG", "gotypesalias=1") + } - 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 + 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]) } - } else { - t.Errorf("%s: %s has no type parameters", test.src, names[0]) - continue } - } - if got := obj.String(); got != test.want { - t.Errorf("%s: got %s, want %s", test.src, got, test.want) - } + if got := obj.String(); got != test.want { + t.Errorf("%s: got %s, want %s", test.src, got, test.want) + } + }) } } diff --git a/src/go/types/object.go b/src/go/types/object.go index 7662e94aaf99b..400b47ebe10c5 100644 --- a/src/go/types/object.go +++ b/src/go/types/object.go @@ -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) } } diff --git a/src/go/types/object_test.go b/src/go/types/object_test.go index c4f361e370342..a9f7eed69cc86 100644 --- a/src/go/types/object_test.go +++ b/src/go/types/object_test.go @@ -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 - } + for i, test := range testObjects { + t.Run(fmt.Sprint(i), func(t *testing.T) { + if test.alias { + t.Setenv("GODEBUG", "gotypesalias=1") + } - 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 + 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]) } - } else { - t.Errorf("%s: %s has no type parameters", test.src, names[0]) - continue } - } - if got := obj.String(); got != test.want { - t.Errorf("%s: got %s, want %s", test.src, got, test.want) - } + if got := obj.String(); got != test.want { + t.Errorf("%s: got %s, want %s", test.src, got, test.want) + } + }) } }