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 {
 
        // comparable
        types2.Universe.Lookup("comparable").Type(),
 
-       // any
-       types2.Universe.Lookup("any").Type(),
+       // "any" has special handling: see usage of predeclared.
 }
 
 type anyType struct{}
 
 
        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
 
        // 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
 }
 
 // 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
                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()
+               })
+       }
+}
 
        "go/constant"
        "internal/godebug"
        . "internal/types/errors"
+       "sync/atomic"
 )
 
 // nopos indicates an unknown position
 // 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
 // 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()
 
 }
 
 func TestIssue50646(t *testing.T) {
-       anyType := Universe.Lookup("any").Type()
+       anyType := Universe.Lookup("any").Type().Underlying()
        comparableType := Universe.Lookup("comparable").Type()
 
        if !Comparable(anyType) {
 
        // 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
        }
 
 // 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
 
 
        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
                        }
 
                        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
 
        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
 )
        UntypedNil:     {UntypedNil, IsUntyped, "untyped nil"},
 }
 
-var aliases = [...]*Basic{
+var basicAliases = [...]*Basic{
        {Byte, IsInteger | IsUnsigned, "byte"},
        {Rune, IsInteger, "rune"},
 }
        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 }
        {
        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")
 }
 
        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 {
 
        // comparable
        types.Universe.Lookup("comparable").Type(),
 
-       // any
-       types.Universe.Lookup("any").Type(),
+       // "any" has special handling: see usage of predeclared.
 }
 
 type anyType 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
 }
 
 
 // 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
 }
 
 func TestAlias_Rhs(t *testing.T) {
-       t.Setenv("GODEBUG", "gotypesalias=1")
+       setGotypesalias(t, true)
        const src = `package p
 
 type A = B
                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")
+       }
+}
 
        "internal/godebug"
        . "internal/types/errors"
        "strings"
+       "sync/atomic"
 )
 
 // nopos, noposn indicate an unknown position
 // 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
 // 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()
 
 }
 
 func TestIssue50646(t *testing.T) {
-       anyType := Universe.Lookup("any").Type()
+       anyType := Universe.Lookup("any").Type().Underlying()
        comparableType := Universe.Lookup("comparable").Type()
 
        if !Comparable(anyType) {
 
        // 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
        }
 
 // 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
 
 
        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
                        }
 
                        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
                }
 
        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
 )
        UntypedNil:     {UntypedNil, IsUntyped, "untyped nil"},
 }
 
-var aliases = [...]*Basic{
+var basicAliases = [...]*Basic{
        {Byte, IsInteger | IsUnsigned, "byte"},
        {Rune, IsInteger, "rune"},
 }
        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 }
        {
        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")
 }