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