interp: fix closure in a struct field

Functions in a struct fields are always wrapped (as potentially
used by the runtime), so generate a function wrapper also for
closure when assigned to a struct field.

When such a function is called from the interpreter, ensure that
interface arguments are also wrapped so method and receiver resolution
can be performed.

Fixes partially #1043.
This commit is contained in:
Marc Vertes
2021-03-11 15:26:04 +01:00
committed by GitHub
parent fdfcb9c1df
commit 7d8fdbc1fc
5 changed files with 70 additions and 10 deletions

26
_test/method36.go Normal file
View File

@@ -0,0 +1,26 @@
package main
type I interface{ Hello() }
type T struct{ Name string }
func (t *T) Hello() { println("Hello", t.Name) }
type FT func(i I)
type ST struct{ Handler FT }
func newF() FT {
return func(i I) {
i.Hello()
}
}
func main() {
st := &ST{}
st.Handler = newF()
st.Handler(&T{"test"})
}
// Output:
// Hello test

View File

@@ -90,7 +90,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
return false
}
if !isInterface(dest.typ) {
// Interface types are not propagated, and will be resolved at post-order.
// Interface type are not propagated, and will be resolved at post-order.
n.typ = dest.typ
}
case binaryExpr, unaryExpr, parenExpr:
@@ -578,6 +578,8 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
// Setting a map entry requires an additional step, do not optimize.
// As we only write, skip the default useless getIndexMap dest action.
dest.gen = nop
case isFuncField(dest):
// Setting a struct field of function type requires an extra step. Do not optimize.
case isCall(src) && dest.typ.cat != interfaceT && !isRecursiveField(dest) && n.kind != defineStmt:
// Call action may perform the assignment directly.
n.gen = nop
@@ -2395,6 +2397,10 @@ func isMethod(n *node) bool {
return len(n.child[0].child) > 0 // receiver defined
}
func isFuncField(n *node) bool {
return isField(n) && isFunc(n.typ)
}
func isMapEntry(n *node) bool {
return n.action == aGetIndex && isMap(n.child[0].typ)
}

View File

@@ -1334,16 +1334,18 @@ func callBin(n *node) {
tnext := getExec(n.tnext)
fnext := getExec(n.fnext)
child := n.child[1:]
value := genValue(n.child[0])
c0 := n.child[0]
value := genValue(c0)
var values []func(*frame) reflect.Value
funcType := n.child[0].typ.rtype
funcType := c0.typ.rtype
wt := wrappedType(c0)
variadic := -1
if funcType.IsVariadic() {
variadic = funcType.NumIn() - 1
}
// A method signature obtained from reflect.Type includes receiver as 1st arg, except for interface types.
rcvrOffset := 0
if recv := n.child[0].recv; recv != nil && !isInterface(recv.node.typ) {
if recv := c0.recv; recv != nil && !isInterface(recv.node.typ) {
if variadic > 0 || funcType.NumIn() > len(child) {
rcvrOffset = 1
}
@@ -1392,6 +1394,11 @@ func callBin(n *node) {
}
}
if wt != nil && wt.arg[i].cat == interfaceT {
values = append(values, genValueInterface(c))
break
}
switch c.typ.cat {
case funcT:
values = append(values, genFunctionWrapper(c))

View File

@@ -1631,6 +1631,13 @@ func defRecvType(n *node) *itype {
return nil
}
func wrappedType(n *node) *itype {
if n.typ.cat != valueT {
return nil
}
return n.typ.val
}
func isShiftNode(n *node) bool {
switch n.action {
case aShl, aShr, aShlAssign, aShrAssign:

View File

@@ -45,21 +45,35 @@ func genValueBinMethodOnInterface(n *node, defaultGen func(*frame) reflect.Value
n.child[0].child == nil || n.child[0].child[0] == nil {
return defaultGen
}
if n.child[0].child[1] == nil || n.child[0].child[1].ident == "" {
c0 := n.child[0]
if c0.child[1] == nil || c0.child[1].ident == "" {
return defaultGen
}
value0 := genValue(n.child[0].child[0])
value0 := genValue(c0.child[0])
return func(f *frame) reflect.Value {
val, ok := value0(f).Interface().(valueInterface)
if !ok {
v := value0(f)
var nod *node
for v.IsValid() {
// Traverse interface indirections to find out concrete type.
vi, ok := v.Interface().(valueInterface)
if !ok {
break
}
v = vi.value
nod = vi.node
}
if nod == nil {
return defaultGen(f)
}
typ := val.node.typ
typ := nod.typ
if typ.node != nil || typ.cat != valueT {
return defaultGen(f)
}
meth, _ := typ.rtype.MethodByName(n.child[0].child[1].ident)
meth, _ := typ.rtype.MethodByName(c0.child[1].ident)
return meth.Func
}
}