Merge "Optimize (Extend|Append|Prepend)[Matching]Properties" into main
diff --git a/proptools/extend.go b/proptools/extend.go
index 4e2f498..63ff1d7 100644
--- a/proptools/extend.go
+++ b/proptools/extend.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"reflect"
+	"strings"
 )
 
 // AppendProperties appends the values of properties in the property struct src to the property
@@ -157,29 +158,19 @@
 	Replace
 )
 
-type ExtendPropertyFilterFunc func(property string,
-	dstField, srcField reflect.StructField,
-	dstValue, srcValue interface{}) (bool, error)
+type ExtendPropertyFilterFunc func(dstField, srcField reflect.StructField) (bool, error)
 
-type ExtendPropertyOrderFunc func(property string,
-	dstField, srcField reflect.StructField,
-	dstValue, srcValue interface{}) (Order, error)
+type ExtendPropertyOrderFunc func(dstField, srcField reflect.StructField) (Order, error)
 
-func OrderAppend(property string,
-	dstField, srcField reflect.StructField,
-	dstValue, srcValue interface{}) (Order, error) {
+func OrderAppend(dstField, srcField reflect.StructField) (Order, error) {
 	return Append, nil
 }
 
-func OrderPrepend(property string,
-	dstField, srcField reflect.StructField,
-	dstValue, srcValue interface{}) (Order, error) {
+func OrderPrepend(dstField, srcField reflect.StructField) (Order, error) {
 	return Prepend, nil
 }
 
-func OrderReplace(property string,
-	dstField, srcField reflect.StructField,
-	dstValue, srcValue interface{}) (Order, error) {
+func OrderReplace(dstField, srcField reflect.StructField) (Order, error) {
 	return Replace, nil
 }
 
@@ -221,7 +212,7 @@
 
 	dstValues := []reflect.Value{dstValue}
 
-	return extendPropertiesRecursive(dstValues, srcValue, "", filter, true, order)
+	return extendPropertiesRecursive(dstValues, srcValue, make([]string, 0, 8), filter, true, order)
 }
 
 func extendMatchingProperties(dst []interface{}, src interface{}, filter ExtendPropertyFilterFunc,
@@ -244,22 +235,30 @@
 		}
 	}
 
-	return extendPropertiesRecursive(dstValues, srcValue, "", filter, false, order)
+	return extendPropertiesRecursive(dstValues, srcValue, make([]string, 0, 8), filter, false, order)
 }
 
 func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value,
-	prefix string, filter ExtendPropertyFilterFunc, sameTypes bool,
+	prefix []string, filter ExtendPropertyFilterFunc, sameTypes bool,
 	orderFunc ExtendPropertyOrderFunc) error {
 
 	dstValuesCopied := false
 
+	propertyName := func(field reflect.StructField) string {
+		names := make([]string, 0, len(prefix)+1)
+		for _, s := range prefix {
+			names = append(names, PropertyNameForField(s))
+		}
+		names = append(names, PropertyNameForField(field.Name))
+		return strings.Join(names, ".")
+	}
+
 	srcType := srcValue.Type()
 	for i, srcField := range typeFields(srcType) {
 		if ShouldSkipProperty(srcField) {
 			continue
 		}
 
-		propertyName := prefix + PropertyNameForField(srcField.Name)
 		srcFieldValue := srcValue.Field(i)
 
 		// Step into source interfaces
@@ -271,7 +270,7 @@
 			srcFieldValue = srcFieldValue.Elem()
 
 			if srcFieldValue.Kind() != reflect.Ptr {
-				return extendPropertyErrorf(propertyName, "interface not a pointer")
+				return extendPropertyErrorf(propertyName(srcField), "interface not a pointer")
 			}
 		}
 
@@ -311,8 +310,8 @@
 							embeddedDstValue = embeddedDstValue.Elem()
 						}
 						if !isStruct(embeddedDstValue.Type()) {
-							return extendPropertyErrorf(propertyName, "%s is not a struct (%s)",
-								prefix+field.Name, embeddedDstValue.Type())
+							return extendPropertyErrorf(propertyName(srcField), "%s is not a struct (%s)",
+								propertyName(field), 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
@@ -337,13 +336,13 @@
 			// Step into destination interfaces
 			if dstFieldValue.Kind() == reflect.Interface {
 				if dstFieldValue.IsNil() {
-					return extendPropertyErrorf(propertyName, "nilitude mismatch")
+					return extendPropertyErrorf(propertyName(srcField), "nilitude mismatch")
 				}
 
 				dstFieldValue = dstFieldValue.Elem()
 
 				if dstFieldValue.Kind() != reflect.Ptr {
-					return extendPropertyErrorf(propertyName, "interface not a pointer")
+					return extendPropertyErrorf(propertyName(srcField), "interface not a pointer")
 				}
 			}
 
@@ -360,7 +359,7 @@
 			switch srcFieldValue.Kind() {
 			case reflect.Struct:
 				if sameTypes && dstFieldValue.Type() != srcFieldValue.Type() {
-					return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
+					return extendPropertyErrorf(propertyName(srcField), "mismatched types %s and %s",
 						dstFieldValue.Type(), srcFieldValue.Type())
 				}
 
@@ -369,34 +368,30 @@
 				continue
 			case reflect.Bool, reflect.String, reflect.Slice, reflect.Map:
 				if srcFieldValue.Type() != dstFieldValue.Type() {
-					return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
+					return extendPropertyErrorf(propertyName(srcField), "mismatched types %s and %s",
 						dstFieldValue.Type(), srcFieldValue.Type())
 				}
 			case reflect.Ptr:
 				if srcFieldValue.Type() != dstFieldValue.Type() {
-					return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
+					return extendPropertyErrorf(propertyName(srcField), "mismatched types %s and %s",
 						dstFieldValue.Type(), srcFieldValue.Type())
 				}
 				switch ptrKind := srcFieldValue.Type().Elem().Kind(); ptrKind {
 				case reflect.Bool, reflect.Int64, reflect.String, reflect.Struct:
 				// Nothing
 				default:
-					return extendPropertyErrorf(propertyName, "pointer is a %s", ptrKind)
+					return extendPropertyErrorf(propertyName(srcField), "pointer is a %s", ptrKind)
 				}
 			default:
-				return extendPropertyErrorf(propertyName, "unsupported kind %s",
+				return extendPropertyErrorf(propertyName(srcField), "unsupported kind %s",
 					srcFieldValue.Kind())
 			}
 
-			dstFieldInterface := dstFieldValue.Interface()
-			srcFieldInterface := srcFieldValue.Interface()
-
 			if filter != nil {
-				b, err := filter(propertyName, dstField, srcField,
-					dstFieldInterface, srcFieldInterface)
+				b, err := filter(dstField, srcField)
 				if err != nil {
 					return &ExtendPropertyError{
-						Property: propertyName,
+						Property: propertyName(srcField),
 						Err:      err,
 					}
 				}
@@ -408,11 +403,10 @@
 			order := Append
 			if orderFunc != nil {
 				var err error
-				order, err = orderFunc(propertyName, dstField, srcField,
-					dstFieldInterface, srcFieldInterface)
+				order, err = orderFunc(dstField, srcField)
 				if err != nil {
 					return &ExtendPropertyError{
-						Property: propertyName,
+						Property: propertyName(srcField),
 						Err:      err,
 					}
 				}
@@ -423,12 +417,12 @@
 
 		if len(recurse) > 0 {
 			err := extendPropertiesRecursive(recurse, srcFieldValue,
-				propertyName+".", filter, sameTypes, orderFunc)
+				append(prefix, srcField.Name), filter, sameTypes, orderFunc)
 			if err != nil {
 				return err
 			}
 		} else if !found {
-			return extendPropertyErrorf(propertyName, "failed to find property to extend")
+			return extendPropertyErrorf(propertyName(srcField), "failed to find property to extend")
 		}
 	}
 
diff --git a/proptools/extend_test.go b/proptools/extend_test.go
index d2dac72..e562776 100644
--- a/proptools/extend_test.go
+++ b/proptools/extend_test.go
@@ -1168,9 +1168,7 @@
 			out: &struct{ S string }{
 				S: "string1string2",
 			},
-			filter: func(property string,
-				dstField, srcField reflect.StructField,
-				dstValue, srcValue interface{}) (bool, error) {
+			filter: func(dstField, srcField reflect.StructField) (bool, error) {
 				return true, nil
 			},
 		},
@@ -1185,9 +1183,7 @@
 			out: &struct{ S string }{
 				S: "string1",
 			},
-			filter: func(property string,
-				dstField, srcField reflect.StructField,
-				dstValue, srcValue interface{}) (bool, error) {
+			filter: func(dstField, srcField reflect.StructField) (bool, error) {
 				return false, nil
 			},
 		},
@@ -1202,12 +1198,8 @@
 			out: &struct{ S string }{
 				S: "string1string2",
 			},
-			filter: func(property string,
-				dstField, srcField reflect.StructField,
-				dstValue, srcValue interface{}) (bool, error) {
-				return property == "s" &&
-					dstField.Name == "S" && srcField.Name == "S" &&
-					dstValue.(string) == "string1" && srcValue.(string) == "string2", nil
+			filter: func(dstField, srcField reflect.StructField) (bool, error) {
+				return dstField.Name == "S" && srcField.Name == "S", nil
 			},
 		},
 		{
@@ -1257,9 +1249,7 @@
 			out: &struct{ S string }{
 				S: "string1",
 			},
-			filter: func(property string,
-				dstField, srcField reflect.StructField,
-				dstValue, srcValue interface{}) (bool, error) {
+			filter: func(dstField, srcField reflect.StructField) (bool, error) {
 				return true, fmt.Errorf("filter error")
 			},
 			err: extendPropertyErrorf("s", "filter error"),
@@ -1300,9 +1290,7 @@
 			var err error
 			var testType string
 
-			order := func(property string,
-				dstField, srcField reflect.StructField,
-				dstValue, srcValue interface{}) (Order, error) {
+			order := func(dstField, srcField reflect.StructField) (Order, error) {
 				switch testCase.order {
 				case Append:
 					return Append, nil
@@ -1764,9 +1752,7 @@
 			var err error
 			var testType string
 
-			order := func(property string,
-				dstField, srcField reflect.StructField,
-				dstValue, srcValue interface{}) (Order, error) {
+			order := func(dstField, srcField reflect.StructField) (Order, error) {
 				switch testCase.order {
 				case Append:
 					return Append, nil