// Package gotypes is plumb's go/types toolkit: the type-graph walks, structural // matching, substitution, and predicates the core needs to reason about loaded // types. Everything here is a pure function of its go/types inputs (no plumb // concepts, no diagnostics), so it can sit below every phase and be tested on its // own. It is to go/types what the gopackages package is to go/packages.
package gotypes import ( ) // Map and Set are keyed by type identity (types.Identical), not the pointer or == // identity a plain Go map uses, because go/types does not canonicalize types: two // distinct *types.Named values can denote the same type. Both wrap typeutil.Map, // giving typed keys and values so callers avoid the any-casts a bare typeutil.Map // forces. Once Go ships a types.Hash (go.dev/issues/69559) both can become plain // generic maps; see typeutil.Map's own TODO. // Map is a type-keyed map with typed values. The zero value is an empty map ready // to use. type Map[ types.Type, any] struct{ m typeutil.Map } // At returns the value stored for key and whether it was present. func ( *Map[, ]) ( ) (, bool) { if := .m.At(); != nil { return .(), true } var return , false } // Set stores value for key, replacing any previous value. func ( *Map[, ]) ( , ) { .m.Set(, ) } // Len returns the number of entries. func ( *Map[, ]) () int { return .m.Len() } // All iterates the entries in unspecified order; it satisfies iter.Seq2[K, V]. func ( *Map[, ]) ( func(, ) bool) { := false .m.Iterate(func( types.Type, any) { if ! && !(.(), .()) { = true } }) } // Set is a type-keyed set. The zero value is an empty set ready to use; it is the // visited-set for cyclic-type-safe graph walks and the presence-set the solver // keeps of demanded, consumed, and already-instantiated types. type Set[ types.Type] struct{ m typeutil.Map } // Add records t and reports whether it was newly added: false if t was already // present. A cyclic-type walk guards recursion with `if !seen.Add(t) { return }`, // which stops on a revisit. func ( *Set[]) ( ) bool { if .m.At() != nil { return false } .m.Set(, true) return true } // Contains reports whether t is in the set. func ( *Set[]) ( ) bool { return .m.At() != nil } // Elements iterates the members in unspecified order; it satisfies iter.Seq[T]. // Callers that need a deterministic order sort what they collect. func ( *Set[]) ( func() bool) { := false .m.Iterate(func( types.Type, any) { if ! && !(.()) { = true } }) } // ListKey packs a type list into a tuple usable as a typeutil.Map key: // types.Identical compares tuples element-wise (ignoring the synthetic names), so // two identical lists collapse to one entry. It backs the (provider, type-args) // instantiation keys, where the provider supplies the outer map dimension. func ( []types.Type) *types.Tuple { := make([]*types.Var, len()) for , := range { [] = types.NewVar(token.NoPos, nil, "", ) } return types.NewTuple(...) } // pointerElem returns the element of a pointer to a bridgeable type and true, when // t is *E for some bridgeable E (a named type, a type parameter, or a predeclared // basic; see bridgeable). Used for the value/pointer bridge. func ( types.Type) (types.Type, bool) { , := types.Unalias().(*types.Pointer) if ! { return nil, false } if bridgeable(.Elem()) { return .Elem(), true } return nil, false } // bridgeable reports whether t is a type whose value/pointer dual is meaningful // for the bridge: a named type, a type parameter, or a predeclared basic type // (so int⇄*int bridges just like MyInt⇄*MyInt). A composite type written inline // (a slice, map, struct, func, etc.) has no dual; only a pointer to a bridgeable // type does. func ( types.Type) bool { switch types.Unalias().(type) { case *types.Named, *types.TypeParam, *types.Basic: return true } return false } // DualType returns the value/pointer dual of a bridgeable type. The argument // may be either form: for a pointer to a bridgeable type it returns the element // (*E → E), for a bridgeable non-pointer the pointer type (T → *T), and for // everything else (nil, false). func ( types.Type) (types.Type, bool) { if , := pointerElem(); { return , true } if bridgeable() { return types.NewPointer(), true } return nil, false } // --- type predicates --------------------------------------------------------- var errorType = types.Universe.Lookup("error").Type() // comparableType is the predeclared comparable constraint, which must not be // collapsed to "any". var comparableType = types.Universe.Lookup("comparable").Type() // ConstraintCollapsesToAny reports whether a type-parameter constraint renders as // the bare keyword "any": an interface with no methods and no embeddeds that is // not the predeclared comparable. emit renders such a constraint as "any" and // names neither it nor its package, so solve's reachability check must agree. // This is the single predicate both consult, so they cannot drift (a collapsed // unexported constraint must not be rejected, and a rendered one must be imported). func ( types.Type) bool { , := .Underlying().(*types.Interface) return && .NumMethods() == 0 && .NumEmbeddeds() == 0 && !types.Identical(, comparableType) } // IsErrorType reports whether t is exactly the predeclared error interface. func ( types.Type) bool { return types.Identical(, errorType) } // IsBareCleanup reports whether t is the bare type func() (no params, no // results). A *named* function type is not bare; a type *alias* to func() is, // since an alias and its target are the same type. func ( types.Type) bool { // No variadic check: a variadic signature always has at least one parameter, // so the zero-parameter test already excludes it. , := types.Unalias().(*types.Signature) return && .Params().Len() == 0 && .Results().Len() == 0 } // IsFailableCleanup reports whether t is the bare type func() error. func ( types.Type) bool { , := types.Unalias().(*types.Signature) if ! || .Params().Len() != 0 || .Results().Len() != 1 { return false } return IsErrorType(.Results().At(0).Type()) } // IsUntyped reports whether t is an untyped basic type (e.g. an untyped // constant's default-less type). func ( types.Type) bool { , := .(*types.Basic) return && .Info()&types.IsUntyped != 0 } // IsUntypedNil reports whether t is the predeclared untyped nil. func ( types.Type) bool { , := .(*types.Basic) return && .Kind() == types.UntypedNil } // TypeName renders t for diagnostics, qualifying foreign packages by name. func ( types.Type) string { return types.TypeString(, func( *types.Package) string { if == nil { return "" } return .Name() }) } // KindOfType names the broad shape of t for a "not a struct" diagnostic: // interfaces are called out by name, and everything else is "a non-struct type". func ( types.Type) string { if , := .(*types.Interface); { return "an interface" } return "a non-struct type" } // GenericOrigin returns the generic origin of t. t is a provider's declared or // receiver type, which is always a *types.Named or a *types.Alias; any other // shape is a broken invariant and panics rather than passing t through, which // would mask the bug downstream. func ( types.Type) types.Type { switch u := .(type) { case *types.Named: return .Origin() case *types.Alias: return .Origin() } panic(fmt.Sprintf("plumb: GenericOrigin on %T; want a defined type or alias", )) } // TypeParamsOf returns the type parameters of t. As with GenericOrigin, t is a // provider's declared or receiver type and is always a *types.Named or a // *types.Alias; any other shape panics. func ( types.Type) *types.TypeParamList { switch u := .(type) { case *types.Named: return .TypeParams() case *types.Alias: return .TypeParams() } panic(fmt.Sprintf("plumb: TypeParamsOf on %T; want a defined type or alias", )) } // TypeNameOf returns the declaring object of t. t is a provider's declared or // receiver type, always a *types.Named or a *types.Alias; any other shape // panics. Returning a nil *types.TypeName instead would wrap a nil pointer in a // non-nil types.Object interface (a typed-nil that slips past a caller's // obj != nil guard and nil-panics on first use), so the invariant is enforced // here, where every caller then gets a non-nil result. func ( types.Type) *types.TypeName { switch u := .(type) { case *types.Named: return .Obj() case *types.Alias: return .Obj() } panic(fmt.Sprintf("plumb: TypeNameOf on %T; want a defined type or alias", )) }