Skip to content

Commit

Permalink
interp: improve field and method resolution in presence of collisions.
Browse files Browse the repository at this point in the history
The resolution method was not compliant with the Go specification which
requires to retain the object where the field or method is the most
shallowed.

The detection of ambiguous fields or methods (same depth in different
objects) has also been added.

Fixes #1163.
  • Loading branch information
mvertes authored Jul 6, 2021
1 parent bd2cb06 commit 297b40d
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 0 deletions.
41 changes: 41 additions & 0 deletions _test/issue-1163.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package main

import "fmt"

type WidgetEvent struct {
Nothing string
}

type WidgetControl interface {
HandleEvent(e *WidgetEvent)
}

type Button struct{}

func (b *Button) HandleEvent(e *WidgetEvent) {
}

type WindowEvent struct {
Something int
}

type Window struct {
Widget WidgetControl
}

func (w *Window) HandleEvent(e *WindowEvent) {
}

func main() {
window := &Window{
Widget: &Button{},
}
windowevent := &WindowEvent{}
// The next line uses the signature from the wrong method, resulting in an error.
// Renaming one of the clashing method names fixes the problem.
window.HandleEvent(windowevent)
fmt.Println("OK!")
}

// Output:
// OK!
25 changes: 25 additions & 0 deletions interp/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,18 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case isStruct(n.typ) || isInterfaceSrc(n.typ):
// Find a matching field.
if ti := n.typ.lookupField(n.child[1].ident); len(ti) > 0 {
if isStruct(n.typ) {
// If a method of the same name exists, use it if it is shallower than the struct field.
// if method's depth is the same as field's, this is an error.
d := n.typ.methodDepth(n.child[1].ident)
if d >= 0 && d < len(ti) {
goto tryMethods
}
if d == len(ti) {
err = n.cfgErrorf("ambiguous selector: %s", n.child[1].ident)
break
}
}
n.val = ti
switch {
case isInterfaceSrc(n.typ):
Expand Down Expand Up @@ -1540,11 +1552,24 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
// Handle an embedded binary field into a struct field.
n.gen = getIndexSeqField
lind = append(lind, s.Index...)
if isStruct(n.typ) {
// If a method of the same name exists, use it if it is shallower than the struct field.
// if method's depth is the same as field's, this is an error.
d := n.typ.methodDepth(n.child[1].ident)
if d >= 0 && d < len(lind) {
goto tryMethods
}
if d == len(lind) {
err = n.cfgErrorf("ambiguous selector: %s", n.child[1].ident)
break
}
}
n.val = lind
n.typ = &itype{cat: valueT, rtype: s.Type}
break
}
// No field (embedded or not) matched. Try to match a method.
tryMethods:
fallthrough
default:
// Find a matching method.
Expand Down
11 changes: 11 additions & 0 deletions interp/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -1372,6 +1372,17 @@ func (t *itype) lookupMethod(name string) (*node, []int) {
return m, index
}

// methodDepth returns a depth greater or equal to 0, or -1 if no match.
func (t *itype) methodDepth(name string) int {
if m, lint := t.lookupMethod(name); m != nil {
return len(lint)
}
if _, lint, _, ok := t.lookupBinMethod(name); ok {
return len(lint)
}
return -1
}

// LookupBinMethod returns a method and a path to access a field in a struct object (the receiver).
func (t *itype) lookupBinMethod(name string) (m reflect.Method, index []int, isPtr, ok bool) {
if t.cat == ptrT {
Expand Down

0 comments on commit 297b40d

Please sign in to comment.