1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
|
// 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.
//go:build go1.18
package main
import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"log"
)
const hello = `
//!+input
package main
type Constraint interface {
Value() any
}
type Pair[L, R any] struct {
left L
right R
}
func MakePair[L, R Constraint](l L, r R) Pair[L, R] {
return Pair[L, R]{l, r}
}
//!-input
`
// !+print
func PrintTypeParams(fset *token.FileSet, file *ast.File) error {
conf := types.Config{Importer: importer.Default()}
info := &types.Info{
Scopes: make(map[ast.Node]*types.Scope),
Defs: make(map[*ast.Ident]types.Object),
}
_, err := conf.Check("hello", fset, []*ast.File{file}, info)
if err != nil {
return err
}
// For convenience, we can use ast.Inspect to find the nodes we want to
// investigate.
ast.Inspect(file, func(n ast.Node) bool {
var name *ast.Ident // the name of the generic object, or nil
var tparamFields *ast.FieldList // the list of type parameter fields
var tparams *types.TypeParamList // the list of type parameter types
var scopeNode ast.Node // the node associated with the declaration scope
switch n := n.(type) {
case *ast.TypeSpec:
name = n.Name
tparamFields = n.TypeParams
tparams = info.Defs[name].Type().(*types.Named).TypeParams()
scopeNode = n
case *ast.FuncDecl:
name = n.Name
tparamFields = n.Type.TypeParams
tparams = info.Defs[name].Type().(*types.Signature).TypeParams()
scopeNode = n.Type
default:
// Not a type or function declaration.
return true
}
// Option 1: find type parameters by looking at their declaring field list.
if tparamFields != nil {
fmt.Printf("%s has a type parameter field list with %d fields\n", name.Name, tparamFields.NumFields())
for _, field := range tparamFields.List {
for _, name := range field.Names {
tparam := info.Defs[name]
fmt.Printf(" field %s defines an object %q\n", name.Name, tparam)
}
}
} else {
fmt.Printf("%s does not have a type parameter list\n", name.Name)
}
// Option 2: find type parameters via the TypeParams() method on the
// generic type.
if tparams.Len() > 0 {
fmt.Printf("%s has %d type parameters:\n", name.Name, tparams.Len())
for i := 0; i < tparams.Len(); i++ {
tparam := tparams.At(i)
fmt.Printf(" %s has constraint %s\n", tparam, tparam.Constraint())
}
} else {
fmt.Printf("%s does not have type parameters\n", name.Name)
}
// Option 3: find type parameters by looking in the declaration scope.
scope, ok := info.Scopes[scopeNode]
if ok {
fmt.Printf("%s has a scope with %d objects:\n", name.Name, scope.Len())
for _, name := range scope.Names() {
fmt.Printf(" %s is a %T\n", name, scope.Lookup(name))
}
} else {
fmt.Printf("%s does not have a scope\n", name.Name)
}
return true
})
return nil
}
//!-print
/*
//!+output
> go run golang.org/x/tools/internal/typeparams/example/findtypeparams
Constraint does not have a type parameter list
Constraint does not have type parameters
Constraint does not have a scope
Pair has a type parameter field list with 2 fields
field L defines an object "type parameter L any"
field R defines an object "type parameter R any"
Pair has 2 type parameters:
L has constraint any
R has constraint any
Pair has a scope with 2 objects:
L is a *types.TypeName
R is a *types.TypeName
MakePair has a type parameter field list with 2 fields
field L defines an object "type parameter L hello.Constraint"
field R defines an object "type parameter R hello.Constraint"
MakePair has 2 type parameters:
L has constraint hello.Constraint
R has constraint hello.Constraint
MakePair has a scope with 4 objects:
L is a *types.TypeName
R is a *types.TypeName
l is a *types.Var
r is a *types.Var
//!-output
*/
func main() {
// Parse one file.
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "hello.go", hello, 0)
if err != nil {
log.Fatal(err)
}
if err := PrintTypeParams(fset, f); err != nil {
log.Fatal(err)
}
}
|