name string
}
-var methodCache sync.Map
+var (
+ methodIndexCache sync.Map
+ methodCache sync.Map
+)
+
+// GetMethodByNameForType returns the method with the given name for the given type,
+// or a zero Method if no such method exists.
+// It panics if tp is an interface type.
+// It caches the lookup.
+func GetMethodByNameForType(tp reflect.Type, name string) reflect.Method {
+ if tp.Kind() == reflect.Interface {
+ // Func field is nil for interface types.
+ panic("not supported for interface types")
+ }
+ k := methodKey{tp, name}
+ v, found := methodCache.Load(k)
+ if found {
+ return v.(reflect.Method)
+ }
+ m, _ := tp.MethodByName(name)
+ methodCache.Store(k, m)
+ return m
+}
-// GetMethodByName is the same as reflect.Value.MethodByName, but it caches the
-// type lookup.
+// GetMethodByName is the same as reflect.Value.MethodByName, but it caches the lookup.
func GetMethodByName(v reflect.Value, name string) reflect.Value {
index := GetMethodIndexByName(v.Type(), name)
// -1 if no such method exists.
func GetMethodIndexByName(tp reflect.Type, name string) int {
k := methodKey{tp, name}
- v, found := methodCache.Load(k)
+ v, found := methodIndexCache.Load(k)
if found {
return v.(int)
}
if !ok {
index = -1
}
- methodCache.Store(k, index)
+ methodIndexCache.Store(k, index)
if !ok {
return -1
return v, false
}
+// IndirectElem is like Indirect, but if the final value is a pointer, it unwraps it.
+func IndirectElem(v reflect.Value) (vv reflect.Value, isNil bool) {
+ vv, isNil = Indirect(v)
+ if isNil {
+ return vv, isNil
+ }
+ if vv.Kind() == reflect.Pointer {
+ vv = vv.Elem()
+ }
+ return vv, isNil
+}
+
// IsNil reports whether v is nil.
// Based on reflect.Value.IsNil, but also considers invalid values as nil.
func IsNil(v reflect.Value) bool {
return "Hugo"
}
+func BenchmarkGetMethodByNameForType(b *testing.B) {
+ tp := reflect.TypeFor[*testStruct]()
+ methods := []string{"Method1", "Method2", "Method3", "Method4", "Method5"}
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ for _, method := range methods {
+ _ = GetMethodByNameForType(tp, method)
+ }
+ }
+}
+
func BenchmarkGetMethodByName(b *testing.B) {
v := reflect.ValueOf(&testStruct{})
methods := []string{"Method1", "Method2", "Method3", "Method4", "Method5"}
objPtr = objPtr.Addr()
}
- index := hreflect.GetMethodIndexByName(objPtr.Type(), elemName)
- if index != -1 {
- var args []reflect.Value
- mt := objPtr.Type().Method(index)
+ mt := hreflect.GetMethodByNameForType(objPtr.Type(), elemName)
+ if mt.Func.IsValid() {
+ // Receiver is the first argument.
+ args := []reflect.Value{objPtr}
num := mt.Type.NumIn()
maxNumIn := 1
if num > 1 && hreflect.IsContextType(mt.Type.In(1)) {
- args = []reflect.Value{ctx}
+ args = append(args, ctx)
maxNumIn = 2
}
case mt.Type.NumOut() == 2 && !mt.Type.Out(1).Implements(errorType):
return zero, fmt.Errorf("%s is a method of type %s returning two values but the second value is not an error type", elemName, typ)
}
- res := objPtr.Method(mt.Index).Call(args)
+ res := mt.Func.Call(args)
if len(res) == 2 && !res[1].IsNil() {
return zero, fmt.Errorf("error at calling a method %s of type %s: %s", elemName, typ, res[1].Interface().(error))
}