Situation
writing a code generator that checks fields of a struct and add validation functions using struct tags
Problem
Here I need to check if the type field is a custom type or not
ie,
Following are not custom types
int, []int,*int,[]Integer,map[string]PhoneNumber
But the following are custom type
Integer,PhoneNumber,*PhoneNumber
I think I can do it using functions like the following that looks for exact match and may add map,[] support
func isBuiltInType(typ string) bool {
switch typ {
case "bool", "byte", "complex128", "complex64", "error":
case "float32", "float64":
case "int", "int16", "int32", "int64", "int8":
case "rune", "string":
case "uint", "uint16", "uint32", "uint64", "uint8", "uintptr":
default:
return false
}
return true
}
But is there a better way to do it using parse.ParseExpr
etc
For any kind of reliable result you will want to involve Go's type checker using the go/types package. It is not trivial to use, but there is a helpful introductory article at https://golang.org/s/types-tutorial.
I threw together an example program, so you can see what to expect. The important bit is the isBasic
function, the rest is just boilerplate to make it executable. In particlar, the AST traversal is hardcoded for the specific sample source code. I presume you already have your own code in place for that.
The key point is that the types.Info
structure contains all the type information you need to implement your own logic.
I found github.com/fatih/astrewrite and golang.org/x/tools/go/loader helpful when dealing with code generation and/or parsing (the loader package is kind of required for complete type information).
https://play.golang.org/p/A9hdPy-Oy-
package main
import (
"bufio"
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"log"
"strings"
)
var src = strings.TrimSpace(`
package main
type T struct{}
func f() {
var _ T
var _ *T
var _ int
var _ *int
var _ **int
var _ []int
var _ []T
var _ map[string]int
var _ map[string]T
}
`)
func main() {
// Parse source
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "src.go", src, 0)
if err != nil {
log.Fatal(err)
}
// Run type checker
info := types.Info{Types: make(map[ast.Expr]types.TypeAndValue)}
_, err = (&types.Config{}).Check("mypkg", fset, []*ast.File{f}, &info)
if err != nil {
log.Fatal(err)
}
printSrc()
// Inspect variable types in f()
for _, varDecl := range f.Decls[1].(*ast.FuncDecl).Body.List {
value := varDecl.(*ast.DeclStmt).Decl.(*ast.GenDecl).Specs[0].(*ast.ValueSpec)
pos := fset.Position(value.Type.Pos())
typ := info.Types[value.Type].Type
fmt.Println(pos, "basic:", isBasic(typ))
}
}
func isBasic(t types.Type) bool {
switch x := t.(type) {
case *types.Basic:
return true
case *types.Slice:
return true
case *types.Map:
return true
case *types.Pointer:
return isBasic(x.Elem())
default:
return false
}
}
func printSrc() {
s := bufio.NewScanner(strings.NewReader(src))
for i := 1; s.Scan(); i++ {
fmt.Printf("%2d: %s\n", i, s.Text())
}
fmt.Println("")
}