├── .gitignore ├── cmd └── check │ └── main.go ├── cmpequal ├── cmpequal.go ├── cmpequal_test.go └── testdata │ └── src │ └── a │ └── a.go ├── go.mod ├── go.sum ├── presentations ├── florence.key ├── london.key ├── singapore.key └── tokyo.key └── sametype ├── sametype.go ├── sametype_test.go └── testdata └── src ├── a └── a.go ├── annotate └── sametype.go └── mycmp └── equal.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /cmd/check/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/matloob/analysistalk/cmpequal" 5 | "golang.org/x/tools/go/analysis/singlechecker" 6 | ) 7 | 8 | func main() { 9 | singlechecker.Main(cmpequal.Analyzer) 10 | } -------------------------------------------------------------------------------- /cmpequal/cmpequal.go: -------------------------------------------------------------------------------- 1 | package cmpequal 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/ast" 7 | "go/format" 8 | "go/types" 9 | 10 | "golang.org/x/tools/go/analysis" 11 | "golang.org/x/tools/go/analysis/passes/inspect" 12 | "golang.org/x/tools/go/ast/inspector" 13 | "golang.org/x/tools/go/types/typeutil" 14 | ) 15 | 16 | var Analyzer = &analysis.Analyzer{ 17 | Name: "cmpequal", 18 | Doc: "Check arg types of cmp.Equal", 19 | Requires: []*analysis.Analyzer{inspect.Analyzer}, 20 | Run: run, 21 | } 22 | 23 | func run(pass *analysis.Pass) (interface{}, error) { 24 | inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 25 | inspectNode := func(n ast.Node) { 26 | call := n.(*ast.CallExpr) 27 | fn, _ := typeutil.Callee(pass.TypesInfo, call).(*types.Func) 28 | if fn == nil { 29 | return // not a function call 30 | } 31 | 32 | if fn.FullName() != "github.com/google/go-cmp/cmp.Equal" { // should also check cmp.Diff, etc. 33 | return // not a call to Equal 34 | } 35 | 36 | typ0 := pass.TypesInfo.Types[call.Args[0]].Type 37 | typ1 := pass.TypesInfo.Types[call.Args[1]].Type 38 | if !types.Identical(typ0, typ1) { 39 | var fixes []analysis.SuggestedFix 40 | if isPointerTo(typ0, typ1) { 41 | fixes = fixDereference(pass, call.Args[0]) 42 | } else if isPointerTo(typ1, typ0) { 43 | fixes = fixDereference(pass, call.Args[1]) 44 | } 45 | reportWithFixes(pass, call, fixes, "\n cmp.Equal's arguments must have the same type\n"+ 46 | " but it's called with \u001b[31m%s\u001b[0m and \u001b[31m%s\u001b[0m values", typeName(typ0), typeName(typ1)) 47 | } 48 | } 49 | inspect.Preorder( 50 | []ast.Node{(*ast.CallExpr)(nil)}, 51 | inspectNode) 52 | return nil, nil 53 | } 54 | 55 | func typeName(t types.Type) string { 56 | switch t := t.(type) { 57 | case *types.Named: 58 | return t.Obj().Pkg().Name() + "." + t.Obj().Name() 59 | case *types.Pointer: 60 | return "*" + typeName(t.Elem()) 61 | } 62 | return fmt.Sprint(t) 63 | } 64 | 65 | func reportWithFixes(pass *analysis.Pass, node ast.Node, fixes []analysis.SuggestedFix, format string, formatArgs ...interface{}) { 66 | pass.Report(analysis.Diagnostic{Pos: node.Pos(), End: node.End(), Message: fmt.Sprintf(format, formatArgs...), SuggestedFixes: fixes}) 67 | } 68 | 69 | func isPointerTo(a, b types.Type) bool { 70 | if ptr, ok := a.(*types.Pointer); ok { 71 | return types.Identical(ptr.Elem(), b) 72 | } 73 | return false 74 | } 75 | 76 | func fixDereference(pass *analysis.Pass, expr ast.Expr) []analysis.SuggestedFix { 77 | // dereference typ0 78 | var buf bytes.Buffer 79 | format.Node(&buf, pass.Fset, &ast.StarExpr{X: expr}) 80 | fix := analysis.SuggestedFix{ 81 | Message: "dereference pointer", 82 | TextEdits: []analysis.TextEdit{{expr.Pos(), expr.End(), buf.Bytes()}}, 83 | } 84 | return []analysis.SuggestedFix{fix} 85 | } 86 | -------------------------------------------------------------------------------- /cmpequal/cmpequal_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package cmpequal_test 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/matloob/analysistalk/cmpequal" 11 | "golang.org/x/tools/go/analysis/analysistest" 12 | ) 13 | 14 | func Test(t *testing.T) { 15 | testdata := analysistest.TestData() 16 | analysistest.Run(t, testdata, cmpequal.Analyzer, "a") 17 | } 18 | -------------------------------------------------------------------------------- /cmpequal/testdata/src/a/a.go: -------------------------------------------------------------------------------- 1 | // Package a is a good package. 2 | package a 3 | 4 | import ( 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | type X struct { 11 | } 12 | 13 | func NewX() *X { 14 | return &X{} 15 | } 16 | 17 | func TestSomething(t *testing.T) { 18 | want := X{} 19 | got := NewX() 20 | if !cmp.Equal(got, want) { 21 | t.Error("but they're not equal!") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/matloob/analysistalk 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/google/go-cmp v0.2.0 7 | golang.org/x/tools v0.0.0-20190816183240-caa95bb40b63 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= 2 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 3 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 4 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 5 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 6 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 7 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 8 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 9 | golang.org/x/tools v0.0.0-20190424031103-cb2dda6eabdf h1:Yv3pKbXQqpdhrt53r+Yr1XveoqVgIFTCQdaamSalWwM= 10 | golang.org/x/tools v0.0.0-20190424031103-cb2dda6eabdf/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 11 | golang.org/x/tools v0.0.0-20190816183240-caa95bb40b63 h1:w//3yKt/hJYZ8laT2BB5r7uIgxLE859kTdrn1J6lp5w= 12 | golang.org/x/tools v0.0.0-20190816183240-caa95bb40b63/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 13 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 14 | -------------------------------------------------------------------------------- /presentations/florence.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matloob/analysistalk/9f09e2715c27c2f8fbdd9dfc8657b1a0ce702c81/presentations/florence.key -------------------------------------------------------------------------------- /presentations/london.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matloob/analysistalk/9f09e2715c27c2f8fbdd9dfc8657b1a0ce702c81/presentations/london.key -------------------------------------------------------------------------------- /presentations/singapore.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matloob/analysistalk/9f09e2715c27c2f8fbdd9dfc8657b1a0ce702c81/presentations/singapore.key -------------------------------------------------------------------------------- /presentations/tokyo.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matloob/analysistalk/9f09e2715c27c2f8fbdd9dfc8657b1a0ce702c81/presentations/tokyo.key -------------------------------------------------------------------------------- /sametype/sametype.go: -------------------------------------------------------------------------------- 1 | package sametype 2 | 3 | import ( 4 | "go/ast" 5 | "go/types" 6 | "golang.org/x/tools/go/analysis" 7 | "golang.org/x/tools/go/analysis/passes/inspect" 8 | "golang.org/x/tools/go/ast/inspector" 9 | "golang.org/x/tools/go/types/typeutil" 10 | ) 11 | 12 | var Analyzer = &analysis.Analyzer { 13 | Name: "cmpequal", 14 | Doc: "Check arg types of cmp.Equal", 15 | Requires: 16 | []*analysis.Analyzer{inspect.Analyzer}, 17 | FactTypes: []analysis.Fact{(*SameType)(nil)}, 18 | Run: run, 19 | 20 | } 21 | 22 | type SameType struct{} 23 | 24 | func (s *SameType) AFact() {} 25 | 26 | func run(pass *analysis.Pass) (interface{}, error) { 27 | inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 28 | checkForFact := func(n ast.Node) { 29 | call := n.(*ast.CallExpr) 30 | fn, _ := typeutil.Callee(pass.TypesInfo, call).(*types.Func) 31 | if fn == nil { 32 | return // not a function call 33 | } 34 | 35 | var sameType SameType 36 | if !pass.ImportObjectFact(fn, &sameType) { 37 | return 38 | } 39 | 40 | typ0 := pass.TypesInfo.Types[call.Args[0]].Type 41 | typ1 := pass.TypesInfo.Types[call.Args[1]].Type 42 | if !types.Identical(typ0, typ1) { 43 | pass.Reportf(call.Pos(), 44 | "Calls to %v must have arguments of the same type; "+ 45 | "is called with %v and %v", 46 | fn.Name(), typ0, typ1) 47 | }} 48 | maybeAddFact := func(n ast.Node, push bool, stack []ast.Node) bool { 49 | if !push { 50 | return true 51 | } 52 | 53 | call := n.(*ast.CallExpr) 54 | fn, _ := typeutil.Callee(pass.TypesInfo, call).(*types.Func) 55 | if fn == nil { 56 | return false // not a function call 57 | } 58 | 59 | if fn.FullName() != "annotate.SameType" { 60 | return false // not an annotation 61 | } 62 | 63 | var enclosingFunc *ast.FuncDecl 64 | for _, node := range stack { 65 | if v, ok := node.(*ast.FuncDecl); ok { 66 | enclosingFunc = v 67 | break 68 | } 69 | } 70 | if enclosingFunc == nil { 71 | return false // we didn't find the enclosing call 72 | } else if len(enclosingFunc.Type.Params.List) != 2 { 73 | pass.Reportf(call.Pos(), "SameType annotation can only be added to funcs with two arguments") 74 | } 75 | 76 | obj := pass.TypesInfo.Defs[enclosingFunc.Name] 77 | pass.ExportObjectFact(obj, &SameType{}) 78 | return false 79 | } 80 | inspect.WithStack([]ast.Node{(*ast.CallExpr)(nil)}, maybeAddFact) 81 | inspect.Preorder( 82 | []ast.Node{(*ast.CallExpr)(nil)}, 83 | checkForFact) 84 | return nil, nil 85 | } 86 | -------------------------------------------------------------------------------- /sametype/sametype_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package sametype_test 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/matloob/analysistalk/sametype" 11 | "golang.org/x/tools/go/analysis/analysistest" 12 | ) 13 | 14 | func Test(t *testing.T) { 15 | testdata := analysistest.TestData() 16 | analysistest.Run(t, testdata, sametype.Analyzer, "a") 17 | } 18 | -------------------------------------------------------------------------------- /sametype/testdata/src/a/a.go: -------------------------------------------------------------------------------- 1 | package a 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "mycmp" 8 | ) 9 | 10 | func TestSomething(t *testing.T) { 11 | var x int = 0 12 | var y int = 0 13 | if !mycmp.Equal(x, &y) { // want "Calls to Equal must have arguments of the same type" 14 | fmt.Println("but they're not equal!") 15 | } 16 | } -------------------------------------------------------------------------------- /sametype/testdata/src/annotate/sametype.go: -------------------------------------------------------------------------------- 1 | package annotate 2 | 3 | func SameType() { 4 | } -------------------------------------------------------------------------------- /sametype/testdata/src/mycmp/equal.go: -------------------------------------------------------------------------------- 1 | package mycmp 2 | 3 | import ( 4 | "annotate" 5 | "reflect" 6 | ) 7 | 8 | func Equal(a, b interface{}) bool { 9 | annotate.SameType() 10 | return reflect.DeepEqual(a, b) 11 | } --------------------------------------------------------------------------------