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
This commit is contained in:
Colin Cross 2021-06-25 16:44:30 -07:00
parent 1602226f23
commit 1c3530ab58
2 changed files with 198 additions and 1 deletions

View file

@ -247,6 +247,8 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
prefix string, filter ExtendPropertyFilterFunc, sameTypes bool, prefix string, filter ExtendPropertyFilterFunc, sameTypes bool,
orderFunc ExtendPropertyOrderFunc) error { orderFunc ExtendPropertyOrderFunc) error {
dstValuesCopied := false
srcType := srcValue.Type() srcType := srcValue.Type()
for i, srcField := range typeFields(srcType) { for i, srcField := range typeFields(srcType) {
if srcField.PkgPath != "" { if srcField.PkgPath != "" {
@ -284,7 +286,9 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
found := false found := false
var recurse []reflect.Value 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() dstType := dstValue.Type()
var dstField reflect.StructField var dstField reflect.StructField
@ -297,6 +301,27 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
if field.Name == srcField.Name { if field.Name == srcField.Name {
dstField = field dstField = field
ok = true 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 { if !ok {

View file

@ -813,6 +813,54 @@ func appendPropertiesTestCases() []appendPropertyTestCase {
}, },
}, },
}, },
{
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", name: "Anonymous interface",
dst: &struct { dst: &struct {
@ -1476,6 +1524,130 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
}, },
}, },
}, },
{
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 // Errors