Skip to content

Commit

Permalink
interp: fix closure in a struct field
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mvertes authored Mar 11, 2021
1 parent fdfcb9c commit 7d8fdbc
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 10 deletions.
26 changes: 26 additions & 0 deletions _test/method36.go
Original file line number Diff line number Diff line change
@@ -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
8 changes: 7 additions & 1 deletion interp/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
13 changes: 10 additions & 3 deletions interp/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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))
Expand Down
7 changes: 7 additions & 0 deletions interp/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
26 changes: 20 additions & 6 deletions interp/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down

0 comments on commit 7d8fdbc

Please sign in to comment.