The golang.org/x/exp/typeparams package provides common utilities for writing tools that interact with generic Go code, as introduced with Go 1.18. It offers proxy APIs for the new type parameter features in the standard library, along with supplemental utilities for working with generic constructs.
go get golang.org/x/exp/typeparamsimport (
"golang.org/x/exp/typeparams"
"go/ast"
"go/types"
"go/token"
)package main
import (
"fmt"
"golang.org/x/exp/typeparams"
"go/ast"
"go/types"
)
func main() {
// Check if type parameters are enabled
if typeparams.Enabled() {
fmt.Println("Type parameters are supported")
}
// Create a type parameter with a constraint
typeName := types.NewTypeName(0, nil, "T", nil)
constraint := types.Universe.Lookup("any").Type()
tp := typeparams.NewTypeParam(typeName, constraint)
fmt.Printf("Type parameter: %v\n", tp)
// Create a union type
term1 := typeparams.NewTerm(false, types.Typ[types.Int])
term2 := typeparams.NewTerm(false, types.Typ[types.String])
union := typeparams.NewUnion([]*typeparams.Term{term1, term2})
fmt.Printf("Union: %v\n", union)
}This package bridges the gap between code that needs to work with generic Go constructs and varying Go versions. Many APIs are aliases or proxies for Go 1.18+ standard library features, but have stub implementations for older Go versions. Key capabilities include:
Functions for checking whether a type is a type parameter and creating new type parameters.
func IsTypeParam(t types.Type) boolChecks if a type is a type parameter.
func NewTypeParam(name *types.TypeName, constraint types.Type) *TypeParamCreates a new type parameter with the given name and constraint. This function calls types.NewTypeParam.
func Enabled() boolReports whether type parameters are enabled in the current build environment.
Functions for extracting type parameter information from AST nodes representing types and functions.
func ForTypeSpec(n *ast.TypeSpec) *ast.FieldListExtracts type parameters from a type specification. Returns n.TypeParams.
func ForFuncType(n *ast.FuncType) *ast.FieldListExtracts type parameters from a function type. Returns n.TypeParams.
Functions for working with named types and their type arguments.
func ForNamed(named *types.Named) *TypeParamListExtracts the type parameter list from a named type. Returns the possibly empty type parameter object list from the named type.
func NamedTypeArgs(named *types.Named) *TypeListReturns the type arguments of a named type. This function calls named.TypeArgs().
func NamedTypeOrigin(named *types.Named) types.TypeReturns the origin type of a named type. This function calls named.Orig().
func SetForNamed(n *types.Named, tparams []*TypeParam)Sets the type parameters on a named type. Each tparam must be of dynamic type *types.TypeParam.
Functions for working with function signatures, their type parameters, and generic methods.
func ForSignature(sig *types.Signature) *TypeParamListReturns the type parameters of a function signature. This function calls sig.TypeParams().
func RecvTypeParams(sig *types.Signature) *TypeParamListReturns the receiver type parameters of a function signature. This function calls sig.RecvTypeParams().
func NewSignatureType(recv *types.Var, recvTypeParams, typeParams []*TypeParam, params, results *types.Tuple, variadic bool) *types.SignatureCreates a new signature type with the given receiver, receiver type parameters, type parameters, parameters, results, and variadic flag. This function calls types.NewSignatureType.
func OriginMethod(fn *types.Func) *types.FuncReturns the origin method associated with a method function. For methods on non-generic receiver base types, this is just fn. For methods with a generic receiver, this returns the corresponding method in the method set of the origin type. As a special case, if fn is not a method (has no receiver), it returns fn.
Functions for instantiating generic types with concrete type arguments.
func Instantiate(ctxt *Context, typ types.Type, targs []types.Type, validate bool) (types.Type, error)Instantiates a generic type with the provided type arguments. This function calls types.Instantiate.
Functions for analyzing and normalizing type constraints and structural restrictions.
func NormalTerms(typ types.Type) ([]*Term, error)Returns a slice of terms representing the normalized structural type restrictions of a type. For types whose underlying type is not *types.TypeParam, *types.Interface, or *types.Union, this returns a single term. The function normalizes complex constraints by expanding interface embeddings and unions, then computing their intersection.
Returns an error if the type is invalid, exceeds complexity bounds, or has an empty type set. In the latter case, returns ErrEmptyTypeSet.
func GenericAssignableTo(ctxt *Context, V, T types.Type) boolA generalization of types.AssignableTo that handles uninstantiated generic types. V is considered assignable to T if:
Functions for checking and modifying interface properties related to generic constraints.
func IsComparable(iface *types.Interface) boolChecks if an interface is comparable. This function calls iface.IsComparable().
func IsImplicit(iface *types.Interface) boolChecks if an interface is implicit. This function calls iface.IsImplicit().
func IsMethodSet(iface *types.Interface) boolChecks if an interface is a method set. This function calls iface.IsMethodSet().
func MarkImplicit(iface *types.Interface)Marks an interface as implicit. This function calls iface.MarkImplicit().
Functions for tracking and retrieving information about type and function instances.
func InitInstances(info *types.Info)Initializes a types.Info structure to record information about type and function instances.
func GetInstances(info *types.Info) map[*ast.Ident]InstanceReturns the instances map from a types.Info structure. This function calls info.Instances.
Functions for packing and unpacking index expressions in AST nodes.
func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) ast.ExprCreates an AST index expression node. Returns an *ast.IndexExpr or *ast.IndexListExpr depending on the number of indices. Will panic if len(indices) == 0.
func UnpackIndexExpr(n ast.Node) (x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos)Extracts data from AST nodes representing index expressions. For an ast.IndexExpr, the resulting indices slice contains exactly one index. For an ast.IndexListExpr (Go 1.18+), it may have a variable number of index expressions. For nodes that don't represent index expressions, the first return value is nil.
Functions for creating type checking contexts.
func NewContext() *ContextCreates a new type checking context. This function calls types.NewContext.
type Context = types.ContextContext is an alias for types.Context used in type checking operations.
type Instance = types.InstanceInstance is an alias for types.Instance representing a type or function instance.
type IndexListExpr = ast.IndexListExprIndexListExpr is an alias for ast.IndexListExpr representing a list of indices in an index expression.
type Term = types.TermTerm is an alias for types.Term representing a term in a union type constraint.
type TypeList = types.TypeListTypeList is an alias for types.TypeList representing a list of types.
type TypeParam = types.TypeParamTypeParam is an alias for types.TypeParam representing a type parameter.
type TypeParamList = types.TypeParamListTypeParamList is an alias for types.TypeParamList representing a list of type parameters.
type Union = types.UnionUnion is an alias for types.Union representing a union of type terms.
func NewTerm(tilde bool, typ types.Type) *TermCreates a new term with an optional tilde. The tilde indicates a type constraint approximation. This function calls types.NewTerm.
func NewUnion(terms []*Term) *UnionCreates a new union type from a slice of terms. This function calls types.NewUnion.
var ErrEmptyTypeSet = errors.New("empty type set")ErrEmptyTypeSet is returned if a type set computation results in a type set with no types. This error indicates that the structural constraints defined in a type parameter are contradictory and cannot be satisfied by any type.
package main
import (
"fmt"
"golang.org/x/exp/typeparams"
"go/ast"
"go/parser"
"go/types"
)
func analyzeGenericFunction(source string) error {
// Parse the source code
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "example.go", source, 0)
if err != nil {
return err
}
// For each function declaration
for _, decl := range file.Decls {
if fn, ok := decl.(*ast.FuncDecl); ok {
// Extract type parameters from function
tparams := typeparams.ForFuncType(fn.Type)
if tparams != nil && len(tparams.List) > 0 {
fmt.Printf("Function %s has type parameters\n", fn.Name.Name)
for _, param := range tparams.List {
fmt.Printf(" - %s\n", param.Names[0].Name)
}
}
}
}
return nil
}package main
import (
"fmt"
"golang.org/x/exp/typeparams"
"go/types"
)
func analyzeConstraint(constraint types.Type) error {
// Get the normalized terms representing the constraint
terms, err := typeparams.NormalTerms(constraint)
if err != nil {
if err == typeparams.ErrEmptyTypeSet {
fmt.Println("Constraint has an empty type set")
}
return err
}
fmt.Printf("Normalized constraint terms:\n")
for _, term := range terms {
if term.Tilde() {
fmt.Printf(" ~%v\n", term.Type())
} else {
fmt.Printf(" %v\n", term.Type())
}
}
return nil
}package main
import (
"fmt"
"golang.org/x/exp/typeparams"
"go/types"
)
func createConstraintUnion() *typeparams.Union {
// Create terms for int and string
intTerm := typeparams.NewTerm(false, types.Typ[types.Int])
strTerm := typeparams.NewTerm(false, types.Typ[types.String])
// Create a union allowing int or string
union := typeparams.NewUnion([]*typeparams.Term{intTerm, strTerm})
fmt.Printf("Union created: %v\n", union)
return union
}package main
import (
"fmt"
"golang.org/x/exp/typeparams"
"go/types"
)
func instantiateGeneric() error {
// Create a context for type checking
ctxt := typeparams.NewContext()
// Assume we have a generic type List[T]
// and want to instantiate it with int
typeArgs := []types.Type{types.Typ[types.Int]}
// Note: In practice, you would extract the generic type from
// parsed code or type information
// inst, err := typeparams.Instantiate(ctxt, genericType, typeArgs, true)
// if err != nil {
// return err
// }
// fmt.Printf("Instantiated type: %v\n", inst)
return nil
}package main
import (
"fmt"
"golang.org/x/exp/typeparams"
"go/types"
)
func analyzeGenericMethod(method *types.Func) {
sig := method.Type().(*types.Signature)
// Check if method has receiver type parameters
recvTypeParams := typeparams.RecvTypeParams(sig)
fmt.Printf("Method %s receiver type parameters: %d\n",
method.Name(), recvTypeParams.Len())
// Check signature type parameters
typeParams := typeparams.ForSignature(sig)
fmt.Printf("Method %s type parameters: %d\n",
method.Name(), typeParams.Len())
// Get the origin method
origin := typeparams.OriginMethod(method)
fmt.Printf("Origin method: %s\n", origin.Name())
}package main
import (
"fmt"
"golang.org/x/exp/typeparams"
"go/types"
)
func analyzeNamedType(named *types.Named) {
// Get type parameters
tparams := typeparams.ForNamed(named)
fmt.Printf("Named type %s has %d type parameters\n",
named.String(), tparams.Len())
// Get type arguments
targs := typeparams.NamedTypeArgs(named)
fmt.Printf("Named type %s has %d type arguments\n",
named.String(), targs.Len())
// Get origin type
origin := typeparams.NamedTypeOrigin(named)
fmt.Printf("Origin type: %v\n", origin)
}The golang.org/x/exp/typeparams package is designed to work across different Go versions:
This compatibility approach allows developers to write generic-aware tools that can be used across Go versions, with full functionality on Go 1.18+ and graceful degradation on earlier versions.