Gophers With Hammers
Josh Bleecher Snyder
PayPal
Josh Bleecher Snyder
PayPal
Simple, regular syntax
Simple semantics
Batteries included
and more...
4$ go list -f '{{.Deps}}' bytes [errors io runtime sync sync/atomic unicode unicode/utf8 unsafe]
from
for{ fmt.Println( "I feel pretty." ); }
to
for { fmt.Println("I feel pretty.") }
$ godoc strings Repeat func Repeat(s string, count int) string Repeat returns a new string consisting of count copies of the string s.
Oops
if suffix != ".md" || suffix != ".markdown" {
Flagged
suspect or: suffix != ".md" || suffix != ".markdown"
func Repeat(s string, count int) string { b := make([]byte, len(s)*count) bp := 0 for i := 0; i < count; i++ { bp += copy(b[bp:], s) } return string(b) }
to
func Repeat(s string, count int) string { GoCover.Count[0] = 1 b := make([]byte, len(s)*count) bp := 0 for i := 0; i < count; i++ { GoCover.Count[2] = 1 bp += copy(b[bp:], s) } GoCover.Count[1] = 1 return string(b) }
$ go test -coverprofile=c.out strings ok strings 0.455s coverage: 96.9% of statements $ go tool cover -func=c.out strings/reader.go: Len 66.7% strings/reader.go: Read 100.0% strings/reader.go: ReadAt 100.0% strings/reader.go: ReadByte 100.0% strings/reader.go: UnreadByte 100.0% strings/reader.go: ReadRune 100.0% strings/reader.go: UnreadRune 100.0% strings/reader.go: Seek 90.9% strings/reader.go: WriteTo 83.3% ... $ go tool cover -html=c.out # opens a browser window, shows line-by-line coverage
and more...
11Generate implementation stubs given an interface.
go get github.com/josharian/impl
Generate
$ impl 'f *File' io.Reader func (f *File) Read(p []byte) (n int, err error) { }
from
package io type Reader interface { Read(p []byte) (n int, err error) }
Generate
$ impl 'f *File' io.ReadWriter func (f *File) Read(p []byte) (n int, err error) { } func (f *File) Write(p []byte) (n int, err error) { }
from
package io type ReadWriter interface { Reader Writer }
Generate
$ impl 'c *Ctx' http.Handler func (c *Ctx) ServeHTTP(http.ResponseWriter, *http.Request) { }
from
package http type Handler interface { ServeHTTP(ResponseWriter, *Request) }
Find import path and interface name
http.Handler ⇒ net/http, Handler
Parse interface
net/http, Handler ⇒ {{"ServeHTTP", {{"", "http.ResponseWriter"}, {"", "*http.Request"}}, {}}}}
Generate output
{{"ServeHTTP", {{"", "http.ResponseWriter"}, {"", "*http.Request"}}, {}}}} ⇒ profit!
import "golang.org/x/tools/imports"
// +build ignore
package main
import (
"fmt"
"golang.org/x/tools/imports"
)
func main() { iface := "http.Handler" src := "package hack; var i " + iface fmt.Println(src, "\n---") imp, _ := imports.Process("", []byte(src), nil) // ignoring errors throughout this presentation fmt.Println(string(imp)) }
*ast.File { . Package: 1:1 . Name: *ast.Ident { . . NamePos: 1:9 . . Name: "hack" . } . Decls: []ast.Decl (len = 2) { . . 0: *ast.GenDecl { . . . TokPos: 1:15 . . . Tok: import . . . Lparen: - . . . Specs: []ast.Spec (len = 1) { . . . . 0: *ast.ImportSpec { . . . . . Path: *ast.BasicLit { . . . . . . ValuePos: 1:22 . . . . . . Kind: STRING . . . . . . Value: "\"net/http\"" . . . . . }
[truncated]
18import ( "go/parser" "go/token" )
// +build ignore
package main
import (
"fmt"
"go/parser"
"go/token"
"strconv"
)
func main() { src := `package hack; import "net/http"; var i http.Handler` fset := token.NewFileSet() f, _ := parser.ParseFile(fset, "", src, 0) raw := f.Imports[0].Path.Value path, _ := strconv.Unquote(raw) fmt.Println(raw, "\n", path) }
import "go/ast"
// +build ignore
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
)
func main() { src := `package hack; import "net/http"; var i http.Handler` f, _ := parser.ParseFile(token.NewFileSet(), "", src, 0) decl := f.Decls[1].(*ast.GenDecl) // var i http.Handler spec := decl.Specs[0].(*ast.ValueSpec) // i http.Handler sel := spec.Type.(*ast.SelectorExpr) // http.Handler id := sel.Sel.Name // Handler fmt.Println(id) }
A GenDecl
can have many Specs
var ( r io.Reader w io.Writer )
Find import path and interface name
http.Handler ⇒ net/http, Handler
Parse interface
net/http, Handler ⇒ {{"ServeHTTP", {{"", "http.ResponseWriter"}, {"", "*http.Request"}}, {}}}}
Generate output
{{"ServeHTTP", {{"", "http.ResponseWriter"}, {"", "*http.Request"}}, {}}}} ⇒ profit!
Represent
Read(p []byte) (n int, err error)
as
Func{ Name: "Read", Params: []Param{{Name: "p", Type: "[]byte"}}, Res: []Param{ {Name: "n", Type: "int"}, {Name: "err", Type: "error"}, }, },
type Func struct { Name string Params []Param Res []Param }
type Param struct { Name string Type string }
import "go/build"
// +build ignore
package main
import (
"fmt"
"go/build"
)
func main() { pkg, _ := build.Import("net/http", "", 0) fmt.Println(pkg.Dir) fmt.Println(pkg.GoFiles) }
import "go/printer"
// +build ignore
package main
import (
"go/ast"
"go/build"
"go/parser"
"go/printer"
"go/token"
"os"
"path/filepath"
)
func main() { fset, files := parsePackage("net/http") id := "Handler" for _, f := range files { for _, decl := range f.Decls { decl, ok := decl.(*ast.GenDecl) if !ok || decl.Tok != token.TYPE { continue } for _, spec := range decl.Specs { spec := spec.(*ast.TypeSpec) if spec.Name.Name == id { printer.Fprint(os.Stdout, fset, spec) } } } } }
func parsePackage(path string) (*token.FileSet, []*ast.File) {
pkg, err := build.Import(path, "", 0)
if err != nil {
panic(err)
}
fset := token.NewFileSet()
var files []*ast.File
for _, file := range pkg.GoFiles {
f, err := parser.ParseFile(fset, filepath.Join(pkg.Dir, file), nil, 0)
if err != nil {
continue
}
files = append(files, f)
}
return fset, files
}
No name? It's an embedded interface. Recurse.
type ByteScanner interface { ByteReader UnreadByte() error }
No name? Just use ""
.
type ByteWriter interface { WriteByte(c byte) error }
Types can be arbitrarily complicated.
type CrazyGopher interface { CrazyGoph(int) func(chan<- [32]byte, map[string]int64) ([]rune, error) }
And we need to rewrite some of them.
int ⇒ int *Request ⇒ *http.Request io.Reader ⇒ io.Reader func(io.Reader, chan map[S][]*T) ⇒ func(io.Reader, chan map[foo.S][]*foo.T))
// +build ignore
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
)
func main() { src := ` package http type Handler interface { ServeHTTP(ResponseWriter, *Request) } ` f, _ := parser.ParseFile(token.NewFileSet(), "", src, 0) typ := f.Decls[0].(*ast.GenDecl).Specs[0].(*ast.TypeSpec).Type.(*ast.InterfaceType) fndecl := typ.Methods.List[0].Type.(*ast.FuncType) // fndecl: (ResponseWriter, *Request) ast.Inspect(fndecl, func(n ast.Node) bool { if ident, ok := n.(*ast.Ident); ok { fmt.Println(ident.Name) } return true }) }
Find import path and interface name
http.Handler ⇒ net/http, Handler
Parse interface
net/http, Handler ⇒ {{"ServeHTTP", {{"", "http.ResponseWriter"}, {"", "*http.Request"}}, {}}}}
Generate output
{{"ServeHTTP", {{"", "http.ResponseWriter"}, {"", "*http.Request"}}, {}}}} ⇒ profit!
type Method struct { Recv string Func }
type Func struct { Name string Params []Param Res []Param }
type Param struct { Name string Type string }
// +build ignore
package main
import (
"os"
"text/template"
)
func main() { const stub = "func ({{.Recv}}) {{.Name}}" + "({{range .Params}}{{.Name}} {{.Type}}, {{end}})" + "({{range .Res}}{{.Name}} {{.Type}}, {{end}})" + "{\n}\n\n" tmpl := template.Must(template.New("test").Parse(stub)) m := Method{ Recv: "f *File", Func: Func{ Name: "Close", Res: []Param{{Type: "error"}}, }, } tmpl.Execute(os.Stdout, m) }
// Method represents a method signature.
type Method struct {
Recv string
Func
}
// Func represents a function signature.
type Func struct {
Name string
Params []Param
Res []Param
}
// Param represents a parameter in a function or method signature.
type Param struct {
Name string
Type string
}
import "go/format"
// +build ignore
package main
import (
"fmt"
"go/format"
)
func main() { ugly := `func (f *File) Read(p []byte, )(n int, err error, ){}` fmt.Println(ugly) pretty, _ := format.Source([]byte(ugly)) fmt.Println(string(pretty)) }
Full code plus tests at github.com/josharian/impl
Use go get -d
to download lots of code from godoc.org/-/index
. (Don't forget to set a temporary GOPATH
!)
Use (and improve) github.com/yuroyoro/goast-viewer
.
You don't have to generate all the code. And generating data is even better.
The go/ast
docs are your friend.
go.tools/go/types
is powerful.
go generate
is coming.