reflect_helpers.go (4988B)
1 // Copyright 2017 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package collections 15 16 import ( 17 "fmt" 18 "reflect" 19 20 "errors" 21 22 "github.com/mitchellh/hashstructure" 23 ) 24 25 var ( 26 zero reflect.Value 27 errorType = reflect.TypeOf((*error)(nil)).Elem() 28 ) 29 30 func numberToFloat(v reflect.Value) (float64, error) { 31 switch kind := v.Kind(); { 32 case isFloat(kind): 33 return v.Float(), nil 34 case isInt(kind): 35 return float64(v.Int()), nil 36 case isUint(kind): 37 return float64(v.Uint()), nil 38 case kind == reflect.Interface: 39 return numberToFloat(v.Elem()) 40 default: 41 return 0, fmt.Errorf("invalid kind %s in numberToFloat", kind) 42 } 43 } 44 45 // normalizes different numeric types if isNumber 46 // or get the hash values if not Comparable (such as map or struct) 47 // to make them comparable 48 func normalize(v reflect.Value) any { 49 k := v.Kind() 50 51 switch { 52 case !v.Type().Comparable(): 53 h, err := hashstructure.Hash(v.Interface(), nil) 54 if err != nil { 55 panic(err) 56 } 57 return h 58 case isNumber(k): 59 f, err := numberToFloat(v) 60 if err == nil { 61 return f 62 } 63 } 64 return v.Interface() 65 } 66 67 // collects identities from the slices in seqs into a set. Numeric values are normalized, 68 // pointers unwrapped. 69 func collectIdentities(seqs ...any) (map[any]bool, error) { 70 seen := make(map[any]bool) 71 for _, seq := range seqs { 72 v := reflect.ValueOf(seq) 73 switch v.Kind() { 74 case reflect.Array, reflect.Slice: 75 for i := 0; i < v.Len(); i++ { 76 ev, _ := indirectInterface(v.Index(i)) 77 78 if !ev.Type().Comparable() { 79 return nil, errors.New("elements must be comparable") 80 } 81 82 seen[normalize(ev)] = true 83 } 84 default: 85 return nil, fmt.Errorf("arguments must be slices or arrays") 86 } 87 } 88 89 return seen, nil 90 } 91 92 // We have some different numeric and string types that we try to behave like 93 // they were the same. 94 func convertValue(v reflect.Value, to reflect.Type) (reflect.Value, error) { 95 if v.Type().AssignableTo(to) { 96 return v, nil 97 } 98 switch kind := to.Kind(); { 99 case kind == reflect.String: 100 s, err := toString(v) 101 return reflect.ValueOf(s), err 102 case isNumber(kind): 103 return convertNumber(v, kind) 104 default: 105 return reflect.Value{}, fmt.Errorf("%s is not assignable to %s", v.Type(), to) 106 } 107 } 108 109 // There are potential overflows in this function, but the downconversion of 110 // int64 etc. into int8 etc. is coming from the synthetic unit tests for Union etc. 111 // TODO(bep) We should consider normalizing the slices to int64 etc. 112 func convertNumber(v reflect.Value, to reflect.Kind) (reflect.Value, error) { 113 var n reflect.Value 114 if isFloat(to) { 115 f, err := toFloat(v) 116 if err != nil { 117 return n, err 118 } 119 switch to { 120 case reflect.Float32: 121 n = reflect.ValueOf(float32(f)) 122 default: 123 n = reflect.ValueOf(float64(f)) 124 } 125 } else if isInt(to) { 126 i, err := toInt(v) 127 if err != nil { 128 return n, err 129 } 130 switch to { 131 case reflect.Int: 132 n = reflect.ValueOf(int(i)) 133 case reflect.Int8: 134 n = reflect.ValueOf(int8(i)) 135 case reflect.Int16: 136 n = reflect.ValueOf(int16(i)) 137 case reflect.Int32: 138 n = reflect.ValueOf(int32(i)) 139 case reflect.Int64: 140 n = reflect.ValueOf(int64(i)) 141 } 142 } else if isUint(to) { 143 i, err := toUint(v) 144 if err != nil { 145 return n, err 146 } 147 switch to { 148 case reflect.Uint: 149 n = reflect.ValueOf(uint(i)) 150 case reflect.Uint8: 151 n = reflect.ValueOf(uint8(i)) 152 case reflect.Uint16: 153 n = reflect.ValueOf(uint16(i)) 154 case reflect.Uint32: 155 n = reflect.ValueOf(uint32(i)) 156 case reflect.Uint64: 157 n = reflect.ValueOf(uint64(i)) 158 } 159 160 } 161 162 if !n.IsValid() { 163 return n, errors.New("invalid values") 164 } 165 166 return n, nil 167 } 168 169 func newSliceElement(items any) any { 170 tp := reflect.TypeOf(items) 171 if tp == nil { 172 return nil 173 } 174 switch tp.Kind() { 175 case reflect.Array, reflect.Slice: 176 tp = tp.Elem() 177 if tp.Kind() == reflect.Ptr { 178 tp = tp.Elem() 179 } 180 181 return reflect.New(tp).Interface() 182 } 183 return nil 184 } 185 186 func isNumber(kind reflect.Kind) bool { 187 return isInt(kind) || isUint(kind) || isFloat(kind) 188 } 189 190 func isInt(kind reflect.Kind) bool { 191 switch kind { 192 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 193 return true 194 default: 195 return false 196 } 197 } 198 199 func isUint(kind reflect.Kind) bool { 200 switch kind { 201 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 202 return true 203 default: 204 return false 205 } 206 } 207 208 func isFloat(kind reflect.Kind) bool { 209 switch kind { 210 case reflect.Float32, reflect.Float64: 211 return true 212 default: 213 return false 214 } 215 }