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
|
package coll
import (
"fmt"
"math"
"reflect"
)
// much of the code here is taken from the Go source code, in particular from
// text/template/exec.go and text/template/funcs.go
// Index returns the result of indexing the given map, slice, or array by the
// given index arguments. This is similar to the `index` template function, but
// will return an error if the key is not found. Note that the argument order is
// different from the template function definition found in `funcs/coll.go` to
// allow for variadic indexes.
func Index(v any, keys ...any) (any, error) {
item := reflect.ValueOf(v)
item = indirectInterface(item)
if !item.IsValid() {
return nil, fmt.Errorf("index of untyped nil")
}
indexes := make([]reflect.Value, len(keys))
for i, k := range keys {
indexes[i] = reflect.ValueOf(k)
}
for _, index := range indexes {
index = indirectInterface(index)
var isNil bool
if item, isNil = indirect(item); isNil {
return nil, fmt.Errorf("index of nil pointer")
}
switch item.Kind() {
case reflect.Array, reflect.Slice, reflect.String:
x, err := indexArg(index, item.Len())
if err != nil {
return nil, err
}
item = item.Index(x)
case reflect.Map:
x, err := prepareArg(index, item.Type().Key())
if err != nil {
return nil, err
}
if v := item.MapIndex(x); v.IsValid() {
item = v
} else {
// the map doesn't contain the key, so return an error
return nil, fmt.Errorf("map has no key %v", x.Interface())
}
case reflect.Invalid:
// the loop holds invariant: item.IsValid()
panic("unreachable")
default:
return nil, fmt.Errorf("can't index item of type %s", item.Type())
}
}
return item.Interface(), nil
}
// indexArg checks if a reflect.Value can be used as an index, and converts it to int if possible.
//
//nolint:revive
func indexArg(index reflect.Value, cap int) (int, error) {
var x int64
switch index.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
x = index.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
val := index.Uint()
if val > math.MaxInt64 {
return -1, fmt.Errorf("cannot index slice/array with %d (too large)", val)
}
x = int64(val)
case reflect.Invalid:
return 0, fmt.Errorf("cannot index slice/array with nil")
default:
return 0, fmt.Errorf("cannot index slice/array with type %s", index.Type())
}
// note - this has been modified from the original to check for x == cap as
// well. IMO the original (> only) is a bug.
if x < 0 || int(x) < 0 || int(x) >= cap {
return 0, fmt.Errorf("index out of range: %d", x)
}
return int(x), nil
}
// prepareArg checks if value can be used as an argument of type argType, and
// converts an invalid value to appropriate zero if possible.
func prepareArg(value reflect.Value, argType reflect.Type) (reflect.Value, error) {
if !value.IsValid() {
if !canBeNil(argType) {
return reflect.Value{}, fmt.Errorf("value is nil; should be of type %s", argType)
}
value = reflect.Zero(argType)
}
if value.Type().AssignableTo(argType) {
return value, nil
}
if intLike(value.Kind()) && intLike(argType.Kind()) && value.Type().ConvertibleTo(argType) {
value = value.Convert(argType)
return value, nil
}
return reflect.Value{}, fmt.Errorf("value has type %s; should be %s", value.Type(), argType)
}
var reflectValueType = reflect.TypeOf((*reflect.Value)(nil)).Elem()
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
func canBeNil(typ reflect.Type) bool {
switch typ.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
return true
case reflect.Struct:
return typ == reflectValueType
}
return false
}
func intLike(typ reflect.Kind) bool {
switch typ {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return true
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return true
}
return false
}
// indirect returns the item at the end of indirection, and a bool to indicate
// if it's nil. If the returned bool is true, the returned value's kind will be
// either a pointer or interface.
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
for ; v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface; v = v.Elem() {
if v.IsNil() {
return v, true
}
}
return v, false
}
// indirectInterface returns the concrete value in an interface value,
// or else the zero reflect.Value.
// That is, if v represents the interface value x, the result is the same as reflect.ValueOf(x):
// the fact that x was an interface value is forgotten.
func indirectInterface(v reflect.Value) reflect.Value {
if v.Kind() != reflect.Interface {
return v
}
if v.IsNil() {
return reflect.Value{}
}
return v.Elem()
}
|