Skip to content

Commit 058d147

Browse files
committed
typeparams: a new module with a transitional API for generics in tools
Add a new module for use by tool authors that need operate on generic code, while still building at older Go versions. This module API proxies the Go 1.18 API for type parameters, providing stubs at earlier Go versions. It also contains some common helpers to supplement the go/types API. For golang/go#50447 Change-Id: I97feb834eb2da646620f8377904520b511871915 Reviewed-on: https://go-review.googlesource.com/c/exp/+/383375 Trust: Robert Findley <[email protected]> Run-TryBot: Robert Findley <[email protected]> Trust: Dominik Honnef <[email protected]> Reviewed-by: Ian Cottrell <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 3e31098 commit 058d147

File tree

11 files changed

+1659
-0
lines changed

11 files changed

+1659
-0
lines changed

typeparams/common.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package typeparams contains common utilities for writing tools that interact
6+
// with generic Go code, as introduced with Go 1.18.
7+
//
8+
// THIS PACKAGE IS CURRENTLY EXPERIMENTAL AND MAY CHANGE. While the API is
9+
// being tested, we may find the need for improvement. This caveat will be
10+
// removed shortly.
11+
//
12+
// Many of the types and functions in this package are proxies for the new APIs
13+
// introduced in the standard library with Go 1.18. For example, the
14+
// typeparams.Union type is an alias for go/types.Union, and the ForTypeSpec
15+
// function returns the value of the go/ast.TypeSpec.TypeParams field. At Go
16+
// versions older than 1.18 these helpers are implemented as stubs, allowing
17+
// users of this package to write code that handles generic constructs inline,
18+
// even if the Go version being used to compile does not support generics.
19+
//
20+
// Additionally, this package contains common utilities for working with the
21+
// new generic constructs, to supplement the standard library APIs. Notably,
22+
// the NormalTerms API computes a minimal representation of the structural
23+
// restrictions on a type parameter. In the future, these supplemental APIs may
24+
// be available in the standard library..
25+
package typeparams
26+
27+
import (
28+
"go/ast"
29+
"go/token"
30+
"go/types"
31+
)
32+
33+
// Enabled reports whether type parameters are enabled in the current build
34+
// environment.
35+
func Enabled() bool {
36+
return enabled
37+
}
38+
39+
// UnpackIndexExpr extracts data from AST nodes that represent index
40+
// expressions.
41+
//
42+
// For an ast.IndexExpr, the resulting indices slice will contain exactly one
43+
// index expression. For an ast.IndexListExpr (go1.18+), it may have a variable
44+
// number of index expressions.
45+
//
46+
// For nodes that don't represent index expressions, the first return value of
47+
// UnpackIndexExpr will be nil.
48+
func UnpackIndexExpr(n ast.Node) (x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) {
49+
switch e := n.(type) {
50+
case *ast.IndexExpr:
51+
return e.X, e.Lbrack, []ast.Expr{e.Index}, e.Rbrack
52+
case *IndexListExpr:
53+
return e.X, e.Lbrack, e.Indices, e.Rbrack
54+
}
55+
return nil, token.NoPos, nil, token.NoPos
56+
}
57+
58+
// PackIndexExpr returns an *ast.IndexExpr or *ast.IndexListExpr, depending on
59+
// the cardinality of indices. Calling PackIndexExpr with len(indices) == 0
60+
// will panic.
61+
func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) ast.Expr {
62+
switch len(indices) {
63+
case 0:
64+
panic("empty indices")
65+
case 1:
66+
return &ast.IndexExpr{
67+
X: x,
68+
Lbrack: lbrack,
69+
Index: indices[0],
70+
Rbrack: rbrack,
71+
}
72+
default:
73+
return &IndexListExpr{
74+
X: x,
75+
Lbrack: lbrack,
76+
Indices: indices,
77+
Rbrack: rbrack,
78+
}
79+
}
80+
}
81+
82+
// IsTypeParam reports whether t is a type parameter.
83+
func IsTypeParam(t types.Type) bool {
84+
_, ok := t.(*TypeParam)
85+
return ok
86+
}
87+
88+
// OriginMethod returns the origin method associated with the method fn. For
89+
// methods on a non-generic receiver base type, this is just fn. However, for
90+
// methods with a generic receiver, OriginMethod returns the corresponding
91+
// method in the method set of the origin type.
92+
//
93+
// As a special case, if fn is not a method (has no receiver), OriginMethod
94+
// returns fn.
95+
func OriginMethod(fn *types.Func) *types.Func {
96+
recv := fn.Type().(*types.Signature).Recv()
97+
if recv == nil {
98+
return fn
99+
}
100+
base := recv.Type()
101+
p, isPtr := base.(*types.Pointer)
102+
if isPtr {
103+
base = p.Elem()
104+
}
105+
named, isNamed := base.(*types.Named)
106+
if !isNamed {
107+
// Receiver is a *types.Interface.
108+
return fn
109+
}
110+
if ForNamed(named).Len() == 0 {
111+
// Receiver base has no type parameters, so we can avoid the lookup below.
112+
return fn
113+
}
114+
orig := NamedTypeOrigin(named)
115+
gfn, _, _ := types.LookupFieldOrMethod(orig, true, fn.Pkg(), fn.Name())
116+
return gfn.(*types.Func)
117+
}
118+
119+
// GenericAssignableTo is a generalization of types.AssignableTo that
120+
// implements the following rule for uninstantiated generic types:
121+
//
122+
// If V and T are generic named types, then V is considered assignable to T if,
123+
// for every possible instantation of V[A_1, ..., A_N], the instantiation
124+
// T[A_1, ..., A_N] is valid and V[A_1, ..., A_N] implements T[A_1, ..., A_N].
125+
//
126+
// If T has structural constraints, they must be satisfied by V.
127+
//
128+
// For example, consider the following type declarations:
129+
//
130+
// type Interface[T any] interface {
131+
// Accept(T)
132+
// }
133+
//
134+
// type Container[T any] struct {
135+
// Element T
136+
// }
137+
//
138+
// func (c Container[T]) Accept(t T) { c.Element = t }
139+
//
140+
// In this case, GenericAssignableTo reports that instantiations of Container
141+
// are assignable to the corresponding instantiation of Interface.
142+
func GenericAssignableTo(ctxt *Context, V, T types.Type) bool {
143+
// If V and T are not both named, or do not have matching non-empty type
144+
// parameter lists, fall back on types.AssignableTo.
145+
146+
VN, Vnamed := V.(*types.Named)
147+
TN, Tnamed := T.(*types.Named)
148+
if !Vnamed || !Tnamed {
149+
return types.AssignableTo(V, T)
150+
}
151+
152+
vtparams := ForNamed(VN)
153+
ttparams := ForNamed(TN)
154+
if vtparams.Len() == 0 || vtparams.Len() != ttparams.Len() || NamedTypeArgs(VN).Len() != 0 || NamedTypeArgs(TN).Len() != 0 {
155+
return types.AssignableTo(V, T)
156+
}
157+
158+
// V and T have the same (non-zero) number of type params. Instantiate both
159+
// with the type parameters of V. This must always succeed for V, and will
160+
// succeed for T if and only if the type set of each type parameter of V is a
161+
// subset of the type set of the corresponding type parameter of T, meaning
162+
// that every instantiation of V corresponds to a valid instantiation of T.
163+
164+
// Minor optimization: ensure we share a context across the two
165+
// instantiations below.
166+
if ctxt == nil {
167+
ctxt = NewContext()
168+
}
169+
170+
var targs []types.Type
171+
for i := 0; i < vtparams.Len(); i++ {
172+
targs = append(targs, vtparams.At(i))
173+
}
174+
175+
vinst, err := Instantiate(ctxt, V, targs, true)
176+
if err != nil {
177+
panic("type parameters should satisfy their own constraints")
178+
}
179+
180+
tinst, err := Instantiate(ctxt, T, targs, true)
181+
if err != nil {
182+
return false
183+
}
184+
185+
return types.AssignableTo(vinst, tinst)
186+
}

0 commit comments

Comments
 (0)