Skip to content

Commit

Permalink
fix(gnovm): annotate specific reason a value does not implement an in…
Browse files Browse the repository at this point in the history
…terface (gnolang#2492)

Related to gnolang#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):`

<!-- please provide a detailed description of the changes made in this
pull request. -->

<details><summary>Contributors' checklist...</summary>

- [ ] 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).
</details>
  • Loading branch information
Villaquiranm authored and gfanton committed Jul 23, 2024
1 parent 899cf32 commit 4c200f1
Show file tree
Hide file tree
Showing 16 changed files with 53 additions and 29 deletions.
10 changes: 5 additions & 5 deletions gnovm/pkg/gnolang/op_expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
7 changes: 4 additions & 3 deletions gnovm/pkg/gnolang/type_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 12 additions & 8 deletions gnovm/pkg/gnolang/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1009,21 +1009,21 @@ 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
}
}
// 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.
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion gnovm/tests/files/access6_stdlibs.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 1 addition & 1 deletion gnovm/tests/files/access7_stdlibs.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 1 addition & 1 deletion gnovm/tests/files/fun27.gno
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ func main() {
}

// Error:
// main/files/fun27.gno:8:2: <untyped> bigint does not implement main.Foo
// main/files/fun27.gno:8:2: <untyped> bigint does not implement main.Foo (missing method foo)
2 changes: 1 addition & 1 deletion gnovm/tests/files/type24b.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 1 addition & 1 deletion gnovm/tests/files/typeassert4a.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions gnovm/tests/files/typeassert6a.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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)
19 changes: 19 additions & 0 deletions gnovm/tests/files/typeassert9a.gno
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion gnovm/tests/files/types/assign_type_assertion_c.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 1 addition & 1 deletion gnovm/tests/files/types/eql_0b4_stdlibs.gno
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ func main() {
}

// Error:
// main/files/types/eql_0b4_stdlibs.gno:9:10: <untyped> bigint does not implement .uverse.error
// main/files/types/eql_0b4_stdlibs.gno:9:10: <untyped> bigint does not implement .uverse.error (missing method Error)
2 changes: 1 addition & 1 deletion gnovm/tests/files/types/eql_0f0_stdlibs.gno
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ func main() {
}

// Error:
// main/files/types/eql_0f0_stdlibs.gno:19:5: <untyped> bigint does not implement .uverse.error
// main/files/types/eql_0f0_stdlibs.gno:19:5: <untyped> bigint does not implement .uverse.error (missing method Error)
2 changes: 1 addition & 1 deletion gnovm/tests/files/types/eql_0f1_stdlibs.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 1 addition & 1 deletion gnovm/tests/files/types/eql_0f41_stdlibs.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 1 addition & 1 deletion gnovm/tests/files/types/eql_0f8_stdlibs.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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)

0 comments on commit 4c200f1

Please sign in to comment.