Procházet zdrojové kódy

Add some more robust variants of TypeFor and PackageFor which use generic type inferences instead of being untyped. Update Tests

Signed-off-by: Matt Bolt <mbolt35@gmail.com>
Matt Bolt před 2 roky
rodič
revize
465b6db018

+ 23 - 15
core/pkg/util/typeutil/typeutil.go

@@ -25,8 +25,26 @@ func TypeOf[T any]() string {
 		panic(fmt.Sprintf("failed to locate non-pointer type: %+v", reflect.TypeFor[T]()))
 	}
 
+	name := t.Name()
+
+	// special cases for built-ins struct{} and interface{}
+	if name == "" {
+		name = t.String()
+	}
+
+	// no package path, do not use a / separator
+	if t.PkgPath() == "" {
+		return prefix + name
+	}
+
 	// combine the prefix, package path, and the type name
-	return fmt.Sprintf("%s%s/%s", prefix, t.PkgPath(), t.Name())
+	return fmt.Sprintf("%s%s/%s", prefix, t.PkgPath(), name)
+}
+
+// TypeFor uses type inferencing to accepts a value and returns the fully qualified package
+// and type name
+func TypeFor[T any](value T) string {
+	return TypeOf[T]()
 }
 
 // PackageOf is a utility that can return the package name for the type provided.
@@ -45,20 +63,10 @@ func PackageOf[T any]() string {
 	return t.PkgPath()
 }
 
-// PackageFor accepts a value and returns the package name for the type of the value.
-func PackageFor(value any) string {
-	t := reflect.TypeOf(value)
-
-	for t != nil && t.Kind() == reflect.Ptr {
-		t = t.Elem()
-	}
-
-	// this should not be possible, but in the event that it does, we want to be loud about it
-	if t == nil {
-		panic(fmt.Sprintf("failed to locate package for: %+v", reflect.TypeOf(value)))
-	}
-
-	return t.PkgPath()
+// PackageFor uses type inferencing to accepts a value and returns
+// the package name for the type of the value.
+func PackageFor[T any](value T) string {
+	return PackageOf[T]()
 }
 
 // PackageFromCaller returns the package name of the caller at the specified depth.

+ 33 - 0
core/pkg/util/typeutil/typeutil_test.go

@@ -33,6 +33,8 @@ func cmp[T comparable](t *testing.T, result, expected T) {
 	}
 }
 
+type InterfaceType interface{}
+
 var packageScoped = typeutil.CurrentPackage()
 
 func TestTypeOf(t *testing.T) {
@@ -40,7 +42,16 @@ func TestTypeOf(t *testing.T) {
 	const testTypeName = packageName + "/TestType"
 	const genericTestTypeName = packageName + "/GenericTestType"
 	const genericTypeParameterTypeName = packageName + ".GenericTestType"
+	const interfaceTypeName = packageName + "/InterfaceType"
+
+	// Basic Types
+	cmp(t, typeutil.TypeOf[int](), "int")
+	cmp(t, typeutil.TypeOf[int8](), "int8")
+	cmp(t, typeutil.TypeOf[any](), "interface {}")
+	cmp(t, typeutil.TypeOf[interface{}](), "interface {}")
+	cmp(t, typeutil.TypeOf[struct{}](), "struct {}")
 
+	// Specific Types
 	cmp(t, typeutil.TypeOf[TestType](), testTypeName)
 	cmp(t, typeutil.TypeOf[*TestType](), "*"+testTypeName)
 	cmp(t, typeutil.TypeOf[**TestType](), "**"+testTypeName)
@@ -48,6 +59,21 @@ func TestTypeOf(t *testing.T) {
 	cmp(t, typeutil.TypeOf[GenericTestType[GenericTestType[string]]](), genericTestTypeName+"["+genericTypeParameterTypeName+"[string]"+"]")
 	cmp(t, typeutil.TypeOf[GenericTestType[*GenericTestType[string]]](), genericTestTypeName+"[*"+genericTypeParameterTypeName+"[string]"+"]")
 	cmp(t, typeutil.TypeOf[GenericTestType[*GenericTestType[map[int][]float64]]](), genericTestTypeName+"[*"+genericTypeParameterTypeName+"[map[int][]float64]"+"]")
+
+	// interface types
+	cmp(t, typeutil.TypeOf[InterfaceType](), interfaceTypeName)
+	cmp(t, typeutil.TypeOf[*InterfaceType](), "*"+interfaceTypeName)
+	cmp(t, typeutil.TypeOf[**InterfaceType](), "**"+interfaceTypeName)
+
+	// TypeFor variants
+	var value any
+	cmp(t, typeutil.TypeFor(value), "interface {}")
+
+	var ivalue InterfaceType
+	cmp(t, typeutil.TypeFor(ivalue), interfaceTypeName)
+
+	var testType **TestType
+	cmp(t, typeutil.TypeFor(testType), "**"+testTypeName)
 }
 
 func DeferredCurrentPackage() (result string) {
@@ -87,4 +113,11 @@ func TestPackageOf(t *testing.T) {
 	// this will normally return something like:
 	// "github.com/opencost/opencost/core/pkg/util/typeutil_test.init"
 	cmp(t, packageScoped, currentPackageName)
+
+	// PackageFor variants
+	var value any
+	cmp(t, typeutil.PackageFor(value), "")
+
+	var ivalue InterfaceType
+	cmp(t, typeutil.PackageFor(ivalue), currentPackageName)
 }