blob: 50a1fcc925fe52fa124e934d6cfecd03cb85a1f1 [file] [log] [blame]
// 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
// +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)
}
}