Skip to content

Commit

Permalink
fix: handle use of functions in struct fields
Browse files Browse the repository at this point in the history
  • Loading branch information
traefiker authored Mar 5, 2020
1 parent cfb7344 commit 2edd18a
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 17 deletions.
39 changes: 39 additions & 0 deletions _test/struct32.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package main

type T0 struct {
name string
}

type lookupFunc func(s string) T0

type T1 struct {
name string
info lookupFunc
}

func (t T0) F1() bool { println("in F1"); return true }

type T2 struct {
t1 T1
}

func (t2 *T2) f() {
info := t2.t1.info("foo")
println(info.F1())
}

var t0 = T0{"t0"}

func main() {
t := &T2{T1{
"bar", func(s string) T0 { return t0 },
}}

println("hello")
println(t.t1.info("foo").F1())
}

// Output:
// hello
// in F1
// true
34 changes: 34 additions & 0 deletions _test/struct33.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

type T0 struct {
name string
}

type lookupFunc func(s string) T0

type T1 struct {
name string
info lookupFunc
}

func (t T0) F1() bool { println("in F1"); return true }

var t0 = T0{"t0"}

func look(s string) T0 { println("in look"); return t0 }

var table = []*T1{{
name: "bar",
info: look,
},
}

func main() {
info := table[0].info
println(info("foo").F1())
}

// Output:
// in look
// in F1
// true
26 changes: 20 additions & 6 deletions interp/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -777,10 +777,20 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case isBinCall(n):
n.gen = callBin
if typ := n.child[0].typ.rtype; typ.NumOut() > 0 {
n.typ = &itype{cat: valueT, rtype: typ.Out(0)}
n.findex = sc.add(n.typ)
for i := 1; i < typ.NumOut(); i++ {
sc.add(&itype{cat: valueT, rtype: typ.Out(i)})
if funcType := n.child[0].typ.val; funcType != nil {
// Use the original unwrapped function type, to allow future field and
// methods resolutions, otherwise impossible on the opaque bin type.
n.typ = funcType.ret[0]
n.findex = sc.add(n.typ)
for i := 1; i < len(funcType.ret); i++ {
sc.add(funcType.ret[i])
}
} else {
n.typ = &itype{cat: valueT, rtype: typ.Out(0)}
n.findex = sc.add(n.typ)
for i := 1; i < typ.NumOut(); i++ {
sc.add(&itype{cat: valueT, rtype: typ.Out(i)})
}
}
}
default:
Expand Down Expand Up @@ -1225,15 +1235,15 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
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}
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}
n.typ = &itype{cat: valueT, rtype: rtype, val: n.typ}
}
}
} else if s, lind, ok := n.typ.lookupBinField(n.child[1].ident); ok {
Expand Down Expand Up @@ -1741,6 +1751,10 @@ func isKey(n *node) bool {
(n.anc.kind == fieldExpr && len(n.anc.child) > 1 && n.anc.child[0] == n)
}

func isField(n *node) bool {
return n.kind == selectorExpr && len(n.child) > 0 && n.child[0].typ != nil && isStruct(n.child[0].typ)
}

// isNewDefine returns true if node refers to a new definition
func isNewDefine(n *node, sc *scope) bool {
if n.ident == "_" {
Expand Down
12 changes: 10 additions & 2 deletions interp/interp_eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,11 @@ func eval(t *testing.T, i *interp.Interpreter, src string) reflect.Value {
t.Helper()
res, err := i.Eval(src)
if err != nil {
t.Fatal(err)
t.Logf("Error: %v", err)
if e, ok := err.(interp.Panic); ok {
t.Logf(string(e.Stack))
}
t.FailNow()
}
return res
}
Expand All @@ -541,7 +545,11 @@ func assertEval(t *testing.T, i *interp.Interpreter, src, expectedError, expecte
}

if err != nil {
t.Fatalf("got an error %v", err)
t.Logf("got an error: %v", err)
if e, ok := err.(interp.Panic); ok {
t.Logf(string(e.Stack))
}
t.FailNow()
}

if fmt.Sprintf("%v", res) != expectedRes {
Expand Down
17 changes: 12 additions & 5 deletions interp/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,9 @@ func assign(n *node) {
svalue[i] = genValueInterface(src)
case (dest.typ.cat == valueT || dest.typ.cat == errorT) && dest.typ.rtype.Kind() == reflect.Interface:
svalue[i] = genInterfaceWrapper(src, dest.typ.rtype)
case dest.typ.cat == valueT && src.typ.cat == funcT:
case src.typ.cat == funcT && dest.typ.cat == valueT:
svalue[i] = genFunctionWrapper(src)
case src.typ.cat == funcT && isField(dest):
svalue[i] = genFunctionWrapper(src)
case dest.typ.cat == funcT && src.typ.cat == valueT:
svalue[i] = genValueNode(src)
Expand Down Expand Up @@ -708,19 +710,24 @@ func call(n *node) {
}

n.exec = func(f *frame) bltn {
def := value(f).Interface().(*node)
var def *node
var ok bool
bf := value(f)
if def, ok = bf.Interface().(*node); ok {
bf = def.rval
}

// Call bin func if defined
if def.rval.IsValid() {
if bf.IsValid() {
in := make([]reflect.Value, len(values))
for i, v := range values {
in[i] = v(f)
}
if goroutine {
go def.rval.Call(in)
go bf.Call(in)
return tnext
}
out := def.rval.Call(in)
out := bf.Call(in)
for i, v := range rvalues {
if v != nil {
v(f).Set(out[i])
Expand Down
15 changes: 11 additions & 4 deletions interp/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,8 @@ func (t *itype) finalize() (*itype, error) {
sym, _, found := t.scope.lookup(t.name)
if found && !sym.typ.incomplete {
sym.typ.method = append(sym.typ.method, t.method...)
t.method = sym.typ.method
t.incomplete = false
return sym.typ, nil
}
m := t.method
Expand Down Expand Up @@ -1017,12 +1019,17 @@ func isInterface(t *itype) bool {
func isStruct(t *itype) bool {
// Test first for a struct category, because a recursive interpreter struct may be
// represented by an interface{} at reflect level.
if t.cat == structT {
switch t.cat {
case structT:
return true
case aliasT, ptrT:
return isStruct(t.val)
case valueT:
k := t.rtype.Kind()
return k == reflect.Struct || (k == reflect.Ptr && t.rtype.Elem().Kind() == reflect.Struct)
default:
return false
}
rt := t.TypeOf()
k := rt.Kind()
return k == reflect.Struct || (k == reflect.Ptr && rt.Elem().Kind() == reflect.Struct)
}

func isBool(t *itype) bool { return t.TypeOf().Kind() == reflect.Bool }
Expand Down

0 comments on commit 2edd18a

Please sign in to comment.