Skip to content

Instantly share code, notes, and snippets.

@TripleDogDare
Last active December 30, 2022 18:19
Show Gist options
  • Save TripleDogDare/556f6c3dcbc6e34807229bc7f29a8149 to your computer and use it in GitHub Desktop.
Save TripleDogDare/556f6c3dcbc6e34807229bc7f29a8149 to your computer and use it in GitHub Desktop.
serum debugging Cause method validation
module gist.github.com/556f6c3dcbc6e34807229bc7f29a8149
go 1.18
require (
github.com/frankban/quicktest v1.14.4 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
)
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
package serrah
type ReeError interface {
Code() string
Message() string
Details() map[string]string
Cause() error
Error() string
}
type TheError struct {
TheCode string
}
func (e *TheError) Code() string { return e.TheCode }
func (e *TheError) Message() string { return e.TheCode }
func (e *TheError) Details() map[string]string { return nil }
func (e *TheError) Cause() error { return nil }
func (e *TheError) Error() string { return e.Message() }
package serrah_test
import (
"bytes"
"embed"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"go/types"
"sort"
"strings"
"testing"
qt "github.com/frankban/quicktest"
)
//go:embed interface.go
var content embed.FS
// var tErrorFunc = types.NewFunc(token.NoPos, nil, "Error", types.NewSignature(nil, nil, types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.String])), false))
var tError = types.NewInterfaceType([]*types.Func{
types.NewFunc(token.NoPos, nil, "Error", types.NewSignature(nil, nil, types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.String])), false)),
// tErrorFunc,
}, nil).Complete()
var tReeError = types.NewInterfaceType([]*types.Func{
types.NewFunc(token.NoPos, nil, "Error", types.NewSignature(nil, nil, types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.String])), false)),
types.NewFunc(token.NoPos, nil, "Code", types.NewSignature(nil, nil, types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.String])), false)),
}, nil).Complete()
var tReeErrorWithCause = types.NewInterfaceType([]*types.Func{
tReeError.Method(0),
tReeError.Method(1),
types.NewFunc(token.NoPos, nil, "Cause", types.NewSignature(nil, nil, types.NewTuple(types.NewVar(token.NoPos, nil, "", types.NewNamed(types.NewTypeName(token.NoPos, nil, "error", tError), nil, nil))), false)),
}, nil).Complete()
func TestImplements(t *testing.T) {
fset := token.NewFileSet()
data, err := content.ReadFile("interface.go")
qt.Assert(t, err, qt.IsNil)
f, err := parser.ParseFile(fset, "interface.go", data, 0)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, f, qt.IsNotNil)
// This is largely an example from the types.Info package. It's a good starting point for setting up what we need to debug.
// Type-check the package.
// We create an empty map for each kind of input
// we're interested in, and Check populates them.
info := types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
}
var conf types.Config
pkg, err := conf.Check("interface.go", fset, []*ast.File{f}, &info)
qt.Assert(t, err, qt.IsNil)
// Print package-level variables in initialization order.
t.Logf("InitOrder: %v\n\n", info.InitOrder)
// For each named object, print the line and
// column of its definition and each of its uses.
usesByObj := make(map[types.Object][]string)
for id, obj := range info.Uses {
posn := fset.Position(id.Pos())
lineCol := fmt.Sprintf("%d:%d", posn.Line, posn.Column)
usesByObj[obj] = append(usesByObj[obj], lineCol)
}
var items []string
for obj, uses := range usesByObj {
sort.Strings(uses)
item := fmt.Sprintf("%s:\n defined at %s\n used at %s",
types.ObjectString(obj, types.RelativeTo(pkg)),
fset.Position(obj.Pos()),
strings.Join(uses, ", "))
items = append(items, item)
}
sort.Strings(items) // sort by line:col, in effect
t.Logf("\n%s\n%s\n",
"=== Defs and Uses of each named object ===",
strings.Join(items, "\n"),
)
t.Log()
items = nil
for expr, tv := range info.Types {
var buf bytes.Buffer
posn := fset.Position(expr.Pos())
tvstr := tv.Type.String()
if tv.Value != nil {
tvstr += " = " + tv.Value.String()
}
// line:col | expr | mode : type = value
fmt.Fprintf(&buf, "%2d:%2d | %-28s | %-7s : %s",
posn.Line, posn.Column, exprString(fset, expr),
mode(tv), tvstr)
items = append(items, buf.String())
}
sort.Strings(items)
t.Logf("\n%s\n%s\n%s\n",
"=== Types and Values of each expression ===",
"line:col | expr | mode | type",
strings.Join(items, "\n"),
)
t.Log("\n=== Defs ===")
causeDefs := make(map[*ast.Ident]types.Object)
for id, obj := range info.Defs {
if obj == nil {
continue
}
posn := fset.Position(id.Pos())
objStr := types.ObjectString(obj, types.RelativeTo(pkg))
t.Logf("%2d:%2d | %-20s | %-27s | %s\n", posn.Line, posn.Column, obj.Name(), obj.Type(), objStr)
if obj.Name() == "Cause" {
causeDefs[id] = obj
}
}
// This is where we actually test what we're trying to figure out
t.Log("\n=== Decl ===")
for _, decl := range f.Decls {
funcDecl, ok := decl.(*ast.FuncDecl)
if !ok {
continue
}
posn := fset.Position(decl.Pos())
t.Logf("%2d:%2d | %s\n", posn.Line, posn.Column, funcDecl.Name.Name)
if funcDecl.Name.Name != "Cause" {
continue
}
if isMethod(funcDecl) {
if funcDecl.Name.Name != "Cause" {
continue
}
errType := info.TypeOf(funcDecl.Type.Results.List[0].Type)
t.Log("err type:", errType.Underlying())
qt.Assert(t, types.Implements(errType, tError), qt.IsTrue, qt.Commentf("Cause does not return an error type"))
qt.Assert(t, types.Implements(errType.Underlying(), tError), qt.IsTrue, qt.Commentf("Cause does not return an error type"))
recv := funcDecl.Recv.List[0]
receiverType := info.TypeOf(recv.Type)
ptr := receiverType.(*types.Pointer)
named := ptr.Elem().(*types.Named)
// testValue := named
for _, m := range getMethods(named) {
t.Logf("%-35s | %s\n", m.FullName(), m.Type())
}
testValue := receiverType
// testValue := types.NewPointer(ptr.Elem().Underlying())
// testValue := ptr.Elem().Underlying()
// testValue := ptr
t.Log("test value:", testValue)
isConvertible := types.ConvertibleTo(testValue, tReeErrorWithCause)
isAssignable := types.AssignableTo(testValue, tReeErrorWithCause)
t.Log("assignable:", isAssignable, "convertible:", isConvertible)
{
expect := tError
m, wrongType := types.MissingMethod(testValue, expect, true)
implements := m == nil && wrongType == false
qt.Assert(t, implements, qt.IsTrue, qt.Commentf("%%2d:%2d | q does not implement %q: wrongType=%t: missing method: %s", posn.Line, posn.Column, testValue, expect, wrongType, m))
t.Logf("%q implements %q\n", testValue, expect)
}
{
expect := tReeError
m, wrongType := types.MissingMethod(testValue, expect, true)
implements := m == nil && wrongType == false
qt.Assert(t, implements, qt.IsTrue, qt.Commentf("%%2d:%2d | q does not implement %q: wrongType=%t: missing method: %s", posn.Line, posn.Column, testValue, expect, wrongType, m))
t.Logf("%q implements %q\n", testValue, expect)
}
{
expect := tReeErrorWithCause
m, wrongType := types.MissingMethod(testValue, expect, true)
implements := m == nil && wrongType == false
qt.Assert(t, implements, qt.IsTrue, qt.Commentf("%%2d:%2d | q does not implement %q: wrongType=%t: missing method: %s", posn.Line, posn.Column, testValue, expect, wrongType, m))
t.Logf("%q implements %q\n", testValue, expect)
}
}
}
}
func getMethods(named *types.Named) []*types.Func {
result := make([]*types.Func, 0, named.NumMethods())
for i := 0; i < named.NumMethods(); i++ {
method := named.Method(i)
result = append(result, method)
}
return result
}
// isMethod checks if funcDecl is a method by looking if it has a single receiver.
func isMethod(funcDecl *ast.FuncDecl) bool {
return funcDecl != nil && funcDecl.Recv != nil && len(funcDecl.Recv.List) == 1
}
func mode(tv types.TypeAndValue) string {
switch {
case tv.IsVoid():
return "void"
case tv.IsType():
return "type"
case tv.IsBuiltin():
return "builtin"
case tv.IsNil():
return "nil"
case tv.Assignable():
if tv.Addressable() {
return "var"
}
return "mapindex"
case tv.IsValue():
return "value"
default:
return "unknown"
}
}
func exprString(fset *token.FileSet, expr ast.Expr) string {
var buf bytes.Buffer
format.Node(&buf, fset, expr)
lines := strings.Split(buf.String(), "\n")
result := make([]string, 0, len(lines))
for _, l := range lines {
result = append(result, strings.TrimSpace(l))
}
return strings.Join(result, `\n`)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment