Reputation: 653
I'm trying to write a parser for golang code to examine the fields of a referenced struct. For example, given:
type Hello struct {
id int64
}
func Test(ref Hello) {}
I would like to be able to statically analyze this code and go from the args of Test and inspect Hello
's fields.
I'm currently using the analysis package. I know how to inspect the struct definition itself in the ast, and also how to parse the function's args for its types. But is there a way to go from reference to parsing the struct? What if the struct is defined in a different file?
Upvotes: 2
Views: 615
Reputation: 38303
If you're doing static analysis and you'd like to better understand how the packages go/ast
, go/types
, etc. work together then you should definitely check out Alan Donovan's go types document.
You can use the golang.org/x/tools/go/packages
package to get the syntax tree and the type info. There may be better, less involved, approaches to achieve the same but this one's the one I'm familiar with.
To get the go/types
representation of Hello
you can do the following:
func main() {
cfg := new(packages.Config)
cfg.Mode = packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo
cfg.Fset = token.NewFileSet()
// "." specifies the current directory.
// You should replace it with a pattern that
// will match the package you want to analyse.
pkgs, err := packages.Load(cfg, ".")
if err != nil {
panic(err)
}
for _, pkg := range pkgs {
// Loop over the list of files in the package.
for _, syn := range pkg.Syntax {
// Loop over the top-level declarations in the file.
for _, dec := range syn.Decls {
// Look for the func declaration
// of your Help function.
fd, ok := dec.(*ast.FuncDecl)
if !ok || fd.Name.Name != "Test" {
continue
}
// Get the expression node that
// represents the identifier of
// the parameter's type i.e. Hello.
p := fd.Type.Params.List[0].Type
// NOTE: if the type is not a named
// package-local type, e.g. a pointer,
// a slice, or an imported type, then
// you'll have have to "dig deeper"
// to get to the *ast.Ident.
id, ok := p.(*ast.Ident)
if !ok {
continue
}
// With the packages.NeedTypesInfo mode set
// the package will also include the result
// of the complete type-check of the package's
// syntax trees.
//
// The TypeInfo.Types field maps ast expressions
// to their types, this allows you to get the type
// information using the identifier.
typ := pkg.TypesInfo.Types[id]
named := typ.Type.(*types.Named)
fmt.Println(named) // Hello's *types.Named
fmt.Println(named.Underlying().(*types.Struct)) // Hello's *types.Struct
}
}
}
}
To get the go/ast
representation of the Hello
type's definition you can do the following:
func main() {
// You'll need to repeat the steps above
// to load the packages as well as finding
// the *types.Named instance which will be
// used to determine the position of the
// type's definition ast.
pos := named.Obj().Pos() // the source position of the type's name
for _, pkg := range pkgs {
// Loop over the files in the package.
for _, syn := range pkg.Syntax {
// Use the position to determine whether
// or not the type is declared in this
// file, if not then go to the next one.
if syn.Pos() >= pos || pos >= syn.End() {
continue
}
// Loop over the top-level declarations in the file.
for _, dec := range syn.Decls {
// If the declaration is something
// other than a type declaration then
// continue to the next one.
gd, ok := dec.(*ast.GenDecl)
if !ok || gd.Tok != token.TYPE {
continue
}
// Loop over the specs in the declaration.
for _, spec := range gd.Specs {
// Look for the type spec whose name matches
// the name of the *types.Named instance.
ts, ok := spec.(*ast.TypeSpec)
if !ok || ts.Name.Name != named.Obj().Name() {
continue
}
fmt.Println(ts) // Hello's *ast.TypeSpec
fmt.Println(ts.Type.(*ast.StructType)) // Hello's *ast.StructType
}
}
}
}
}
Upvotes: 2