Add proptools.CloneEmptyProperties

Modules that want properties that vary by variant often need to create
zeroed copies of property structs.  Add CloneEmptyProperties to proptools
that is the equivalent of calling CloneProperties and then ZeroProperties,
but is much faster because it directly creates zeroed objects.

Saves 200ms in Context.ParseBlueprintsFiles and Context.ResolveDependencies
in one case, which will be valuble when we start parsing Blueprints files
for cases where we are not regenerating the manifest, for example when
generating documentation or doing context-aware bpfmt.

Change-Id: I3d4a6af2f393886d95f27d15afc1a455d8dd5fc6
diff --git a/proptools/proptools.go b/proptools/proptools.go
index 4868408..2a71ea1 100644
--- a/proptools/proptools.go
+++ b/proptools/proptools.go
@@ -146,3 +146,49 @@
 		}
 	}
 }
+
+func CloneEmptyProperties(structValue reflect.Value) reflect.Value {
+	result := reflect.New(structValue.Type())
+	cloneEmptyProperties(result.Elem(), structValue)
+	return result
+}
+
+func cloneEmptyProperties(dstValue, srcValue reflect.Value) {
+	typ := srcValue.Type()
+	for i := 0; i < srcValue.NumField(); i++ {
+		field := typ.Field(i)
+		if field.PkgPath != "" {
+			// The field is not exported so just skip it.
+			continue
+		}
+
+		srcFieldValue := srcValue.Field(i)
+		dstFieldValue := dstValue.Field(i)
+
+		switch srcFieldValue.Kind() {
+		case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
+			// Nothing
+		case reflect.Struct:
+			cloneEmptyProperties(dstFieldValue, srcFieldValue)
+		case reflect.Ptr, reflect.Interface:
+			if !srcFieldValue.IsNil() {
+				elem := srcFieldValue.Elem()
+				if srcFieldValue.Kind() == reflect.Interface {
+					if elem.Kind() != reflect.Ptr {
+						panic(fmt.Errorf("can't clone field %q: interface "+
+							"refers to a non-pointer", field.Name))
+					}
+					elem = elem.Elem()
+				}
+				if elem.Elem().Kind() != reflect.Struct {
+					panic(fmt.Errorf("can't clone field %q: points to a "+
+						"non-struct", field.Name))
+				}
+				dstFieldValue.Set(CloneEmptyProperties(elem.Elem()))
+			}
+		default:
+			panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
+				field.Name, srcFieldValue.Kind()))
+		}
+	}
+}