Last active
December 30, 2022 18:19
-
-
Save TripleDogDare/556f6c3dcbc6e34807229bc7f29a8149 to your computer and use it in GitHub Desktop.
serum debugging Cause method validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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