go/types package
This past weekend I spent some time playing with the go/types
package. It was
pretty cool!
I have this idea for a tool that will take a Go program and automatically add a bunch of fault injection code, so you can ensure that rare faults are handled in a sane way. I read a paper a year or two ago that did a bunch of analysis of bugs in distributed systems and showed that most of the bugs would have been found by just running the code in the error paths. My idea is just to make that trivial, or at least easier than faking weird distributed system codepaths.
While trying to write the above, I stumbled across The Go Type
Checker, a ten thousand
word document by Alan Donovan about go’s go/types
package (and go/ast
.) The
document is really eye opening and a very welcome narrative introduction to such
an intimidating, complex package. If you ever intend to, or even are interested
in writing some kind of Go linter, I suggest reading the above document.
After reading something like half of the above doc I was able to write this
little tool, which will at least identify all the locations where a variable
conforms to the error
interface.
package main
import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"log"
)
type visitor func(n ast.Node) ast.Visitor
func (v visitor) Visit(n ast.Node) ast.Visitor {
return v(n)
}
// errType just defines error in terms of go/types
var errType *types.Interface
func init() {
errType = types.NewInterfaceType([]*types.Func{
types.NewFunc(0, nil, "Error",
types.NewSignature(
nil, // Receiver
types.NewTuple(), // Params
types.NewTuple(types.NewParam(0, nil, "", types.Typ[types.String])), // Result
false,
),
),
}, nil)
errType.Complete()
}
func main() {
// parse the directory of go code
fset := token.NewFileSet()
src, err := parser.ParseDir(fset, ".", nil, 0)
if err != nil {
panic(err)
}
// type check the code
conf := types.Config{Importer: importer.Default()}
fs := []*ast.File{}
for _, tree := range src {
for _, f := range tree.Files {
fs = append(fs, f)
}
}
i := &types.Info{Types: map[ast.Expr]types.TypeAndValue{}}
if _, err := conf.Check("cmd/hello", fset, fs, i); err != nil {
log.Fatal(err) // type error
}
// walk the ast, priting any expression that implements to error
var v visitor
v = func(n ast.Node) ast.Visitor {
e, ok := n.(ast.Expr)
if !ok {
return v
}
t := i.Types[e].Type
if implements(t, errType) {
fmt.Printf("%s, %T %+v %v\n", fset.Position(n.Pos()), n, n, t)
}
return v
}
ast.Walk(v, fs[0])
}
// implements returns false if t doesn't implement i
//
// The standard types.Implements panics on false, making it inconvenient for
// simple checking.
func implements(t types.Type, i *types.Interface) (ok bool) {
defer func() {
if r := recover(); r != nil {
ok = false
}
}()
ok = types.Implements(t, i)
return
}
I wish the above were less hard earned, but the surface area of go/types
and
go/ast
made it much harder for me to write. On top of it the ast.Node
interface is not the typical interface in any OO language; basically there are
only a few types in go/ast
that implement the interface, and when you check it
you are expected to check from a small, specific set of types implementing it.
(This is called a discriminated union, a term I either forgot or never knew.)
Anyway, hopefully next time I play with this code I’ll make more progress and be able to share it then!
If you are interested in learning Go, this is my recommendation:
(The following includes affiliate links.)
If you don’t already know Go, you should definitely check out The Go Programming Language. It’s not just a great Go book but a great programming book in general with a generous dollop of concurrency.
Another book to consider learning Go with is Go Programming Blueprints. It has a nearly interactive style where you write code, see it get syntax errors (or whatever,) fix it, and iterate. A useful book that shows that you don’t have to get all of your programs perfectly working on the first compile.
Posted Tue, May 21, 2019If you're interested in being notified when new posts are published, you can subscribe here; you'll get an email once a week at the most.