github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/tpl/collections/reflect_helpers.go (about)

     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  }