interp: improve handling of interface values

In selector resolution, struct field matching now precedes
method matching. Before struct field matching could be skipped
in case of a matching method, which is incorrect, as demontrated
by _test/issue-1156.go.

Field lookup has been fixed to operate on recursive structures.

Concrete type values are derived when filling a receiver for
interface methods.

LookupBinField has been fixed to skip non struct values.

LookupMethod has been fixed to iterate on interface values as
well as concrete type values.

Fixes #1156.
This commit is contained in:
Marc Vertes
2021-06-24 16:20:13 +02:00
committed by GitHub
parent 1df5dc2e93
commit e32b2ab6bd
4 changed files with 197 additions and 140 deletions

25
_test/issue-1156.go Normal file
View File

@@ -0,0 +1,25 @@
package main
type myInterface interface {
myFunc() string
}
type V struct{}
func (v *V) myFunc() string { return "hello" }
type U struct {
v myInterface
}
func (u *U) myFunc() string { return u.v.myFunc() }
func main() {
x := V{}
y := myInterface(&x)
y = &U{y}
println(y.myFunc())
}
// Output:
// hello

View File

@@ -1474,72 +1474,8 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
err = n.cfgErrorf("undefined type") err = n.cfgErrorf("undefined type")
break break
} }
if n.typ.cat == valueT || n.typ.cat == errorT { switch {
// Handle object defined in runtime, try to find field or method case n.typ.cat == binPkgT:
// Search for method first, as it applies both to types T and *T
// Search for field must then be performed on type T only (not *T)
switch method, ok := n.typ.rtype.MethodByName(n.child[1].ident); {
case ok:
hasRecvType := n.typ.rtype.Kind() != reflect.Interface
n.val = method.Index
n.gen = getIndexBinMethod
n.action = aGetMethod
n.recv = &receiver{node: n.child[0]}
n.typ = &itype{cat: valueT, rtype: method.Type, isBinMethod: true}
if hasRecvType {
n.typ.recv = n.typ
}
case n.typ.rtype.Kind() == reflect.Ptr:
if field, ok := n.typ.rtype.Elem().FieldByName(n.child[1].ident); ok {
n.typ = &itype{cat: valueT, rtype: 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.rtype.Kind() == reflect.Struct:
if field, ok := n.typ.rtype.FieldByName(n.child[1].ident); ok {
n.typ = &itype{cat: valueT, rtype: 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 = &itype{cat: valueT, rtype: m2.Type, recv: &itype{cat: valueT, rtype: pt}, isBinMethod: true}
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 = &itype{cat: valueT, rtype: method.Type, recv: n.typ, isBinMethod: true}
n.recv = &receiver{node: n.child[0]}
n.gen = getIndexBinMethod
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 = &itype{cat: valueT, rtype: method.Type, recv: &itype{cat: valueT, rtype: reflect.PtrTo(n.typ.val.rtype)}, isBinMethod: true}
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 = &itype{cat: valueT, rtype: field.Type}
n.val = field.Index
n.gen = getPtrIndexSeq
} else {
err = n.cfgErrorf("undefined selector: %s", n.child[1].ident)
}
} else if n.typ.cat == binPkgT {
// Resolve binary package symbol: a type or a value // Resolve binary package symbol: a type or a value
name := n.child[1].ident name := n.child[1].ident
pkg := n.child[0].sym.typ.path pkg := n.child[0].sym.typ.path
@@ -1555,7 +1491,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
} else { } else {
err = n.cfgErrorf("package %s \"%s\" has no symbol %s", n.child[0].ident, pkg, name) err = n.cfgErrorf("package %s \"%s\" has no symbol %s", n.child[0].ident, pkg, name)
} }
} else if n.typ.cat == srcPkgT { case n.typ.cat == srcPkgT:
pkg, name := n.child[0].sym.typ.path, n.child[1].ident pkg, name := n.child[0].sym.typ.path, n.child[1].ident
// Resolve source package symbol // Resolve source package symbol
if sym, ok := interp.srcPkg[pkg][name]; ok { if sym, ok := interp.srcPkg[pkg][name]; ok {
@@ -1572,66 +1508,139 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
} else { } else {
err = n.cfgErrorf("undefined selector: %s.%s", pkg, name) err = n.cfgErrorf("undefined selector: %s.%s", pkg, name)
} }
} else if m, lind := n.typ.lookupMethod(n.child[1].ident); m != nil { case isStruct(n.typ) || isInterfaceSrc(n.typ):
n.action = aGetMethod // Find a matching field.
if n.child[0].isType(sc) { if ti := n.typ.lookupField(n.child[1].ident); len(ti) > 0 {
// Handle method as a function with receiver in 1st argument n.val = ti
n.val = m switch {
n.findex = notInFrame case isInterfaceSrc(n.typ):
n.gen = nop n.typ = n.typ.fieldSeq(ti)
n.typ = &itype{} n.gen = getMethodByName
*n.typ = *m.typ n.action = aMethod
n.typ.arg = append([]*itype{n.child[0].typ}, m.typ.arg...) case n.typ.cat == ptrT:
} else { n.typ = n.typ.fieldSeq(ti)
// Handle method with receiver n.gen = getPtrIndexSeq
n.gen = getMethod if n.typ.cat == funcT {
n.val = m // Function in a struct field is always wrapped in reflect.Value.
n.typ = m.typ rtype := n.typ.TypeOf()
n.typ = &itype{cat: valueT, rtype: rtype, val: n.typ}
}
default:
n.gen = getIndexSeq
n.typ = n.typ.fieldSeq(ti)
if n.typ.cat == funcT {
// Function in a struct field is always wrapped in reflect.Value.
rtype := n.typ.TypeOf()
n.typ = &itype{cat: valueT, rtype: rtype, val: n.typ}
}
}
break
}
if s, lind, ok := n.typ.lookupBinField(n.child[1].ident); ok {
// Handle an embedded binary field into a struct field.
n.gen = getIndexSeqField
lind = append(lind, s.Index...)
n.val = lind
n.typ = &itype{cat: valueT, rtype: s.Type}
break
}
// No field (embedded or not) matched. Try to match a method.
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.rtype.Kind() != reflect.Interface
n.val = method.Index
n.gen = getIndexBinMethod
n.action = aGetMethod
n.recv = &receiver{node: n.child[0]}
n.typ = &itype{cat: valueT, rtype: method.Type, isBinMethod: true}
if hasRecvType {
n.typ.recv = n.typ
}
case n.typ.rtype.Kind() == reflect.Ptr:
if field, ok := n.typ.rtype.Elem().FieldByName(n.child[1].ident); ok {
n.typ = &itype{cat: valueT, rtype: 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.rtype.Kind() == reflect.Struct:
if field, ok := n.typ.rtype.FieldByName(n.child[1].ident); ok {
n.typ = &itype{cat: valueT, rtype: 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 = &itype{cat: valueT, rtype: m2.Type, recv: &itype{cat: valueT, rtype: pt}, isBinMethod: true}
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 = &itype{cat: valueT, rtype: method.Type, recv: n.typ, isBinMethod: true}
n.recv = &receiver{node: n.child[0]}
n.gen = getIndexBinMethod
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 = &itype{cat: valueT, rtype: method.Type, recv: &itype{cat: valueT, rtype: reflect.PtrTo(n.typ.val.rtype)}, isBinMethod: true}
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 = &itype{cat: valueT, rtype: 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
if isPtr && n.typ.fieldSeq(lind).cat != ptrT {
n.gen = getIndexSeqPtrMethod
} else {
n.gen = getIndexSeqMethod
}
n.recv = &receiver{node: n.child[0], index: lind} n.recv = &receiver{node: n.child[0], index: lind}
} n.val = append([]int{m.Index}, lind...)
} else if m, lind, isPtr, ok := n.typ.lookupBinMethod(n.child[1].ident); ok { n.typ = &itype{cat: valueT, rtype: m.Type, recv: n.child[0].typ, isBinMethod: true}
n.action = aGetMethod
if isPtr && n.typ.fieldSeq(lind).cat != ptrT {
n.gen = getIndexSeqPtrMethod
} else { } else {
n.gen = getIndexSeqMethod err = n.cfgErrorf("undefined selector: %s", n.child[1].ident)
} }
n.recv = &receiver{node: n.child[0], index: lind}
n.val = append([]int{m.Index}, lind...)
n.typ = &itype{cat: valueT, rtype: m.Type, recv: n.child[0].typ, isBinMethod: true}
} else if ti := n.typ.lookupField(n.child[1].ident); len(ti) > 0 {
// Handle struct field
n.val = ti
switch {
case isInterfaceSrc(n.typ):
n.typ = n.typ.fieldSeq(ti)
n.gen = getMethodByName
n.action = aMethod
case n.typ.cat == ptrT:
n.typ = n.typ.fieldSeq(ti)
n.gen = getPtrIndexSeq
if n.typ.cat == funcT {
// function in a struct field is always wrapped in reflect.Value
rtype := n.typ.TypeOf()
n.typ = &itype{cat: valueT, rtype: rtype, val: n.typ}
}
default:
n.gen = getIndexSeq
n.typ = n.typ.fieldSeq(ti)
if n.typ.cat == funcT {
// function in a struct field is always wrapped in reflect.Value
rtype := n.typ.TypeOf()
n.typ = &itype{cat: valueT, rtype: rtype, val: n.typ}
}
}
} else if s, lind, ok := n.typ.lookupBinField(n.child[1].ident); ok {
// Handle an embedded binary field into a struct field
n.gen = getIndexSeqField
lind = append(lind, s.Index...)
n.val = lind
n.typ = &itype{cat: valueT, rtype: s.Type}
} else {
err = n.cfgErrorf("undefined selector: %s", n.child[1].ident)
} }
if err == nil && n.findex != -1 { if err == nil && n.findex != -1 {
n.findex = sc.add(n.typ) n.findex = sc.add(n.typ)

View File

@@ -1230,6 +1230,14 @@ func call(n *node) {
src = def.recv.val src = def.recv.val
} else { } else {
src = v(f) src = v(f)
for src.IsValid() {
// traverse interface indirections to find out concrete type
vi, ok := src.Interface().(valueInterface)
if !ok {
break
}
src = vi.value
}
} }
if recvIndexLater && def.recv != nil && len(def.recv.index) > 0 { if recvIndexLater && def.recv != nil && len(def.recv.index) > 0 {
if src.Kind() == reflect.Ptr { if src.Kind() == reflect.Ptr {

View File

@@ -1254,24 +1254,36 @@ func (t *itype) fieldSeq(seq []int) *itype {
// lookupField returns a list of indices, i.e. a path to access a field in a struct object. // lookupField returns a list of indices, i.e. a path to access a field in a struct object.
func (t *itype) lookupField(name string) []int { func (t *itype) lookupField(name string) []int {
switch t.cat { seen := map[*itype]bool{}
case aliasT, ptrT: var lookup func(*itype) []int
return t.val.lookupField(name)
}
if fi := t.fieldIndex(name); fi >= 0 {
return []int{fi}
}
for i, f := range t.field { lookup = func(typ *itype) []int {
switch f.typ.cat { if seen[typ] {
case ptrT, structT, interfaceT, aliasT: return nil
if index2 := f.typ.lookupField(name); len(index2) > 0 { }
return append([]int{i}, index2...) seen[typ] = true
switch typ.cat {
case aliasT, ptrT:
return lookup(typ.val)
}
if fi := typ.fieldIndex(name); fi >= 0 {
return []int{fi}
}
for i, f := range typ.field {
switch f.typ.cat {
case ptrT, structT, interfaceT, aliasT:
if index2 := lookup(f.typ); len(index2) > 0 {
return append([]int{i}, index2...)
}
} }
} }
return nil
} }
return nil return lookup(t)
} }
// lookupBinField returns a structfield and a path to access an embedded binary field in a struct object. // lookupBinField returns a structfield and a path to access an embedded binary field in a struct object.
@@ -1282,10 +1294,13 @@ func (t *itype) lookupBinField(name string) (s reflect.StructField, index []int,
if !isStruct(t) { if !isStruct(t) {
return return
} }
rt := t.rtype rt := t.TypeOf()
if t.cat == valueT && rt.Kind() == reflect.Ptr { for t.cat == valueT && rt.Kind() == reflect.Ptr {
rt = rt.Elem() rt = rt.Elem()
} }
if rt.Kind() != reflect.Struct {
return
}
s, ok = rt.FieldByName(name) s, ok = rt.FieldByName(name)
if !ok { if !ok {
for i, f := range t.field { for i, f := range t.field {
@@ -1350,7 +1365,7 @@ func (t *itype) lookupMethod(name string) (*node, []int) {
} }
} }
} }
if t.cat == aliasT { if t.cat == aliasT || isInterfaceSrc(t) && t.val != nil {
return t.val.lookupMethod(name) return t.val.lookupMethod(name)
} }
} }