Source File
named.go
Belonging Package
go/types
// Code generated by "go test -run=Generate -write=all"; DO NOT EDIT.// Source: ../../cmd/compile/internal/types2/named.go// 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 typesimport ()// Type-checking Named types is subtle, because they may be recursively// defined, and because their full details may be spread across multiple// declarations (via methods). For this reason they are type-checked lazily,// to avoid information being accessed before it is complete.//// Conceptually, it is helpful to think of named types as having two distinct// sets of information:// - "LHS" information, defining their identity: Obj() and TypeArgs()// - "RHS" information, defining their details: TypeParams(), Underlying(),// and methods.//// In this taxonomy, LHS information is available immediately, but RHS// information is lazy. Specifically, a named type N may be constructed in any// of the following ways:// 1. type-checked from the source// 2. loaded eagerly from export data// 3. loaded lazily from export data (when using unified IR)// 4. instantiated from a generic type//// In cases 1, 3, and 4, it is possible that the underlying type or methods of// N may not be immediately available.// - During type-checking, we allocate N before type-checking its underlying// type or methods, so that we can create recursive references.// - When loading from export data, we may load its methods and underlying// type lazily using a provided load function.// - After instantiating, we lazily expand the underlying type and methods// (note that instances may be created while still in the process of// type-checking the original type declaration).//// In cases 3 and 4 this lazy construction may also occur concurrently, due to// concurrent use of the type checker API (after type checking or importing has// finished). It is critical that we keep track of state, so that Named types// are constructed exactly once and so that we do not access their details too// soon.//// We achieve this by tracking state with an atomic state variable, and// guarding potentially concurrent calculations with a mutex. See [stateMask]// for details.//// GLOSSARY: Here are a few terms used in this file to describe Named types:// - We say that a Named type is "instantiated" if it has been constructed by// instantiating a generic named type with type arguments.// - We say that a Named type is "declared" if it corresponds to a type// declaration in the source. Instantiated named types correspond to a type// instantiation in the source, not a declaration. But their Origin type is// a declared type.// - We say that a Named type is "unpacked" if its RHS information has been// populated, normalizing its representation for use in type-checking// operations and abstracting away how it was created:// - For a Named type constructed from unified IR, this involves invoking// a lazy loader function to extract details from UIR as needed.// - For an instantiated Named type, this involves extracting information// from its 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 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".//// Some invariants to keep in mind: each declared Named type has a single// corresponding object, and that object's type is the (possibly generic) Named// type. Declared Named types are identical if and only if their pointers are// identical. On the other hand, multiple instantiated Named types may be// identical even though their pointers are not identical. One has to use// Identical to compare them. For instantiated named types, their obj is a// synthetic placeholder that records their position of the corresponding// instantiation in the source (if they were constructed during type checking).//// To prevent infinite expansion of named instances that are created outside of// type-checking, instances share a Context with other instances created during// their expansion. Via the pidgeonhole principle, this guarantees that in the// presence of a cycle of named types, expansion will eventually find an// existing instance in the Context and short-circuit the expansion.//// Once an instance is fully expanded, we can nil out this shared Context to unpin// memory, though the Context may still be held by other incomplete instances// in its "lineage".// A Named represents a named (defined) type.//// A declaration such as://// type S struct { ... }//// creates a defined type whose underlying type is a struct,// and binds this type to the object S, a [TypeName].// Use [Named.Underlying] to access the underlying type.// Use [Named.Obj] to obtain the object S.//// Before type aliases (Go 1.9), the spec called defined types "named types".type Named struct {check *Checker // non-nil during type-checking; nil otherwiseobj *TypeName // corresponding declared object for declared types; see above for instantiated types// flags indicating temporary violations of the invariants for fromRHS and underlyingallowNilRHS bool // same as below, as well as briefly during checking of a type declarationallowNilUnderlying bool // may be true from creation via [NewNamed] until [Named.SetUnderlying]inst *instance // information for instantiated types; nil otherwisemu sync.Mutex // guards all fields belowstate_ uint32 // the current state of this type; must only be accessed atomically or when mu is heldfromRHS Type // the declaration RHS this type is derived fromtparams *TypeParamList // type parameters, or nilunderlying Type // underlying type, or nilfinite bool // whether the type has finite size// methods declared for this type (not the method set of this type)// Signatures are type-checked lazily.// For non-instantiated types, this is a fully populated list of methods. For// instantiated types, methods are individually expanded when they are first// accessed.methods []*Func// loader may be provided to lazily load type parameters, underlying type, methods, and delayed functionsloader func(*Named) ([]*TypeParam, Type, []*Func, []func())}// instance holds information that is only necessary for instantiated named// types.type instance struct {orig *Named // original, uninstantiated typetargs *TypeList // type argumentsexpandedMethods int // number of expanded methods; expandedMethods <= len(orig.methods)ctxt *Context // local Context; set to nil after full expansion}// stateMask represents each state in the lifecycle of a named type.//// Each named type begins in the initial state. A named type may transition to a new state// according to the below diagram://// initial// lazyLoaded// unpacked// └── hasMethods// └── hasUnder// └── hasFinite//// That is, descent down the tree is mostly linear (initial through unpacked), except upon// reaching the leaves (hasMethods, hasUnder, and hasFinite). A type may occupy any// combination of the leaf states at once (they are independent states).//// To represent this independence, the set of active states is represented with a bit set. State// transitions are monotonic. Once a state bit is set, it remains set.//// The above constraints significantly narrow the possible bit sets for a named type. With bits// set left-to-right, they are://// 00000 | initial// 10000 | lazyLoaded// 11000 | unpacked, which implies lazyLoaded// 11100 | hasMethods, which implies unpacked (which in turn implies lazyLoaded)// 11010 | hasUnder, which implies unpacked ...// 11001 | hasFinite, which implies unpacked ...// 11110 | both hasMethods and hasUnder which implies unpacked ...// ... | (other combinations of leaf states)//// To read the state of a named type, use [Named.stateHas]; to write, use [Named.setState].type stateMask uint32const (// initially, type parameters, RHS, underlying, and methods might be unavailablelazyLoaded stateMask = 1 << iota // methods are available, but constraints might be unexpanded (for generic types)unpacked // methods might be unexpanded (for instances)hasMethods // methods are all expanded (for instances)hasUnder // underlying type is availablehasFinite // size finiteness is available)// 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 ( *TypeName, Type, []*Func) *Named {if asNamed() != nil {panic("underlying type must not be *Named")}:= (*Checker)(nil).newNamed(, , )if == nil {.allowNilRHS = true.allowNilUnderlying = true} else {.SetUnderlying()}return}// unpack populates the type parameters, methods, and RHS of n.//// For the purposes of unpacking, there are three categories of named types:// 1. Lazy loaded types// 2. Instantiated types// 3. All others//// Note that the above form a partition.//// Lazy loaded types:// Type parameters, methods, and RHS of n become accessible and are fully// expanded.//// Instantiated types:// Type parameters, methods, and RHS of n become accessible, though methods// are lazily populated as needed.//// All others:// Effectively, nothing happens.func ( *Named) () *Named {if .stateHas(lazyLoaded | unpacked) { // avoid locking belowreturn}// TODO(rfindley): if n.check is non-nil we can avoid locking here, since// type-checking is not concurrent. Evaluate if this is worth doing..mu.Lock()defer .mu.Unlock()// only atomic for consistency; we are holding the mutexif .stateHas(lazyLoaded | unpacked) {return}// underlying comes after unpacking, do not set itdefer (func() { assert(!.stateHas(hasUnder)) })()if .inst != nil {assert(.fromRHS == nil) // instantiated types are not declared typesassert(.loader == nil) // cannot import an instantiation:= .inst.orig.().fromRHS = .expandRHS().tparams = .tparamsif len(.methods) == 0 {.setState(lazyLoaded | unpacked | hasMethods) // nothing further to do.inst.ctxt = nil} else {.setState(lazyLoaded | unpacked)}return}// TODO(mdempsky): Since we're passing n to the loader 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// SetTypeParams, 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.if .loader != nil {assert(.fromRHS == nil) // not loaded yetassert(.inst == nil) // cannot import an instantiation, , , := .loader().loader = nil.tparams = bindTParams().fromRHS = // for cycle detection.methods =.setState(lazyLoaded) // avoid deadlock calling delayed functionsfor , := range {()}}.setState(lazyLoaded | unpacked | hasMethods)return}// stateHas atomically determines whether the current state includes any active bit in sm.func ( *Named) ( stateMask) bool {return stateMask(atomic.LoadUint32(&.state_))& != 0}// setState atomically sets the current state to include each active bit in sm.// Must only be called while holding n.mu.func ( *Named) ( stateMask) {atomic.OrUint32(&.state_, uint32())// verify state transitionsif debug {:= stateMask(atomic.LoadUint32(&.state_)):= &unpacked != 0// unpacked => lazyLoadedif {assert(&lazyLoaded != 0)}// hasMethods => unpackedif &hasMethods != 0 {assert()}// hasUnder => unpackedif &hasUnder != 0 {assert()}// hasFinite => unpackedif &hasFinite != 0 {assert()}}}// newNamed is like NewNamed but with a *Checker receiver.func ( *Checker) ( *TypeName, Type, []*Func) *Named {:= &Named{check: , obj: , fromRHS: , methods: }if .typ == nil {.typ =}// Ensure that typ is always sanity-checked.if != nil {.needsCleanup()}return}// newNamedInstance creates a new named instance for the given origin and type// arguments, recording pos as the position of its synthetic object (for error// reporting).//// If set, expanding is the named type instance currently being expanded, that// led to the creation of this instance.func ( *Checker) ( token.Pos, *Named, []Type, *Named) *Named {assert(len() > 0):= NewTypeName(, .obj.pkg, .obj.name, nil):= &instance{orig: , targs: newTypeList()}// Only pass the expanding context to the new instance if their packages// match. Since type reference cycles are only possible within a single// package, this is sufficient for the purposes of short-circuiting cycles.// Avoiding passing the context in other cases prevents unnecessary coupling// of types across packages.if != nil && .Obj().pkg == .pkg {.ctxt = .inst.ctxt}:= &Named{check: , obj: , inst: }.typ =// Ensure that typ is always sanity-checked.if != nil {.needsCleanup()}return}func ( *Named) () {// 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 .inst == nil {.Underlying()}.check = nil}// Obj returns the type name for the declaration defining the named type t. For// instantiated types, this is same as the type name of the origin type.func ( *Named) () *TypeName {if .inst == nil {return .obj}return .inst.orig.obj}// Origin returns the generic type from which the named type t is// instantiated. If t is not an instantiated type, the result is t.func ( *Named) () *Named {if .inst == nil {return}return .inst.orig}// TypeParams returns the type parameters of the named type t, or nil.// The result is non-nil for an (originally) generic type even if it is instantiated.func ( *Named) () *TypeParamList { return .unpack().tparams }// SetTypeParams sets the type parameters of the named type t.// t must not have type arguments.func ( *Named) ( []*TypeParam) {assert(.inst == nil).unpack().tparams = bindTParams()}// TypeArgs returns the type arguments used to instantiate the named type t.func ( *Named) () *TypeList {if .inst == nil {return nil}return .inst.targs}// NumMethods returns the number of explicit methods defined for t.func ( *Named) () int {return len(.Origin().unpack().methods)}// Method returns the i'th method of named type t for 0 <= i < t.NumMethods().//// For an ordinary or instantiated type t, the receiver base type of this// method is the named type t. For an uninstantiated generic type t, each// method receiver is instantiated with its receiver type parameters.//// Methods are numbered deterministically: given the same list of source files// presented to the type checker, or the same sequence of NewMethod and AddMethod// calls, the mapping from method index to corresponding method remains the same.// But the specific ordering is not specified and must not be relied on as it may// change in the future.func ( *Named) ( int) *Func {.unpack()if .stateHas(hasMethods) {return .methods[]}assert(.inst != nil) // only instances should have unexpanded methods:= .inst.orig.mu.Lock()defer .mu.Unlock()if len(.methods) != len(.methods) {assert(len(.methods) == 0).methods = make([]*Func, len(.methods))}if .methods[] == nil {assert(.inst.ctxt != nil) // we should still have a context remaining from the resolution phase.methods[] = .expandMethod().inst.expandedMethods++// Check if we've created all methods at this point. If we have, mark the// type as having all of its methods.if .inst.expandedMethods == len(.methods) {.setState(hasMethods).inst.ctxt = nil // no need for a context anymore}}return .methods[]}// expandMethod substitutes type arguments in the i'th method for an// instantiated receiver.func ( *Named) ( int) *Func {// t.orig.methods is not lazy. origm is the method instantiated with its// receiver type parameters (the "origin" method).:= .inst.orig.Method()assert( != nil):= .check// Ensure that the original method is type-checked.if != nil {.objDecl()}:= .typ.(*Signature), := deref(.Recv().Type())// If rbase is t, then origm is already the instantiated method we're looking// for. In this case, we return origm to preserve the invariant that// traversing Method->Receiver Type->Method should get back to the same// method.//// This occurs if t is instantiated with the receiver type parameters, as in// the use of m in func (r T[_]) m() { r.m() }.if == {return}:=// We can only substitute if we have a correspondence between type arguments// and type parameters. This check is necessary in the presence of invalid// code.if .RecvTypeParams().Len() == .inst.targs.Len() {:= makeSubstMap(.RecvTypeParams().list(), .inst.targs.list())var *Contextif != nil {= .context()}= .subst(.pos, , , , ).(*Signature)}if == {// No substitution occurred, but we still need to create a new signature to// hold the instantiated receiver.:= *= &}var Typeif .hasPtrRecv() {= NewPointer()} else {=}.recv = cloneVar(.recv, )return cloneFunc(, )}// SetUnderlying sets the underlying type and marks t as complete.// t must not have type arguments.func ( *Named) ( Type) {assert(.inst == nil)if == nil {panic("underlying type must not be nil")}if asNamed() != nil {panic("underlying type must not be *Named")}// be careful to uphold the state invariants.mu.Lock()defer .mu.Unlock().fromRHS =.allowNilRHS = false.setState(lazyLoaded | unpacked | hasMethods) // TODO(markfreeman): Why hasMethods?.underlying =.allowNilUnderlying = false.setState(hasUnder)}// AddMethod adds method m unless it is already in the method list.// The method must be in the same package as t, and t must not have// type arguments.func ( *Named) ( *Func) {assert(samePkg(.obj.pkg, .pkg))assert(.inst == nil).unpack()if .methodIndex(.name, false) < 0 {.methods = append(.methods, )}}// methodIndex returns the index of the method with the given name.// If foldCase is set, capitalization in the name is ignored.// The result is negative if no such method exists.func ( *Named) ( string, bool) int {if == "_" {return -1}if {for , := range .methods {if strings.EqualFold(.name, ) {return}}} else {for , := range .methods {if .name == {return}}}return -1}// rhs returns [Named.fromRHS].//// In debug mode, it also asserts that n is in an appropriate state.func ( *Named) () Type {if debug {assert(.stateHas(lazyLoaded | unpacked))}return .fromRHS}// Underlying returns the [underlying type] of the named type t, resolving all// forwarding declarations. Underlying types are never Named, TypeParam, or// Alias types.//// [underlying type]: https://go.dev/ref/spec#Underlying_types.func ( *Named) () Type {.unpack()// The gccimporter depends on writing a nil underlying via NewNamed and// immediately reading it back. Rather than putting that in Named.under// and complicating things there, we just check for that special case here.if .rhs() == nil {assert(.allowNilRHS)if .allowNilUnderlying {return nil}}if !.stateHas(hasUnder) { // minor performance optimization.resolveUnderlying()}return .underlying}func ( *Named) () string { return TypeString(, nil) }// ----------------------------------------------------------------------------// Implementation//// TODO(rfindley): reorganize the loading and expansion methods under this// heading.// resolveUnderlying computes the underlying type of n. If n already has an// underlying type, nothing happens.//// It does so by following RHS type chains for alias and named types. If any// other type T is found, each named type in the chain has its underlying// type set to T. Aliases are skipped because their underlying type is// not memoized.//// resolveUnderlying assumes that there are no direct cycles; if there were// any, they were broken (by setting the respective types to invalid) during// the directCycles check phase.func ( *Named) () {assert(.stateHas(unpacked))var map[*Named]bool // for debugging onlyif debug {= make(map[*Named]bool)}var []*Namedvar Typefor := Type(); == nil; {switch t := .(type) {case nil:= Typ[Invalid]case *Alias:= unalias()case *Named:if debug {assert(![])[] = true}// don't recalculate the underlyingif .stateHas(hasUnder) {= .underlyingbreak}if debug {[] = true}= append(, ).unpack()assert(.rhs() != nil || .allowNilRHS)= .rhs()default:= // any type literal or predeclared type works}}for , := range {func() {.mu.Lock()defer .mu.Unlock()// Careful, t.underlying has lock-free readers. Since we might be racing// another call to resolveUnderlying, we have to avoid overwriting// t.underlying. Otherwise, the race detector will be tripped.if !.stateHas(hasUnder) {.underlying =.setState(hasUnder)}}()}}func ( *Named) ( *Package, string, bool) (int, *Func) {.unpack()if samePkg(.obj.pkg, ) || isExported() || {// If n is an instance, we may not have yet instantiated all of its methods.// Look up the method index in orig, and only instantiate method at the// matching index (if any).if := .Origin().methodIndex(, ); >= 0 {// For instances, m.Method(i) will be different from the orig method.return , .Method()}}return -1, nil}// context returns the type-checker context.func ( *Checker) () *Context {if .ctxt == nil {.ctxt = NewContext()}return .ctxt}// 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 ( *Named) () ( Type) {:= .checkif != nil && .conf._Trace {.trace(.obj.pos, "-- Named.expandRHS %s", ).indent++defer func() {.indent--.trace(.obj.pos, "=> %s (rhs = %s)", , )}()}assert(!.stateHas(unpacked))assert(.inst.orig.stateHas(lazyLoaded | unpacked))if .inst.ctxt == nil {.inst.ctxt = NewContext()}:= .inst.ctxt:= .inst.orig:= .inst.targs:= .tparamsif .Len() != .Len() {return Typ[Invalid]}:= .instanceHash(, .list()):= .update(, , .list(), ) // block fixed point infinite instantiationassert( == ):= makeSubstMap(.list(), .list())if != nil {= .context()}= .subst(.obj.pos, .rhs(), , , )// 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 , := .(*Interface); != nil {if , := replaceRecvType(.methods, , ); {// If the RHS doesn't use type parameters, it may not have been// substituted; we need to craft a new interface first.if == .rhs() {assert(.complete) // otherwise we are copying incomplete data:= .newInterface().complete = true.implicit = false.embeddeds = .embeddeds=}.methods =.tset = nil // recompute type set with new methods// go.dev/issue/61561: We have to complete the interface even without a checker.if == nil {.typeSet()}return}}return}// safeUnderlying returns the underlying type of typ without expanding// instances, to avoid infinite recursion.//// TODO(rfindley): eliminate this function or give it a better name.func safeUnderlying( Type) Type {if := asNamed(); != nil {return .underlying}return .Underlying()}
![]() |
The pages are generated with Golds v0.8.3-preview. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |