2

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

Scott Stensland
  • 26,870
  • 12
  • 93
  • 104
  • Are you trying to check type declarations, or an arbitrary value in the source? `ParseExpr` can't give you an actual type on it's own, since an expression doesn't have any type information. It would probably help if you showed an example of where you need to determine the type. Also a "custom type" as you call it may have any of the types you listed here as its underlying type. – JimB Sep 28 '17 at 19:23
  • @JimB struct field has three parts `field name` , `field type` and `struct tag`.I need to checkif `field type` .It s okey to have the `custom type` has underlying type as built-in type. Since I am writing code to generate Validate functions to structs for nested checking i need to call Validate on fields if it implements `validater` f – Sarath Sadasivan Pillai Sep 28 '17 at 19:42
  • Better approach might be to, after parsing the source, collect all the types that implement the method you want to call which in this case seems to be something like `Validate() error`. Then when you are generating the validation code for some struct and inspecting the types of its fields, check if a given type is contained in the set of collected types that implement the method that you intend to generate a call of. – mkopriva Sep 28 '17 at 20:19
  • ...remember that there might be a [`bool` that impelments the `validate` method.](https://play.golang.org/p/E98VOxwtol) – mkopriva Sep 28 '17 at 20:21
  • exactly and that is why i need to check if it is anyting but built in types ,arra etc . – Sarath Sadasivan Pillai Sep 28 '17 at 20:23

1 Answers1

4

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("")
}
Peter
  • 29,454
  • 5
  • 48
  • 60