Skip to content

Commit

Permalink
interp: handle struct with multiple recursive fields (#1372)
Browse files Browse the repository at this point in the history
* 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 <mathieu.lonjaret@gmail.com>

* fix lint

* fix comment

Co-authored-by: mpl <mathieu.lonjaret@gmail.com>
  • Loading branch information
mvertes and mpl committed Apr 7, 2022
1 parent c93b836 commit f07f25f
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 6 deletions.
18 changes: 18 additions & 0 deletions _test/issue-1371.go
Original file line number Diff line number Diff line change
@@ -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: &{<nil> [] root}
19 changes: 13 additions & 6 deletions interp/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -1827,13 +1827,17 @@ 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 {
for i := 0; i < s.rtype.NumField(); i++ {
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)
}
}
}
}
Expand All @@ -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() {
Expand Down

0 comments on commit f07f25f

Please sign in to comment.