diff options
Diffstat (limited to 'src/cmd')
| -rw-r--r-- | src/cmd/compile/internal/importer/iimport.go | 3 | ||||
| -rw-r--r-- | src/cmd/compile/internal/importer/support.go | 3 | ||||
| -rw-r--r-- | src/cmd/compile/internal/noder/writer.go | 5 | ||||
| -rw-r--r-- | src/cmd/compile/internal/types2/api.go | 10 | ||||
| -rw-r--r-- | src/cmd/compile/internal/types2/api_test.go | 27 | ||||
| -rw-r--r-- | src/cmd/compile/internal/types2/check.go | 38 | ||||
| -rw-r--r-- | src/cmd/compile/internal/types2/issues_test.go | 2 | ||||
| -rw-r--r-- | src/cmd/compile/internal/types2/object.go | 2 | ||||
| -rw-r--r-- | src/cmd/compile/internal/types2/scope.go | 14 | ||||
| -rw-r--r-- | src/cmd/compile/internal/types2/typestring.go | 7 | ||||
| -rw-r--r-- | src/cmd/compile/internal/types2/typexpr.go | 10 | ||||
| -rw-r--r-- | src/cmd/compile/internal/types2/universe.go | 42 |
12 files changed, 141 insertions, 22 deletions
diff --git a/src/cmd/compile/internal/importer/iimport.go b/src/cmd/compile/internal/importer/iimport.go index 498134755d..4a7fece188 100644 --- a/src/cmd/compile/internal/importer/iimport.go +++ b/src/cmd/compile/internal/importer/iimport.go @@ -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 { diff --git a/src/cmd/compile/internal/importer/support.go b/src/cmd/compile/internal/importer/support.go index 5810f5e172..a443b4d862 100644 --- a/src/cmd/compile/internal/importer/support.go +++ b/src/cmd/compile/internal/importer/support.go @@ -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{} diff --git a/src/cmd/compile/internal/noder/writer.go b/src/cmd/compile/internal/noder/writer.go index 785176b3b5..453b08dbf9 100644 --- a/src/cmd/compile/internal/noder/writer.go +++ b/src/cmd/compile/internal/noder/writer.go @@ -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 diff --git a/src/cmd/compile/internal/types2/api.go b/src/cmd/compile/internal/types2/api.go index f3931dd262..b9ec874d45 100644 --- a/src/cmd/compile/internal/types2/api.go +++ b/src/cmd/compile/internal/types2/api.go @@ -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 } diff --git a/src/cmd/compile/internal/types2/api_test.go b/src/cmd/compile/internal/types2/api_test.go index cf3c105f6c..5126ac5111 100644 --- a/src/cmd/compile/internal/types2/api_test.go +++ b/src/cmd/compile/internal/types2/api_test.go @@ -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() + }) + } +} diff --git a/src/cmd/compile/internal/types2/check.go b/src/cmd/compile/internal/types2/check.go index ee7e2e8683..9203a10217 100644 --- a/src/cmd/compile/internal/types2/check.go +++ b/src/cmd/compile/internal/types2/check.go @@ -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() diff --git a/src/cmd/compile/internal/types2/issues_test.go b/src/cmd/compile/internal/types2/issues_test.go index b087550b80..3d500811d4 100644 --- a/src/cmd/compile/internal/types2/issues_test.go +++ b/src/cmd/compile/internal/types2/issues_test.go @@ -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) { diff --git a/src/cmd/compile/internal/types2/object.go b/src/cmd/compile/internal/types2/object.go index 3026777cad..f9a25473a1 100644 --- a/src/cmd/compile/internal/types2/object.go +++ b/src/cmd/compile/internal/types2/object.go @@ -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 } diff --git a/src/cmd/compile/internal/types2/scope.go b/src/cmd/compile/internal/types2/scope.go index b75e5cbaf7..f5ad25e81e 100644 --- a/src/cmd/compile/internal/types2/scope.go +++ b/src/cmd/compile/internal/types2/scope.go @@ -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 diff --git a/src/cmd/compile/internal/types2/typestring.go b/src/cmd/compile/internal/types2/typestring.go index 723c074e60..e067c3f4a7 100644 --- a/src/cmd/compile/internal/types2/typestring.go +++ b/src/cmd/compile/internal/types2/typestring.go @@ -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 } diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go index ec012c24eb..1e00c7bd86 100644 --- a/src/cmd/compile/internal/types2/typexpr.go +++ b/src/cmd/compile/internal/types2/typexpr.go @@ -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 diff --git a/src/cmd/compile/internal/types2/universe.go b/src/cmd/compile/internal/types2/universe.go index 8e1e4a2bb7..9c76ac2373 100644 --- a/src/cmd/compile/internal/types2/universe.go +++ b/src/cmd/compile/internal/types2/universe.go @@ -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") } |
