From AST to type info: a real Go static analysis walkthrough
Static analysis tools for Go typically follow a three-step pipeline: locate a construct in source code, examine it at the AST level, then resolve its semantic meaning using type information. A recent Stack Overflow question about inspecting the fields of a struct type used as a function parameter is a perfect illustration of this workflow.
The goal is straightforward: given a function declaration such as func foo(s SomeType), the tool must report the fields of SomeType and their types. The complete implementation is available on GitHub, but the core logic can be broken down into three distinct phases.
Loading packages and locating declarations
The tool starts with golang.org/x/tools/go/packages, the standard entry point for loading and analyzing Go code. The module path is passed as a command-line argument, and after loading, the program iterates over every package in the module, calling a processing function for each one.
pkgs, err := packages.Load(&packages.Config{
Mode: packages.NeedName | packages.NeedSyntax | packages.NeedTypesInfo,
}, pattern)
For each package, the tool walks each source file's AST using ast.Inspect, filtering for *ast.FuncDecl nodes. Crucially, the packages.NeedTypesInfo flag is set in the load mode — without it, the package struct won't carry the type-checking information needed later.
Examining parameters at the syntax level
Once a function declaration is found, its parameter list is inspected. In Go, multiple parameters can share a single type declaration (func foo(a, b int)), so the tool collects all names associated with each type expression into a slice for reporting. The type expression itself is an ast.Expr — the syntactic representation of the type as written in code.
This is where the two-level nature of Go type handling becomes evident. At the syntax level, MyType is just an identifier node. But the standard library's go/types package, which the tooling infrastructure runs automatically, provides a semantic layer: it maps those syntactic nodes to their resolved, meaningful type objects.
Resolving types with semantic information
The resolution function recursively strips away pointer indirections and array/slice wrappers until it hits a type it can resolve semantically. It receives the *types.Info map extracted from the package struct, using it to look up the actual type behind an ast.Expr.
Some patterns are surprising on first encounter. Dot-separated names like http.Handler appear as ast.SelectorExpr nodes and have direct entries in the type info map. Pointer types such as *MyType do not — the ast.StarExpr must be peeled off manually before the underlying identifier can be looked up. These quirks are manageable once you are comfortable dumping and inspecting ASTs.
The default case in the resolution switch handles named types (both user-defined and from the standard library), basic types like string, and anonymous struct types such as struct { x int }. Once the underlying type is resolved, a helper walks its struct fields and prints each field's name and type. For named struct types, it also reports the definition location — file, line, and column — using information from the type's Obj().
Testing on a sample module
Consider a module with a main package containing a function that accepts a pointer to a User struct:
type User struct {
Name string
Age int
}
Running the tool on this module produces output that shows the tool's correctness in a few ways. It sees through pointer types to the underlying struct, it handles both named and anonymous struct types, and for named types it reports a definition location that can point into either user code or the standard library.
The three-part pattern demonstrated here — loading code with type info, finding relevant syntax nodes, then resolving them through the type checker — recurs across most Go analysis tools. Getting comfortable with the boundary between ast.Expr and types.Type, and knowing which AST node shapes require manual unwrapping, is the key to writing these tools efficiently.



