Add isStruct and isStructPtr helpers

Test: proptools tests
Change-Id: I7814b2138cd19b538a3a33036a15119e118d7644
diff --git a/proptools/clone.go b/proptools/clone.go
index fe4e115..1de432b 100644
--- a/proptools/clone.go
+++ b/proptools/clone.go
@@ -67,13 +67,9 @@
 
 			srcFieldValue = srcFieldValue.Elem()
 
-			if srcFieldValue.Kind() != reflect.Ptr {
-				panic(fmt.Errorf("can't clone field %q: interface refers to a non-pointer",
-					field.Name))
-			}
-			if srcFieldValue.Type().Elem().Kind() != reflect.Struct {
-				panic(fmt.Errorf("can't clone field %q: interface points to a non-struct",
-					field.Name))
+			if !isStructPtr(srcFieldValue.Type()) {
+				panic(fmt.Errorf("can't clone field %q: expected interface to contain *struct, found %s",
+					field.Name, srcFieldValue.Type()))
 			}
 
 			if dstFieldValue.IsNil() || dstFieldValue.Elem().Type() != srcFieldValue.Type() {
@@ -146,13 +142,9 @@
 			// We leave the pointer intact and zero out the struct that's
 			// pointed to.
 			fieldValue = fieldValue.Elem()
-			if fieldValue.Kind() != reflect.Ptr {
-				panic(fmt.Errorf("can't zero field %q: interface refers to a non-pointer",
-					field.Name))
-			}
-			if fieldValue.Type().Elem().Kind() != reflect.Struct {
-				panic(fmt.Errorf("can't zero field %q: interface points to a non-struct",
-					field.Name))
+			if !isStructPtr(fieldValue.Type()) {
+				panic(fmt.Errorf("can't zero field %q: expected interface to contain *struct, found %s",
+					field.Name, fieldValue.Type()))
 			}
 			fallthrough
 		case reflect.Ptr:
@@ -206,13 +198,9 @@
 			}
 
 			srcFieldValue = srcFieldValue.Elem()
-			if srcFieldValue.Kind() != reflect.Ptr {
-				panic(fmt.Errorf("can't clone empty field %q: interface refers to a non-pointer",
-					field.Name))
-			}
-			if srcFieldValue.Type().Elem().Kind() != reflect.Struct {
-				panic(fmt.Errorf("can't clone empty field %q: interface points to a non-struct",
-					field.Name))
+			if !isStructPtr(srcFieldValue.Type()) {
+				panic(fmt.Errorf("can't clone empty field %q: expected interface to contain *struct, found %s",
+					field.Name, srcFieldValue.Type()))
 			}
 
 			newValue := reflect.New(srcFieldValue.Type()).Elem()
diff --git a/proptools/extend.go b/proptools/extend.go
index b0c7459..d3c2b79 100644
--- a/proptools/extend.go
+++ b/proptools/extend.go
@@ -274,7 +274,7 @@
 		}
 
 		// Step into source pointers to structs
-		if srcFieldValue.Kind() == reflect.Ptr && srcFieldValue.Type().Elem().Kind() == reflect.Struct {
+		if isStructPtr(srcFieldValue.Type()) {
 			if srcFieldValue.IsNil() {
 				continue
 			}
@@ -323,7 +323,7 @@
 			}
 
 			// Step into destination pointers to structs
-			if dstFieldValue.Kind() == reflect.Ptr && dstFieldValue.Type().Elem().Kind() == reflect.Struct {
+			if isStructPtr(dstFieldValue.Type()) {
 				if dstFieldValue.IsNil() {
 					dstFieldValue = reflect.New(dstFieldValue.Type().Elem())
 					origDstFieldValue.Set(dstFieldValue)
@@ -501,11 +501,8 @@
 
 func getStruct(in interface{}) (reflect.Value, error) {
 	value := reflect.ValueOf(in)
-	if value.Kind() != reflect.Ptr {
-		return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in)
-	}
-	if value.Type().Elem().Kind() != reflect.Struct {
-		return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in)
+	if !isStructPtr(value.Type()) {
+		return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %s", value.Type())
 	}
 	if value.IsNil() {
 		return reflect.Value{}, getStructEmptyError{}
diff --git a/proptools/filter.go b/proptools/filter.go
index 59eca5a..e6b3336 100644
--- a/proptools/filter.go
+++ b/proptools/filter.go
@@ -50,8 +50,7 @@
 
 		if maxTypeNameSize > 0 && structNameSize+fieldTypeNameSize > maxTypeNameSize {
 			if len(filteredFields) == 0 {
-				if field.Type.Kind() == reflect.Struct ||
-					field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
+				if isStruct(field.Type) || isStructPtr(field.Type) {
 					// An error fitting the nested struct should have been caught when recursing
 					// into the nested struct.
 					panic(fmt.Errorf("Shouldn't happen: can't fit nested struct %q (%d) into %d",
@@ -82,12 +81,12 @@
 		}
 
 		ptrToStruct := false
-		if field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
+		if isStructPtr(field.Type) {
 			ptrToStruct = true
 		}
 
 		// Recurse into structs
-		if ptrToStruct || field.Type.Kind() == reflect.Struct {
+		if ptrToStruct || isStruct(field.Type) {
 			subMaxTypeNameSize := maxTypeNameSize
 			if maxTypeNameSize > 0 {
 				// In the worst case where only this nested struct will fit in the outer struct, the
diff --git a/proptools/proptools.go b/proptools/proptools.go
index 6881828..b91c92a 100644
--- a/proptools/proptools.go
+++ b/proptools/proptools.go
@@ -15,6 +15,7 @@
 package proptools
 
 import (
+	"reflect"
 	"unicode"
 	"unicode/utf8"
 )
@@ -97,3 +98,11 @@
 func Int(i *int64) int {
 	return IntDefault(i, 0)
 }
+
+func isStruct(t reflect.Type) bool {
+	return t.Kind() == reflect.Struct
+}
+
+func isStructPtr(t reflect.Type) bool {
+	return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
+}
diff --git a/proptools/tag.go b/proptools/tag.go
index af5b97e..d69853a 100644
--- a/proptools/tag.go
+++ b/proptools/tag.go
@@ -36,7 +36,7 @@
 // are tagged with the given key and value, including ones found in embedded structs or pointers to structs.
 func PropertyIndexesWithTag(ps interface{}, key, value string) [][]int {
 	t := reflect.TypeOf(ps)
-	if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
+	if !isStructPtr(t) {
 		panic(fmt.Errorf("type %s is not a pointer to a struct", t))
 	}
 	t = t.Elem()
@@ -49,7 +49,7 @@
 	for i := 0; i < t.NumField(); i++ {
 		field := t.Field(i)
 		ft := field.Type
-		if ft.Kind() == reflect.Struct || (ft.Kind() == reflect.Ptr && ft.Elem().Kind() == reflect.Struct) {
+		if isStruct(ft) || isStructPtr(ft) {
 			if ft.Kind() == reflect.Ptr {
 				ft = ft.Elem()
 			}
diff --git a/proptools/unpack.go b/proptools/unpack.go
index e7d4fff..344327f 100644
--- a/proptools/unpack.go
+++ b/proptools/unpack.go
@@ -49,14 +49,11 @@
 
 	for _, properties := range propertiesStructs {
 		propertiesValue := reflect.ValueOf(properties)
-		if propertiesValue.Kind() != reflect.Ptr {
-			panic("properties must be a pointer to a struct")
+		if !isStructPtr(propertiesValue.Type()) {
+			panic(fmt.Errorf("properties must be *struct, got %s",
+				propertiesValue.Type()))
 		}
-
 		propertiesValue = propertiesValue.Elem()
-		if propertiesValue.Kind() != reflect.Struct {
-			panic("properties must be a pointer to a struct")
-		}
 
 		newErrs := unpackStructValue("", propertiesValue, propertyMap)
 		errs = append(errs, newErrs...)
@@ -212,7 +209,7 @@
 			panic(fmt.Errorf("unsupported kind for field %s: %s", propertyName, kind))
 		}
 
-		if field.Anonymous && fieldValue.Kind() == reflect.Struct {
+		if field.Anonymous && isStruct(fieldValue.Type()) {
 			newErrs := unpackStructValue(namePrefix, fieldValue, propertyMap)
 			errs = append(errs, newErrs...)
 			continue
@@ -239,7 +236,7 @@
 
 		var newErrs []error
 
-		if fieldValue.Kind() == reflect.Struct {
+		if isStruct(fieldValue.Type()) {
 			newErrs = unpackStruct(propertyName+".", fieldValue,
 				packedProperty.property, propertyMap)