From 447b7635a9366325a5d1040045411436d2e6b875 Mon Sep 17 00:00:00 2001 From: Miguel Victoria Villaquiran Date: Wed, 17 Jul 2024 17:29:18 +0200 Subject: [PATCH] fix(gnovm): annotate specific reason a value does not implement an interface (#2492) Related to #1684 . This is a first draft that will increment the amount of information Gno returns in the case of an struct not implementing a defined interface. ### First Case `A non defined function on interface` ```go package main type Car interface{ Run(speed int) } type Toyota struct {} func main(){ var car Car = &Toyota{} } ``` **Before** we had something like: `panic: *main.Toyota does not implement main.Car [recovered]` **now**: `panic: *main.Toyota does not implement main.Car (missing method Run) [recovered]` ### Second Case `A defined function with bad type on function signature` ```go package main type Car interface{ Run(speed int) } type Toyota struct {} func (toyota *Toyota) Run(quick bool){ } func main(){ var car Car = &Toyota{} } ``` **Before** we had something like: `panic: *main.Toyota does not implement main.Car [recovered]` **now**: `panic: *main.Toyota does not implement main.Car (wrong type for method Run) [recovered]` ### Third Case `A defined function with a pointer receiver but not pointer variable` ```go package main type Car interface { Run() } type Toyota struct { } func (t *Toyota) Run() { } func main() { var car Car = Toyota{} } ``` **Before** we had something like: `panic: *main.Toyota does not implement main.Car [recovered]` **now**: `panic: main.Toyota does not implement main.Car (method Run has pointer receiver):`
Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
--- gnovm/pkg/gnolang/op_expressions.go | 10 +++++----- gnovm/pkg/gnolang/type_check.go | 7 ++++--- gnovm/pkg/gnolang/types.go | 20 +++++++++++-------- gnovm/tests/files/access6_stdlibs.gno | 2 +- gnovm/tests/files/access7_stdlibs.gno | 2 +- gnovm/tests/files/fun27.gno | 2 +- gnovm/tests/files/type24b.gno | 2 +- gnovm/tests/files/typeassert4a.gno | 2 +- gnovm/tests/files/typeassert6a.gno | 4 ++-- gnovm/tests/files/typeassert9a.gno | 19 ++++++++++++++++++ .../files/types/assign_type_assertion_c.gno | 2 +- gnovm/tests/files/types/eql_0b4_stdlibs.gno | 2 +- gnovm/tests/files/types/eql_0f0_stdlibs.gno | 2 +- gnovm/tests/files/types/eql_0f1_stdlibs.gno | 2 +- gnovm/tests/files/types/eql_0f41_stdlibs.gno | 2 +- gnovm/tests/files/types/eql_0f8_stdlibs.gno | 2 +- 16 files changed, 53 insertions(+), 29 deletions(-) create mode 100644 gnovm/tests/files/typeassert9a.gno diff --git a/gnovm/pkg/gnolang/op_expressions.go b/gnovm/pkg/gnolang/op_expressions.go index 4bbeef2dace..36130ccbf4d 100644 --- a/gnovm/pkg/gnolang/op_expressions.go +++ b/gnovm/pkg/gnolang/op_expressions.go @@ -238,14 +238,14 @@ func (m *Machine) doOpTypeAssert1() { // t is Gno interface. // assert that x implements type. - var impl bool - impl = it.IsImplementedBy(xt) - if !impl { + err := it.VerifyImplementedBy(xt) + if err != nil { // TODO: default panic type? ex := fmt.Sprintf( - "%s doesn't implement %s", + "%s doesn't implement %s (%s)", xt.String(), - it.String()) + it.String(), + err.Error()) m.Panic(typedString(ex)) return } diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index 870eb10b690..3f25667f353 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -237,14 +237,15 @@ func checkAssignableTo(xt, dt Type, autoNative bool) error { if idt.IsEmptyInterface() { // XXX, can this be merged with IsImplementedBy? // if dt is an empty Gno interface, any x ok. return nil // ok - } else if idt.IsImplementedBy(xt) { + } else if err := idt.VerifyImplementedBy(xt); err == nil { // if dt implements idt, ok. return nil // ok } else { return errors.New( - "%s does not implement %s", + "%s does not implement %s (%s)", xt.String(), - dt.String()) + dt.String(), + err.Error()) } } else if ndt, ok := baseOf(dt).(*NativeType); ok { nidt := ndt.Type diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index ab8e9effdc8..b43f623ea99 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -1009,13 +1009,13 @@ func (it *InterfaceType) FindEmbeddedFieldType(callerPath string, n Name, m map[ // For run-time type assertion. // TODO: optimize somehow. -func (it *InterfaceType) IsImplementedBy(ot Type) (result bool) { +func (it *InterfaceType) VerifyImplementedBy(ot Type) error { for _, im := range it.Methods { if im.Type.Kind() == InterfaceKind { // field is embedded interface... im2 := baseOf(im.Type).(*InterfaceType) - if !im2.IsImplementedBy(ot) { - return false + if err := im2.VerifyImplementedBy(ot); err != nil { + return err } else { continue } @@ -1023,7 +1023,7 @@ func (it *InterfaceType) IsImplementedBy(ot Type) (result bool) { // find method in field. tr, hp, rt, ft, _ := findEmbeddedFieldType(it.PkgPath, ot, im.Name, nil) if tr == nil { // not found. - return false + return fmt.Errorf("missing method %s", im.Name) } if nft, ok := ft.(*NativeType); ok { // Treat native function types as autoNative calls. @@ -1033,22 +1033,26 @@ func (it *InterfaceType) IsImplementedBy(ot Type) (result bool) { // ie, if each of ft's arg types can match // against the desired arg types in im.Types. if !gno2GoTypeMatches(im.Type, nft.Type) { - return false + return fmt.Errorf("wrong type for method %s", im.Name) } } else if mt, ok := ft.(*FuncType); ok { // if method is pointer receiver, check addressability: if _, ptrRcvr := rt.(*PointerType); ptrRcvr && !hp { - return false // not addressable. + return fmt.Errorf("method %s has pointer receiver", im.Name) // not addressable. } // check for func type equality. dmtid := mt.TypeID() imtid := im.Type.TypeID() if dmtid != imtid { - return false + return fmt.Errorf("wrong type for method %s", im.Name) } } } - return true + return nil +} + +func (it *InterfaceType) IsImplementedBy(ot Type) bool { + return it.VerifyImplementedBy(ot) == nil } func (it *InterfaceType) GetPathForName(n Name) ValuePath { diff --git a/gnovm/tests/files/access6_stdlibs.gno b/gnovm/tests/files/access6_stdlibs.gno index 57b3d63d1a6..443f2f5291d 100644 --- a/gnovm/tests/files/access6_stdlibs.gno +++ b/gnovm/tests/files/access6_stdlibs.gno @@ -16,4 +16,4 @@ func main() { } // Error: -// main/files/access6_stdlibs.gno:15:2: main.mystruct does not implement gno.land/p/demo/testutils.PrivateInterface +// main/files/access6_stdlibs.gno:15:2: main.mystruct does not implement gno.land/p/demo/testutils.PrivateInterface (missing method privateMethod) diff --git a/gnovm/tests/files/access7_stdlibs.gno b/gnovm/tests/files/access7_stdlibs.gno index 6ba10780856..01c9ed83fa0 100644 --- a/gnovm/tests/files/access7_stdlibs.gno +++ b/gnovm/tests/files/access7_stdlibs.gno @@ -20,4 +20,4 @@ func main() { } // Error: -// main/files/access7_stdlibs.gno:19:2: main.PrivateInterface2 does not implement gno.land/p/demo/testutils.PrivateInterface +// main/files/access7_stdlibs.gno:19:2: main.PrivateInterface2 does not implement gno.land/p/demo/testutils.PrivateInterface (missing method privateMethod) diff --git a/gnovm/tests/files/fun27.gno b/gnovm/tests/files/fun27.gno index 8f4d6c02e5f..87a878fd24a 100644 --- a/gnovm/tests/files/fun27.gno +++ b/gnovm/tests/files/fun27.gno @@ -13,4 +13,4 @@ func main() { } // Error: -// main/files/fun27.gno:8:2: bigint does not implement main.Foo +// main/files/fun27.gno:8:2: bigint does not implement main.Foo (missing method foo) diff --git a/gnovm/tests/files/type24b.gno b/gnovm/tests/files/type24b.gno index 54c1bb38df4..fa31104e4ff 100644 --- a/gnovm/tests/files/type24b.gno +++ b/gnovm/tests/files/type24b.gno @@ -46,4 +46,4 @@ func assertValue() { // Output: // int is not of type string // interface{} is not of type string -// *github.com/gnolang/gno/_test/net/http/httptest.ResponseRecorder doesn't implement interface{Push func(string;*github.com/gnolang/gno/_test/net/http.PushOptions)(.uverse.error)} +// *github.com/gnolang/gno/_test/net/http/httptest.ResponseRecorder doesn't implement interface{Push func(string;*github.com/gnolang/gno/_test/net/http.PushOptions)(.uverse.error)} (missing method Push) diff --git a/gnovm/tests/files/typeassert4a.gno b/gnovm/tests/files/typeassert4a.gno index 46d3728de1f..09e9ae06aa1 100644 --- a/gnovm/tests/files/typeassert4a.gno +++ b/gnovm/tests/files/typeassert4a.gno @@ -58,5 +58,5 @@ func main() { // interface conversion: interface is nil, not main.Setter // interface conversion: interface is nil, not main.Setter // ok -// main.ValueSetter doesn't implement interface{Set func(string)()} +// main.ValueSetter doesn't implement interface{Set func(string)()} (method Set has pointer receiver) // ok diff --git a/gnovm/tests/files/typeassert6a.gno b/gnovm/tests/files/typeassert6a.gno index f4b925cfeee..e55f077c334 100644 --- a/gnovm/tests/files/typeassert6a.gno +++ b/gnovm/tests/files/typeassert6a.gno @@ -36,5 +36,5 @@ func main() { } // Output: -// int doesn't implement interface{Do func(string)()} -// *int doesn't implement interface{Do func(string)()} +// int doesn't implement interface{Do func(string)()} (missing method Do) +// *int doesn't implement interface{Do func(string)()} (missing method Do) diff --git a/gnovm/tests/files/typeassert9a.gno b/gnovm/tests/files/typeassert9a.gno new file mode 100644 index 00000000000..8de8aa773b1 --- /dev/null +++ b/gnovm/tests/files/typeassert9a.gno @@ -0,0 +1,19 @@ +package main + +// First interface +type Reader interface { + Read(int) string +} + +type csvReader struct{} +func (r*csvReader) Read(string) string{ + return "" +} + + +func main() { + var csvReader Reader = &csvReader{} +} + +// Error: +// main/files/typeassert9a.gno:15:6: *main.csvReader does not implement main.Reader (wrong type for method Read) diff --git a/gnovm/tests/files/types/assign_type_assertion_c.gno b/gnovm/tests/files/types/assign_type_assertion_c.gno index b274497357a..d1c1cadcb91 100644 --- a/gnovm/tests/files/types/assign_type_assertion_c.gno +++ b/gnovm/tests/files/types/assign_type_assertion_c.gno @@ -30,4 +30,4 @@ func main() { } // Error: -// main/files/types/assign_type_assertion_c.gno:23:2: interface{IsSet func()(bool)} does not implement interface{IsNotSet func()(bool)} +// main/files/types/assign_type_assertion_c.gno:23:2: interface{IsSet func()(bool)} does not implement interface{IsNotSet func()(bool)} (missing method IsNotSet) diff --git a/gnovm/tests/files/types/eql_0b4_stdlibs.gno b/gnovm/tests/files/types/eql_0b4_stdlibs.gno index 70f5bf31296..eac923c6d31 100644 --- a/gnovm/tests/files/types/eql_0b4_stdlibs.gno +++ b/gnovm/tests/files/types/eql_0b4_stdlibs.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// main/files/types/eql_0b4_stdlibs.gno:9:10: bigint does not implement .uverse.error +// main/files/types/eql_0b4_stdlibs.gno:9:10: bigint does not implement .uverse.error (missing method Error) diff --git a/gnovm/tests/files/types/eql_0f0_stdlibs.gno b/gnovm/tests/files/types/eql_0f0_stdlibs.gno index bbd49d6cf93..4947627cba4 100644 --- a/gnovm/tests/files/types/eql_0f0_stdlibs.gno +++ b/gnovm/tests/files/types/eql_0f0_stdlibs.gno @@ -25,4 +25,4 @@ func main() { } // Error: -// main/files/types/eql_0f0_stdlibs.gno:19:5: bigint does not implement .uverse.error +// main/files/types/eql_0f0_stdlibs.gno:19:5: bigint does not implement .uverse.error (missing method Error) diff --git a/gnovm/tests/files/types/eql_0f1_stdlibs.gno b/gnovm/tests/files/types/eql_0f1_stdlibs.gno index f3feffa7ec6..cab7fcfab33 100644 --- a/gnovm/tests/files/types/eql_0f1_stdlibs.gno +++ b/gnovm/tests/files/types/eql_0f1_stdlibs.gno @@ -25,4 +25,4 @@ func main() { } // Error: -// main/files/types/eql_0f1_stdlibs.gno:19:5: int64 does not implement .uverse.error +// main/files/types/eql_0f1_stdlibs.gno:19:5: int64 does not implement .uverse.error (missing method Error) diff --git a/gnovm/tests/files/types/eql_0f41_stdlibs.gno b/gnovm/tests/files/types/eql_0f41_stdlibs.gno index f4d35a2af4b..be78ea6ed79 100644 --- a/gnovm/tests/files/types/eql_0f41_stdlibs.gno +++ b/gnovm/tests/files/types/eql_0f41_stdlibs.gno @@ -32,4 +32,4 @@ func main() { } // Error: -// main/files/types/eql_0f41_stdlibs.gno:27:5: main.animal does not implement .uverse.error +// main/files/types/eql_0f41_stdlibs.gno:27:5: main.animal does not implement .uverse.error (missing method Error) diff --git a/gnovm/tests/files/types/eql_0f8_stdlibs.gno b/gnovm/tests/files/types/eql_0f8_stdlibs.gno index 1ab191c59c4..a6e24110432 100644 --- a/gnovm/tests/files/types/eql_0f8_stdlibs.gno +++ b/gnovm/tests/files/types/eql_0f8_stdlibs.gno @@ -24,4 +24,4 @@ func main() { } // Error: -// main/files/types/eql_0f8_stdlibs.gno:19:5: int64 does not implement .uverse.error +// main/files/types/eql_0f8_stdlibs.gno:19:5: int64 does not implement .uverse.error (missing method Error)