Skip to content

Commit 009bfea

Browse files
committed
reflect: add VisibleFields function
When writing code that reflects over a struct type, it's a common requirement to know the full set of struct fields, including fields available due to embedding of anonymous members while excluding fields that are erased because they're at the same level as another field with the same name. The logic to do this is not that complex, but it's a little subtle and easy to get wrong. This CL adds a new `VisibleFields` function to the reflect package that returns the full set of effective fields that apply in a given struct type. Performance isn't a prime consideration, as it's common to cache results by type. Fixes golang#42782 Change-Id: I7f1af76cecff9b8a2490f17eec058826e396f660 Reviewed-on: https://go-review.googlesource.com/c/go/+/281233 Reviewed-by: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Go Bot <[email protected]> Trust: Daniel Martí <[email protected]>
1 parent f901ea7 commit 009bfea

2 files changed

Lines changed: 427 additions & 0 deletions

File tree

src/reflect/visiblefields.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package reflect
2+
3+
// VisibleFields returns all the visible fields in t, which must be a
4+
// struct type. A field is defined as visible if it's accessible
5+
// directly with a FieldByName call. The returned fields include fields
6+
// inside anonymous struct members and unexported fields. They follow
7+
// the same order found in the struct, with anonymous fields followed
8+
// immediately by their promoted fields.
9+
//
10+
// For each element e of the returned slice, the corresponding field
11+
// can be retrieved from a value v of type t by calling v.FieldByIndex(e.Index).
12+
func VisibleFields(t Type) []StructField {
13+
if t == nil {
14+
panic("reflect: VisibleFields(nil)")
15+
}
16+
if t.Kind() != Struct {
17+
panic("reflect.VisibleFields of non-struct type")
18+
}
19+
w := &visibleFieldsWalker{
20+
byName: make(map[string]int),
21+
visiting: make(map[Type]bool),
22+
fields: make([]StructField, 0, t.NumField()),
23+
index: make([]int, 0, 2),
24+
}
25+
w.walk(t)
26+
// Remove all the fields that have been hidden.
27+
// Use an in-place removal that avoids copying in
28+
// the common case that there are no hidden fields.
29+
j := 0
30+
for i := range w.fields {
31+
f := &w.fields[i]
32+
if f.Name == "" {
33+
continue
34+
}
35+
if i != j {
36+
// A field has been removed. We need to shuffle
37+
// all the subsequent elements up.
38+
w.fields[j] = *f
39+
}
40+
j++
41+
}
42+
return w.fields[:j]
43+
}
44+
45+
type visibleFieldsWalker struct {
46+
byName map[string]int
47+
visiting map[Type]bool
48+
fields []StructField
49+
index []int
50+
}
51+
52+
// walk walks all the fields in the struct type t, visiting
53+
// fields in index preorder and appending them to w.fields
54+
// (this maintains the required ordering).
55+
// Fields that have been overridden have their
56+
// Name field cleared.
57+
func (w *visibleFieldsWalker) walk(t Type) {
58+
if w.visiting[t] {
59+
return
60+
}
61+
w.visiting[t] = true
62+
for i := 0; i < t.NumField(); i++ {
63+
f := t.Field(i)
64+
w.index = append(w.index, i)
65+
add := true
66+
if oldIndex, ok := w.byName[f.Name]; ok {
67+
old := &w.fields[oldIndex]
68+
if len(w.index) == len(old.Index) {
69+
// Fields with the same name at the same depth
70+
// cancel one another out. Set the field name
71+
// to empty to signify that has happened, and
72+
// there's no need to add this field.
73+
old.Name = ""
74+
add = false
75+
} else if len(w.index) < len(old.Index) {
76+
// The old field loses because it's deeper than the new one.
77+
old.Name = ""
78+
} else {
79+
// The old field wins because it's shallower than the new one.
80+
add = false
81+
}
82+
}
83+
if add {
84+
// Copy the index so that it's not overwritten
85+
// by the other appends.
86+
f.Index = append([]int(nil), w.index...)
87+
w.byName[f.Name] = len(w.fields)
88+
w.fields = append(w.fields, f)
89+
}
90+
if f.Anonymous {
91+
if f.Type.Kind() == Ptr {
92+
f.Type = f.Type.Elem()
93+
}
94+
if f.Type.Kind() == Struct {
95+
w.walk(f.Type)
96+
}
97+
}
98+
w.index = w.index[:len(w.index)-1]
99+
}
100+
delete(w.visiting, t)
101+
}

0 commit comments

Comments
 (0)