Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

interp: improve handling of methods defined on interfaces #1516

Merged
merged 12 commits into from
Mar 6, 2023
43 changes: 43 additions & 0 deletions _test/issue-1515.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

type I1 interface {
I2
Wrap() *S3
}

type I2 interface {
F()
}

type S2 struct {
I2
}

func newS2(i2 I2) I1 {
return &S2{i2}
}

type S3 struct {
base *S2
}

func (s *S2) Wrap() *S3 {
i2 := s
return &S3{i2}
}

type T struct {
name string
}

func (t *T) F() { println("in F", t.name) }

func main() {
t := &T{"test"}
s2 := newS2(t)
s3 := s2.Wrap()
s3.base.F()
}

// Output:
// in F test
216 changes: 119 additions & 97 deletions interp/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -1898,103 +1898,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
tryMethods:
fallthrough
default:
// Find a matching method.
// TODO (marc): simplify the following if/elseif blocks.
if n.typ.cat == valueT || n.typ.cat == errorT {
switch method, ok := n.typ.rtype.MethodByName(n.child[1].ident); {
case ok:
hasRecvType := n.typ.TypeOf().Kind() != reflect.Interface
n.val = method.Index
n.gen = getIndexBinMethod
n.action = aGetMethod
n.recv = &receiver{node: n.child[0]}
n.typ = valueTOf(method.Type, isBinMethod())
if hasRecvType {
n.typ.recv = n.typ
}
case n.typ.TypeOf().Kind() == reflect.Ptr:
if field, ok := n.typ.rtype.Elem().FieldByName(n.child[1].ident); ok {
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getPtrIndexSeq
break
}
err = n.cfgErrorf("undefined field or method: %s", n.child[1].ident)
case n.typ.TypeOf().Kind() == reflect.Struct:
if field, ok := n.typ.rtype.FieldByName(n.child[1].ident); ok {
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getIndexSeq
break
}
fallthrough
default:
// method lookup failed on type, now lookup on pointer to type
pt := reflect.PtrTo(n.typ.rtype)
if m2, ok2 := pt.MethodByName(n.child[1].ident); ok2 {
n.val = m2.Index
n.gen = getIndexBinPtrMethod
n.typ = valueTOf(m2.Type, isBinMethod(), withRecv(valueTOf(pt)))
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
break
}
err = n.cfgErrorf("undefined field or method: %s", n.child[1].ident)
}
} else if n.typ.cat == ptrT && (n.typ.val.cat == valueT || n.typ.val.cat == errorT) {
// Handle pointer on object defined in runtime
if method, ok := n.typ.val.rtype.MethodByName(n.child[1].ident); ok {
n.val = method.Index
n.typ = valueTOf(method.Type, isBinMethod(), withRecv(n.typ))
n.recv = &receiver{node: n.child[0]}
n.gen = getIndexBinElemMethod
n.action = aGetMethod
} else if method, ok := reflect.PtrTo(n.typ.val.rtype).MethodByName(n.child[1].ident); ok {
n.val = method.Index
n.gen = getIndexBinMethod
n.typ = valueTOf(method.Type, withRecv(valueTOf(reflect.PtrTo(n.typ.val.rtype), isBinMethod())))
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
} else if field, ok := n.typ.val.rtype.FieldByName(n.child[1].ident); ok {
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getPtrIndexSeq
} else {
err = n.cfgErrorf("undefined selector: %s", n.child[1].ident)
}
} else if m, lind := n.typ.lookupMethod(n.child[1].ident); m != nil {
n.action = aGetMethod
if n.child[0].isType(sc) {
// Handle method as a function with receiver in 1st argument.
n.val = m
n.findex = notInFrame
n.gen = nop
n.typ = &itype{}
*n.typ = *m.typ
n.typ.arg = append([]*itype{n.child[0].typ}, m.typ.arg...)
} else {
// Handle method with receiver.
n.gen = getMethod
n.val = m
n.typ = m.typ
n.recv = &receiver{node: n.child[0], index: lind}
}
} else if m, lind, isPtr, ok := n.typ.lookupBinMethod(n.child[1].ident); ok {
n.action = aGetMethod
switch {
case isPtr && n.typ.fieldSeq(lind).cat != ptrT:
n.gen = getIndexSeqPtrMethod
case isInterfaceSrc(n.typ):
n.gen = getMethodByName
default:
n.gen = getIndexSeqMethod
}
n.recv = &receiver{node: n.child[0], index: lind}
n.val = append([]int{m.Index}, lind...)
n.typ = valueTOf(m.Type, isBinMethod(), withRecv(n.child[0].typ))
} else {
err = n.cfgErrorf("undefined selector: %s", n.child[1].ident)
}
err = matchSelectorMethod(sc, n)
}
if err == nil && n.findex != -1 && n.typ.cat != genericT {
n.findex = sc.add(n.typ)
Expand Down Expand Up @@ -3024,6 +2928,124 @@ func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerat
return gen
}

// matchSelectorMethod, given that n represents a selector for a method, tries
// to find the corresponding method, and populates n accordingly.
func matchSelectorMethod(sc *scope, n *node) (err error) {
name := n.child[1].ident
if n.typ.cat == valueT || n.typ.cat == errorT {
switch method, ok := n.typ.rtype.MethodByName(name); {
case ok:
hasRecvType := n.typ.TypeOf().Kind() != reflect.Interface
n.val = method.Index
n.gen = getIndexBinMethod
n.action = aGetMethod
n.recv = &receiver{node: n.child[0]}
n.typ = valueTOf(method.Type, isBinMethod())
if hasRecvType {
n.typ.recv = n.typ
}
case n.typ.TypeOf().Kind() == reflect.Ptr:
if field, ok := n.typ.rtype.Elem().FieldByName(name); ok {
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getPtrIndexSeq
break
}
err = n.cfgErrorf("undefined field or method: %s", name)
mvertes marked this conversation as resolved.
Show resolved Hide resolved
case n.typ.TypeOf().Kind() == reflect.Struct:
if field, ok := n.typ.rtype.FieldByName(name); ok {
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getIndexSeq
break
}
fallthrough
default:
// method lookup failed on type, now lookup on pointer to type
pt := reflect.PtrTo(n.typ.rtype)
if m2, ok2 := pt.MethodByName(name); ok2 {
n.val = m2.Index
n.gen = getIndexBinPtrMethod
n.typ = valueTOf(m2.Type, isBinMethod(), withRecv(valueTOf(pt)))
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
break
}
err = n.cfgErrorf("undefined field or method: %s", name)
mvertes marked this conversation as resolved.
Show resolved Hide resolved
}
return err
}

if n.typ.cat == ptrT && (n.typ.val.cat == valueT || n.typ.val.cat == errorT) {
// Handle pointer on object defined in runtime
if method, ok := n.typ.val.rtype.MethodByName(name); ok {
n.val = method.Index
n.typ = valueTOf(method.Type, isBinMethod(), withRecv(n.typ))
n.recv = &receiver{node: n.child[0]}
n.gen = getIndexBinElemMethod
n.action = aGetMethod
} else if method, ok := reflect.PtrTo(n.typ.val.rtype).MethodByName(name); ok {
n.val = method.Index
n.gen = getIndexBinMethod
n.typ = valueTOf(method.Type, withRecv(valueTOf(reflect.PtrTo(n.typ.val.rtype), isBinMethod())))
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
} else if field, ok := n.typ.val.rtype.FieldByName(name); ok {
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getPtrIndexSeq
} else {
err = n.cfgErrorf("undefined selector: %s", name)
}
return err
}

if m, lind := n.typ.lookupMethod(name); m != nil {
n.action = aGetMethod
if n.child[0].isType(sc) {
// Handle method as a function with receiver in 1st argument.
n.val = m
n.findex = notInFrame
n.gen = nop
n.typ = &itype{}
*n.typ = *m.typ
n.typ.arg = append([]*itype{n.child[0].typ}, m.typ.arg...)
} else {
// Handle method with receiver.
n.gen = getMethod
n.val = m
n.typ = m.typ
n.recv = &receiver{node: n.child[0], index: lind}
}
return nil
}

if m, lind, isPtr, ok := n.typ.lookupBinMethod(name); ok {
n.action = aGetMethod
switch {
case isPtr && n.typ.fieldSeq(lind).cat != ptrT:
n.gen = getIndexSeqPtrMethod
case isInterfaceSrc(n.typ):
n.gen = getMethodByName
default:
n.gen = getIndexSeqMethod
}
n.recv = &receiver{node: n.child[0], index: lind}
n.val = append([]int{m.Index}, lind...)
n.typ = valueTOf(m.Type, isBinMethod(), withRecv(n.child[0].typ))
return nil
}

if typ := n.typ.interfaceMethod(name); typ != nil {
n.typ = typ
n.action = aGetMethod
n.gen = getMethodByName
return nil
}

return n.cfgErrorf("undefined selector: %s", name)
}

// arrayTypeLen returns the node's array length. If the expression is an
// array variable it is determined from the value's type, otherwise it is
// computed from the source definition.
Expand Down
Loading