You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
205 lines
6.6 KiB
205 lines
6.6 KiB
// Copyright 2021 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 typeparams contains common utilities for writing tools that interact
|
|
// with generic Go code, as introduced with Go 1.18.
|
|
//
|
|
// Many of the types and functions in this package are proxies for the new APIs
|
|
// introduced in the standard library with Go 1.18. For example, the
|
|
// typeparams.Union type is an alias for go/types.Union, and the ForTypeSpec
|
|
// function returns the value of the go/ast.TypeSpec.TypeParams field. At Go
|
|
// versions older than 1.18 these helpers are implemented as stubs, allowing
|
|
// users of this package to write code that handles generic constructs inline,
|
|
// even if the Go version being used to compile does not support generics.
|
|
//
|
|
// Additionally, this package contains common utilities for working with the
|
|
// new generic constructs, to supplement the standard library APIs. Notably,
|
|
// the StructuralTerms API computes a minimal representation of the structural
|
|
// restrictions on a type parameter.
|
|
//
|
|
// An external version of these APIs is available in the
|
|
// golang.org/x/exp/typeparams module.
|
|
package typeparams
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
)
|
|
|
|
// UnpackIndexExpr extracts data from AST nodes that represent index
|
|
// expressions.
|
|
//
|
|
// For an ast.IndexExpr, the resulting indices slice will contain exactly one
|
|
// index expression. For an ast.IndexListExpr (go1.18+), it may have a variable
|
|
// number of index expressions.
|
|
//
|
|
// For nodes that don't represent index expressions, the first return value of
|
|
// UnpackIndexExpr will be nil.
|
|
func UnpackIndexExpr(n ast.Node) (x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) {
|
|
switch e := n.(type) {
|
|
case *ast.IndexExpr:
|
|
return e.X, e.Lbrack, []ast.Expr{e.Index}, e.Rbrack
|
|
case *IndexListExpr:
|
|
return e.X, e.Lbrack, e.Indices, e.Rbrack
|
|
}
|
|
return nil, token.NoPos, nil, token.NoPos
|
|
}
|
|
|
|
// PackIndexExpr returns an *ast.IndexExpr or *ast.IndexListExpr, depending on
|
|
// the cardinality of indices. Calling PackIndexExpr with len(indices) == 0
|
|
// will panic.
|
|
func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) ast.Expr {
|
|
switch len(indices) {
|
|
case 0:
|
|
panic("empty indices")
|
|
case 1:
|
|
return &ast.IndexExpr{
|
|
X: x,
|
|
Lbrack: lbrack,
|
|
Index: indices[0],
|
|
Rbrack: rbrack,
|
|
}
|
|
default:
|
|
return &IndexListExpr{
|
|
X: x,
|
|
Lbrack: lbrack,
|
|
Indices: indices,
|
|
Rbrack: rbrack,
|
|
}
|
|
}
|
|
}
|
|
|
|
// IsTypeParam reports whether t is a type parameter.
|
|
func IsTypeParam(t types.Type) bool {
|
|
_, ok := t.(*TypeParam)
|
|
return ok
|
|
}
|
|
|
|
// OriginMethod returns the origin method associated with the method fn.
|
|
// For methods on a non-generic receiver base type, this is just
|
|
// fn. However, for methods with a generic receiver, OriginMethod 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), OriginMethod
|
|
// returns fn.
|
|
func OriginMethod(fn *types.Func) *types.Func {
|
|
recv := fn.Type().(*types.Signature).Recv()
|
|
if recv == nil {
|
|
return fn
|
|
}
|
|
base := recv.Type()
|
|
p, isPtr := base.(*types.Pointer)
|
|
if isPtr {
|
|
base = p.Elem()
|
|
}
|
|
named, isNamed := base.(*types.Named)
|
|
if !isNamed {
|
|
// Receiver is a *types.Interface.
|
|
return fn
|
|
}
|
|
if ForNamed(named).Len() == 0 {
|
|
// Receiver base has no type parameters, so we can avoid the lookup below.
|
|
return fn
|
|
}
|
|
orig := NamedTypeOrigin(named)
|
|
gfn, _, _ := types.LookupFieldOrMethod(orig, true, fn.Pkg(), fn.Name())
|
|
|
|
// This is a fix for a gopls crash (#60628) due to a go/types bug (#60634). In:
|
|
// package p
|
|
// type T *int
|
|
// func (*T) f() {}
|
|
// LookupFieldOrMethod(T, true, p, f)=nil, but NewMethodSet(*T)={(*T).f}.
|
|
// Here we make them consistent by force.
|
|
// (The go/types bug is general, but this workaround is reached only
|
|
// for generic T thanks to the early return above.)
|
|
if gfn == nil {
|
|
mset := types.NewMethodSet(types.NewPointer(orig))
|
|
for i := 0; i < mset.Len(); i++ {
|
|
m := mset.At(i)
|
|
if m.Obj().Id() == fn.Id() {
|
|
gfn = m.Obj()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// In golang/go#61196, we observe another crash, this time inexplicable.
|
|
if gfn == nil {
|
|
panic(fmt.Sprintf("missing origin method for %s.%s; named == origin: %t, named.NumMethods(): %d, origin.NumMethods(): %d", named, fn, named == orig, named.NumMethods(), orig.NumMethods()))
|
|
}
|
|
|
|
return gfn.(*types.Func)
|
|
}
|
|
|
|
// GenericAssignableTo is a generalization of types.AssignableTo that
|
|
// implements the following rule for uninstantiated generic types:
|
|
//
|
|
// If V and T are generic named types, then V is considered assignable to T if,
|
|
// for every possible instantation of V[A_1, ..., A_N], the instantiation
|
|
// T[A_1, ..., A_N] is valid and V[A_1, ..., A_N] implements T[A_1, ..., A_N].
|
|
//
|
|
// If T has structural constraints, they must be satisfied by V.
|
|
//
|
|
// For example, consider the following type declarations:
|
|
//
|
|
// type Interface[T any] interface {
|
|
// Accept(T)
|
|
// }
|
|
//
|
|
// type Container[T any] struct {
|
|
// Element T
|
|
// }
|
|
//
|
|
// func (c Container[T]) Accept(t T) { c.Element = t }
|
|
//
|
|
// In this case, GenericAssignableTo reports that instantiations of Container
|
|
// are assignable to the corresponding instantiation of Interface.
|
|
func GenericAssignableTo(ctxt *Context, V, T types.Type) bool {
|
|
// If V and T are not both named, or do not have matching non-empty type
|
|
// parameter lists, fall back on types.AssignableTo.
|
|
|
|
VN, Vnamed := V.(*types.Named)
|
|
TN, Tnamed := T.(*types.Named)
|
|
if !Vnamed || !Tnamed {
|
|
return types.AssignableTo(V, T)
|
|
}
|
|
|
|
vtparams := ForNamed(VN)
|
|
ttparams := ForNamed(TN)
|
|
if vtparams.Len() == 0 || vtparams.Len() != ttparams.Len() || NamedTypeArgs(VN).Len() != 0 || NamedTypeArgs(TN).Len() != 0 {
|
|
return types.AssignableTo(V, T)
|
|
}
|
|
|
|
// V and T have the same (non-zero) number of type params. Instantiate both
|
|
// with the type parameters of V. This must always succeed for V, and will
|
|
// succeed for T if and only if the type set of each type parameter of V is a
|
|
// subset of the type set of the corresponding type parameter of T, meaning
|
|
// that every instantiation of V corresponds to a valid instantiation of T.
|
|
|
|
// Minor optimization: ensure we share a context across the two
|
|
// instantiations below.
|
|
if ctxt == nil {
|
|
ctxt = NewContext()
|
|
}
|
|
|
|
var targs []types.Type
|
|
for i := 0; i < vtparams.Len(); i++ {
|
|
targs = append(targs, vtparams.At(i))
|
|
}
|
|
|
|
vinst, err := Instantiate(ctxt, V, targs, true)
|
|
if err != nil {
|
|
panic("type parameters should satisfy their own constraints")
|
|
}
|
|
|
|
tinst, err := Instantiate(ctxt, T, targs, true)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return types.AssignableTo(vinst, tinst)
|
|
}
|