]> Cypherpunks repositories - gostls13.git/commitdiff
go/types, types: represent any using Alias
authorRob Findley <rfindley@google.com>
Fri, 19 Apr 2024 14:51:18 +0000 (14:51 +0000)
committerRobert Findley <rfindley@google.com>
Tue, 7 May 2024 20:08:23 +0000 (20:08 +0000)
When GODEBUG=gotypesalias=1 is set, use an actual Alias type to
represent any, rather than a legacy alias representation. This makes any
consistent with other interface aliases, and will eventually make
obsolete the various workarounds for formatting any as 'any' rather than
'interface{}'.

Since any is a global in the Universe scope, we must hijack Scope.Lookup
to select the correct representation. Of course, this also means that we
can't support type checking concurrently while mutating gotypesalias
(or, in the case of types2, Config.EnableAlias). Some care is taken to
ensure that the type checker panics in the event of this type of misuse.

For now, we must still support the legacy representation of any, and the
existing workarounds that look for a distinguished any pointer. This is
done by ensuring that both representations have the same underlying
pointer, and by updating workarounds to consider Underlying.

Fixes golang/go#66921

Change-Id: I81db7e8e15317b7a6ed3b406545db15a2fc42f57
Reviewed-on: https://go-review.googlesource.com/c/go/+/580355
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

23 files changed:
src/cmd/compile/internal/importer/iimport.go
src/cmd/compile/internal/importer/support.go
src/cmd/compile/internal/noder/writer.go
src/cmd/compile/internal/types2/api.go
src/cmd/compile/internal/types2/api_test.go
src/cmd/compile/internal/types2/check.go
src/cmd/compile/internal/types2/issues_test.go
src/cmd/compile/internal/types2/object.go
src/cmd/compile/internal/types2/scope.go
src/cmd/compile/internal/types2/typestring.go
src/cmd/compile/internal/types2/typexpr.go
src/cmd/compile/internal/types2/universe.go
src/go/internal/gcimporter/iimport.go
src/go/internal/gcimporter/support.go
src/go/types/api.go
src/go/types/api_test.go
src/go/types/check.go
src/go/types/issues_test.go
src/go/types/object.go
src/go/types/scope.go
src/go/types/typestring.go
src/go/types/typexpr.go
src/go/types/universe.go

index 498134755d9ab6c1deff53fd3d71d770ed5969af..4a7fece188d236e52442e881064d7871432a1dab 100644 (file)
@@ -131,6 +131,9 @@ func ImportData(imports map[string]*types2.Package, data, path string) (pkg *typ
        for i, pt := range predeclared {
                p.typCache[uint64(i)] = pt
        }
+       // Special handling for "any", whose representation may be changed by the
+       // gotypesalias GODEBUG variable.
+       p.typCache[uint64(len(predeclared))] = types2.Universe.Lookup("any").Type()
 
        pkgList := make([]*types2.Package, r.uint64())
        for i := range pkgList {
index 5810f5e172b8139767c27c67ffd5be4c5ba9306d..a443b4d8621e547844008136f4dbfa05ef5708f5 100644 (file)
@@ -130,8 +130,7 @@ var predeclared = []types2.Type{
        // comparable
        types2.Universe.Lookup("comparable").Type(),
 
-       // any
-       types2.Universe.Lookup("any").Type(),
+       // "any" has special handling: see usage of predeclared.
 }
 
 type anyType struct{}
index 785176b3b57b8454f9a4c2bfa76ae5bd870a125d..453b08dbf9761ef853a643452c736f4f15860e71 100644 (file)
@@ -569,7 +569,10 @@ func (pw *pkgWriter) typIdx(typ types2.Type, dict *writerDict) typeInfo {
 
        case *types2.Interface:
                // Handle "any" as reference to its TypeName.
-               if typ == anyTypeName.Type() {
+               // The underlying "any" interface is canonical, so this logic handles both
+               // GODEBUG=gotypesalias=1 (when any is represented as a types2.Alias), and
+               // gotypesalias=0.
+               if types2.Unalias(typ) == types2.Unalias(anyTypeName.Type()) {
                        w.Code(pkgbits.TypeNamed)
                        w.obj(anyTypeName, nil)
                        break
index f3931dd2628c2d5c601d959697705046ab5ad03d..b9ec874d4598cccb87a506200d1c8c0b938d1a6d 100644 (file)
@@ -176,9 +176,13 @@ type Config struct {
        // exactly one "%s" format, e.g. "[go.dev/e/%s]".
        ErrorURL string
 
-       // If EnableAlias is set, alias declarations produce an Alias type.
-       // Otherwise the alias information is only in the type name, which
-       // points directly to the actual (aliased) type.
+       // If EnableAlias is set, alias declarations produce an Alias type. Otherwise
+       // the alias information is only in the type name, which points directly to
+       // the actual (aliased) type.
+       //
+       // This setting must not differ among concurrent type-checking operations,
+       // since it affects the behavior of Universe.Lookup("any").
+       //
        // This flag will eventually be removed (with Go 1.24 at the earliest).
        EnableAlias bool
 }
index cf3c105f6c638391a8a9848e51db06958199fec3..5126ac51116cd92daf357c8a5c1acce8ac04e1d0 100644 (file)
@@ -1867,7 +1867,10 @@ func sameSlice(a, b []int) bool {
 // the correct result at various positions within the source.
 func TestScopeLookupParent(t *testing.T) {
        imports := make(testImporter)
-       conf := Config{Importer: imports}
+       conf := Config{
+               Importer:    imports,
+               EnableAlias: true, // must match default Universe.Lookup behavior
+       }
        var info Info
        makePkg := func(path, src string) {
                var err error
@@ -3022,3 +3025,25 @@ type C = int
                t.Errorf("A.Rhs = %s, want %s", got, want)
        }
 }
+
+// Test the hijacking described of "any" described in golang/go#66921, for
+// (concurrent) type checking.
+func TestAnyHijacking_Check(t *testing.T) {
+       for _, enableAlias := range []bool{false, true} {
+               t.Run(fmt.Sprintf("EnableAlias=%t", enableAlias), func(t *testing.T) {
+                       var wg sync.WaitGroup
+                       for i := 0; i < 10; i++ {
+                               wg.Add(1)
+                               go func() {
+                                       defer wg.Done()
+                                       pkg := mustTypecheck("package p; var x any", &Config{EnableAlias: enableAlias}, nil)
+                                       x := pkg.Scope().Lookup("x")
+                                       if _, gotAlias := x.Type().(*Alias); gotAlias != enableAlias {
+                                               t.Errorf(`Lookup("x").Type() is %T: got Alias: %t, want %t`, x.Type(), gotAlias, enableAlias)
+                                       }
+                               }()
+                       }
+                       wg.Wait()
+               })
+       }
+}
index ee7e2e86836a89cf18eb7aa40579c877f19ad751..9203a102177116ed671e19dc022295332ea9cb83 100644 (file)
@@ -12,6 +12,7 @@ import (
        "go/constant"
        "internal/godebug"
        . "internal/types/errors"
+       "sync/atomic"
 )
 
 // nopos indicates an unknown position
@@ -26,6 +27,29 @@ const debug = false // leave on during development
 // This GODEBUG flag will be removed in the near future (tentatively Go 1.24).
 var gotypesalias = godebug.New("gotypesalias")
 
+// _aliasAny changes the behavior of [Scope.Lookup] for "any" in the
+// [Universe] scope.
+//
+// This is necessary because while Alias creation is controlled by
+// [Config.EnableAlias], the representation of "any" is a global. In
+// [Scope.Lookup], we select this global representation based on the result of
+// [aliasAny], but as a result need to guard against this behavior changing
+// during the type checking pass. Therefore we implement the following rule:
+// any number of goroutines can type check concurrently with the same
+// EnableAlias value, but if any goroutine tries to type check concurrently
+// with a different EnableAlias value, we panic.
+//
+// To achieve this, _aliasAny is a state machine:
+//
+//     0:        no type checking is occurring
+//     negative: type checking is occurring without EnableAlias set
+//     positive: type checking is occurring with EnableAlias set
+var _aliasAny int32
+
+func aliasAny() bool {
+       return atomic.LoadInt32(&_aliasAny) >= 0 // default true
+}
+
 // exprInfo stores information about an untyped expression.
 type exprInfo struct {
        isLhs bool // expression is lhs operand of a shift with delayed type-check
@@ -397,6 +421,20 @@ func (check *Checker) Files(files []*syntax.File) (err error) {
 // syntax is properly type annotated even in a package containing
 // errors.
 func (check *Checker) checkFiles(files []*syntax.File) {
+       // Ensure that EnableAlias is consistent among concurrent type checking
+       // operations. See the documentation of [_aliasAny] for details.
+       if check.conf.EnableAlias {
+               if atomic.AddInt32(&_aliasAny, 1) <= 0 {
+                       panic("EnableAlias set while !EnableAlias type checking is ongoing")
+               }
+               defer atomic.AddInt32(&_aliasAny, -1)
+       } else {
+               if atomic.AddInt32(&_aliasAny, -1) >= 0 {
+                       panic("!EnableAlias set while EnableAlias type checking is ongoing")
+               }
+               defer atomic.AddInt32(&_aliasAny, 1)
+       }
+
        print := func(msg string) {
                if check.conf.Trace {
                        fmt.Println()
index b087550b8087ad802354a7d3e07a04415abd5940..3d500811d4a67a0b51fa72296cb7296a6c25700b 100644 (file)
@@ -600,7 +600,7 @@ var _ T = template /* ERRORx "cannot use.*text/template.* as T value" */.Templat
 }
 
 func TestIssue50646(t *testing.T) {
-       anyType := Universe.Lookup("any").Type()
+       anyType := Universe.Lookup("any").Type().Underlying()
        comparableType := Universe.Lookup("comparable").Type()
 
        if !Comparable(anyType) {
index 3026777cada84c357f0110df6cf70bf9497703b2..f9a25473a15cd7e4d26146edfd438aca0044435e 100644 (file)
@@ -577,7 +577,7 @@ func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) {
        // Special handling for any: because WriteType will format 'any' as 'any',
        // resulting in the object string `type any = any` rather than `type any =
        // interface{}`. To avoid this, swap in a different empty interface.
-       if obj == universeAny {
+       if obj.Name() == "any" && obj.Parent() == Universe {
                assert(Identical(typ, &emptyInterface))
                typ = &emptyInterface
        }
index b75e5cbaf7cd182700dfaa95cc4fa1ba3ae6dd9c..f5ad25e81e1c3ef2ddad263e3c83e3d303d99c68 100644 (file)
@@ -68,7 +68,19 @@ func (s *Scope) Child(i int) *Scope { return s.children[i] }
 // Lookup returns the object in scope s with the given name if such an
 // object exists; otherwise the result is nil.
 func (s *Scope) Lookup(name string) Object {
-       return resolve(name, s.elems[name])
+       obj := resolve(name, s.elems[name])
+       // Hijack Lookup for "any": with gotypesalias=1, we want the Universe to
+       // return an Alias for "any", and with gotypesalias=0 we want to return
+       // the legacy representation of aliases.
+       //
+       // This is rather tricky, but works out after auditing of the usage of
+       // s.elems. The only external API to access scope elements is Lookup.
+       //
+       // TODO: remove this once gotypesalias=0 is no longer supported.
+       if obj == universeAnyAlias && !aliasAny() {
+               return universeAnyNoAlias
+       }
+       return obj
 }
 
 // LookupParent follows the parent chain of scopes starting with s until
index 723c074e60b65832b9a8ea9a726f3eed40be046e..e067c3f4a7d03574109d255e2debda840b1519e4 100644 (file)
@@ -211,10 +211,11 @@ func (w *typeWriter) typ(typ Type) {
 
        case *Interface:
                if w.ctxt == nil {
-                       if t == universeAny.Type() {
+                       if t == universeAnyAlias.Type().Underlying() {
                                // When not hashing, we can try to improve type strings by writing "any"
-                               // for a type that is pointer-identical to universeAny. This logic should
-                               // be deprecated by more robust handling for aliases.
+                               // for a type that is pointer-identical to universeAny.
+                               // TODO(rfindley): this logic should not be necessary with
+                               // gotypesalias=1. Remove once that is always the case.
                                w.string("any")
                                break
                        }
index ec012c24eba93832dc0341622dbe20188fd02378..1e00c7bd86b813e0fe51c5015fac303c972deb32 100644 (file)
@@ -41,11 +41,19 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType
                        check.errorf(e, UndeclaredName, "undefined: %s", e.Value)
                }
                return
-       case universeAny, universeComparable:
+       case universeComparable:
                if !check.verifyVersionf(e, go1_18, "predeclared %s", e.Value) {
                        return // avoid follow-on errors
                }
        }
+       // Because the representation of any depends on gotypesalias, we don't check
+       // pointer identity here.
+       if obj.Name() == "any" && obj.Parent() == Universe {
+               if !check.verifyVersionf(e, go1_18, "predeclared %s", e.Value) {
+                       return // avoid follow-on errors
+               }
+       }
+
        check.recordUse(e, obj)
 
        // If we want a type but don't have one, stop right here and avoid potential problems
index 8e1e4a2bb78c6a032129fa3d8bfe86d33a3ca42d..9c76ac23730f0fbd10d5c68777188831ee6cae11 100644 (file)
@@ -23,7 +23,8 @@ var (
        universeIota       Object
        universeByte       Type // uint8 alias, but has name "byte"
        universeRune       Type // int32 alias, but has name "rune"
-       universeAny        Object
+       universeAnyNoAlias *TypeName
+       universeAnyAlias   *TypeName
        universeError      Type
        universeComparable Object
 )
@@ -65,7 +66,7 @@ var Typ = [...]*Basic{
        UntypedNil:     {UntypedNil, IsUntyped, "untyped nil"},
 }
 
-var aliases = [...]*Basic{
+var basicAliases = [...]*Basic{
        {Byte, IsInteger | IsUnsigned, "byte"},
        {Rune, IsInteger, "rune"},
 }
@@ -74,15 +75,41 @@ func defPredeclaredTypes() {
        for _, t := range Typ {
                def(NewTypeName(nopos, nil, t.name, t))
        }
-       for _, t := range aliases {
+       for _, t := range basicAliases {
                def(NewTypeName(nopos, nil, t.name, t))
        }
 
        // type any = interface{}
-       // Note: don't use &emptyInterface for the type of any. Using a unique
-       // pointer allows us to detect any and format it as "any" rather than
-       // interface{}, which clarifies user-facing error messages significantly.
-       def(NewTypeName(nopos, nil, "any", &Interface{complete: true, tset: &topTypeSet}))
+       //
+       // Implement two representations of any: one for the legacy gotypesalias=0,
+       // and one for gotypesalias=1. This is necessary for consistent
+       // representation of interface aliases during type checking, and is
+       // implemented via hijacking [Scope.Lookup] for the [Universe] scope.
+       //
+       // Both representations use the same distinguished pointer for their RHS
+       // interface type, allowing us to detect any (even with the legacy
+       // representation), and format it as "any" rather than interface{}, which
+       // clarifies user-facing error messages significantly.
+       //
+       // TODO(rfindley): once the gotypesalias GODEBUG variable is obsolete (and we
+       // consistently use the Alias node), we should be able to clarify user facing
+       // error messages without using a distinguished pointer for the any
+       // interface.
+       {
+               universeAnyNoAlias = NewTypeName(nopos, nil, "any", &Interface{complete: true, tset: &topTypeSet})
+               universeAnyNoAlias.setColor(black)
+               // ensure that the any TypeName reports a consistent Parent, after
+               // hijacking Universe.Lookup with gotypesalias=0.
+               universeAnyNoAlias.setParent(Universe)
+
+               // It shouldn't matter which representation of any is actually inserted
+               // into the Universe, but we lean toward the future and insert the Alias
+               // representation.
+               universeAnyAlias = NewTypeName(nopos, nil, "any", nil)
+               universeAnyAlias.setColor(black)
+               _ = NewAlias(universeAnyAlias, universeAnyNoAlias.Type().Underlying()) // Link TypeName and Alias
+               def(universeAnyAlias)
+       }
 
        // type error interface{ Error() string }
        {
@@ -250,7 +277,6 @@ func init() {
        universeIota = Universe.Lookup("iota")
        universeByte = Universe.Lookup("byte").Type()
        universeRune = Universe.Lookup("rune").Type()
-       universeAny = Universe.Lookup("any")
        universeError = Universe.Lookup("error").Type()
        universeComparable = Universe.Lookup("comparable")
 }
index 9e3c945b5674c3b4069cc12d62bf2f73b91f1d2a..59cd411ceee5d4c9762be6b16f8059b8394d8fb0 100644 (file)
@@ -145,6 +145,9 @@ func iImportData(fset *token.FileSet, imports map[string]*types.Package, dataRea
        for i, pt := range predeclared {
                p.typCache[uint64(i)] = pt
        }
+       // Special handling for "any", whose representation may be changed by the
+       // gotypesalias GODEBUG variable.
+       p.typCache[uint64(len(predeclared))] = types.Universe.Lookup("any").Type()
 
        pkgList := make([]*types.Package, r.uint64())
        for i := range pkgList {
index 7ed8c9a4043d1cf3e22af4678e30667c1c1757b0..b5e5f6ab544bc6076fab5fe5ad499599f70d54b2 100644 (file)
@@ -147,8 +147,7 @@ var predeclared = []types.Type{
        // comparable
        types.Universe.Lookup("comparable").Type(),
 
-       // any
-       types.Universe.Lookup("any").Type(),
+       // "any" has special handling: see usage of predeclared.
 }
 
 type anyType struct{}
index cfe86f9dd6ce453d1d3c0217bccd236d9367b186..2db67e5329cf1589f014901898cfdf410de8e7a8 100644 (file)
@@ -181,9 +181,13 @@ type Config struct {
        // exactly one "%s" format, e.g. "[go.dev/e/%s]".
        _ErrorURL string
 
-       // If _EnableAlias is set, alias declarations produce an Alias type.
-       // Otherwise the alias information is only in the type name, which
-       // points directly to the actual (aliased) type.
+       // If EnableAlias is set, alias declarations produce an Alias type. Otherwise
+       // the alias information is only in the type name, which points directly to
+       // the actual (aliased) type.
+       //
+       // This setting must not differ among concurrent type-checking operations,
+       // since it affects the behavior of Universe.Lookup("any").
+       //
        // This flag will eventually be removed (with Go 1.24 at the earliest).
        _EnableAlias bool
 }
index 6f8dddb936fb572498e4fc05a70eeb764fa21650..38cd7f7ec2d8032b593dd19f99f3e233fbc04d0b 100644 (file)
@@ -2997,7 +2997,7 @@ func TestTooNew(t *testing.T) {
 
 // This is a regression test for #66704.
 func TestUnaliasTooSoonInCycle(t *testing.T) {
-       t.Setenv("GODEBUG", "gotypesalias=1")
+       setGotypesalias(t, true)
        const src = `package a
 
 var x T[B] // this appears to cause Unalias to be called on B while still Invalid
@@ -3016,7 +3016,7 @@ type B = T[A]
 }
 
 func TestAlias_Rhs(t *testing.T) {
-       t.Setenv("GODEBUG", "gotypesalias=1")
+       setGotypesalias(t, true)
        const src = `package p
 
 type A = B
@@ -3032,3 +3032,48 @@ type C = int
                t.Errorf("A.Rhs = %s, want %s", got, want)
        }
 }
+
+// Test the hijacking described of "any" described in golang/go#66921, for type
+// checking.
+func TestAnyHijacking_Check(t *testing.T) {
+       for _, enableAlias := range []bool{false, true} {
+               t.Run(fmt.Sprintf("EnableAlias=%t", enableAlias), func(t *testing.T) {
+                       setGotypesalias(t, enableAlias)
+                       var wg sync.WaitGroup
+                       for i := 0; i < 10; i++ {
+                               wg.Add(1)
+                               go func() {
+                                       defer wg.Done()
+                                       pkg := mustTypecheck("package p; var x any", nil, nil)
+                                       x := pkg.Scope().Lookup("x")
+                                       if _, gotAlias := x.Type().(*Alias); gotAlias != enableAlias {
+                                               t.Errorf(`Lookup("x").Type() is %T: got Alias: %t, want %t`, x.Type(), gotAlias, enableAlias)
+                                       }
+                               }()
+                       }
+                       wg.Wait()
+               })
+       }
+}
+
+// Test the hijacking described of "any" described in golang/go#66921, for
+// Scope.Lookup outside of type checking.
+func TestAnyHijacking_Lookup(t *testing.T) {
+       for _, enableAlias := range []bool{false, true} {
+               t.Run(fmt.Sprintf("EnableAlias=%t", enableAlias), func(t *testing.T) {
+                       setGotypesalias(t, enableAlias)
+                       a := Universe.Lookup("any")
+                       if _, gotAlias := a.Type().(*Alias); gotAlias != enableAlias {
+                               t.Errorf(`Lookup("x").Type() is %T: got Alias: %t, want %t`, a.Type(), gotAlias, enableAlias)
+                       }
+               })
+       }
+}
+
+func setGotypesalias(t *testing.T, enable bool) {
+       if enable {
+               t.Setenv("GODEBUG", "gotypesalias=1")
+       } else {
+               t.Setenv("GODEBUG", "gotypesalias=0")
+       }
+}
index 94f2bbfd786f005c34a6ab1f294b005cf7234a3d..789c130e1ce1aef769046a3d6a74fa88dea77fb6 100644 (file)
@@ -14,6 +14,7 @@ import (
        "internal/godebug"
        . "internal/types/errors"
        "strings"
+       "sync/atomic"
 )
 
 // nopos, noposn indicate an unknown position
@@ -29,6 +30,36 @@ const debug = false // leave on during development
 // This GODEBUG flag will be removed in the near future (tentatively Go 1.24).
 var gotypesalias = godebug.New("gotypesalias")
 
+// _aliasAny changes the behavior of [Scope.Lookup] for "any" in the
+// [Universe] scope.
+//
+// This is necessary because while Alias creation is controlled by
+// [Config._EnableAlias], based on the gotypealias variable, the representation
+// of "any" is a global. In [Scope.Lookup], we select this global
+// representation based on the result of [aliasAny], but as a result need to
+// guard against this behavior changing during the type checking pass.
+// Therefore we implement the following rule: any number of goroutines can type
+// check concurrently with the same EnableAlias value, but if any goroutine
+// tries to type check concurrently with a different EnableAlias value, we
+// panic.
+//
+// To achieve this, _aliasAny is a state machine:
+//
+//     0:        no type checking is occurring
+//     negative: type checking is occurring without _EnableAlias set
+//     positive: type checking is occurring with _EnableAlias set
+var _aliasAny int32
+
+func aliasAny() bool {
+       v := gotypesalias.Value()
+       useAlias := v != "0"
+       inuse := atomic.LoadInt32(&_aliasAny)
+       if inuse != 0 && useAlias != (inuse > 0) {
+               panic(fmt.Sprintf("gotypealias mutated during type checking, gotypesalias=%s, inuse=%d", v, inuse))
+       }
+       return useAlias
+}
+
 // exprInfo stores information about an untyped expression.
 type exprInfo struct {
        isLhs bool // expression is lhs operand of a shift with delayed type-check
@@ -405,6 +436,20 @@ func (check *Checker) Files(files []*ast.File) (err error) {
 // syntax is properly type annotated even in a package containing
 // errors.
 func (check *Checker) checkFiles(files []*ast.File) {
+       // Ensure that _EnableAlias is consistent among concurrent type checking
+       // operations. See the documentation of [_aliasAny] for details.
+       if check.conf._EnableAlias {
+               if atomic.AddInt32(&_aliasAny, 1) <= 0 {
+                       panic("EnableAlias set while !EnableAlias type checking is ongoing")
+               }
+               defer atomic.AddInt32(&_aliasAny, -1)
+       } else {
+               if atomic.AddInt32(&_aliasAny, -1) >= 0 {
+                       panic("!EnableAlias set while EnableAlias type checking is ongoing")
+               }
+               defer atomic.AddInt32(&_aliasAny, 1)
+       }
+
        print := func(msg string) {
                if check.conf._Trace {
                        fmt.Println()
index 379d833bf21f6955b74f8d699c87caf4a7412a1d..d51dd94cf3b173430f2b7b144d9affbd00e1cdad 100644 (file)
@@ -607,7 +607,7 @@ var _ T = template /* ERRORx "cannot use.*text/template.* as T value" */.Templat
 }
 
 func TestIssue50646(t *testing.T) {
-       anyType := Universe.Lookup("any").Type()
+       anyType := Universe.Lookup("any").Type().Underlying()
        comparableType := Universe.Lookup("comparable").Type()
 
        if !Comparable(anyType) {
index d564d37e7a6f1a54e61f92d5bfea2f0a53c72814..cc014188324a3e25381e0fa3c1baa8365081ad95 100644 (file)
@@ -580,7 +580,7 @@ func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) {
        // Special handling for any: because WriteType will format 'any' as 'any',
        // resulting in the object string `type any = any` rather than `type any =
        // interface{}`. To avoid this, swap in a different empty interface.
-       if obj == universeAny {
+       if obj.Name() == "any" && obj.Parent() == Universe {
                assert(Identical(typ, &emptyInterface))
                typ = &emptyInterface
        }
index 476b26ac3fd4c39ae76ac4693b09cefc8c7e7c7d..176928eda92a55a6d032276c7a85fe9955fb26e4 100644 (file)
@@ -71,7 +71,19 @@ func (s *Scope) Child(i int) *Scope { return s.children[i] }
 // Lookup returns the object in scope s with the given name if such an
 // object exists; otherwise the result is nil.
 func (s *Scope) Lookup(name string) Object {
-       return resolve(name, s.elems[name])
+       obj := resolve(name, s.elems[name])
+       // Hijack Lookup for "any": with gotypesalias=1, we want the Universe to
+       // return an Alias for "any", and with gotypesalias=0 we want to return
+       // the legacy representation of aliases.
+       //
+       // This is rather tricky, but works out after auditing of the usage of
+       // s.elems. The only external API to access scope elements is Lookup.
+       //
+       // TODO: remove this once gotypesalias=0 is no longer supported.
+       if obj == universeAnyAlias && !aliasAny() {
+               return universeAnyNoAlias
+       }
+       return obj
 }
 
 // LookupParent follows the parent chain of scopes starting with s until
index 8ce9e869948ac808e3f2eb412a6902e98a1d8be8..9285bcbb818fc739bbc25d874476fde82e916484 100644 (file)
@@ -214,10 +214,11 @@ func (w *typeWriter) typ(typ Type) {
 
        case *Interface:
                if w.ctxt == nil {
-                       if t == universeAny.Type() {
+                       if t == universeAnyAlias.Type().Underlying() {
                                // When not hashing, we can try to improve type strings by writing "any"
-                               // for a type that is pointer-identical to universeAny. This logic should
-                               // be deprecated by more robust handling for aliases.
+                               // for a type that is pointer-identical to universeAny.
+                               // TODO(rfindley): this logic should not be necessary with
+                               // gotypesalias=1. Remove once that is always the case.
                                w.string("any")
                                break
                        }
index 4bbc8b24481209bd31ee81943e37a5728497a049..302de4caab68720736fa73d11c2f120632cf8518 100644 (file)
@@ -42,7 +42,14 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *TypeName, wantType bo
                        check.errorf(e, UndeclaredName, "undefined: %s", e.Name)
                }
                return
-       case universeAny, universeComparable:
+       case universeComparable:
+               if !check.verifyVersionf(e, go1_18, "predeclared %s", e.Name) {
+                       return // avoid follow-on errors
+               }
+       }
+       // Because the representation of any depends on gotypesalias, we don't check
+       // pointer identity here.
+       if obj.Name() == "any" && obj.Parent() == Universe {
                if !check.verifyVersionf(e, go1_18, "predeclared %s", e.Name) {
                        return // avoid follow-on errors
                }
index 9a882896c9fa9c853906549917a7551827aeb943..09b882ce055551c6d3529926707780957bc76163 100644 (file)
@@ -26,7 +26,8 @@ var (
        universeIota       Object
        universeByte       Type // uint8 alias, but has name "byte"
        universeRune       Type // int32 alias, but has name "rune"
-       universeAny        Object
+       universeAnyNoAlias *TypeName
+       universeAnyAlias   *TypeName
        universeError      Type
        universeComparable Object
 )
@@ -68,7 +69,7 @@ var Typ = []*Basic{
        UntypedNil:     {UntypedNil, IsUntyped, "untyped nil"},
 }
 
-var aliases = [...]*Basic{
+var basicAliases = [...]*Basic{
        {Byte, IsInteger | IsUnsigned, "byte"},
        {Rune, IsInteger, "rune"},
 }
@@ -77,15 +78,41 @@ func defPredeclaredTypes() {
        for _, t := range Typ {
                def(NewTypeName(nopos, nil, t.name, t))
        }
-       for _, t := range aliases {
+       for _, t := range basicAliases {
                def(NewTypeName(nopos, nil, t.name, t))
        }
 
        // type any = interface{}
-       // Note: don't use &emptyInterface for the type of any. Using a unique
-       // pointer allows us to detect any and format it as "any" rather than
-       // interface{}, which clarifies user-facing error messages significantly.
-       def(NewTypeName(nopos, nil, "any", &Interface{complete: true, tset: &topTypeSet}))
+       //
+       // Implement two representations of any: one for the legacy gotypesalias=0,
+       // and one for gotypesalias=1. This is necessary for consistent
+       // representation of interface aliases during type checking, and is
+       // implemented via hijacking [Scope.Lookup] for the [Universe] scope.
+       //
+       // Both representations use the same distinguished pointer for their RHS
+       // interface type, allowing us to detect any (even with the legacy
+       // representation), and format it as "any" rather than interface{}, which
+       // clarifies user-facing error messages significantly.
+       //
+       // TODO(rfindley): once the gotypesalias GODEBUG variable is obsolete (and we
+       // consistently use the Alias node), we should be able to clarify user facing
+       // error messages without using a distinguished pointer for the any
+       // interface.
+       {
+               universeAnyNoAlias = NewTypeName(nopos, nil, "any", &Interface{complete: true, tset: &topTypeSet})
+               universeAnyNoAlias.setColor(black)
+               // ensure that the any TypeName reports a consistent Parent, after
+               // hijacking Universe.Lookup with gotypesalias=0.
+               universeAnyNoAlias.setParent(Universe)
+
+               // It shouldn't matter which representation of any is actually inserted
+               // into the Universe, but we lean toward the future and insert the Alias
+               // representation.
+               universeAnyAlias = NewTypeName(nopos, nil, "any", nil)
+               universeAnyAlias.setColor(black)
+               _ = NewAlias(universeAnyAlias, universeAnyNoAlias.Type().Underlying()) // Link TypeName and Alias
+               def(universeAnyAlias)
+       }
 
        // type error interface{ Error() string }
        {
@@ -253,7 +280,6 @@ func init() {
        universeIota = Universe.Lookup("iota")
        universeByte = Universe.Lookup("byte").Type()
        universeRune = Universe.Lookup("rune").Type()
-       universeAny = Universe.Lookup("any")
        universeError = Universe.Lookup("error").Type()
        universeComparable = Universe.Lookup("comparable")
 }