diff options
Diffstat (limited to 'src/go/types/named.go')
-rw-r--r-- | src/go/types/named.go | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/src/go/types/named.go b/src/go/types/named.go new file mode 100644 index 0000000000..f26b50aa81 --- /dev/null +++ b/src/go/types/named.go @@ -0,0 +1,296 @@ +// Copyright 2011 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 types + +import ( + "go/token" + "sync" +) + +// TODO(rfindley) Clean up Named struct below; specifically the fromRHS field (can we use underlying?). + +// A Named represents a named (defined) type. +type Named struct { + check *Checker + info typeInfo // for cycle detection + obj *TypeName // corresponding declared object + orig *Named // original, uninstantiated type + fromRHS Type // type (on RHS of declaration) this *Named type is derived of (for cycle reporting) + underlying Type // possibly a *Named during setup; never a *Named once set up completely + instance *instance // syntactic information for lazy instantiation + tparams *TypeParams // type parameters, or nil + targs []Type // type arguments (after instantiation), or nil + methods []*Func // methods declared for this type (not the method set of this type); signatures are type-checked lazily + + resolve func(*Named) ([]*TypeName, Type, []*Func) + once sync.Once +} + +// NewNamed returns a new named type for the given type name, underlying type, and associated methods. +// If the given type name obj doesn't have a type yet, its type is set to the returned named type. +// The underlying type must not be a *Named. +func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named { + if _, ok := underlying.(*Named); ok { + panic("types.NewNamed: underlying type must not be *Named") + } + return (*Checker)(nil).newNamed(obj, nil, underlying, nil, methods) +} + +func (t *Named) load() *Named { + // If t is an instantiated type, it derives its methods and tparams from its + // base type. Since we expect type parameters and methods to be set after a + // call to load, we must load the base and copy here. + // + // underlying is set when t is expanded. + // + // By convention, a type instance is loaded iff its tparams are set. + if len(t.targs) > 0 && t.tparams == nil { + t.orig.load() + t.tparams = t.orig.tparams + t.methods = t.orig.methods + } + if t.resolve == nil { + return t + } + + t.once.Do(func() { + // TODO(mdempsky): Since we're passing t to resolve anyway + // (necessary because types2 expects the receiver type for methods + // on defined interface types to be the Named rather than the + // underlying Interface), maybe it should just handle calling + // SetTParams, SetUnderlying, and AddMethod instead? Those + // methods would need to support reentrant calls though. It would + // also make the API more future-proof towards further extensions + // (like SetTParams). + + tparams, underlying, methods := t.resolve(t) + + switch underlying.(type) { + case nil, *Named: + panic("invalid underlying type") + } + + t.tparams = bindTParams(tparams) + t.underlying = underlying + t.methods = methods + }) + return t +} + +// newNamed is like NewNamed but with a *Checker receiver and additional orig argument. +func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, tparams *TypeParams, methods []*Func) *Named { + typ := &Named{check: check, obj: obj, orig: orig, fromRHS: underlying, underlying: underlying, tparams: tparams, methods: methods} + if typ.orig == nil { + typ.orig = typ + } + if obj.typ == nil { + obj.typ = typ + } + // Ensure that typ is always expanded, at which point the check field can be + // nilled out. + // + // Note that currently we cannot nil out check inside typ.under(), because + // it's possible that typ is expanded multiple times. + // + // TODO(rFindley): clean this up so that under is the only function mutating + // named types. + if check != nil { + check.later(func() { + switch typ.under().(type) { + case *Named: + panic("internal error: unexpanded underlying type") + } + typ.check = nil + }) + } + return typ +} + +// Obj returns the type name for the named type t. +func (t *Named) Obj() *TypeName { return t.obj } + +// _Orig returns the original generic type an instantiated type is derived from. +// If t is not an instantiated type, the result is t. +func (t *Named) _Orig() *Named { return t.orig } + +// TODO(gri) Come up with a better representation and API to distinguish +// between parameterized instantiated and non-instantiated types. + +// TParams returns the type parameters of the named type t, or nil. +// The result is non-nil for an (originally) parameterized type even if it is instantiated. +func (t *Named) TParams() *TypeParams { return t.load().tparams } + +// SetTParams sets the type parameters of the named type t. +func (t *Named) SetTParams(tparams []*TypeName) { t.load().tparams = bindTParams(tparams) } + +// NumTArgs returns the number of type arguments used to instantiate the named +// type t, or 0 if t is not an instantiated type. +func (t *Named) NumTArgs() int { return len(t.targs) } + +// TArgs returns the i'th type argument of the named type t for 0 <= i < t.NumTArgs(). +func (t *Named) TArg(i int) Type { return t.targs[i] } + +// SetTArgs sets the type arguments of the named type t. +func (t *Named) SetTArgs(args []Type) { t.targs = args } + +// NumMethods returns the number of explicit methods whose receiver is named type t. +func (t *Named) NumMethods() int { return len(t.load().methods) } + +// Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). +func (t *Named) Method(i int) *Func { return t.load().methods[i] } + +// SetUnderlying sets the underlying type and marks t as complete. +func (t *Named) SetUnderlying(underlying Type) { + if underlying == nil { + panic("types.Named.SetUnderlying: underlying type must not be nil") + } + if _, ok := underlying.(*Named); ok { + panic("types.Named.SetUnderlying: underlying type must not be *Named") + } + t.load().underlying = underlying +} + +// AddMethod adds method m unless it is already in the method list. +func (t *Named) AddMethod(m *Func) { + t.load() + if i, _ := lookupMethod(t.methods, m.pkg, m.name); i < 0 { + t.methods = append(t.methods, m) + } +} + +func (t *Named) Underlying() Type { return t.load().underlying } +func (t *Named) String() string { return TypeString(t, nil) } + +// ---------------------------------------------------------------------------- +// Implementation + +// 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. +func (n0 *Named) under() Type { + n0.expand() + + u := n0.Underlying() + + if u == Typ[Invalid] { + return u + } + + // If the underlying type of a defined type is not a defined + // (incl. instance) type, then that is the desired underlying + // type. + switch u.(type) { + case nil: + return Typ[Invalid] + default: + // common case + return u + case *Named: + // handled below + } + + if n0.check == nil { + panic("internal error: Named.check == nil but type is incomplete") + } + + // 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 + + // If we can't expand u at this point, it is invalid. + n := asNamed(u) + if n == nil { + n0.underlying = Typ[Invalid] + return n0.underlying + } + + // Otherwise, follow the forward chain. + seen := map[*Named]int{n0: 0} + path := []Object{n0.obj} + for { + u = n.Underlying() + if u == nil { + u = Typ[Invalid] + break + } + var n1 *Named + switch u1 := u.(type) { + case *Named: + u1.expand() + n1 = u1 + } + if n1 == nil { + break // end of chain + } + + seen[n] = len(seen) + path = append(path, n.obj) + n = n1 + + if i, ok := seen[n]; ok { + // cycle + check.cycleError(path[i:]) + u = Typ[Invalid] + break + } + } + + 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 issue #31749). + // Do this check always, not just in debug mode (it's cheap). + if n.obj.pkg != check.pkg { + panic("internal error: imported type with unresolved underlying type") + } + n.underlying = u + } + + return u +} + +func (n *Named) setUnderlying(typ Type) { + if n != nil { + n.underlying = typ + } +} + +// instance holds position information for use in lazy instantiation. +// +// TODO(rfindley): come up with a better name for this type, now that its usage +// has changed. +type instance struct { + pos token.Pos // position of type instantiation; for error reporting only + posList []token.Pos // position of each targ; for error reporting only +} + +// expand ensures that the underlying type of n is instantiated. +// The underlying type will be Typ[Invalid] if there was an error. +// TODO(rfindley): expand would be a better name for this method, but conflicts +// with the existing concept of lazy expansion. Need to reconcile this. +func (n *Named) expand() { + if n.instance != nil { + // n must be loaded before instantiation, in order to have accurate + // tparams. This is done implicitly by the call to n.TParams, but making it + // explicit is harmless: load is idempotent. + n.load() + inst := n.check.instantiate(n.instance.pos, n.orig.underlying, n.TParams().list(), n.targs, n.instance.posList) + n.underlying = inst + n.fromRHS = inst + n.instance = nil + } +} + +// expand expands uninstantiated named types and leaves all other types alone. +// expand does not recurse. +func expand(typ Type) Type { + if t, _ := typ.(*Named); t != nil { + t.expand() + } + return typ +} |