Skip to content

Commit

Permalink
interp: fix use of function as field of a recursive struct.
Browse files Browse the repository at this point in the history
The logic of patching reflect struct representation has been fixed in presence of function fields and to better handle indirect recursivity (struct recursive through nested struct).

Fixes #1519.
  • Loading branch information
mvertes authored Mar 21, 2023
1 parent c473dce commit c4a297c
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 19 deletions.
16 changes: 16 additions & 0 deletions _test/recurse1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

type F func(a *A)

type A struct {
Name string
F
}

func main() {
a := &A{"Test", func(a *A) { println("in f", a.Name) }}
a.F(a)
}

// Output:
// in f Test
27 changes: 27 additions & 0 deletions _test/recurse2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

type F func(a *A)

type A struct {
B string
D
f F
}

type D struct {
*A
E *A
}

func f1(a *A) { println("in f1", a.B) }

func main() {
a := &A{B: "b", f: f1}
a.D = D{E: a}
println(a.D.E.B)
a.f(a)
}

// Output:
// b
// in f1 b
25 changes: 25 additions & 0 deletions _test/recurse3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main

type F func(a *A)

type A struct {
B string
D
}

type D struct {
*A
E *A
f F
}

func f1(a *A) { println("in f1", a.B) }

func main() {
a := &A{B: "b"}
a.D = D{f: f1}
a.f(a)
}

// Output:
// in f1 b
43 changes: 24 additions & 19 deletions interp/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -1958,19 +1958,14 @@ var (
constVal = reflect.TypeOf((*constant.Value)(nil)).Elem()
)

type fieldRebuild struct {
typ *itype
idx int
}

type refTypeContext struct {
defined map[string]*itype

// refs keeps track of all the places (in the same type recursion) where the
// type name (as key) is used as a field of another (or possibly the same) struct
// type. Each of these fields will then live as an unsafe2.dummy type until the
// whole recursion is fully resolved, and the type is fixed.
refs map[string][]fieldRebuild
refs map[string][]*itype

// When we detect for the first time that we are in a recursive type (thanks to
// defined), we keep track of the first occurrence of the type where the recursion
Expand Down Expand Up @@ -2043,7 +2038,7 @@ func (t *itype) refType(ctx *refTypeContext) reflect.Type {
if ctx == nil {
ctx = &refTypeContext{
defined: map[string]*itype{},
refs: map[string][]fieldRebuild{},
refs: map[string][]*itype{},
}
}
if t.incomplete || t.cat == nilT {
Expand All @@ -2068,14 +2063,14 @@ func (t *itype) refType(ctx *refTypeContext) reflect.Type {

// The recursion has not been fully resolved yet.
// To indicate that a rebuild is needed on the englobing struct,
// return a dummy field type and create an entry with an empty fieldRebuild.
// return a dummy field type and create an empty entry.
flds := ctx.refs[name]
ctx.rect = dt

// We know we are used as a field by someone, but we don't know by who
// at this point in the code, so we just mark it as an empty fieldRebuild for now.
// We'll complete the fieldRebuild in the caller.
ctx.refs[name] = append(flds, fieldRebuild{})
// at this point in the code, so we just mark it as an empty *itype for now.
// We'll complete the *itype in the caller.
ctx.refs[name] = append(flds, (*itype)(nil))
return unsafe2.DummyType
}
if isGeneric(t) {
Expand Down Expand Up @@ -2132,7 +2127,7 @@ func (t *itype) refType(ctx *refTypeContext) reflect.Type {
}
ctx.slevel++
var fields []reflect.StructField
for i, f := range t.field {
for _, f := range t.field {
field := reflect.StructField{
Name: exportName(f.name), Type: f.typ.refType(ctx),
Tag: reflect.StructTag(f.tag), Anonymous: f.embed,
Expand All @@ -2141,14 +2136,18 @@ func (t *itype) refType(ctx *refTypeContext) reflect.Type {
// Find any nil type refs that indicates a rebuild is needed on this field.
for _, flds := range ctx.refs {
for j, fld := range flds {
if fld.typ == nil {
flds[j] = fieldRebuild{typ: t, idx: i}
if fld == nil {
flds[j] = t
}
}
}
}
ctx.slevel--
fieldFix := []int{} // Slice of field indices to fix for recursivity.
type fixStructField struct {
name string
index int
}
fieldFix := []fixStructField{} // Slice of field indices to fix for recursivity.
t.rtype = reflect.StructOf(fields)
if ctx.isComplete() {
for _, s := range ctx.defined {
Expand All @@ -2157,8 +2156,12 @@ func (t *itype) refType(ctx *refTypeContext) reflect.Type {
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)
fieldFix = append(fieldFix, fixStructField{s.name, i})
}
continue
}
if f.Type.Kind() == reflect.Func && strings.Contains(f.Type.String(), "unsafe2.dummy") {
fieldFix = append(fieldFix, fixStructField{s.name, i})
}
}
}
Expand All @@ -2173,9 +2176,11 @@ func (t *itype) refType(ctx *refTypeContext) reflect.Type {
// 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] {
for _, index := range fieldFix {
ftyp := f.typ.field[index].typ.refType(&refTypeContext{defined: ctx.defined, rebuilding: true})
unsafe2.SetFieldType(f.typ.rtype, index, ftyp)
for _, ff := range fieldFix {
if ff.name == f.name {
ftyp := f.field[ff.index].typ.refType(&refTypeContext{defined: ctx.defined, rebuilding: true})
unsafe2.SetFieldType(f.rtype, ff.index, ftyp)
}
}
}
default:
Expand Down

0 comments on commit c4a297c

Please sign in to comment.