mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
go/types, types2: change and enforce lifecycle of Named.fromRHS and Named.underlying fields
A type definition or alias declaration consists of a type name (LHS)
which is bound to a type expression (RHS) by the declaration.
This CL consistently uses the fromRHS fields of Named and Alias types
to represent that RHS type expression, and sets Named.underlying and
Alias.actual only once those types have been computed.
Currently, Named types use Named.underlying for some of this
functionality, which makes the code difficult to understand. Operations
which used Named.underlying now use Named.fromRHS.
For example, in:
type A = B
type B = int
A.fromRHS is B (Alias) and B.fromRHS is int (Basic).
Meanwhile, in:
type A B
type B int
A.underlying is B (Named) and B.underlying is int (Basic) initially.
Note that despite A.underlying pointing to B, B is not the underlying
type of A (it is int). At some point during type checking, A walks
through the chain A.underlying -> B.underlying -> int and sets
A.underlying to int.
While this approach works, it introduces some problems:
1. Whether A.underlying refers to the underlying type (int) or not
(B) depends on when the field is accessed.
2. There is no convenient mechanism to check if the underlying type
of B has been deduced. One can check if B.underlying is a named
type, but since B.underlying is already B's underlying type (int),
it's still ambiguous.
Operations derived from Named.underlying share similar problems. For
example, Named.expandUnderlying() (which substitutes type arguments)
returns an instantiated named type whose Named.underlying also may or
may not refer to its underlying type.
With this change, Named.underlying is nil as long as it is unknown, and
non-nil and not a named type once it is known. Additional assertions are
added to enforce that:
1. Named.underlying is not set until Named has been resolved.
2. Named is not resolved until Named.fromRHS is populated, unless it
is given explicit permission. This permission is briefly given
while type-checking declarations of named types to account for
cycles of alias types represented as TypeNames. It is also given to
named types created through NewNamed for backward compatibility.
This permission is revoked when SetUnderlying is called.
Accessors of Named.underlying are responsible for first resolving
the named type, unless they are in a context where they know the
type to already be resolved.
This change also exposed a bug in validType wherein the underlying
type for struct types containing invalid types did not have their
underlying type set to invalid (see #75194). This bug was exploited by a
test in x/tools, which has been disabled for Go 1.26 (via CL 700395).
Other minor adjustments are made for instantiated and loaded types.
Instantiated types have no RHS as they are not declared, and loaded
types set their RHS to the underlying from export data directly.
Minor simplifications are also made throughout.
Fixes #75194
Change-Id: I72644d7329c996eb1e67514063fe51c3ae06c38d
Reviewed-on: https://go-review.googlesource.com/c/go/+/695977
Auto-Submit: Mark Freeman <markfreeman@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Griesemer <gri@google.com>
This commit is contained in:
parent
41f5659347
commit
1099436f1b
21 changed files with 428 additions and 412 deletions
|
|
@ -476,7 +476,7 @@ func (check *Checker) isImportedConstraint(typ Type) bool {
|
|||
if named == nil || named.obj.pkg == check.pkg || named.obj.pkg == nil {
|
||||
return false
|
||||
}
|
||||
u, _ := named.under().(*Interface)
|
||||
u, _ := named.Underlying().(*Interface)
|
||||
return u != nil && !u.IsMethodSet()
|
||||
}
|
||||
|
||||
|
|
@ -558,28 +558,33 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *TypeN
|
|||
named := check.newNamed(obj, nil, nil)
|
||||
setDefType(def, named)
|
||||
|
||||
// The RHS of a named N can be nil if, for example, N is defined as a cycle of aliases with
|
||||
// gotypesalias=0. Consider:
|
||||
//
|
||||
// type D N // N.resolve() will panic
|
||||
// type N A
|
||||
// type A = N // N.fromRHS is not set before N.resolve(), since A does not call setDefType
|
||||
//
|
||||
// There is likely a better way to detect such cases, but it may not be worth the effort.
|
||||
// Instead, we briefly permit a nil N.fromRHS while type-checking D.
|
||||
named.allowNilRHS = true
|
||||
defer (func() { named.allowNilRHS = false })()
|
||||
|
||||
if tdecl.TParamList != nil {
|
||||
check.openScope(tdecl, "type parameters")
|
||||
defer check.closeScope()
|
||||
check.collectTypeParams(&named.tparams, tdecl.TParamList)
|
||||
}
|
||||
|
||||
// determine underlying type of named
|
||||
rhs = check.definedType(tdecl.Type, obj)
|
||||
assert(rhs != nil)
|
||||
named.fromRHS = rhs
|
||||
|
||||
// If the underlying type was not set while type-checking the right-hand
|
||||
// side, it is invalid and an error should have been reported elsewhere.
|
||||
if named.underlying == nil {
|
||||
named.underlying = Typ[Invalid]
|
||||
}
|
||||
|
||||
// spec: "In a type definition the given type cannot be a type parameter."
|
||||
// (See also go.dev/issue/45639.)
|
||||
if isTypeParam(rhs) {
|
||||
check.error(tdecl.Type, MisplacedTypeParam, "cannot use a type parameter as RHS in type declaration")
|
||||
named.underlying = Typ[Invalid]
|
||||
named.fromRHS = Typ[Invalid]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -721,7 +726,7 @@ func (check *Checker) collectMethods(obj *TypeName) {
|
|||
}
|
||||
|
||||
func (check *Checker) checkFieldUniqueness(base *Named) {
|
||||
if t, _ := base.under().(*Struct); t != nil {
|
||||
if t, _ := base.Underlying().(*Struct); t != nil {
|
||||
var mset objset
|
||||
for i := 0; i < base.NumMethods(); i++ {
|
||||
m := base.Method(i)
|
||||
|
|
|
|||
|
|
@ -62,14 +62,14 @@ import (
|
|||
// - We say that a Named type is "resolved" if its RHS information has been
|
||||
// loaded or fully type-checked. For Named types constructed from export
|
||||
// data, this may involve invoking a loader function to extract information
|
||||
// from export data. For instantiated named types this involves reading
|
||||
// information from their origin.
|
||||
// from export data. For instantiated Named types this involves reading
|
||||
// information from their origin and substituting type arguments into a
|
||||
// "synthetic" RHS; this process is called "expanding" the RHS (see below).
|
||||
// - We say that a Named type is "expanded" if it is an instantiated type and
|
||||
// type parameters in its underlying type and methods have been substituted
|
||||
// with the type arguments from the instantiation. A type may be partially
|
||||
// expanded if some but not all of these details have been substituted.
|
||||
// Similarly, we refer to these individual details (underlying type or
|
||||
// method) as being "expanded".
|
||||
// type parameters in its RHS and methods have been substituted with the type
|
||||
// arguments from the instantiation. A type may be partially expanded if some
|
||||
// but not all of these details have been substituted. Similarly, we refer to
|
||||
// these individual details (RHS or method) as being "expanded".
|
||||
// - When all information is known for a named type, we say it is "complete".
|
||||
//
|
||||
// Some invariants to keep in mind: each declared Named type has a single
|
||||
|
|
@ -107,18 +107,17 @@ type Named struct {
|
|||
check *Checker // non-nil during type-checking; nil otherwise
|
||||
obj *TypeName // corresponding declared object for declared types; see above for instantiated types
|
||||
|
||||
// fromRHS holds the type (on RHS of declaration) this *Named type is derived
|
||||
// from (for cycle reporting). Only used by validType, and therefore does not
|
||||
// require synchronization.
|
||||
fromRHS Type
|
||||
// flags indicating temporary violations of the invariants for fromRHS and underlying
|
||||
allowNilRHS bool // same as below, as well as briefly during checking of a type declaration
|
||||
allowNilUnderlying bool // may be true from creation via [NewNamed] until [Named.SetUnderlying]
|
||||
|
||||
// information for instantiated types; nil otherwise
|
||||
inst *instance
|
||||
underlying Type // underlying type, or nil
|
||||
inst *instance // information for instantiated types; nil otherwise
|
||||
|
||||
mu sync.Mutex // guards all fields below
|
||||
state_ uint32 // the current state of this type; must only be accessed atomically
|
||||
underlying Type // possibly a *Named during setup; never a *Named once set up completely
|
||||
tparams *TypeParamList // type parameters, or nil
|
||||
mu sync.Mutex // guards all fields below
|
||||
state_ uint32 // the current state of this type; must only be accessed atomically
|
||||
fromRHS Type // the declaration RHS this type is derived from
|
||||
tparams *TypeParamList // type parameters, or nil
|
||||
|
||||
// methods declared for this type (not the method set of this type)
|
||||
// Signatures are type-checked lazily.
|
||||
|
|
@ -145,7 +144,7 @@ type namedState uint32
|
|||
|
||||
// Note: the order of states is relevant
|
||||
const (
|
||||
unresolved namedState = iota // tparams, underlying type and methods might be unavailable
|
||||
unresolved namedState = iota // type parameters, RHS, underlying, and methods might be unavailable
|
||||
resolved // resolve has run; methods might be unexpanded (for instances)
|
||||
loaded // loader has run; constraints might be unexpanded (for generic types)
|
||||
complete // all data is known
|
||||
|
|
@ -158,10 +157,18 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
|
|||
if asNamed(underlying) != nil {
|
||||
panic("underlying type must not be *Named")
|
||||
}
|
||||
return (*Checker)(nil).newNamed(obj, underlying, methods)
|
||||
n := (*Checker)(nil).newNamed(obj, underlying, methods)
|
||||
if underlying == nil {
|
||||
n.allowNilRHS = true
|
||||
n.allowNilUnderlying = true
|
||||
} else {
|
||||
n.SetUnderlying(underlying)
|
||||
}
|
||||
return n
|
||||
|
||||
}
|
||||
|
||||
// resolve resolves the type parameters, methods, and underlying type of n.
|
||||
// resolve resolves the type parameters, methods, and RHS of n.
|
||||
//
|
||||
// For the purposes of resolution, there are three categories of named types:
|
||||
// 1. Instantiated Types
|
||||
|
|
@ -171,18 +178,17 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
|
|||
// Note that the above form a partition.
|
||||
//
|
||||
// Instantiated types:
|
||||
// Type parameters, methods, and underlying type of n become accessible,
|
||||
// though methods are lazily populated as needed.
|
||||
// Type parameters, methods, and RHS of n become accessible, though methods
|
||||
// are lazily populated as needed.
|
||||
//
|
||||
// Lazy loaded types:
|
||||
// Type parameters, methods, and underlying type of n become accessible
|
||||
// and are fully expanded.
|
||||
// Type parameters, methods, and RHS of n become accessible and are fully
|
||||
// expanded.
|
||||
//
|
||||
// All others:
|
||||
// Effectively, nothing happens. The underlying type of n may still be
|
||||
// a named type.
|
||||
// Effectively, nothing happens.
|
||||
func (n *Named) resolve() *Named {
|
||||
if n.state() > unresolved { // avoid locking below
|
||||
if n.state() >= resolved { // avoid locking below
|
||||
return n
|
||||
}
|
||||
|
||||
|
|
@ -191,21 +197,19 @@ func (n *Named) resolve() *Named {
|
|||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
if n.state() > unresolved {
|
||||
if n.state() >= resolved {
|
||||
return n
|
||||
}
|
||||
|
||||
if n.inst != nil {
|
||||
assert(n.underlying == nil) // n is an unresolved instance
|
||||
assert(n.loader == nil) // instances are created by instantiation, in which case n.loader is nil
|
||||
assert(n.fromRHS == nil) // instantiated types are not declared types
|
||||
assert(n.loader == nil) // cannot import an instantiation
|
||||
|
||||
orig := n.inst.orig
|
||||
orig.resolve()
|
||||
underlying := n.expandUnderlying()
|
||||
|
||||
n.fromRHS = n.expandRHS()
|
||||
n.tparams = orig.tparams
|
||||
n.underlying = underlying
|
||||
n.fromRHS = orig.fromRHS // for cycle detection
|
||||
|
||||
if len(orig.methods) == 0 {
|
||||
n.setState(complete) // nothing further to do
|
||||
|
|
@ -224,25 +228,25 @@ func (n *Named) resolve() *Named {
|
|||
// methods would need to support reentrant calls though. It would
|
||||
// also make the API more future-proof towards further extensions.
|
||||
if n.loader != nil {
|
||||
assert(n.underlying == nil)
|
||||
assert(n.TypeArgs().Len() == 0) // instances are created by instantiation, in which case n.loader is nil
|
||||
assert(n.fromRHS == nil) // not loaded yet
|
||||
assert(n.inst == nil) // cannot import an instantiation
|
||||
|
||||
tparams, underlying, methods, delayed := n.loader(n)
|
||||
n.loader = nil
|
||||
|
||||
n.tparams = bindTParams(tparams)
|
||||
n.underlying = underlying
|
||||
n.fromRHS = underlying // for cycle detection
|
||||
n.methods = methods
|
||||
|
||||
// advance state to avoid deadlock calling delayed functions
|
||||
n.setState(loaded)
|
||||
n.setState(loaded) // avoid deadlock calling delayed functions
|
||||
|
||||
for _, f := range delayed {
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
assert(n.fromRHS != nil || n.allowNilRHS)
|
||||
assert(n.underlying == nil) // underlying comes after resolving
|
||||
n.setState(complete)
|
||||
return n
|
||||
}
|
||||
|
|
@ -259,8 +263,8 @@ func (n *Named) setState(state namedState) {
|
|||
}
|
||||
|
||||
// newNamed is like NewNamed but with a *Checker receiver.
|
||||
func (check *Checker) newNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
|
||||
typ := &Named{check: check, obj: obj, fromRHS: underlying, underlying: underlying, methods: methods}
|
||||
func (check *Checker) newNamed(obj *TypeName, fromRHS Type, methods []*Func) *Named {
|
||||
typ := &Named{check: check, obj: obj, fromRHS: fromRHS, methods: methods}
|
||||
if obj.typ == nil {
|
||||
obj.typ = typ
|
||||
}
|
||||
|
|
@ -300,25 +304,13 @@ func (check *Checker) newNamedInstance(pos syntax.Pos, orig *Named, targs []Type
|
|||
return typ
|
||||
}
|
||||
|
||||
func (t *Named) cleanup() {
|
||||
assert(t.inst == nil || t.inst.orig.inst == nil)
|
||||
// Ensure that every defined type created in the course of type-checking has
|
||||
// either non-*Named underlying type, or is unexpanded.
|
||||
//
|
||||
// This guarantees that we don't leak any types whose underlying type is
|
||||
// *Named, because any unexpanded instances will lazily compute their
|
||||
// underlying type by substituting in the underlying type of their origin.
|
||||
// The origin must have either been imported or type-checked and expanded
|
||||
// here, and in either case its underlying type will be fully expanded.
|
||||
switch t.underlying.(type) {
|
||||
case nil:
|
||||
if t.TypeArgs().Len() == 0 {
|
||||
panic("nil underlying")
|
||||
}
|
||||
case *Named, *Alias:
|
||||
t.under() // t.under may add entries to check.cleaners
|
||||
func (n *Named) cleanup() {
|
||||
// Instances can have a nil underlying at the end of type checking — they
|
||||
// will lazily expand it as needed. All other types must have one.
|
||||
if n.inst == nil {
|
||||
n.resolve().under()
|
||||
}
|
||||
t.check = nil
|
||||
n.check = nil
|
||||
}
|
||||
|
||||
// Obj returns the type name for the declaration defining the named type t. For
|
||||
|
|
@ -477,10 +469,12 @@ func (t *Named) SetUnderlying(underlying Type) {
|
|||
if asNamed(underlying) != nil {
|
||||
panic("underlying type must not be *Named")
|
||||
}
|
||||
t.resolve().underlying = underlying
|
||||
if t.fromRHS == nil {
|
||||
t.fromRHS = underlying // for cycle detection
|
||||
}
|
||||
// Invariant: Presence of underlying type implies it was resolved.
|
||||
t.fromRHS = underlying
|
||||
t.allowNilRHS = false
|
||||
t.resolve()
|
||||
t.underlying = underlying
|
||||
t.allowNilUnderlying = false
|
||||
}
|
||||
|
||||
// AddMethod adds method m unless it is already in the method list.
|
||||
|
|
@ -523,9 +517,20 @@ func (t *Named) methodIndex(name string, foldCase bool) int {
|
|||
// Alias types.
|
||||
//
|
||||
// [underlying type]: https://go.dev/ref/spec#Underlying_types.
|
||||
func (t *Named) Underlying() Type {
|
||||
// TODO(gri) Investigate if Unalias can be moved to where underlying is set.
|
||||
return Unalias(t.resolve().underlying)
|
||||
func (n *Named) Underlying() Type {
|
||||
n.resolve()
|
||||
|
||||
// The gccimporter depends on writing a nil underlying via NewNamed and
|
||||
// immediately reading it back. Rather than putting that in under() and
|
||||
// complicating things there, we just check for that special case here.
|
||||
if n.fromRHS == nil {
|
||||
assert(n.allowNilRHS)
|
||||
if n.allowNilUnderlying {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return n.under()
|
||||
}
|
||||
|
||||
func (t *Named) String() string { return TypeString(t, nil) }
|
||||
|
|
@ -536,89 +541,55 @@ func (t *Named) String() string { return TypeString(t, nil) }
|
|||
// TODO(rfindley): reorganize the loading and expansion methods under this
|
||||
// heading.
|
||||
|
||||
// under returns the expanded underlying type of n0; possibly by following
|
||||
// forward chains of named types. If an underlying type is found, resolve
|
||||
// the chain by setting the underlying type for each defined type in the
|
||||
// chain before returning it. If no underlying type is found or a cycle
|
||||
// is detected, the result is Typ[Invalid]. If a cycle is detected and
|
||||
// n0.check != nil, the cycle is reported.
|
||||
// under returns the (possibly expanded) underlying type of n.
|
||||
//
|
||||
// This is necessary because the underlying type of named may be itself a
|
||||
// named type that is incomplete:
|
||||
// It does so by following RHS type chains. If a type literal is found, each
|
||||
// named type in the chain has its underlying set to that type. Aliases are
|
||||
// skipped because their underlying type is not memoized.
|
||||
//
|
||||
// type (
|
||||
// A B
|
||||
// B *C
|
||||
// C A
|
||||
// )
|
||||
//
|
||||
// The type of C is the (named) type of A which is incomplete,
|
||||
// and which has as its underlying type the named type B.
|
||||
func (n0 *Named) under() Type {
|
||||
u := n0.Underlying()
|
||||
// This function also checks for instantiated layout cycles, which are
|
||||
// reachable only in the case where resolve() expanded an instantiated
|
||||
// type which became self-referencing without indirection. If such a
|
||||
// cycle is found, the result is Typ[Invalid]; if n.check != nil, the
|
||||
// cycle is also reported.
|
||||
func (n *Named) under() Type {
|
||||
assert(n.state() >= resolved)
|
||||
|
||||
// If the underlying type of a defined type is not a defined
|
||||
// (incl. instance) type, then that is the desired underlying
|
||||
// type.
|
||||
var n1 *Named
|
||||
switch u1 := u.(type) {
|
||||
case nil:
|
||||
// After expansion via Underlying(), we should never encounter a nil
|
||||
// underlying.
|
||||
panic("nil underlying")
|
||||
default:
|
||||
// common case
|
||||
return u
|
||||
case *Named:
|
||||
// handled below
|
||||
n1 = u1
|
||||
if n.underlying != nil {
|
||||
return n.underlying
|
||||
}
|
||||
|
||||
if n0.check == nil {
|
||||
panic("Named.check == nil but type is incomplete")
|
||||
}
|
||||
var rhs Type = n
|
||||
var u Type
|
||||
|
||||
// Invariant: after this point n0 as well as any named types in its
|
||||
// underlying chain should be set up when this function exits.
|
||||
check := n0.check
|
||||
n := n0
|
||||
seen := make(map[*Named]int)
|
||||
var path []Object
|
||||
|
||||
seen := make(map[*Named]int) // types that need their underlying type resolved
|
||||
var path []Object // objects encountered, for cycle reporting
|
||||
|
||||
loop:
|
||||
for {
|
||||
seen[n] = len(seen)
|
||||
path = append(path, n.obj)
|
||||
n = n1
|
||||
if i, ok := seen[n]; ok {
|
||||
// cycle
|
||||
check.cycleError(path[i:], firstInSrc(path[i:]))
|
||||
u = Typ[Invalid]
|
||||
break
|
||||
}
|
||||
u = n.Underlying()
|
||||
switch u1 := u.(type) {
|
||||
for u == nil {
|
||||
switch t := rhs.(type) {
|
||||
case nil:
|
||||
u = Typ[Invalid]
|
||||
break loop
|
||||
default:
|
||||
break loop
|
||||
case *Alias:
|
||||
rhs = unalias(t)
|
||||
case *Named:
|
||||
// Continue collecting *Named types in the chain.
|
||||
n1 = u1
|
||||
if i, ok := seen[t]; ok {
|
||||
n.check.cycleError(path[i:], firstInSrc(path[i:]))
|
||||
u = Typ[Invalid]
|
||||
break
|
||||
}
|
||||
seen[t] = len(seen)
|
||||
path = append(path, t.obj)
|
||||
t.resolve()
|
||||
assert(t.fromRHS != nil || t.allowNilRHS)
|
||||
rhs = t.fromRHS
|
||||
default:
|
||||
u = rhs // any type literal works
|
||||
}
|
||||
}
|
||||
|
||||
for n := range seen {
|
||||
// We should never have to update the underlying type of an imported type;
|
||||
// those underlying types should have been resolved during the import.
|
||||
// Also, doing so would lead to a race condition (was go.dev/issue/31749).
|
||||
// Do this check always, not just in debug mode (it's cheap).
|
||||
if n.obj.pkg != check.pkg {
|
||||
panic("imported type with unresolved underlying type")
|
||||
}
|
||||
n.underlying = u
|
||||
// go back up the chain
|
||||
for t := range seen {
|
||||
t.underlying = u
|
||||
}
|
||||
|
||||
return u
|
||||
|
|
@ -646,78 +617,108 @@ func (check *Checker) context() *Context {
|
|||
return check.ctxt
|
||||
}
|
||||
|
||||
// expandUnderlying substitutes type arguments in the underlying type n.orig,
|
||||
// returning the result. Returns Typ[Invalid] if there was an error.
|
||||
func (n *Named) expandUnderlying() Type {
|
||||
// expandRHS crafts a synthetic RHS for an instantiated type using the RHS of
|
||||
// its origin type (which must be a generic type).
|
||||
//
|
||||
// Suppose that we had:
|
||||
//
|
||||
// type T[P any] struct {
|
||||
// f P
|
||||
// }
|
||||
//
|
||||
// type U T[int]
|
||||
//
|
||||
// When we go to U, we observe T[int]. Since T[int] is an instantiation, it has no
|
||||
// declaration. Here, we craft a synthetic RHS for T[int] as if it were declared,
|
||||
// somewhat similar to:
|
||||
//
|
||||
// type T[int] struct {
|
||||
// f int
|
||||
// }
|
||||
//
|
||||
// And note that the synthetic RHS here is the same as the underlying for U. Now,
|
||||
// consider:
|
||||
//
|
||||
// type T[_ any] U
|
||||
// type U int
|
||||
// type V T[U]
|
||||
//
|
||||
// The synthetic RHS for T[U] becomes:
|
||||
//
|
||||
// type T[U] U
|
||||
//
|
||||
// Whereas the underlying of V is int, not U.
|
||||
func (n *Named) expandRHS() (rhs Type) {
|
||||
check := n.check
|
||||
if check != nil && check.conf.Trace {
|
||||
check.trace(n.obj.pos, "-- Named.expandUnderlying %s", n)
|
||||
check.trace(n.obj.pos, "-- Named.expandRHS %s", n)
|
||||
check.indent++
|
||||
defer func() {
|
||||
check.indent--
|
||||
check.trace(n.obj.pos, "=> %s (tparams = %s, under = %s)", n, n.tparams.list(), n.underlying)
|
||||
check.trace(n.obj.pos, "=> %s (rhs = %s)", n, rhs)
|
||||
}()
|
||||
}
|
||||
|
||||
assert(n.inst.orig.underlying != nil)
|
||||
assert(n.state() == unresolved)
|
||||
|
||||
if n.inst.ctxt == nil {
|
||||
n.inst.ctxt = NewContext()
|
||||
}
|
||||
|
||||
ctxt := n.inst.ctxt
|
||||
orig := n.inst.orig
|
||||
|
||||
assert(orig.state() >= resolved)
|
||||
assert(orig.fromRHS != nil)
|
||||
|
||||
targs := n.inst.targs
|
||||
tpars := orig.tparams
|
||||
|
||||
if asNamed(orig.underlying) != nil {
|
||||
// We should only get a Named underlying type here during type checking
|
||||
// (for example, in recursive type declarations).
|
||||
assert(check != nil)
|
||||
}
|
||||
|
||||
if orig.tparams.Len() != targs.Len() {
|
||||
// Mismatching arg and tparam length may be checked elsewhere.
|
||||
if targs.Len() != tpars.Len() {
|
||||
return Typ[Invalid]
|
||||
}
|
||||
|
||||
// Ensure that an instance is recorded before substituting, so that we
|
||||
// resolve n for any recursive references.
|
||||
h := n.inst.ctxt.instanceHash(orig, targs.list())
|
||||
n2 := n.inst.ctxt.update(h, orig, n.TypeArgs().list(), n)
|
||||
assert(n == n2)
|
||||
h := ctxt.instanceHash(orig, targs.list())
|
||||
u := ctxt.update(h, orig, targs.list(), n) // block fixed point infinite instantiation
|
||||
assert(n == u)
|
||||
|
||||
smap := makeSubstMap(orig.tparams.list(), targs.list())
|
||||
var ctxt *Context
|
||||
m := makeSubstMap(tpars.list(), targs.list())
|
||||
if check != nil {
|
||||
ctxt = check.context()
|
||||
}
|
||||
underlying := n.check.subst(n.obj.pos, orig.underlying, smap, n, ctxt)
|
||||
// If the underlying type of n is an interface, we need to set the receiver of
|
||||
// its methods accurately -- we set the receiver of interface methods on
|
||||
// the RHS of a type declaration to the defined type.
|
||||
if iface, _ := underlying.(*Interface); iface != nil {
|
||||
|
||||
rhs = check.subst(n.obj.pos, orig.fromRHS, m, n, ctxt)
|
||||
|
||||
// TODO(markfreeman): Can we handle this in substitution?
|
||||
// If the RHS is an interface, we must set the receiver of interface methods
|
||||
// to the named type.
|
||||
if iface, _ := rhs.(*Interface); iface != nil {
|
||||
if methods, copied := replaceRecvType(iface.methods, orig, n); copied {
|
||||
// If the underlying type doesn't actually use type parameters, it's
|
||||
// possible that it wasn't substituted. In this case we need to create
|
||||
// a new *Interface before modifying receivers.
|
||||
if iface == orig.underlying {
|
||||
old := iface
|
||||
iface = check.newInterface()
|
||||
iface.embeddeds = old.embeddeds
|
||||
assert(old.complete) // otherwise we are copying incomplete data
|
||||
iface.complete = old.complete
|
||||
iface.implicit = old.implicit // should be false but be conservative
|
||||
underlying = iface
|
||||
// If the RHS doesn't use type parameters, it may not have been
|
||||
// substituted; we need to craft a new interface first.
|
||||
if iface == orig.fromRHS {
|
||||
assert(iface.complete) // otherwise we are copying incomplete data
|
||||
|
||||
crafted := check.newInterface()
|
||||
crafted.complete = true
|
||||
crafted.implicit = false
|
||||
crafted.embeddeds = iface.embeddeds
|
||||
|
||||
iface = crafted
|
||||
}
|
||||
iface.methods = methods
|
||||
iface.tset = nil // recompute type set with new methods
|
||||
|
||||
// If check != nil, check.newInterface will have saved the interface for later completion.
|
||||
if check == nil { // golang/go#61561: all newly created interfaces must be fully evaluated
|
||||
// go.dev/issue/61561: We have to complete the interface even without a checker.
|
||||
if check == nil {
|
||||
iface.typeSet()
|
||||
}
|
||||
|
||||
return iface
|
||||
}
|
||||
}
|
||||
|
||||
return underlying
|
||||
return rhs
|
||||
}
|
||||
|
||||
// safeUnderlying returns the underlying type of typ without expanding
|
||||
|
|
|
|||
|
|
@ -295,7 +295,8 @@ func NewTypeName(pos syntax.Pos, pkg *Package, name string, typ Type) *TypeName
|
|||
// lazily calls resolve to finish constructing the Named object.
|
||||
func NewTypeNameLazy(pos syntax.Pos, pkg *Package, name string, load func(*Named) ([]*TypeParam, Type, []*Func, []func())) *TypeName {
|
||||
obj := NewTypeName(pos, pkg, name, nil)
|
||||
NewNamed(obj, nil, nil).loader = load
|
||||
n := (*Checker)(nil).newNamed(obj, nil, nil)
|
||||
n.loader = load
|
||||
return obj
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -439,7 +439,7 @@ func (check *Checker) validRecv(pos poser, recv *Var) {
|
|||
break
|
||||
}
|
||||
var cause string
|
||||
switch u := T.under().(type) {
|
||||
switch u := T.Underlying().(type) {
|
||||
case *Basic:
|
||||
// unsafe.Pointer is treated like a regular pointer
|
||||
if u.kind == UnsafePointer {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ func TestSizeof(t *testing.T) {
|
|||
{Interface{}, 40, 80},
|
||||
{Map{}, 16, 32},
|
||||
{Chan{}, 12, 24},
|
||||
{Named{}, 60, 112},
|
||||
{Named{}, 64, 120},
|
||||
{TypeParam{}, 28, 48},
|
||||
{term{}, 12, 24},
|
||||
|
||||
|
|
|
|||
|
|
@ -425,7 +425,7 @@ func setDefType(def *TypeName, typ Type) {
|
|||
case *Basic:
|
||||
assert(t == Typ[Invalid])
|
||||
case *Named:
|
||||
t.underlying = typ
|
||||
t.fromRHS = typ
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected type %T", t))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@ import "iter"
|
|||
// under must only be called when a type is known
|
||||
// to be fully set up.
|
||||
func under(t Type) Type {
|
||||
if t := asNamed(t); t != nil {
|
||||
return t.under()
|
||||
}
|
||||
// TODO(markfreeman): Remove this function, it just delegates.
|
||||
return t.Underlying()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -339,7 +339,7 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
|
|||
if traceInference {
|
||||
u.tracef("%s ≡ under %s", x, ny)
|
||||
}
|
||||
y = ny.under()
|
||||
y = ny.Underlying()
|
||||
// Per the spec, a defined type cannot have an underlying type
|
||||
// that is a type parameter.
|
||||
assert(!isTypeParam(y))
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ func defPredeclaredTypes() {
|
|||
{
|
||||
obj := NewTypeName(nopos, nil, "error", nil)
|
||||
obj.setColor(black)
|
||||
typ := NewNamed(obj, nil, nil)
|
||||
typ := (*Checker)(nil).newNamed(obj, nil, nil)
|
||||
|
||||
// error.Error() string
|
||||
recv := newVar(RecvVar, nopos, nil, "", typ)
|
||||
|
|
@ -128,7 +128,8 @@ func defPredeclaredTypes() {
|
|||
ityp := &Interface{methods: []*Func{err}, complete: true}
|
||||
computeInterfaceTypeSet(nil, nopos, ityp) // prevent races due to lazy computation of tset
|
||||
|
||||
typ.SetUnderlying(ityp)
|
||||
typ.fromRHS = ityp
|
||||
typ.Underlying()
|
||||
def(obj)
|
||||
}
|
||||
|
||||
|
|
@ -136,12 +137,13 @@ func defPredeclaredTypes() {
|
|||
{
|
||||
obj := NewTypeName(nopos, nil, "comparable", nil)
|
||||
obj.setColor(black)
|
||||
typ := NewNamed(obj, nil, nil)
|
||||
typ := (*Checker)(nil).newNamed(obj, nil, nil)
|
||||
|
||||
// interface{} // marked as comparable
|
||||
ityp := &Interface{complete: true, tset: &_TypeSet{nil, allTermlist, true}}
|
||||
|
||||
typ.SetUnderlying(ityp)
|
||||
typ.fromRHS = ityp
|
||||
typ.Underlying()
|
||||
def(obj)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,13 +91,6 @@ func (check *Checker) validType0(pos syntax.Pos, typ Type, nest, path []*Named)
|
|||
// break
|
||||
// }
|
||||
|
||||
// Don't report a 2nd error if we already know the type is invalid
|
||||
// (e.g., if a cycle was detected earlier, via under).
|
||||
// Note: ensure that t.orig is fully resolved by calling Underlying().
|
||||
if !isValid(t.Underlying()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the current type t is also found in nest, (the memory of) t is
|
||||
// embedded in itself, indicating an invalid recursive type.
|
||||
for _, e := range nest {
|
||||
|
|
@ -125,8 +118,9 @@ func (check *Checker) validType0(pos syntax.Pos, typ Type, nest, path []*Named)
|
|||
// are not yet available to other goroutines).
|
||||
assert(t.obj.pkg == check.pkg)
|
||||
assert(t.Origin().obj.pkg == check.pkg)
|
||||
t.underlying = Typ[Invalid]
|
||||
t.Origin().underlying = Typ[Invalid]
|
||||
|
||||
// let t become invalid when it resolves
|
||||
t.Origin().fromRHS = Typ[Invalid]
|
||||
|
||||
// Find the starting point of the cycle and report it.
|
||||
// Because each type in nest must also appear in path (see invariant below),
|
||||
|
|
|
|||
|
|
@ -551,7 +551,7 @@ func (check *Checker) isImportedConstraint(typ Type) bool {
|
|||
if named == nil || named.obj.pkg == check.pkg || named.obj.pkg == nil {
|
||||
return false
|
||||
}
|
||||
u, _ := named.under().(*Interface)
|
||||
u, _ := named.Underlying().(*Interface)
|
||||
return u != nil && !u.IsMethodSet()
|
||||
}
|
||||
|
||||
|
|
@ -640,28 +640,33 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *TypeName
|
|||
named := check.newNamed(obj, nil, nil)
|
||||
setDefType(def, named)
|
||||
|
||||
// The RHS of a named N can be nil if, for example, N is defined as a cycle of aliases with
|
||||
// gotypesalias=0. Consider:
|
||||
//
|
||||
// type D N // N.resolve() will panic
|
||||
// type N A
|
||||
// type A = N // N.fromRHS is not set before N.resolve(), since A does not call setDefType
|
||||
//
|
||||
// There is likely a better way to detect such cases, but it may not be worth the effort.
|
||||
// Instead, we briefly permit a nil N.fromRHS while type-checking D.
|
||||
named.allowNilRHS = true
|
||||
defer (func() { named.allowNilRHS = false })()
|
||||
|
||||
if tdecl.TypeParams != nil {
|
||||
check.openScope(tdecl, "type parameters")
|
||||
defer check.closeScope()
|
||||
check.collectTypeParams(&named.tparams, tdecl.TypeParams)
|
||||
}
|
||||
|
||||
// determine underlying type of named
|
||||
rhs = check.definedType(tdecl.Type, obj)
|
||||
assert(rhs != nil)
|
||||
named.fromRHS = rhs
|
||||
|
||||
// If the underlying type was not set while type-checking the right-hand
|
||||
// side, it is invalid and an error should have been reported elsewhere.
|
||||
if named.underlying == nil {
|
||||
named.underlying = Typ[Invalid]
|
||||
}
|
||||
|
||||
// spec: "In a type definition the given type cannot be a type parameter."
|
||||
// (See also go.dev/issue/45639.)
|
||||
if isTypeParam(rhs) {
|
||||
check.error(tdecl.Type, MisplacedTypeParam, "cannot use a type parameter as RHS in type declaration")
|
||||
named.underlying = Typ[Invalid]
|
||||
named.fromRHS = Typ[Invalid]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -814,7 +819,7 @@ func (check *Checker) collectMethods(obj *TypeName) {
|
|||
}
|
||||
|
||||
func (check *Checker) checkFieldUniqueness(base *Named) {
|
||||
if t, _ := base.under().(*Struct); t != nil {
|
||||
if t, _ := base.Underlying().(*Struct); t != nil {
|
||||
var mset objset
|
||||
for i := 0; i < base.NumMethods(); i++ {
|
||||
m := base.Method(i)
|
||||
|
|
|
|||
|
|
@ -65,14 +65,14 @@ import (
|
|||
// - We say that a Named type is "resolved" if its RHS information has been
|
||||
// loaded or fully type-checked. For Named types constructed from export
|
||||
// data, this may involve invoking a loader function to extract information
|
||||
// from export data. For instantiated named types this involves reading
|
||||
// information from their origin.
|
||||
// from export data. For instantiated Named types this involves reading
|
||||
// information from their origin and substituting type arguments into a
|
||||
// "synthetic" RHS; this process is called "expanding" the RHS (see below).
|
||||
// - We say that a Named type is "expanded" if it is an instantiated type and
|
||||
// type parameters in its underlying type and methods have been substituted
|
||||
// with the type arguments from the instantiation. A type may be partially
|
||||
// expanded if some but not all of these details have been substituted.
|
||||
// Similarly, we refer to these individual details (underlying type or
|
||||
// method) as being "expanded".
|
||||
// type parameters in its RHS and methods have been substituted with the type
|
||||
// arguments from the instantiation. A type may be partially expanded if some
|
||||
// but not all of these details have been substituted. Similarly, we refer to
|
||||
// these individual details (RHS or method) as being "expanded".
|
||||
// - When all information is known for a named type, we say it is "complete".
|
||||
//
|
||||
// Some invariants to keep in mind: each declared Named type has a single
|
||||
|
|
@ -110,18 +110,17 @@ type Named struct {
|
|||
check *Checker // non-nil during type-checking; nil otherwise
|
||||
obj *TypeName // corresponding declared object for declared types; see above for instantiated types
|
||||
|
||||
// fromRHS holds the type (on RHS of declaration) this *Named type is derived
|
||||
// from (for cycle reporting). Only used by validType, and therefore does not
|
||||
// require synchronization.
|
||||
fromRHS Type
|
||||
// flags indicating temporary violations of the invariants for fromRHS and underlying
|
||||
allowNilRHS bool // same as below, as well as briefly during checking of a type declaration
|
||||
allowNilUnderlying bool // may be true from creation via [NewNamed] until [Named.SetUnderlying]
|
||||
|
||||
// information for instantiated types; nil otherwise
|
||||
inst *instance
|
||||
underlying Type // underlying type, or nil
|
||||
inst *instance // information for instantiated types; nil otherwise
|
||||
|
||||
mu sync.Mutex // guards all fields below
|
||||
state_ uint32 // the current state of this type; must only be accessed atomically
|
||||
underlying Type // possibly a *Named during setup; never a *Named once set up completely
|
||||
tparams *TypeParamList // type parameters, or nil
|
||||
mu sync.Mutex // guards all fields below
|
||||
state_ uint32 // the current state of this type; must only be accessed atomically
|
||||
fromRHS Type // the declaration RHS this type is derived from
|
||||
tparams *TypeParamList // type parameters, or nil
|
||||
|
||||
// methods declared for this type (not the method set of this type)
|
||||
// Signatures are type-checked lazily.
|
||||
|
|
@ -148,7 +147,7 @@ type namedState uint32
|
|||
|
||||
// Note: the order of states is relevant
|
||||
const (
|
||||
unresolved namedState = iota // tparams, underlying type and methods might be unavailable
|
||||
unresolved namedState = iota // type parameters, RHS, underlying, and methods might be unavailable
|
||||
resolved // resolve has run; methods might be unexpanded (for instances)
|
||||
loaded // loader has run; constraints might be unexpanded (for generic types)
|
||||
complete // all data is known
|
||||
|
|
@ -161,10 +160,18 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
|
|||
if asNamed(underlying) != nil {
|
||||
panic("underlying type must not be *Named")
|
||||
}
|
||||
return (*Checker)(nil).newNamed(obj, underlying, methods)
|
||||
n := (*Checker)(nil).newNamed(obj, underlying, methods)
|
||||
if underlying == nil {
|
||||
n.allowNilRHS = true
|
||||
n.allowNilUnderlying = true
|
||||
} else {
|
||||
n.SetUnderlying(underlying)
|
||||
}
|
||||
return n
|
||||
|
||||
}
|
||||
|
||||
// resolve resolves the type parameters, methods, and underlying type of n.
|
||||
// resolve resolves the type parameters, methods, and RHS of n.
|
||||
//
|
||||
// For the purposes of resolution, there are three categories of named types:
|
||||
// 1. Instantiated Types
|
||||
|
|
@ -174,18 +181,17 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
|
|||
// Note that the above form a partition.
|
||||
//
|
||||
// Instantiated types:
|
||||
// Type parameters, methods, and underlying type of n become accessible,
|
||||
// though methods are lazily populated as needed.
|
||||
// Type parameters, methods, and RHS of n become accessible, though methods
|
||||
// are lazily populated as needed.
|
||||
//
|
||||
// Lazy loaded types:
|
||||
// Type parameters, methods, and underlying type of n become accessible
|
||||
// and are fully expanded.
|
||||
// Type parameters, methods, and RHS of n become accessible and are fully
|
||||
// expanded.
|
||||
//
|
||||
// All others:
|
||||
// Effectively, nothing happens. The underlying type of n may still be
|
||||
// a named type.
|
||||
// Effectively, nothing happens.
|
||||
func (n *Named) resolve() *Named {
|
||||
if n.state() > unresolved { // avoid locking below
|
||||
if n.state() >= resolved { // avoid locking below
|
||||
return n
|
||||
}
|
||||
|
||||
|
|
@ -194,21 +200,19 @@ func (n *Named) resolve() *Named {
|
|||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
if n.state() > unresolved {
|
||||
if n.state() >= resolved {
|
||||
return n
|
||||
}
|
||||
|
||||
if n.inst != nil {
|
||||
assert(n.underlying == nil) // n is an unresolved instance
|
||||
assert(n.loader == nil) // instances are created by instantiation, in which case n.loader is nil
|
||||
assert(n.fromRHS == nil) // instantiated types are not declared types
|
||||
assert(n.loader == nil) // cannot import an instantiation
|
||||
|
||||
orig := n.inst.orig
|
||||
orig.resolve()
|
||||
underlying := n.expandUnderlying()
|
||||
|
||||
n.fromRHS = n.expandRHS()
|
||||
n.tparams = orig.tparams
|
||||
n.underlying = underlying
|
||||
n.fromRHS = orig.fromRHS // for cycle detection
|
||||
|
||||
if len(orig.methods) == 0 {
|
||||
n.setState(complete) // nothing further to do
|
||||
|
|
@ -227,25 +231,25 @@ func (n *Named) resolve() *Named {
|
|||
// methods would need to support reentrant calls though. It would
|
||||
// also make the API more future-proof towards further extensions.
|
||||
if n.loader != nil {
|
||||
assert(n.underlying == nil)
|
||||
assert(n.TypeArgs().Len() == 0) // instances are created by instantiation, in which case n.loader is nil
|
||||
assert(n.fromRHS == nil) // not loaded yet
|
||||
assert(n.inst == nil) // cannot import an instantiation
|
||||
|
||||
tparams, underlying, methods, delayed := n.loader(n)
|
||||
n.loader = nil
|
||||
|
||||
n.tparams = bindTParams(tparams)
|
||||
n.underlying = underlying
|
||||
n.fromRHS = underlying // for cycle detection
|
||||
n.methods = methods
|
||||
|
||||
// advance state to avoid deadlock calling delayed functions
|
||||
n.setState(loaded)
|
||||
n.setState(loaded) // avoid deadlock calling delayed functions
|
||||
|
||||
for _, f := range delayed {
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
assert(n.fromRHS != nil || n.allowNilRHS)
|
||||
assert(n.underlying == nil) // underlying comes after resolving
|
||||
n.setState(complete)
|
||||
return n
|
||||
}
|
||||
|
|
@ -262,8 +266,8 @@ func (n *Named) setState(state namedState) {
|
|||
}
|
||||
|
||||
// newNamed is like NewNamed but with a *Checker receiver.
|
||||
func (check *Checker) newNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
|
||||
typ := &Named{check: check, obj: obj, fromRHS: underlying, underlying: underlying, methods: methods}
|
||||
func (check *Checker) newNamed(obj *TypeName, fromRHS Type, methods []*Func) *Named {
|
||||
typ := &Named{check: check, obj: obj, fromRHS: fromRHS, methods: methods}
|
||||
if obj.typ == nil {
|
||||
obj.typ = typ
|
||||
}
|
||||
|
|
@ -303,25 +307,13 @@ func (check *Checker) newNamedInstance(pos token.Pos, orig *Named, targs []Type,
|
|||
return typ
|
||||
}
|
||||
|
||||
func (t *Named) cleanup() {
|
||||
assert(t.inst == nil || t.inst.orig.inst == nil)
|
||||
// Ensure that every defined type created in the course of type-checking has
|
||||
// either non-*Named underlying type, or is unexpanded.
|
||||
//
|
||||
// This guarantees that we don't leak any types whose underlying type is
|
||||
// *Named, because any unexpanded instances will lazily compute their
|
||||
// underlying type by substituting in the underlying type of their origin.
|
||||
// The origin must have either been imported or type-checked and expanded
|
||||
// here, and in either case its underlying type will be fully expanded.
|
||||
switch t.underlying.(type) {
|
||||
case nil:
|
||||
if t.TypeArgs().Len() == 0 {
|
||||
panic("nil underlying")
|
||||
}
|
||||
case *Named, *Alias:
|
||||
t.under() // t.under may add entries to check.cleaners
|
||||
func (n *Named) cleanup() {
|
||||
// Instances can have a nil underlying at the end of type checking — they
|
||||
// will lazily expand it as needed. All other types must have one.
|
||||
if n.inst == nil {
|
||||
n.resolve().under()
|
||||
}
|
||||
t.check = nil
|
||||
n.check = nil
|
||||
}
|
||||
|
||||
// Obj returns the type name for the declaration defining the named type t. For
|
||||
|
|
@ -480,10 +472,12 @@ func (t *Named) SetUnderlying(underlying Type) {
|
|||
if asNamed(underlying) != nil {
|
||||
panic("underlying type must not be *Named")
|
||||
}
|
||||
t.resolve().underlying = underlying
|
||||
if t.fromRHS == nil {
|
||||
t.fromRHS = underlying // for cycle detection
|
||||
}
|
||||
// Invariant: Presence of underlying type implies it was resolved.
|
||||
t.fromRHS = underlying
|
||||
t.allowNilRHS = false
|
||||
t.resolve()
|
||||
t.underlying = underlying
|
||||
t.allowNilUnderlying = false
|
||||
}
|
||||
|
||||
// AddMethod adds method m unless it is already in the method list.
|
||||
|
|
@ -526,9 +520,20 @@ func (t *Named) methodIndex(name string, foldCase bool) int {
|
|||
// Alias types.
|
||||
//
|
||||
// [underlying type]: https://go.dev/ref/spec#Underlying_types.
|
||||
func (t *Named) Underlying() Type {
|
||||
// TODO(gri) Investigate if Unalias can be moved to where underlying is set.
|
||||
return Unalias(t.resolve().underlying)
|
||||
func (n *Named) Underlying() Type {
|
||||
n.resolve()
|
||||
|
||||
// The gccimporter depends on writing a nil underlying via NewNamed and
|
||||
// immediately reading it back. Rather than putting that in under() and
|
||||
// complicating things there, we just check for that special case here.
|
||||
if n.fromRHS == nil {
|
||||
assert(n.allowNilRHS)
|
||||
if n.allowNilUnderlying {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return n.under()
|
||||
}
|
||||
|
||||
func (t *Named) String() string { return TypeString(t, nil) }
|
||||
|
|
@ -539,89 +544,55 @@ func (t *Named) String() string { return TypeString(t, nil) }
|
|||
// TODO(rfindley): reorganize the loading and expansion methods under this
|
||||
// heading.
|
||||
|
||||
// under returns the expanded underlying type of n0; possibly by following
|
||||
// forward chains of named types. If an underlying type is found, resolve
|
||||
// the chain by setting the underlying type for each defined type in the
|
||||
// chain before returning it. If no underlying type is found or a cycle
|
||||
// is detected, the result is Typ[Invalid]. If a cycle is detected and
|
||||
// n0.check != nil, the cycle is reported.
|
||||
// under returns the (possibly expanded) underlying type of n.
|
||||
//
|
||||
// This is necessary because the underlying type of named may be itself a
|
||||
// named type that is incomplete:
|
||||
// It does so by following RHS type chains. If a type literal is found, each
|
||||
// named type in the chain has its underlying set to that type. Aliases are
|
||||
// skipped because their underlying type is not memoized.
|
||||
//
|
||||
// type (
|
||||
// A B
|
||||
// B *C
|
||||
// C A
|
||||
// )
|
||||
//
|
||||
// The type of C is the (named) type of A which is incomplete,
|
||||
// and which has as its underlying type the named type B.
|
||||
func (n0 *Named) under() Type {
|
||||
u := n0.Underlying()
|
||||
// This function also checks for instantiated layout cycles, which are
|
||||
// reachable only in the case where resolve() expanded an instantiated
|
||||
// type which became self-referencing without indirection. If such a
|
||||
// cycle is found, the result is Typ[Invalid]; if n.check != nil, the
|
||||
// cycle is also reported.
|
||||
func (n *Named) under() Type {
|
||||
assert(n.state() >= resolved)
|
||||
|
||||
// If the underlying type of a defined type is not a defined
|
||||
// (incl. instance) type, then that is the desired underlying
|
||||
// type.
|
||||
var n1 *Named
|
||||
switch u1 := u.(type) {
|
||||
case nil:
|
||||
// After expansion via Underlying(), we should never encounter a nil
|
||||
// underlying.
|
||||
panic("nil underlying")
|
||||
default:
|
||||
// common case
|
||||
return u
|
||||
case *Named:
|
||||
// handled below
|
||||
n1 = u1
|
||||
if n.underlying != nil {
|
||||
return n.underlying
|
||||
}
|
||||
|
||||
if n0.check == nil {
|
||||
panic("Named.check == nil but type is incomplete")
|
||||
}
|
||||
var rhs Type = n
|
||||
var u Type
|
||||
|
||||
// Invariant: after this point n0 as well as any named types in its
|
||||
// underlying chain should be set up when this function exits.
|
||||
check := n0.check
|
||||
n := n0
|
||||
seen := make(map[*Named]int)
|
||||
var path []Object
|
||||
|
||||
seen := make(map[*Named]int) // types that need their underlying type resolved
|
||||
var path []Object // objects encountered, for cycle reporting
|
||||
|
||||
loop:
|
||||
for {
|
||||
seen[n] = len(seen)
|
||||
path = append(path, n.obj)
|
||||
n = n1
|
||||
if i, ok := seen[n]; ok {
|
||||
// cycle
|
||||
check.cycleError(path[i:], firstInSrc(path[i:]))
|
||||
u = Typ[Invalid]
|
||||
break
|
||||
}
|
||||
u = n.Underlying()
|
||||
switch u1 := u.(type) {
|
||||
for u == nil {
|
||||
switch t := rhs.(type) {
|
||||
case nil:
|
||||
u = Typ[Invalid]
|
||||
break loop
|
||||
default:
|
||||
break loop
|
||||
case *Alias:
|
||||
rhs = unalias(t)
|
||||
case *Named:
|
||||
// Continue collecting *Named types in the chain.
|
||||
n1 = u1
|
||||
if i, ok := seen[t]; ok {
|
||||
n.check.cycleError(path[i:], firstInSrc(path[i:]))
|
||||
u = Typ[Invalid]
|
||||
break
|
||||
}
|
||||
seen[t] = len(seen)
|
||||
path = append(path, t.obj)
|
||||
t.resolve()
|
||||
assert(t.fromRHS != nil || t.allowNilRHS)
|
||||
rhs = t.fromRHS
|
||||
default:
|
||||
u = rhs // any type literal works
|
||||
}
|
||||
}
|
||||
|
||||
for n := range seen {
|
||||
// We should never have to update the underlying type of an imported type;
|
||||
// those underlying types should have been resolved during the import.
|
||||
// Also, doing so would lead to a race condition (was go.dev/issue/31749).
|
||||
// Do this check always, not just in debug mode (it's cheap).
|
||||
if n.obj.pkg != check.pkg {
|
||||
panic("imported type with unresolved underlying type")
|
||||
}
|
||||
n.underlying = u
|
||||
// go back up the chain
|
||||
for t := range seen {
|
||||
t.underlying = u
|
||||
}
|
||||
|
||||
return u
|
||||
|
|
@ -649,78 +620,108 @@ func (check *Checker) context() *Context {
|
|||
return check.ctxt
|
||||
}
|
||||
|
||||
// expandUnderlying substitutes type arguments in the underlying type n.orig,
|
||||
// returning the result. Returns Typ[Invalid] if there was an error.
|
||||
func (n *Named) expandUnderlying() Type {
|
||||
// expandRHS crafts a synthetic RHS for an instantiated type using the RHS of
|
||||
// its origin type (which must be a generic type).
|
||||
//
|
||||
// Suppose that we had:
|
||||
//
|
||||
// type T[P any] struct {
|
||||
// f P
|
||||
// }
|
||||
//
|
||||
// type U T[int]
|
||||
//
|
||||
// When we go to U, we observe T[int]. Since T[int] is an instantiation, it has no
|
||||
// declaration. Here, we craft a synthetic RHS for T[int] as if it were declared,
|
||||
// somewhat similar to:
|
||||
//
|
||||
// type T[int] struct {
|
||||
// f int
|
||||
// }
|
||||
//
|
||||
// And note that the synthetic RHS here is the same as the underlying for U. Now,
|
||||
// consider:
|
||||
//
|
||||
// type T[_ any] U
|
||||
// type U int
|
||||
// type V T[U]
|
||||
//
|
||||
// The synthetic RHS for T[U] becomes:
|
||||
//
|
||||
// type T[U] U
|
||||
//
|
||||
// Whereas the underlying of V is int, not U.
|
||||
func (n *Named) expandRHS() (rhs Type) {
|
||||
check := n.check
|
||||
if check != nil && check.conf._Trace {
|
||||
check.trace(n.obj.pos, "-- Named.expandUnderlying %s", n)
|
||||
check.trace(n.obj.pos, "-- Named.expandRHS %s", n)
|
||||
check.indent++
|
||||
defer func() {
|
||||
check.indent--
|
||||
check.trace(n.obj.pos, "=> %s (tparams = %s, under = %s)", n, n.tparams.list(), n.underlying)
|
||||
check.trace(n.obj.pos, "=> %s (rhs = %s)", n, rhs)
|
||||
}()
|
||||
}
|
||||
|
||||
assert(n.inst.orig.underlying != nil)
|
||||
assert(n.state() == unresolved)
|
||||
|
||||
if n.inst.ctxt == nil {
|
||||
n.inst.ctxt = NewContext()
|
||||
}
|
||||
|
||||
ctxt := n.inst.ctxt
|
||||
orig := n.inst.orig
|
||||
|
||||
assert(orig.state() >= resolved)
|
||||
assert(orig.fromRHS != nil)
|
||||
|
||||
targs := n.inst.targs
|
||||
tpars := orig.tparams
|
||||
|
||||
if asNamed(orig.underlying) != nil {
|
||||
// We should only get a Named underlying type here during type checking
|
||||
// (for example, in recursive type declarations).
|
||||
assert(check != nil)
|
||||
}
|
||||
|
||||
if orig.tparams.Len() != targs.Len() {
|
||||
// Mismatching arg and tparam length may be checked elsewhere.
|
||||
if targs.Len() != tpars.Len() {
|
||||
return Typ[Invalid]
|
||||
}
|
||||
|
||||
// Ensure that an instance is recorded before substituting, so that we
|
||||
// resolve n for any recursive references.
|
||||
h := n.inst.ctxt.instanceHash(orig, targs.list())
|
||||
n2 := n.inst.ctxt.update(h, orig, n.TypeArgs().list(), n)
|
||||
assert(n == n2)
|
||||
h := ctxt.instanceHash(orig, targs.list())
|
||||
u := ctxt.update(h, orig, targs.list(), n) // block fixed point infinite instantiation
|
||||
assert(n == u)
|
||||
|
||||
smap := makeSubstMap(orig.tparams.list(), targs.list())
|
||||
var ctxt *Context
|
||||
m := makeSubstMap(tpars.list(), targs.list())
|
||||
if check != nil {
|
||||
ctxt = check.context()
|
||||
}
|
||||
underlying := n.check.subst(n.obj.pos, orig.underlying, smap, n, ctxt)
|
||||
// If the underlying type of n is an interface, we need to set the receiver of
|
||||
// its methods accurately -- we set the receiver of interface methods on
|
||||
// the RHS of a type declaration to the defined type.
|
||||
if iface, _ := underlying.(*Interface); iface != nil {
|
||||
|
||||
rhs = check.subst(n.obj.pos, orig.fromRHS, m, n, ctxt)
|
||||
|
||||
// TODO(markfreeman): Can we handle this in substitution?
|
||||
// If the RHS is an interface, we must set the receiver of interface methods
|
||||
// to the named type.
|
||||
if iface, _ := rhs.(*Interface); iface != nil {
|
||||
if methods, copied := replaceRecvType(iface.methods, orig, n); copied {
|
||||
// If the underlying type doesn't actually use type parameters, it's
|
||||
// possible that it wasn't substituted. In this case we need to create
|
||||
// a new *Interface before modifying receivers.
|
||||
if iface == orig.underlying {
|
||||
old := iface
|
||||
iface = check.newInterface()
|
||||
iface.embeddeds = old.embeddeds
|
||||
assert(old.complete) // otherwise we are copying incomplete data
|
||||
iface.complete = old.complete
|
||||
iface.implicit = old.implicit // should be false but be conservative
|
||||
underlying = iface
|
||||
// If the RHS doesn't use type parameters, it may not have been
|
||||
// substituted; we need to craft a new interface first.
|
||||
if iface == orig.fromRHS {
|
||||
assert(iface.complete) // otherwise we are copying incomplete data
|
||||
|
||||
crafted := check.newInterface()
|
||||
crafted.complete = true
|
||||
crafted.implicit = false
|
||||
crafted.embeddeds = iface.embeddeds
|
||||
|
||||
iface = crafted
|
||||
}
|
||||
iface.methods = methods
|
||||
iface.tset = nil // recompute type set with new methods
|
||||
|
||||
// If check != nil, check.newInterface will have saved the interface for later completion.
|
||||
if check == nil { // golang/go#61561: all newly created interfaces must be fully evaluated
|
||||
// go.dev/issue/61561: We have to complete the interface even without a checker.
|
||||
if check == nil {
|
||||
iface.typeSet()
|
||||
}
|
||||
|
||||
return iface
|
||||
}
|
||||
}
|
||||
|
||||
return underlying
|
||||
return rhs
|
||||
}
|
||||
|
||||
// safeUnderlying returns the underlying type of typ without expanding
|
||||
|
|
|
|||
|
|
@ -298,7 +298,8 @@ func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName {
|
|||
// lazily calls resolve to finish constructing the Named object.
|
||||
func _NewTypeNameLazy(pos token.Pos, pkg *Package, name string, load func(*Named) ([]*TypeParam, Type, []*Func, []func())) *TypeName {
|
||||
obj := NewTypeName(pos, pkg, name, nil)
|
||||
NewNamed(obj, nil, nil).loader = load
|
||||
n := (*Checker)(nil).newNamed(obj, nil, nil)
|
||||
n.loader = load
|
||||
return obj
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -461,7 +461,7 @@ func (check *Checker) validRecv(pos positioner, recv *Var) {
|
|||
break
|
||||
}
|
||||
var cause string
|
||||
switch u := T.under().(type) {
|
||||
switch u := T.Underlying().(type) {
|
||||
case *Basic:
|
||||
// unsafe.Pointer is treated like a regular pointer
|
||||
if u.kind == UnsafePointer {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ func TestSizeof(t *testing.T) {
|
|||
{Interface{}, 40, 80},
|
||||
{Map{}, 16, 32},
|
||||
{Chan{}, 12, 24},
|
||||
{Named{}, 60, 112},
|
||||
{Named{}, 64, 120},
|
||||
{TypeParam{}, 28, 48},
|
||||
{term{}, 12, 24},
|
||||
|
||||
|
|
|
|||
|
|
@ -421,7 +421,7 @@ func setDefType(def *TypeName, typ Type) {
|
|||
case *Basic:
|
||||
assert(t == Typ[Invalid])
|
||||
case *Named:
|
||||
t.underlying = typ
|
||||
t.fromRHS = typ
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected type %T", t))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,7 @@ import "iter"
|
|||
// under must only be called when a type is known
|
||||
// to be fully set up.
|
||||
func under(t Type) Type {
|
||||
if t := asNamed(t); t != nil {
|
||||
return t.under()
|
||||
}
|
||||
// TODO(markfreeman): Remove this function, it just delegates.
|
||||
return t.Underlying()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -342,7 +342,7 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
|
|||
if traceInference {
|
||||
u.tracef("%s ≡ under %s", x, ny)
|
||||
}
|
||||
y = ny.under()
|
||||
y = ny.Underlying()
|
||||
// Per the spec, a defined type cannot have an underlying type
|
||||
// that is a type parameter.
|
||||
assert(!isTypeParam(y))
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ func defPredeclaredTypes() {
|
|||
{
|
||||
obj := NewTypeName(nopos, nil, "error", nil)
|
||||
obj.setColor(black)
|
||||
typ := NewNamed(obj, nil, nil)
|
||||
typ := (*Checker)(nil).newNamed(obj, nil, nil)
|
||||
|
||||
// error.Error() string
|
||||
recv := newVar(RecvVar, nopos, nil, "", typ)
|
||||
|
|
@ -131,7 +131,8 @@ func defPredeclaredTypes() {
|
|||
ityp := &Interface{methods: []*Func{err}, complete: true}
|
||||
computeInterfaceTypeSet(nil, nopos, ityp) // prevent races due to lazy computation of tset
|
||||
|
||||
typ.SetUnderlying(ityp)
|
||||
typ.fromRHS = ityp
|
||||
typ.Underlying()
|
||||
def(obj)
|
||||
}
|
||||
|
||||
|
|
@ -139,12 +140,13 @@ func defPredeclaredTypes() {
|
|||
{
|
||||
obj := NewTypeName(nopos, nil, "comparable", nil)
|
||||
obj.setColor(black)
|
||||
typ := NewNamed(obj, nil, nil)
|
||||
typ := (*Checker)(nil).newNamed(obj, nil, nil)
|
||||
|
||||
// interface{} // marked as comparable
|
||||
ityp := &Interface{complete: true, tset: &_TypeSet{nil, allTermlist, true}}
|
||||
|
||||
typ.SetUnderlying(ityp)
|
||||
typ.fromRHS = ityp
|
||||
typ.Underlying()
|
||||
def(obj)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,13 +94,6 @@ func (check *Checker) validType0(pos token.Pos, typ Type, nest, path []*Named) b
|
|||
// break
|
||||
// }
|
||||
|
||||
// Don't report a 2nd error if we already know the type is invalid
|
||||
// (e.g., if a cycle was detected earlier, via under).
|
||||
// Note: ensure that t.orig is fully resolved by calling Underlying().
|
||||
if !isValid(t.Underlying()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the current type t is also found in nest, (the memory of) t is
|
||||
// embedded in itself, indicating an invalid recursive type.
|
||||
for _, e := range nest {
|
||||
|
|
@ -128,8 +121,9 @@ func (check *Checker) validType0(pos token.Pos, typ Type, nest, path []*Named) b
|
|||
// are not yet available to other goroutines).
|
||||
assert(t.obj.pkg == check.pkg)
|
||||
assert(t.Origin().obj.pkg == check.pkg)
|
||||
t.underlying = Typ[Invalid]
|
||||
t.Origin().underlying = Typ[Invalid]
|
||||
|
||||
// let t become invalid when it resolves
|
||||
t.Origin().fromRHS = Typ[Invalid]
|
||||
|
||||
// Find the starting point of the cycle and report it.
|
||||
// Because each type in nest must also appear in path (see invariant below),
|
||||
|
|
|
|||
14
src/internal/types/testdata/fixedbugs/issue75194.go
vendored
Normal file
14
src/internal/types/testdata/fixedbugs/issue75194.go
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package p
|
||||
|
||||
type A /* ERROR "invalid recursive type: A refers to itself" */ struct {
|
||||
a A
|
||||
}
|
||||
|
||||
type B /* ERROR "invalid recursive type: B refers to itself" */ struct {
|
||||
a A
|
||||
b B
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue