Support AppendMatchingProperties on an embedded anonymous struct

Recurse into embedded anonymous structs and the BlueprintEmbed
workaround structs when looking for properties in
extendPropertiesRecursive.

Test: proptools/extend_test.go
Change-Id: I975651a64e5173747403629a09263562761f1495
diff --git a/proptools/extend.go b/proptools/extend.go
index b92bf90..38f4dee 100644
--- a/proptools/extend.go
+++ b/proptools/extend.go
@@ -247,6 +247,8 @@
 	prefix string, filter ExtendPropertyFilterFunc, sameTypes bool,
 	orderFunc ExtendPropertyOrderFunc) error {
 
+	dstValuesCopied := false
+
 	srcType := srcValue.Type()
 	for i, srcField := range typeFields(srcType) {
 		if srcField.PkgPath != "" {
@@ -284,7 +286,9 @@
 
 		found := false
 		var recurse []reflect.Value
-		for _, dstValue := range dstValues {
+		// Use an iteration loop so elements can be added to the end of dstValues inside the loop.
+		for j := 0; j < len(dstValues); j++ {
+			dstValue := dstValues[j]
 			dstType := dstValue.Type()
 			var dstField reflect.StructField
 
@@ -297,6 +301,27 @@
 					if field.Name == srcField.Name {
 						dstField = field
 						ok = true
+					} else if field.Name == "BlueprintEmbed" || field.Anonymous {
+						embeddedDstValue := dstValue.FieldByIndex(field.Index)
+						if isStructPtr(embeddedDstValue.Type()) {
+							if embeddedDstValue.IsNil() {
+								newEmbeddedDstValue := reflect.New(embeddedDstValue.Type().Elem())
+								embeddedDstValue.Set(newEmbeddedDstValue)
+							}
+							embeddedDstValue = embeddedDstValue.Elem()
+						}
+						if !isStruct(embeddedDstValue.Type()) {
+							return extendPropertyErrorf(propertyName, "%s is not a struct (%s)",
+								prefix+field.Name, embeddedDstValue.Type())
+						}
+						// The destination struct contains an embedded struct, add it to the list
+						// of destinations to consider.  Make a copy of dstValues if necessary
+						// to avoid modifying the backing array of an input parameter.
+						if !dstValuesCopied {
+							dstValues = append([]reflect.Value(nil), dstValues...)
+							dstValuesCopied = true
+						}
+						dstValues = append(dstValues, embeddedDstValue)
 					}
 				}
 				if !ok {
diff --git a/proptools/extend_test.go b/proptools/extend_test.go
index 9dcd045..d2dac72 100644
--- a/proptools/extend_test.go
+++ b/proptools/extend_test.go
@@ -814,6 +814,54 @@
 			},
 		},
 		{
+			name: "BlueprintEmbed struct",
+			dst: &struct {
+				BlueprintEmbed EmbeddedStruct
+				Nested         struct{ BlueprintEmbed EmbeddedStruct }
+			}{
+				BlueprintEmbed: EmbeddedStruct{
+					S: "string1",
+					I: Int64Ptr(55),
+				},
+				Nested: struct{ BlueprintEmbed EmbeddedStruct }{
+					BlueprintEmbed: EmbeddedStruct{
+						S: "string2",
+						I: Int64Ptr(-4),
+					},
+				},
+			},
+			src: &struct {
+				BlueprintEmbed EmbeddedStruct
+				Nested         struct{ BlueprintEmbed EmbeddedStruct }
+			}{
+				BlueprintEmbed: EmbeddedStruct{
+					S: "string3",
+					I: Int64Ptr(66),
+				},
+				Nested: struct{ BlueprintEmbed EmbeddedStruct }{
+					BlueprintEmbed: EmbeddedStruct{
+						S: "string4",
+						I: Int64Ptr(-8),
+					},
+				},
+			},
+			out: &struct {
+				BlueprintEmbed EmbeddedStruct
+				Nested         struct{ BlueprintEmbed EmbeddedStruct }
+			}{
+				BlueprintEmbed: EmbeddedStruct{
+					S: "string1string3",
+					I: Int64Ptr(66),
+				},
+				Nested: struct{ BlueprintEmbed EmbeddedStruct }{
+					BlueprintEmbed: EmbeddedStruct{
+						S: "string2string4",
+						I: Int64Ptr(-8),
+					},
+				},
+			},
+		},
+		{
 			name: "Anonymous interface",
 			dst: &struct {
 				EmbeddedInterface
@@ -1476,6 +1524,130 @@
 				},
 			},
 		},
+		{
+			name: "Append through embedded struct",
+			dst: []interface{}{
+				&struct{ B string }{},
+				&struct{ EmbeddedStruct }{
+					EmbeddedStruct: EmbeddedStruct{
+						S: "string1",
+					},
+				},
+			},
+			src: &struct{ S string }{
+				S: "string2",
+			},
+			out: []interface{}{
+				&struct{ B string }{},
+				&struct{ EmbeddedStruct }{
+					EmbeddedStruct: EmbeddedStruct{
+						S: "string1string2",
+					},
+				},
+			},
+		},
+		{
+			name: "Append through BlueprintEmbed struct",
+			dst: []interface{}{
+				&struct{ B string }{},
+				&struct{ BlueprintEmbed EmbeddedStruct }{
+					BlueprintEmbed: EmbeddedStruct{
+						S: "string1",
+					},
+				},
+			},
+			src: &struct{ S string }{
+				S: "string2",
+			},
+			out: []interface{}{
+				&struct{ B string }{},
+				&struct{ BlueprintEmbed EmbeddedStruct }{
+					BlueprintEmbed: EmbeddedStruct{
+						S: "string1string2",
+					},
+				},
+			},
+		},
+		{
+			name: "Append through embedded pointer to struct",
+			dst: []interface{}{
+				&struct{ B string }{},
+				&struct{ *EmbeddedStruct }{
+					EmbeddedStruct: &EmbeddedStruct{
+						S: "string1",
+					},
+				},
+			},
+			src: &struct{ S string }{
+				S: "string2",
+			},
+			out: []interface{}{
+				&struct{ B string }{},
+				&struct{ *EmbeddedStruct }{
+					EmbeddedStruct: &EmbeddedStruct{
+						S: "string1string2",
+					},
+				},
+			},
+		},
+		{
+			name: "Append through BlueprintEmbed pointer to struct",
+			dst: []interface{}{
+				&struct{ B string }{},
+				&struct{ BlueprintEmbed *EmbeddedStruct }{
+					BlueprintEmbed: &EmbeddedStruct{
+						S: "string1",
+					},
+				},
+			},
+			src: &struct{ S string }{
+				S: "string2",
+			},
+			out: []interface{}{
+				&struct{ B string }{},
+				&struct{ BlueprintEmbed *EmbeddedStruct }{
+					BlueprintEmbed: &EmbeddedStruct{
+						S: "string1string2",
+					},
+				},
+			},
+		},
+		{
+			name: "Append through embedded nil pointer to struct",
+			dst: []interface{}{
+				&struct{ B string }{},
+				&struct{ *EmbeddedStruct }{},
+			},
+			src: &struct{ S string }{
+				S: "string2",
+			},
+			out: []interface{}{
+				&struct{ B string }{},
+				&struct{ *EmbeddedStruct }{
+					EmbeddedStruct: &EmbeddedStruct{
+						S: "string2",
+					},
+				},
+			},
+		},
+		{
+			name: "Append through BlueprintEmbed nil pointer to struct",
+			dst: []interface{}{
+				&struct{ B string }{},
+				&struct{ BlueprintEmbed *EmbeddedStruct }{},
+			},
+			src: &struct{ S string }{
+				S: "string2",
+			},
+			out: []interface{}{
+				&struct{ B string }{},
+				&struct{ BlueprintEmbed *EmbeddedStruct }{
+					BlueprintEmbed: &EmbeddedStruct{
+						S: "string2",
+					},
+				},
+			},
+		},
 
 		// Errors