From f07f25f1ba3c3b6815b9fa5d4dfeb392fdafafd1 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Thu, 7 Apr 2022 14:53:23 +0200 Subject: [PATCH] interp: handle struct with multiple recursive fields (#1372) * interp: handle struct with multiple recursive fields In case of a recursive struct with several recursive fields of different type, only the first one was properly fixed when constructing the corresponding reflect type. We now memorize and process all fields at the same depth level. Fixes #1371. * Update interp/type.go Co-authored-by: mpl * fix lint * fix comment Co-authored-by: mpl --- _test/issue-1371.go | 18 ++++++++++++++++++ interp/type.go | 19 +++++++++++++------ 2 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 _test/issue-1371.go diff --git a/_test/issue-1371.go b/_test/issue-1371.go new file mode 100644 index 00000000..c3c85407 --- /dev/null +++ b/_test/issue-1371.go @@ -0,0 +1,18 @@ +package main + +import "fmt" + +type node struct { + parent *node + child []*node + key string +} + +func main() { + root := &node{key: "root"} + root.child = nil + fmt.Println("root:", root) +} + +// Output: +// root: &{ [] root} diff --git a/interp/type.go b/interp/type.go index a3ccda01..f9812701 100644 --- a/interp/type.go +++ b/interp/type.go @@ -1827,6 +1827,7 @@ func (t *itype) refType(ctx *refTypeContext) reflect.Type { } } } + fieldFix := []int{} // Slice of field indices to fix for recursivity. t.rtype = reflect.StructOf(fields) if ctx.isComplete() { for _, s := range ctx.defined { @@ -1834,6 +1835,9 @@ func (t *itype) refType(ctx *refTypeContext) reflect.Type { f := s.rtype.Field(i) if strings.HasSuffix(f.Type.String(), "unsafe2.dummy") { unsafe2.SetFieldType(s.rtype, i, ctx.rect.fixDummy(s.rtype.Field(i).Type)) + if name == s.path+"/"+s.name { + fieldFix = append(fieldFix, i) + } } } } @@ -1842,13 +1846,16 @@ func (t *itype) refType(ctx *refTypeContext) reflect.Type { // The rtype has now been built, we can go back and rebuild // all the recursive types that relied on this type. // However, as we are keyed by type name, if two or more (recursive) fields at - // the same depth level are of the same type, they "mask" each other, and only one - // of them is in ctx.refs, which means this pass below does not fully do the job. - // Which is why we have the pass above that is done one last time, for all fields, - // one the recursion has been fully resolved. + // the same depth level are of the same type, or a "variation" of the same type + // (slice of, map of, etc), they "mask" each other, and only one + // of them is in ctx.refs. That is why the code around here is a bit convoluted, + // and we need both the loop above, around all the struct fields, and the loop + // below, around the ctx.refs. for _, f := range ctx.refs[name] { - ftyp := f.typ.field[f.idx].typ.refType(&refTypeContext{defined: ctx.defined, rebuilding: true}) - unsafe2.SetFieldType(f.typ.rtype, f.idx, ftyp) + for _, index := range fieldFix { + ftyp := f.typ.field[index].typ.refType(&refTypeContext{defined: ctx.defined, rebuilding: true}) + unsafe2.SetFieldType(f.typ.rtype, index, ftyp) + } } default: if z, _ := t.zero(); z.IsValid() {