-
-
Notifications
You must be signed in to change notification settings - Fork 494
Expand file tree
/
Copy pathutils.go
More file actions
238 lines (214 loc) · 5.47 KB
/
utils.go
File metadata and controls
238 lines (214 loc) · 5.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
package nature
import (
"reflect"
"strings"
"github.com/expr-lang/expr/internal/deref"
)
func fieldName(name string, tag reflect.StructTag, tagKey string) (string, bool) {
tagVal := tag.Get(tagKey)
if i := strings.IndexByte(tagVal, ','); i >= 0 {
tagVal = tagVal[:i]
}
switch tagVal {
case "-":
return "", false
case "":
return name, true
default:
return tagVal, true
}
}
type structData struct {
rType reflect.Type
fields map[string]*structField
numField, ownIdx, anonIdx int
curParent, curChild *structData
curChildIndex []int
}
type structField struct {
Nature
Index []int
}
func (s *structData) finished() bool {
return s.ownIdx >= s.numField && // no own fields left to visit
s.anonIdx >= s.numField && // no embedded fields to visit
s.curChild == nil // no child in process of visiting
}
func (s *structData) structField(c *Cache, parentEmbed *structData, name string) *structField {
if s.fields == nil {
if s.numField > 0 {
s.fields = make(map[string]*structField, s.numField)
}
} else if f := s.fields[name]; f != nil {
return f
}
if s.finished() {
return nil
}
// Lookup own fields first.
for ; s.ownIdx < s.numField; s.ownIdx++ {
field := s.rType.Field(s.ownIdx)
if field.Anonymous && s.anonIdx < 0 {
// start iterating anon fields on the first instead of zero
s.anonIdx = s.ownIdx
}
if !field.IsExported() {
continue
}
fName, ok := fieldName(field.Name, field.Tag, c.tag)
if !ok || fName == "" {
// name can still be empty for a type created at runtime with
// reflect
continue
}
nt := c.FromType(field.Type)
sf := &structField{
Nature: nt,
Index: field.Index,
}
s.fields[fName] = sf
if parentEmbed != nil {
parentEmbed.trySet(fName, sf)
}
if fName == name {
return sf
}
}
if s.curChild != nil {
sf := s.findInEmbedded(c, parentEmbed, s.curChild, s.curChildIndex, name)
if sf != nil {
return sf
}
}
// Lookup embedded fields through anon own fields
for ; s.anonIdx >= 0 && s.anonIdx < s.numField; s.anonIdx++ {
field := s.rType.Field(s.anonIdx)
// we do enter embedded non-exported types because they could contain
// exported fields
if !field.Anonymous {
continue
}
t, k, _ := deref.TypeKind(field.Type, field.Type.Kind())
if k != reflect.Struct {
continue
}
childEmbed := c.getStruct(t).structData
sf := s.findInEmbedded(c, parentEmbed, childEmbed, field.Index, name)
if sf != nil {
return sf
}
}
return nil
}
func (s *structData) findInEmbedded(
c *Cache,
parentEmbed, childEmbed *structData,
childIndex []int,
name string,
) *structField {
// Set current parent/child data. This allows trySet to handle child fields
// and add them to our struct and to the parent as well if needed
s.curParent = parentEmbed
s.curChild = childEmbed
s.curChildIndex = childIndex
defer func() {
// Ensure to cleanup references
s.curParent = nil
if childEmbed.finished() {
// If the child can still have more fields to explore then keep it
// referened to look it up again if we need to
s.curChild = nil
s.curChildIndex = nil
}
}()
// See if the child has already cached its fields. This is still important
// to check even if it's the s.unfinishedEmbedded because it may have
// explored new fields since the last time we visited it
for name, sf := range childEmbed.fields {
s.trySet(name, sf)
}
// Recheck if we have what we needed from the above sync
if sf := s.fields[name]; sf != nil {
return sf
}
// Try finding in the child again in case it hasn't finished
if !childEmbed.finished() {
if childEmbed.structField(c, s, name) != nil {
return s.fields[name]
}
}
return nil
}
func (s *structData) trySet(name string, sf *structField) {
if _, ok := s.fields[name]; ok {
return
}
sf = &structField{
Nature: sf.Nature,
Index: append(s.curChildIndex, sf.Index...),
}
s.fields[name] = sf
if s.curParent != nil {
s.curParent.trySet(name, sf)
}
}
func StructFields(c *Cache, t reflect.Type) map[string]Nature {
table := make(map[string]Nature)
if t == nil {
return table
}
t, k, _ := deref.TypeKind(t, t.Kind())
if k == reflect.Struct {
// lookup for a field with an empty name, which will cause to never find a
// match, meaning everything will have been cached.
sd := c.getStruct(t).structData
sd.structField(c, nil, "")
for name, sf := range sd.fields {
table[name] = sf.Nature
}
}
return table
}
type methodset struct {
rType reflect.Type
kind reflect.Kind
methods map[string]*method
numMethod, idx int
}
type method struct {
reflect.Method
nature Nature
}
func (s *methodset) method(c *Cache, name string) *method {
if s.methods == nil {
s.methods = make(map[string]*method, s.numMethod)
} else if m := s.methods[name]; m != nil {
return m
}
for ; s.idx < s.numMethod; s.idx++ {
rm := s.rType.Method(s.idx)
if !rm.IsExported() {
continue
}
nt := c.FromType(rm.Type)
if s.rType.Kind() != reflect.Interface {
nt.Method = true
nt.MethodIndex = rm.Index
// In case of interface type method will not have a receiver,
// and to prevent checker decreasing numbers of in arguments
// return method type as not method (second argument is false).
// Also, we can not use m.Index here, because it will be
// different indexes for different types which implement
// the same interface.
}
m := &method{
Method: rm,
nature: nt,
}
s.methods[rm.Name] = m
if rm.Name == name {
return m
}
}
return nil
}