@@ -47,10 +47,8 @@ import (
4747// soon.
4848//
4949// We achieve this by tracking state with an atomic state variable, and
50- // guarding potentially concurrent calculations with a mutex. At any point in
51- // time this state variable determines which data on N may be accessed. As
52- // state monotonically progresses, any data available at state M may be
53- // accessed without acquiring the mutex at state N, provided N >= M.
50+ // guarding potentially concurrent calculations with a mutex. See [stateMask]
51+ // for details.
5452//
5553// GLOSSARY: Here are a few terms used in this file to describe Named types:
5654// - We say that a Named type is "instantiated" if it has been constructed by
@@ -111,13 +109,13 @@ type Named struct {
111109 allowNilRHS bool // same as below, as well as briefly during checking of a type declaration
112110 allowNilUnderlying bool // may be true from creation via [NewNamed] until [Named.SetUnderlying]
113111
114- underlying Type // underlying type, or nil
115- inst * instance // information for instantiated types; nil otherwise
112+ inst * instance // information for instantiated types; nil otherwise
116113
117- mu sync.Mutex // guards all fields below
118- state_ uint32 // the current state of this type; must only be accessed atomically
119- fromRHS Type // the declaration RHS this type is derived from
120- tparams * TypeParamList // type parameters, or nil
114+ mu sync.Mutex // guards all fields below
115+ state_ uint32 // the current state of this type; must only be accessed atomically or when mu is held
116+ fromRHS Type // the declaration RHS this type is derived from
117+ tparams * TypeParamList // type parameters, or nil
118+ underlying Type // underlying type, or nil
121119
122120 // methods declared for this type (not the method set of this type)
123121 // Signatures are type-checked lazily.
@@ -139,15 +137,43 @@ type instance struct {
139137 ctxt * Context // local Context; set to nil after full expansion
140138}
141139
142- // namedState represents the possible states that a named type may assume.
143- type namedState uint32
140+ // stateMask represents each state in the lifecycle of a named type.
141+ //
142+ // Each named type begins in the unresolved state. A named type may transition to a new state
143+ // according to the below diagram:
144+ //
145+ // unresolved
146+ // loaded
147+ // resolved
148+ // └── complete
149+ // └── underlying
150+ //
151+ // That is, descent down the tree is mostly linear (unresolved through resolved), except upon
152+ // reaching the leaves (complete and underlying). A type may occupy any combination of the
153+ // leaf states at once (they are independent states).
154+ //
155+ // To represent this independence, the set of active states is represented with a bit set. State
156+ // transitions are monotonic. Once a state bit is set, it remains set.
157+ //
158+ // The above constraints significantly narrow the possible bit sets for a named type. With bits
159+ // set left-to-right, they are:
160+ //
161+ // 0000 | unresolved
162+ // 1000 | loaded
163+ // 1100 | resolved, which implies loaded
164+ // 1110 | completed, which implies resolved (which in turn implies loaded)
165+ // 1101 | underlying, which implies resolved ...
166+ // 1111 | both completed and underlying which implies resolved ...
167+ //
168+ // To read the state of a named type, use [Named.stateHas]; to write, use [Named.setState].
169+ type stateMask uint32
144170
145- // Note: the order of states is relevant
146171const (
147- unresolved namedState = iota // type parameters, RHS, underlying, and methods might be unavailable
148- resolved // resolve has run; methods might be unexpanded (for instances)
149- loaded // loader has run; constraints might be unexpanded (for generic types)
150- complete // all data is known
172+ // before resolved, type parameters, RHS, underlying, and methods might be unavailable
173+ resolved stateMask = 1 << iota // methods might be unexpanded (for instances)
174+ complete // methods are all expanded (for instances)
175+ loaded // methods are available, but constraints might be unexpanded (for generic types)
176+ underlying // underlying type is available
151177)
152178
153179// NewNamed returns a new named type for the given type name, underlying type, and associated methods.
@@ -188,7 +214,7 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
188214// All others:
189215// Effectively, nothing happens.
190216func (n * Named ) resolve () * Named {
191- if n .state () >= resolved { // avoid locking below
217+ if n .stateHas ( resolved | loaded ) { // avoid locking below
192218 return n
193219 }
194220
@@ -197,10 +223,14 @@ func (n *Named) resolve() *Named {
197223 n .mu .Lock ()
198224 defer n .mu .Unlock ()
199225
200- if n .state () >= resolved {
226+ // only atomic for consistency; we are holding the mutex
227+ if n .stateHas (resolved | loaded ) {
201228 return n
202229 }
203230
231+ // underlying comes after resolving, do not set it
232+ defer (func () { assert (! n .stateHas (underlying )) })()
233+
204234 if n .inst != nil {
205235 assert (n .fromRHS == nil ) // instantiated types are not declared types
206236 assert (n .loader == nil ) // cannot import an instantiation
@@ -212,7 +242,7 @@ func (n *Named) resolve() *Named {
212242 n .tparams = orig .tparams
213243
214244 if len (orig .methods ) == 0 {
215- n .setState (complete ) // nothing further to do
245+ n .setState (resolved | complete ) // nothing further to do
216246 n .inst .ctxt = nil
217247 } else {
218248 n .setState (resolved )
@@ -239,27 +269,24 @@ func (n *Named) resolve() *Named {
239269 n .methods = methods
240270
241271 n .setState (loaded ) // avoid deadlock calling delayed functions
242-
243272 for _ , f := range delayed {
244273 f ()
245274 }
246275 }
247276
248- assert (n .fromRHS != nil || n .allowNilRHS )
249- assert (n .underlying == nil ) // underlying comes after resolving
250- n .setState (complete )
277+ n .setState (resolved | complete )
251278 return n
252279}
253280
254- // state atomically accesses the current state of the receiver .
255- func (n * Named ) state () namedState {
256- return namedState ( atomic .LoadUint32 (& n .state_ ))
281+ // stateHas atomically determines whether the current state includes any active bit in sm .
282+ func (n * Named ) stateHas ( sm stateMask ) bool {
283+ return atomic .LoadUint32 (& n .state_ )& uint32 ( sm ) != 0
257284}
258285
259- // setState atomically stores the given state for n .
286+ // setState atomically sets the current state to include each active bit in sm .
260287// Must only be called while holding n.mu.
261- func (n * Named ) setState (state namedState ) {
262- atomic .StoreUint32 (& n .state_ , uint32 (state ))
288+ func (n * Named ) setState (sm stateMask ) {
289+ atomic .OrUint32 (& n .state_ , uint32 (sm ))
263290}
264291
265292// newNamed is like NewNamed but with a *Checker receiver.
@@ -369,7 +396,7 @@ func (t *Named) NumMethods() int {
369396func (t * Named ) Method (i int ) * Func {
370397 t .resolve ()
371398
372- if t .state () >= complete {
399+ if t .stateHas ( complete ) {
373400 return t .methods [i ]
374401 }
375402
@@ -461,20 +488,25 @@ func (t *Named) expandMethod(i int) *Func {
461488
462489// SetUnderlying sets the underlying type and marks t as complete.
463490// t must not have type arguments.
464- func (t * Named ) SetUnderlying (underlying Type ) {
491+ func (t * Named ) SetUnderlying (u Type ) {
465492 assert (t .inst == nil )
466- if underlying == nil {
493+ if u == nil {
467494 panic ("underlying type must not be nil" )
468495 }
469- if asNamed (underlying ) != nil {
496+ if asNamed (u ) != nil {
470497 panic ("underlying type must not be *Named" )
471498 }
472- // Invariant: Presence of underlying type implies it was resolved.
473- t .fromRHS = underlying
499+ // be careful to uphold the state invariants
500+ t .mu .Lock ()
501+ defer t .mu .Unlock ()
502+
503+ t .fromRHS = u
474504 t .allowNilRHS = false
475- t .resolve ()
476- t .underlying = underlying
505+ t .setState (resolved | complete ) // TODO(markfreeman): Why complete?
506+
507+ t .underlying = u
477508 t .allowNilUnderlying = false
509+ t .setState (underlying )
478510}
479511
480512// AddMethod adds method m unless it is already in the method list.
@@ -553,9 +585,10 @@ func (t *Named) String() string { return TypeString(t, nil) }
553585// cycle is found, the result is Typ[Invalid]; if n.check != nil, the
554586// cycle is also reported.
555587func (n * Named ) under () Type {
556- assert (n .state () >= resolved )
588+ assert (n .stateHas ( resolved ) )
557589
558- if n .underlying != nil {
590+ // optimization for likely case
591+ if n .stateHas (underlying ) {
559592 return n .underlying
560593 }
561594
@@ -569,19 +602,34 @@ func (n *Named) under() Type {
569602 switch t := rhs .(type ) {
570603 case nil :
571604 u = Typ [Invalid ]
605+
572606 case * Alias :
573607 rhs = unalias (t )
608+
574609 case * Named :
575610 if i , ok := seen [t ]; ok {
576611 n .check .cycleError (path [i :], firstInSrc (path [i :]))
577612 u = Typ [Invalid ]
578613 break
579614 }
615+
616+ // acquire the lock without checking; performance is probably fine
617+ t .resolve ()
618+ t .mu .Lock ()
619+ defer t .mu .Unlock ()
620+
621+ // t.underlying might have been set while we were locking
622+ if t .stateHas (underlying ) {
623+ u = t .underlying
624+ break
625+ }
626+
580627 seen [t ] = len (seen )
581628 path = append (path , t .obj )
582- t . resolve ()
629+
583630 assert (t .fromRHS != nil || t .allowNilRHS )
584631 rhs = t .fromRHS
632+
585633 default :
586634 u = rhs // any type literal works
587635 }
@@ -590,6 +638,7 @@ func (n *Named) under() Type {
590638 // go back up the chain
591639 for t := range seen {
592640 t .underlying = u
641+ t .setState (underlying )
593642 }
594643
595644 return u
@@ -659,7 +708,8 @@ func (n *Named) expandRHS() (rhs Type) {
659708 }()
660709 }
661710
662- assert (n .state () == unresolved )
711+ assert (! n .stateHas (resolved ))
712+ assert (n .inst .orig .stateHas (resolved | loaded ))
663713
664714 if n .inst .ctxt == nil {
665715 n .inst .ctxt = NewContext ()
@@ -668,9 +718,6 @@ func (n *Named) expandRHS() (rhs Type) {
668718 ctxt := n .inst .ctxt
669719 orig := n .inst .orig
670720
671- assert (orig .state () >= resolved )
672- assert (orig .fromRHS != nil )
673-
674721 targs := n .inst .targs
675722 tpars := orig .tparams
676723
0 commit comments