diff --git a/_test/struct47.go b/_test/struct47.go new file mode 100644 index 00000000..469a4945 --- /dev/null +++ b/_test/struct47.go @@ -0,0 +1,26 @@ +package main + +import "fmt" + +type A struct { + B string + C D +} + +func (a *A) Test() string { + return "test" +} + +type D struct { + E *A +} + +func main() { + a := &A{B: "b"} + d := D{E: a} + a.C = d + fmt.Println(a.C.E.Test()) +} + +// Output: +// test diff --git a/_test/struct48.go b/_test/struct48.go new file mode 100644 index 00000000..531ab350 --- /dev/null +++ b/_test/struct48.go @@ -0,0 +1,37 @@ +package main + +type List struct { + Next *List + Num int +} + +func add(l *List, n int) *List { + if l == nil { + return &List{Num: n} + } + l.Next = add(l.Next, n) + return l +} + +func pr(l *List) { + if l == nil { + println("") + return + } + print(l.Num) + pr(l.Next) +} + +func main() { + a := add(nil, 0) + pr(a) + a = add(a, 1) + pr(a) + a = add(a, 2) + pr(a) +} + +// Output: +// 0 +// 01 +// 012 diff --git a/interp/cfg.go b/interp/cfg.go index 1c693c37..7b863132 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -495,7 +495,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) { // Propagate type // TODO: Check that existing destination type matches source type switch { - case n.action == aAssign && src.action == aCall && dest.typ.cat != interfaceT: + case n.action == aAssign && src.action == aCall && dest.typ.cat != interfaceT && !isRecursiveField(dest): // Call action may perform the assignment directly. n.gen = nop src.level = level @@ -1996,6 +1996,10 @@ func isField(n *node) bool { return n.kind == selectorExpr && len(n.child) > 0 && n.child[0].typ != nil && isStruct(n.child[0].typ) } +func isRecursiveField(n *node) bool { + return isField(n) && (n.typ.recursive || n.typ.cat == ptrT && n.typ.val.recursive) +} + // isNewDefine returns true if node refers to a new definition func isNewDefine(n *node, sc *scope) bool { if n.ident == "_" { diff --git a/interp/run.go b/interp/run.go index e28ee35d..f3e83215 100644 --- a/interp/run.go +++ b/interp/run.go @@ -617,7 +617,11 @@ func call(n *node) { var values []func(*frame) reflect.Value if n.child[0].recv != nil { // Compute method receiver value. - values = append(values, genValueRecv(n.child[0])) + if isRecursiveStruct(n.child[0].recv.node.typ, n.child[0].recv.node.typ.rtype) { + values = append(values, genValueRecvInterfacePtr(n.child[0])) + } else { + values = append(values, genValueRecv(n.child[0])) + } method = true } else if n.child[0].action == aMethod { // Add a place holder for interface method receiver. @@ -656,9 +660,12 @@ func call(n *node) { } convertLiteralValue(c, argType) } - if len(n.child[0].typ.arg) > i && n.child[0].typ.arg[i].cat == interfaceT { + switch { + case len(n.child[0].typ.arg) > i && n.child[0].typ.arg[i].cat == interfaceT: values = append(values, genValueInterface(c)) - } else { + case isRecursiveStruct(c.typ, c.typ.rtype): + values = append(values, genValueDerefInterfacePtr(c)) + default: values = append(values, genValue(c)) } } @@ -823,7 +830,10 @@ func call(n *node) { vararg.Set(reflect.Append(vararg, v(f))) } default: - dest[i].Set(v(f)) + val := v(f) + if !val.IsZero() { + dest[i].Set(val) + } } } } diff --git a/interp/value.go b/interp/value.go index e81ca06c..34590a30 100644 --- a/interp/value.go +++ b/interp/value.go @@ -54,6 +54,25 @@ func genValueRecv(n *node) func(*frame) reflect.Value { } } +func genValueRecvInterfacePtr(n *node) func(*frame) reflect.Value { + v := genValue(n.recv.node) + fi := n.recv.index + + return func(f *frame) reflect.Value { + r := v(f) + r = r.Elem().Elem() + + if len(fi) == 0 { + return r + } + + if r.Kind() == reflect.Ptr { + r = r.Elem() + } + return r.FieldByIndex(fi) + } +} + func genValueAsFunctionWrapper(n *node) func(*frame) reflect.Value { value := genValue(n) typ := n.typ.TypeOf() @@ -176,6 +195,18 @@ func genValueInterface(n *node) func(*frame) reflect.Value { } } +func genValueDerefInterfacePtr(n *node) func(*frame) reflect.Value { + value := genValue(n) + + return func(f *frame) reflect.Value { + v := value(f) + if v.IsZero() { + return v + } + return v.Elem().Elem() + } +} + func zeroInterfaceValue() reflect.Value { n := &node{kind: basicLit, typ: &itype{cat: nilT, untyped: true}} v := reflect.New(interf).Elem()