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:
25
_test/issue-1156.go
Normal file
25
_test/issue-1156.go
Normal 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
|
||||||
257
interp/cfg.go
257
interp/cfg.go
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user