fix: handle method on interface type objects (#112)

Define a new Method action to detect method calls on interface types.
In this case the receiver is resolved from a dynamic type rather than a static one.

Handle method calls where receiver is a pointer.

In typeAssert, always return an interface value with the concrete type, to fix method lookup on interface objects.

Add relevant tests.
This commit is contained in:
Marc Vertes
2019-03-04 18:51:48 +01:00
committed by Ludovic Fernandez
parent 80f20189b4
commit 22a6d011f4
4 changed files with 61 additions and 7 deletions

View File

@@ -209,6 +209,7 @@ const (
Lor
Lower
LowerEqual
Method
Mul
MulAssign
Negate
@@ -263,6 +264,7 @@ var actions = [...]string{
Land: "&&",
Lor: "||",
Lower: "<",
Method: "Method",
Mul: "*",
MulAssign: "*=",
Negate: "-",

View File

@@ -956,7 +956,9 @@ func (interp *Interpreter) Cfg(root *Node) ([]*Node, error) {
n.val = ti
switch n.typ.cat {
case InterfaceT:
n.typ = n.typ.fieldSeq(ti)
n.gen = getMethodByName
n.action = Method
case PtrT:
n.typ = n.typ.fieldSeq(ti)
n.gen = getPtrIndexSeq

View File

@@ -250,6 +250,38 @@ func TestEvalUnary(t *testing.T) {
})
}
func TestEvalMethod(t *testing.T) {
i := interp.New(interp.Opt{})
eval(t, i, `
type Root struct {
Name string
}
type One struct {
Root
}
type Hi interface {
Hello() string
}
func (r *Root) Hello() string { return "Hello " + r.Name }
var r = Root{"R"}
var o = One{r}
var root interface{} = &Root{Name: "test1"}
var one interface{} = &One{Root{Name: "test2"}}
`)
runTests(t, i, []testCase{
{src: "r.Hello()", res: "Hello R"},
{src: "(&r).Hello()", res: "Hello R"},
{src: "o.Hello()", res: "Hello R"},
{src: "(&o).Hello()", res: "Hello R"},
{src: "root.(Hi).Hello()", res: "Hello test1"},
{src: "one.(Hi).Hello()", res: "Hello test2"},
})
}
func runTests(t *testing.T, i *interp.Interpreter, tests []testCase) {
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {

View File

@@ -114,12 +114,19 @@ func typeAssert(n *Node) {
i := n.findex
next := getExec(n.tnext)
if n.child[0].typ.cat == ValueT {
switch {
case n.child[0].typ.cat == ValueT:
n.exec = func(f *Frame) Builtin {
f.data[i] = value(f).Elem()
return next
}
} else {
case n.child[1].typ.cat == InterfaceT:
n.exec = func(f *Frame) Builtin {
v := value(f).Interface().(valueInterface)
f.data[i] = reflect.ValueOf(valueInterface{v.node, v.value})
return next
}
default:
n.exec = func(f *Frame) Builtin {
v := value(f).Interface().(valueInterface)
f.data[i] = v.value
@@ -134,7 +141,8 @@ func typeAssert2(n *Node) {
value1 := genValue(n.anc.child[1]) // returned status
next := getExec(n.tnext)
if n.child[0].typ.cat == ValueT {
switch {
case n.child[0].typ.cat == ValueT:
n.exec = func(f *Frame) Builtin {
if value(f).IsValid() && !value(f).IsNil() {
value0(f).Set(value(f).Elem())
@@ -142,7 +150,14 @@ func typeAssert2(n *Node) {
value1(f).SetBool(true)
return next
}
} else {
case n.child[1].typ.cat == InterfaceT:
n.exec = func(f *Frame) Builtin {
v, ok := value(f).Interface().(valueInterface)
value0(f).Set(reflect.ValueOf(valueInterface{v.node, v.value}))
value1(f).SetBool(ok)
return next
}
default:
n.exec = func(f *Frame) Builtin {
v, ok := value(f).Interface().(valueInterface)
value0(f).Set(v.value)
@@ -453,8 +468,7 @@ func call(n *Node) {
// Compute method receiver value
values = append(values, genValueRecv(n.child[0]))
method = true
}
if n.child[0].typ.cat == InterfaceT {
} else if n.child[0].action == Method {
// add a place holder for interface method receiver
values = append(values, nil)
method = true
@@ -529,7 +543,11 @@ func call(n *Node) {
if v == nil {
src = def.recv.val
if len(def.recv.index) > 0 {
src = src.FieldByIndex(def.recv.index)
if src.Kind() == reflect.Ptr {
src = src.Elem().FieldByIndex(def.recv.index)
} else {
src = src.FieldByIndex(def.recv.index)
}
}
} else {
src = v(f)