Deviation in type checking from the standard Golang
Severity: Informational
Description
In mempackage_type:gnovm/pkg/gnolang/types.go:1650–1704, the FindEmbeddedFieldType function is responsible for resolving fields and methods within a given data type. In line 1684, if a method is not found directly on the input type, the function continues the search in its base type.
This behavior deviates from the Go language specification. In standard Go, declared types do not automatically inherit methods from their underlying types. A method is only considered part of a declared type if it is explicitly defined with that type as its receiver. Allowing implicit method inheritance from base types introduces non-standard behavior and may lead to unexpected results or incorrect assumptions in contract logic.
Recommendation
We recommend aligning the method resolution logic with the standard Go specification by restricting method lookup to those explicitly defined on the declared type. If this deviation is intentional for Gno, it should be clearly documented to avoid confusion for developers expecting Go-compliant behavior.
Hello 👋
I ran some tests to verify the behavior described in the report.
1. Declared types do not inherit methods from their underlying types
Both Go and Gno already behave correctly here:
type Base struct{}
func (Base) Foo() {}
type Derived Base
func ShouldNotWork() {
var d Derived
d.Foo() // correctly rejected in both Go and Gno
}
So Gno does not incorrectly promote methods from underlying types.
invalid gno package; type check errors:
┃ │ gno.land/r/demo/issue4789/issue4789.gno:15:4: d.Foo undefined (type Derived has no field or method Foo)
2. Why Gno searches through Base
Gno needs to walk through the underlying type when resolving embedded fields, because declared types whose underlying type is a struct inherit its structural layout, including embedded fields.
Example that should work (and does work in both Go and Gno):
type B struct{}
func (B) Foo() {}
type A struct{ B } // embeds B
type T A // T retains the struct layout of A
type S struct{ T }
func main() {
var s S
s.Foo() // valid in both Go and Gno
}
Here Foo is promoted through the embedding:
S → T → underlying A → embedded B → Foo
3. Possible patch
If any change is desired, the potential change would be to only recurse into Base when the underlying type can actually contain embedded fields , when the underlying type eventually resolves to:
- a struct, or
- an interface (embedded interfaces promote methods too)
4. Conclusion
From what I’ve tested, Gno’s current behavior matches Go for both:
- declared type method
- method promotion through embedded fields.
So I don’t believe a change is needed, but in case the core team want to change the code i would be happy to help or contribute on a patch.
@thehowl @ltzmaxwell what do you think about the possible patch?
I also pinged @jaekwon on TG for feedback
It appears to be a false alarm. Thank @MikaelVallenet for the comment and the code — they already convey the intended idea. There’s potential to reduce the cost of the recursion as noted in both the comment above by @MikaelVallenet and the in original code comment. We can address that later anyway.
also will appreciate that if this kind of report can include an example for verification — a single filetest would be enough. thank you. cc: / @Kouteki
the simple way to add a new filetest: put the file like method38.gno in gnovm/tests/files, and run :
go test pkg/gnolang/files_test.go -test.short -update-golden-tests -run 'TestFiles$/method38.gno' -v -p 1 -timeout=30m
from xxx/gno/gnovm/ directory.
i'll open a PR that add a test file and link to my comment to close this issue