proto: refactor merge tests

Use protobuild to generate test messages, both to simplify generating
proto2/proto3/extension variants of each test where appropriate and
to make it easier to test alternative message generators in the future.

Add various additional merge tests.

Change-Id: I4ba3ce232304e1d8325543680e2b6aae61de7364
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/219146
Reviewed-by: Joe Tsai <joetsai@google.com>
diff --git a/internal/protobuild/build.go b/internal/protobuild/build.go
index 645f0c1..8790d5f 100644
--- a/internal/protobuild/build.go
+++ b/internal/protobuild/build.go
@@ -144,6 +144,8 @@
 		case pref.EnumKind:
 			v = fd.Enum().Values().ByName(pref.Name(o)).Number()
 		}
+	case []byte:
+		return pref.ValueOf(append([]byte{}, o...))
 	}
 	return pref.ValueOf(v)
 }
diff --git a/proto/merge_test.go b/proto/merge_test.go
index 01b998d..48d8a41 100644
--- a/proto/merge_test.go
+++ b/proto/merge_test.go
@@ -5,377 +5,657 @@
 package proto_test
 
 import (
+	"fmt"
 	"reflect"
 	"sync"
 	"testing"
 
+	"github.com/google/go-cmp/cmp"
+	"google.golang.org/protobuf/encoding/prototext"
 	"google.golang.org/protobuf/internal/encoding/pack"
+	"google.golang.org/protobuf/internal/protobuild"
 	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/testing/protocmp"
+	"google.golang.org/protobuf/types/dynamicpb"
 
+	legacypb "google.golang.org/protobuf/internal/testprotos/legacy"
 	testpb "google.golang.org/protobuf/internal/testprotos/test"
 	test3pb "google.golang.org/protobuf/internal/testprotos/test3"
 )
 
+type testMerge struct {
+	desc  string
+	dst   protobuild.Message
+	src   protobuild.Message
+	want  protobuild.Message // if dst and want are nil, want = src
+	types []proto.Message
+}
+
+var testMerges = []testMerge{{
+	desc: "clone a large message",
+	src: protobuild.Message{
+		"optional_int32":       1001,
+		"optional_int64":       1002,
+		"optional_uint32":      1003,
+		"optional_uint64":      1004,
+		"optional_sint32":      1005,
+		"optional_sint64":      1006,
+		"optional_fixed32":     1007,
+		"optional_fixed64":     1008,
+		"optional_sfixed32":    1009,
+		"optional_sfixed64":    1010,
+		"optional_float":       1011.5,
+		"optional_double":      1012.5,
+		"optional_bool":        true,
+		"optional_string":      "string",
+		"optional_bytes":       []byte("bytes"),
+		"optional_nested_enum": 1,
+		"optional_nested_message": protobuild.Message{
+			"a": 100,
+		},
+		"repeated_int32":       []int32{1001, 2001},
+		"repeated_int64":       []int64{1002, 2002},
+		"repeated_uint32":      []uint32{1003, 2003},
+		"repeated_uint64":      []uint64{1004, 2004},
+		"repeated_sint32":      []int32{1005, 2005},
+		"repeated_sint64":      []int64{1006, 2006},
+		"repeated_fixed32":     []uint32{1007, 2007},
+		"repeated_fixed64":     []uint64{1008, 2008},
+		"repeated_sfixed32":    []int32{1009, 2009},
+		"repeated_sfixed64":    []int64{1010, 2010},
+		"repeated_float":       []float32{1011.5, 2011.5},
+		"repeated_double":      []float64{1012.5, 2012.5},
+		"repeated_bool":        []bool{true, false},
+		"repeated_string":      []string{"foo", "bar"},
+		"repeated_bytes":       []string{"FOO", "BAR"},
+		"repeated_nested_enum": []string{"FOO", "BAR"},
+		"repeated_nested_message": []protobuild.Message{
+			{"a": 200},
+			{"a": 300},
+		},
+	},
+}, {
+	desc: "clone maps",
+	src: protobuild.Message{
+		"map_int32_int32":       map[int32]int32{1056: 1156, 2056: 2156},
+		"map_int64_int64":       map[int64]int64{1057: 1157, 2057: 2157},
+		"map_uint32_uint32":     map[uint32]uint32{1058: 1158, 2058: 2158},
+		"map_uint64_uint64":     map[uint64]uint64{1059: 1159, 2059: 2159},
+		"map_sint32_sint32":     map[int32]int32{1060: 1160, 2060: 2160},
+		"map_sint64_sint64":     map[int64]int64{1061: 1161, 2061: 2161},
+		"map_fixed32_fixed32":   map[uint32]uint32{1062: 1162, 2062: 2162},
+		"map_fixed64_fixed64":   map[uint64]uint64{1063: 1163, 2063: 2163},
+		"map_sfixed32_sfixed32": map[int32]int32{1064: 1164, 2064: 2164},
+		"map_sfixed64_sfixed64": map[int64]int64{1065: 1165, 2065: 2165},
+		"map_int32_float":       map[int32]float32{1066: 1166.5, 2066: 2166.5},
+		"map_int32_double":      map[int32]float64{1067: 1167.5, 2067: 2167.5},
+		"map_bool_bool":         map[bool]bool{true: false, false: true},
+		"map_string_string":     map[string]string{"69.1.key": "69.1.val", "69.2.key": "69.2.val"},
+		"map_string_bytes":      map[string][]byte{"70.1.key": []byte("70.1.val"), "70.2.key": []byte("70.2.val")},
+		"map_string_nested_message": map[string]protobuild.Message{
+			"71.1.key": {"a": 1171},
+			"71.2.key": {"a": 2171},
+		},
+		"map_string_nested_enum": map[string]string{"73.1.key": "FOO", "73.2.key": "BAR"},
+	},
+	types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
+}, {
+	desc: "clone oneof uint32",
+	src: protobuild.Message{
+		"oneof_uint32": 1111,
+	},
+	types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
+}, {
+	desc: "clone oneof string",
+	src: protobuild.Message{
+		"oneof_string": "string",
+	},
+	types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
+}, {
+	desc: "clone oneof bytes",
+	src: protobuild.Message{
+		"oneof_bytes": "bytes",
+	},
+	types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
+}, {
+	desc: "clone oneof bool",
+	src: protobuild.Message{
+		"oneof_bool": true,
+	},
+	types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
+}, {
+	desc: "clone oneof uint64",
+	src: protobuild.Message{
+		"oneof_uint64": 100,
+	},
+	types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
+}, {
+	desc: "clone oneof float",
+	src: protobuild.Message{
+		"oneof_float": 100,
+	},
+	types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
+}, {
+	desc: "clone oneof double",
+	src: protobuild.Message{
+		"oneof_double": 1111,
+	},
+	types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
+}, {
+	desc: "clone oneof enum",
+	src: protobuild.Message{
+		"oneof_enum": 1,
+	},
+	types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
+}, {
+	desc: "clone oneof message",
+	src: protobuild.Message{
+		"oneof_nested_message": protobuild.Message{
+			"a": 1,
+		},
+	},
+	types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
+}, {
+	desc: "clone oneof group",
+	src: protobuild.Message{
+		"oneofgroup": protobuild.Message{
+			"a": 1,
+		},
+	},
+	types: []proto.Message{&testpb.TestAllTypes{}},
+}, {
+	desc: "merge bytes",
+	dst: protobuild.Message{
+		"optional_bytes":   []byte{1, 2, 3},
+		"repeated_bytes":   [][]byte{{1, 2}, {3, 4}},
+		"map_string_bytes": map[string][]byte{"alpha": {1, 2, 3}},
+	},
+	src: protobuild.Message{
+		"optional_bytes":   []byte{4, 5, 6},
+		"repeated_bytes":   [][]byte{{5, 6}, {7, 8}},
+		"map_string_bytes": map[string][]byte{"alpha": {4, 5, 6}, "bravo": {1, 2, 3}},
+	},
+	want: protobuild.Message{
+		"optional_bytes":   []byte{4, 5, 6},
+		"repeated_bytes":   [][]byte{{1, 2}, {3, 4}, {5, 6}, {7, 8}},
+		"map_string_bytes": map[string][]byte{"alpha": {4, 5, 6}, "bravo": {1, 2, 3}},
+	},
+	types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
+}, {
+	desc: "merge singular fields",
+	dst: protobuild.Message{
+		"optional_int32":       1,
+		"optional_int64":       1,
+		"optional_uint32":      1,
+		"optional_uint64":      1,
+		"optional_sint32":      1,
+		"optional_sint64":      1,
+		"optional_fixed32":     1,
+		"optional_fixed64":     1,
+		"optional_sfixed32":    1,
+		"optional_sfixed64":    1,
+		"optional_float":       1,
+		"optional_double":      1,
+		"optional_bool":        false,
+		"optional_string":      "1",
+		"optional_bytes":       "1",
+		"optional_nested_enum": 1,
+		"optional_nested_message": protobuild.Message{
+			"a": 1,
+			"corecursive": protobuild.Message{
+				"optional_int64": 1,
+			},
+		},
+	},
+	src: protobuild.Message{
+		"optional_int32":       2,
+		"optional_int64":       2,
+		"optional_uint32":      2,
+		"optional_uint64":      2,
+		"optional_sint32":      2,
+		"optional_sint64":      2,
+		"optional_fixed32":     2,
+		"optional_fixed64":     2,
+		"optional_sfixed32":    2,
+		"optional_sfixed64":    2,
+		"optional_float":       2,
+		"optional_double":      2,
+		"optional_bool":        true,
+		"optional_string":      "2",
+		"optional_bytes":       "2",
+		"optional_nested_enum": 2,
+		"optional_nested_message": protobuild.Message{
+			"a": 2,
+			"corecursive": protobuild.Message{
+				"optional_int64": 2,
+			},
+		},
+	},
+	want: protobuild.Message{
+		"optional_int32":       2,
+		"optional_int64":       2,
+		"optional_uint32":      2,
+		"optional_uint64":      2,
+		"optional_sint32":      2,
+		"optional_sint64":      2,
+		"optional_fixed32":     2,
+		"optional_fixed64":     2,
+		"optional_sfixed32":    2,
+		"optional_sfixed64":    2,
+		"optional_float":       2,
+		"optional_double":      2,
+		"optional_bool":        true,
+		"optional_string":      "2",
+		"optional_bytes":       "2",
+		"optional_nested_enum": 2,
+		"optional_nested_message": protobuild.Message{
+			"a": 2,
+			"corecursive": protobuild.Message{
+				"optional_int64": 2,
+			},
+		},
+	},
+}, {
+	desc: "no merge of empty singular fields",
+	dst: protobuild.Message{
+		"optional_int32":       1,
+		"optional_int64":       1,
+		"optional_uint32":      1,
+		"optional_uint64":      1,
+		"optional_sint32":      1,
+		"optional_sint64":      1,
+		"optional_fixed32":     1,
+		"optional_fixed64":     1,
+		"optional_sfixed32":    1,
+		"optional_sfixed64":    1,
+		"optional_float":       1,
+		"optional_double":      1,
+		"optional_bool":        false,
+		"optional_string":      "1",
+		"optional_bytes":       "1",
+		"optional_nested_enum": 1,
+		"optional_nested_message": protobuild.Message{
+			"a": 1,
+			"corecursive": protobuild.Message{
+				"optional_int64": 1,
+			},
+		},
+	},
+	src: protobuild.Message{
+		"optional_nested_message": protobuild.Message{
+			"a": 1,
+			"corecursive": protobuild.Message{
+				"optional_int32": 2,
+			},
+		},
+	},
+	want: protobuild.Message{
+		"optional_int32":       1,
+		"optional_int64":       1,
+		"optional_uint32":      1,
+		"optional_uint64":      1,
+		"optional_sint32":      1,
+		"optional_sint64":      1,
+		"optional_fixed32":     1,
+		"optional_fixed64":     1,
+		"optional_sfixed32":    1,
+		"optional_sfixed64":    1,
+		"optional_float":       1,
+		"optional_double":      1,
+		"optional_bool":        false,
+		"optional_string":      "1",
+		"optional_bytes":       "1",
+		"optional_nested_enum": 1,
+		"optional_nested_message": protobuild.Message{
+			"a": 1,
+			"corecursive": protobuild.Message{
+				"optional_int32": 2,
+				"optional_int64": 1,
+			},
+		},
+	},
+}, {
+	desc: "merge list fields",
+	dst: protobuild.Message{
+		"repeated_int32":       []int32{1, 2, 3},
+		"repeated_int64":       []int64{1, 2, 3},
+		"repeated_uint32":      []uint32{1, 2, 3},
+		"repeated_uint64":      []uint64{1, 2, 3},
+		"repeated_sint32":      []int32{1, 2, 3},
+		"repeated_sint64":      []int64{1, 2, 3},
+		"repeated_fixed32":     []uint32{1, 2, 3},
+		"repeated_fixed64":     []uint64{1, 2, 3},
+		"repeated_sfixed32":    []int32{1, 2, 3},
+		"repeated_sfixed64":    []int64{1, 2, 3},
+		"repeated_float":       []float32{1, 2, 3},
+		"repeated_double":      []float64{1, 2, 3},
+		"repeated_bool":        []bool{true},
+		"repeated_string":      []string{"a", "b", "c"},
+		"repeated_bytes":       []string{"a", "b", "c"},
+		"repeated_nested_enum": []int{1, 2, 3},
+		"repeated_nested_message": []protobuild.Message{
+			{"a": 100},
+			{"a": 200},
+		},
+	},
+	src: protobuild.Message{
+		"repeated_int32":       []int32{4, 5, 6},
+		"repeated_int64":       []int64{4, 5, 6},
+		"repeated_uint32":      []uint32{4, 5, 6},
+		"repeated_uint64":      []uint64{4, 5, 6},
+		"repeated_sint32":      []int32{4, 5, 6},
+		"repeated_sint64":      []int64{4, 5, 6},
+		"repeated_fixed32":     []uint32{4, 5, 6},
+		"repeated_fixed64":     []uint64{4, 5, 6},
+		"repeated_sfixed32":    []int32{4, 5, 6},
+		"repeated_sfixed64":    []int64{4, 5, 6},
+		"repeated_float":       []float32{4, 5, 6},
+		"repeated_double":      []float64{4, 5, 6},
+		"repeated_bool":        []bool{false},
+		"repeated_string":      []string{"d", "e", "f"},
+		"repeated_bytes":       []string{"d", "e", "f"},
+		"repeated_nested_enum": []int{4, 5, 6},
+		"repeated_nested_message": []protobuild.Message{
+			{"a": 300},
+			{"a": 400},
+		},
+	},
+	want: protobuild.Message{
+		"repeated_int32":       []int32{1, 2, 3, 4, 5, 6},
+		"repeated_int64":       []int64{1, 2, 3, 4, 5, 6},
+		"repeated_uint32":      []uint32{1, 2, 3, 4, 5, 6},
+		"repeated_uint64":      []uint64{1, 2, 3, 4, 5, 6},
+		"repeated_sint32":      []int32{1, 2, 3, 4, 5, 6},
+		"repeated_sint64":      []int64{1, 2, 3, 4, 5, 6},
+		"repeated_fixed32":     []uint32{1, 2, 3, 4, 5, 6},
+		"repeated_fixed64":     []uint64{1, 2, 3, 4, 5, 6},
+		"repeated_sfixed32":    []int32{1, 2, 3, 4, 5, 6},
+		"repeated_sfixed64":    []int64{1, 2, 3, 4, 5, 6},
+		"repeated_float":       []float32{1, 2, 3, 4, 5, 6},
+		"repeated_double":      []float64{1, 2, 3, 4, 5, 6},
+		"repeated_bool":        []bool{true, false},
+		"repeated_string":      []string{"a", "b", "c", "d", "e", "f"},
+		"repeated_bytes":       []string{"a", "b", "c", "d", "e", "f"},
+		"repeated_nested_enum": []int{1, 2, 3, 4, 5, 6},
+		"repeated_nested_message": []protobuild.Message{
+			{"a": 100},
+			{"a": 200},
+			{"a": 300},
+			{"a": 400},
+		},
+	},
+}, {
+	desc: "merge map fields",
+	dst: protobuild.Message{
+		"map_int32_int32":       map[int]int{1: 1, 3: 1},
+		"map_int64_int64":       map[int]int{1: 1, 3: 1},
+		"map_uint32_uint32":     map[int]int{1: 1, 3: 1},
+		"map_uint64_uint64":     map[int]int{1: 1, 3: 1},
+		"map_sint32_sint32":     map[int]int{1: 1, 3: 1},
+		"map_sint64_sint64":     map[int]int{1: 1, 3: 1},
+		"map_fixed32_fixed32":   map[int]int{1: 1, 3: 1},
+		"map_fixed64_fixed64":   map[int]int{1: 1, 3: 1},
+		"map_sfixed32_sfixed32": map[int]int{1: 1, 3: 1},
+		"map_sfixed64_sfixed64": map[int]int{1: 1, 3: 1},
+		"map_int32_float":       map[int]int{1: 1, 3: 1},
+		"map_int32_double":      map[int]int{1: 1, 3: 1},
+		"map_bool_bool":         map[bool]bool{true: true},
+		"map_string_string":     map[string]string{"a": "1", "ab": "1"},
+		"map_string_bytes":      map[string]string{"a": "1", "ab": "1"},
+		"map_string_nested_message": map[string]protobuild.Message{
+			"a": {"a": 1},
+			"ab": {
+				"a": 1,
+				"corecursive": protobuild.Message{
+					"map_int32_int32": map[int]int{1: 1, 3: 1},
+				},
+			},
+		},
+		"map_string_nested_enum": map[string]int{"a": 1, "ab": 1},
+	},
+	src: protobuild.Message{
+		"map_int32_int32":       map[int]int{2: 2, 3: 2},
+		"map_int64_int64":       map[int]int{2: 2, 3: 2},
+		"map_uint32_uint32":     map[int]int{2: 2, 3: 2},
+		"map_uint64_uint64":     map[int]int{2: 2, 3: 2},
+		"map_sint32_sint32":     map[int]int{2: 2, 3: 2},
+		"map_sint64_sint64":     map[int]int{2: 2, 3: 2},
+		"map_fixed32_fixed32":   map[int]int{2: 2, 3: 2},
+		"map_fixed64_fixed64":   map[int]int{2: 2, 3: 2},
+		"map_sfixed32_sfixed32": map[int]int{2: 2, 3: 2},
+		"map_sfixed64_sfixed64": map[int]int{2: 2, 3: 2},
+		"map_int32_float":       map[int]int{2: 2, 3: 2},
+		"map_int32_double":      map[int]int{2: 2, 3: 2},
+		"map_bool_bool":         map[bool]bool{false: false},
+		"map_string_string":     map[string]string{"b": "2", "ab": "2"},
+		"map_string_bytes":      map[string]string{"b": "2", "ab": "2"},
+		"map_string_nested_message": map[string]protobuild.Message{
+			"b": {"a": 2},
+			"ab": {
+				"a": 2,
+				"corecursive": protobuild.Message{
+					"map_int32_int32": map[int]int{2: 2, 3: 2},
+				},
+			},
+		},
+		"map_string_nested_enum": map[string]int{"b": 2, "ab": 2},
+	},
+	want: protobuild.Message{
+		"map_int32_int32":       map[int]int{1: 1, 2: 2, 3: 2},
+		"map_int64_int64":       map[int]int{1: 1, 2: 2, 3: 2},
+		"map_uint32_uint32":     map[int]int{1: 1, 2: 2, 3: 2},
+		"map_uint64_uint64":     map[int]int{1: 1, 2: 2, 3: 2},
+		"map_sint32_sint32":     map[int]int{1: 1, 2: 2, 3: 2},
+		"map_sint64_sint64":     map[int]int{1: 1, 2: 2, 3: 2},
+		"map_fixed32_fixed32":   map[int]int{1: 1, 2: 2, 3: 2},
+		"map_fixed64_fixed64":   map[int]int{1: 1, 2: 2, 3: 2},
+		"map_sfixed32_sfixed32": map[int]int{1: 1, 2: 2, 3: 2},
+		"map_sfixed64_sfixed64": map[int]int{1: 1, 2: 2, 3: 2},
+		"map_int32_float":       map[int]int{1: 1, 2: 2, 3: 2},
+		"map_int32_double":      map[int]int{1: 1, 2: 2, 3: 2},
+		"map_bool_bool":         map[bool]bool{true: true, false: false},
+		"map_string_string":     map[string]string{"a": "1", "b": "2", "ab": "2"},
+		"map_string_bytes":      map[string]string{"a": "1", "b": "2", "ab": "2"},
+		"map_string_nested_message": map[string]protobuild.Message{
+			"a": {"a": 1},
+			"b": {"a": 2},
+			"ab": {
+				"a": 2,
+				"corecursive": protobuild.Message{
+					// The map item "ab" was entirely replaced, so
+					// this does not contain 1:1 from dst.
+					"map_int32_int32": map[int]int{2: 2, 3: 2},
+				},
+			},
+		},
+		"map_string_nested_enum": map[string]int{"a": 1, "b": 2, "ab": 2},
+	},
+	types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
+}, {
+	desc: "merge oneof message fields",
+	dst: protobuild.Message{
+		"oneof_nested_message": protobuild.Message{
+			"a": 100,
+		},
+	},
+	src: protobuild.Message{
+		"oneof_nested_message": protobuild.Message{
+			"corecursive": protobuild.Message{
+				"optional_int64": 1000,
+			},
+		},
+	},
+	want: protobuild.Message{
+		"oneof_nested_message": protobuild.Message{
+			"a": 100,
+			"corecursive": protobuild.Message{
+				"optional_int64": 1000,
+			},
+		},
+	},
+	types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
+}, {
+	desc: "merge oneof scalar fields",
+	dst: protobuild.Message{
+		"oneof_uint32": 100,
+	},
+	src: protobuild.Message{
+		"oneof_float": 3.14152,
+	},
+	want: protobuild.Message{
+		"oneof_float": 3.14152,
+	},
+	types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
+}, {
+	desc: "merge unknown fields",
+	dst: protobuild.Message{
+		protobuild.Unknown: pack.Message{
+			pack.Tag{Number: 50000, Type: pack.VarintType}, pack.Svarint(-5),
+		}.Marshal(),
+	},
+	src: protobuild.Message{
+		protobuild.Unknown: pack.Message{
+			pack.Tag{Number: 500000, Type: pack.VarintType}, pack.Svarint(-50),
+		}.Marshal(),
+	},
+	want: protobuild.Message{
+		protobuild.Unknown: pack.Message{
+			pack.Tag{Number: 50000, Type: pack.VarintType}, pack.Svarint(-5),
+			pack.Tag{Number: 500000, Type: pack.VarintType}, pack.Svarint(-50),
+		}.Marshal(),
+	},
+}, {
+	desc: "clone legacy message",
+	src: protobuild.Message{"f1": protobuild.Message{
+		"optional_int32":        1,
+		"optional_int64":        1,
+		"optional_uint32":       1,
+		"optional_uint64":       1,
+		"optional_sint32":       1,
+		"optional_sint64":       1,
+		"optional_fixed32":      1,
+		"optional_fixed64":      1,
+		"optional_sfixed32":     1,
+		"optional_sfixed64":     1,
+		"optional_float":        1,
+		"optional_double":       1,
+		"optional_bool":         true,
+		"optional_string":       "string",
+		"optional_bytes":        "bytes",
+		"optional_sibling_enum": 1,
+		"optional_sibling_message": protobuild.Message{
+			"f1": "value",
+		},
+		"repeated_int32":        []int32{1},
+		"repeated_int64":        []int64{1},
+		"repeated_uint32":       []uint32{1},
+		"repeated_uint64":       []uint64{1},
+		"repeated_sint32":       []int32{1},
+		"repeated_sint64":       []int64{1},
+		"repeated_fixed32":      []uint32{1},
+		"repeated_fixed64":      []uint64{1},
+		"repeated_sfixed32":     []int32{1},
+		"repeated_sfixed64":     []int64{1},
+		"repeated_float":        []float32{1},
+		"repeated_double":       []float64{1},
+		"repeated_bool":         []bool{true},
+		"repeated_string":       []string{"string"},
+		"repeated_bytes":        []string{"bytes"},
+		"repeated_sibling_enum": []int{1},
+		"repeated_sibling_message": []protobuild.Message{
+			{"f1": "1"},
+		},
+		"map_bool_int32":    map[bool]int{true: 1},
+		"map_bool_int64":    map[bool]int{true: 1},
+		"map_bool_uint32":   map[bool]int{true: 1},
+		"map_bool_uint64":   map[bool]int{true: 1},
+		"map_bool_sint32":   map[bool]int{true: 1},
+		"map_bool_sint64":   map[bool]int{true: 1},
+		"map_bool_fixed32":  map[bool]int{true: 1},
+		"map_bool_fixed64":  map[bool]int{true: 1},
+		"map_bool_sfixed32": map[bool]int{true: 1},
+		"map_bool_sfixed64": map[bool]int{true: 1},
+		"map_bool_float":    map[bool]int{true: 1},
+		"map_bool_double":   map[bool]int{true: 1},
+		"map_bool_bool":     map[bool]bool{true: false},
+		"map_bool_string":   map[bool]string{true: "1"},
+		"map_bool_bytes":    map[bool]string{true: "1"},
+		"map_bool_sibling_message": map[bool]protobuild.Message{
+			true: {"f1": "1"},
+		},
+		"map_bool_sibling_enum": map[bool]int{true: 1},
+		"oneof_sibling_message": protobuild.Message{
+			"f1": "1",
+		},
+	}},
+	types: []proto.Message{&legacypb.Legacy{}},
+}}
+
 func TestMerge(t *testing.T) {
-	tests := []struct {
-		desc string
-		dst  proto.Message
-		src  proto.Message
-		want proto.Message
+	for _, tt := range testMerges {
+		for _, mt := range templateMessages(tt.types...) {
+			t.Run(fmt.Sprintf("%s (%v)", tt.desc, mt.Descriptor().FullName()), func(t *testing.T) {
+				dst := mt.New().Interface()
+				tt.dst.Build(dst.ProtoReflect())
 
-		// If provided, mutator is run on src after merging.
-		// It reports whether a mutation is expected to be observable in dst
-		// if Shallow is enabled.
-		mutator func(proto.Message) bool
-	}{{
-		desc: "merge from nil message",
-		dst:  new(testpb.TestAllTypes),
-		src:  (*testpb.TestAllTypes)(nil),
-		want: new(testpb.TestAllTypes),
-	}, {
-		desc: "clone a large message",
-		dst:  new(testpb.TestAllTypes),
-		src: &testpb.TestAllTypes{
-			OptionalInt64:      proto.Int64(0),
-			OptionalNestedEnum: testpb.TestAllTypes_NestedEnum(1).Enum(),
-			OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{
-				A: proto.Int32(100),
-			},
-			RepeatedSfixed32: []int32{1, 2, 3},
-			RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{
-				{A: proto.Int32(200)},
-				{A: proto.Int32(300)},
-			},
-			MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{
-				"fizz": 400,
-				"buzz": 500,
-			},
-			MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{
-				"foo": {A: proto.Int32(600)},
-				"bar": {A: proto.Int32(700)},
-			},
-			OneofField: &testpb.TestAllTypes_OneofNestedMessage{
-				&testpb.TestAllTypes_NestedMessage{
-					A: proto.Int32(800),
-				},
-			},
-		},
-		want: &testpb.TestAllTypes{
-			OptionalInt64:      proto.Int64(0),
-			OptionalNestedEnum: testpb.TestAllTypes_NestedEnum(1).Enum(),
-			OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{
-				A: proto.Int32(100),
-			},
-			RepeatedSfixed32: []int32{1, 2, 3},
-			RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{
-				{A: proto.Int32(200)},
-				{A: proto.Int32(300)},
-			},
-			MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{
-				"fizz": 400,
-				"buzz": 500,
-			},
-			MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{
-				"foo": {A: proto.Int32(600)},
-				"bar": {A: proto.Int32(700)},
-			},
-			OneofField: &testpb.TestAllTypes_OneofNestedMessage{
-				&testpb.TestAllTypes_NestedMessage{
-					A: proto.Int32(800),
-				},
-			},
-		},
-		mutator: func(mi proto.Message) bool {
-			m := mi.(*testpb.TestAllTypes)
-			*m.OptionalInt64++
-			*m.OptionalNestedEnum++
-			*m.OptionalNestedMessage.A++
-			m.RepeatedSfixed32[0]++
-			*m.RepeatedNestedMessage[0].A++
-			delete(m.MapStringNestedEnum, "fizz")
-			*m.MapStringNestedMessage["foo"].A++
-			*m.OneofField.(*testpb.TestAllTypes_OneofNestedMessage).OneofNestedMessage.A++
-			return true
-		},
-	}, {
-		desc: "merge bytes",
-		dst: &testpb.TestAllTypes{
-			OptionalBytes:  []byte{1, 2, 3},
-			RepeatedBytes:  [][]byte{{1, 2}, {3, 4}},
-			MapStringBytes: map[string][]byte{"alpha": {1, 2, 3}},
-		},
-		src: &testpb.TestAllTypes{
-			OptionalBytes:  []byte{4, 5, 6},
-			RepeatedBytes:  [][]byte{{5, 6}, {7, 8}},
-			MapStringBytes: map[string][]byte{"alpha": {4, 5, 6}, "bravo": {1, 2, 3}},
-		},
-		want: &testpb.TestAllTypes{
-			OptionalBytes:  []byte{4, 5, 6},
-			RepeatedBytes:  [][]byte{{1, 2}, {3, 4}, {5, 6}, {7, 8}},
-			MapStringBytes: map[string][]byte{"alpha": {4, 5, 6}, "bravo": {1, 2, 3}},
-		},
-		mutator: func(mi proto.Message) bool {
-			m := mi.(*testpb.TestAllTypes)
-			m.OptionalBytes[0]++
-			m.RepeatedBytes[0][0]++
-			m.MapStringBytes["alpha"][0]++
-			return true
-		},
-	}, {
-		desc: "merge singular fields",
-		dst: &testpb.TestAllTypes{
-			OptionalInt32:      proto.Int32(1),
-			OptionalInt64:      proto.Int64(1),
-			OptionalNestedEnum: testpb.TestAllTypes_NestedEnum(10).Enum(),
-			OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{
-				A: proto.Int32(100),
-				Corecursive: &testpb.TestAllTypes{
-					OptionalInt64: proto.Int64(1000),
-				},
-			},
-		},
-		src: &testpb.TestAllTypes{
-			OptionalInt64:      proto.Int64(2),
-			OptionalNestedEnum: testpb.TestAllTypes_NestedEnum(20).Enum(),
-			OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{
-				A: proto.Int32(200),
-			},
-		},
-		want: &testpb.TestAllTypes{
-			OptionalInt32:      proto.Int32(1),
-			OptionalInt64:      proto.Int64(2),
-			OptionalNestedEnum: testpb.TestAllTypes_NestedEnum(20).Enum(),
-			OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{
-				A: proto.Int32(200),
-				Corecursive: &testpb.TestAllTypes{
-					OptionalInt64: proto.Int64(1000),
-				},
-			},
-		},
-		mutator: func(mi proto.Message) bool {
-			m := mi.(*testpb.TestAllTypes)
-			*m.OptionalInt64++
-			*m.OptionalNestedEnum++
-			*m.OptionalNestedMessage.A++
-			return false // scalar mutations are not observable in shallow copy
-		},
-	}, {
-		desc: "merge list fields",
-		dst: &testpb.TestAllTypes{
-			RepeatedSfixed32: []int32{1, 2, 3},
-			RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{
-				{A: proto.Int32(100)},
-				{A: proto.Int32(200)},
-			},
-		},
-		src: &testpb.TestAllTypes{
-			RepeatedSfixed32: []int32{4, 5, 6},
-			RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{
-				{A: proto.Int32(300)},
-				{A: proto.Int32(400)},
-			},
-		},
-		want: &testpb.TestAllTypes{
-			RepeatedSfixed32: []int32{1, 2, 3, 4, 5, 6},
-			RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{
-				{A: proto.Int32(100)},
-				{A: proto.Int32(200)},
-				{A: proto.Int32(300)},
-				{A: proto.Int32(400)},
-			},
-		},
-		mutator: func(mi proto.Message) bool {
-			m := mi.(*testpb.TestAllTypes)
-			m.RepeatedSfixed32[0]++
-			*m.RepeatedNestedMessage[0].A++
-			return true
-		},
-	}, {
-		desc: "merge map fields",
-		dst: &testpb.TestAllTypes{
-			MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{
-				"fizz": 100,
-				"buzz": 200,
-				"guzz": 300,
-			},
-			MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{
-				"foo": {A: proto.Int32(400)},
-			},
-		},
-		src: &testpb.TestAllTypes{
-			MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{
-				"fizz": 1000,
-				"buzz": 2000,
-			},
-			MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{
-				"foo": {A: proto.Int32(3000)},
-				"bar": {},
-			},
-		},
-		want: &testpb.TestAllTypes{
-			MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{
-				"fizz": 1000,
-				"buzz": 2000,
-				"guzz": 300,
-			},
-			MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{
-				"foo": {A: proto.Int32(3000)},
-				"bar": {},
-			},
-		},
-		mutator: func(mi proto.Message) bool {
-			m := mi.(*testpb.TestAllTypes)
-			delete(m.MapStringNestedEnum, "fizz")
-			m.MapStringNestedMessage["bar"].A = proto.Int32(1)
-			return true
-		},
-	}, {
-		desc: "merge oneof message fields",
-		dst: &testpb.TestAllTypes{
-			OneofField: &testpb.TestAllTypes_OneofNestedMessage{
-				&testpb.TestAllTypes_NestedMessage{
-					A: proto.Int32(100),
-				},
-			},
-		},
-		src: &testpb.TestAllTypes{
-			OneofField: &testpb.TestAllTypes_OneofNestedMessage{
-				&testpb.TestAllTypes_NestedMessage{
-					Corecursive: &testpb.TestAllTypes{
-						OptionalInt64: proto.Int64(1000),
-					},
-				},
-			},
-		},
-		want: &testpb.TestAllTypes{
-			OneofField: &testpb.TestAllTypes_OneofNestedMessage{
-				&testpb.TestAllTypes_NestedMessage{
-					A: proto.Int32(100),
-					Corecursive: &testpb.TestAllTypes{
-						OptionalInt64: proto.Int64(1000),
-					},
-				},
-			},
-		},
-		mutator: func(mi proto.Message) bool {
-			m := mi.(*testpb.TestAllTypes)
-			*m.OneofField.(*testpb.TestAllTypes_OneofNestedMessage).OneofNestedMessage.Corecursive.OptionalInt64++
-			return true
-		},
-	}, {
-		desc: "merge oneof scalar fields",
-		dst: &testpb.TestAllTypes{
-			OneofField: &testpb.TestAllTypes_OneofUint32{100},
-		},
-		src: &testpb.TestAllTypes{
-			OneofField: &testpb.TestAllTypes_OneofFloat{3.14152},
-		},
-		want: &testpb.TestAllTypes{
-			OneofField: &testpb.TestAllTypes_OneofFloat{3.14152},
-		},
-		mutator: func(mi proto.Message) bool {
-			m := mi.(*testpb.TestAllTypes)
-			m.OneofField.(*testpb.TestAllTypes_OneofFloat).OneofFloat++
-			return false // scalar mutations are not observable in shallow copy
-		},
-	}, {
-		desc: "merge extension fields",
-		dst: func() proto.Message {
-			m := new(testpb.TestAllExtensions)
-			proto.SetExtension(m, testpb.E_OptionalInt32, int32(32))
-			proto.SetExtension(m, testpb.E_OptionalNestedMessage,
-				&testpb.TestAllExtensions_NestedMessage{
-					A: proto.Int32(50),
-				},
-			)
-			proto.SetExtension(m, testpb.E_RepeatedFixed32, []uint32{1, 2, 3})
-			return m
-		}(),
-		src: func() proto.Message {
-			m2 := new(testpb.TestAllExtensions)
-			proto.SetExtension(m2, testpb.E_OptionalInt64, int64(1000))
-			m := new(testpb.TestAllExtensions)
-			proto.SetExtension(m, testpb.E_OptionalInt64, int64(64))
-			proto.SetExtension(m, testpb.E_OptionalNestedMessage,
-				&testpb.TestAllExtensions_NestedMessage{
-					Corecursive: m2,
-				},
-			)
-			proto.SetExtension(m, testpb.E_RepeatedFixed32, []uint32{4, 5, 6})
-			return m
-		}(),
-		want: func() proto.Message {
-			m2 := new(testpb.TestAllExtensions)
-			proto.SetExtension(m2, testpb.E_OptionalInt64, int64(1000))
-			m := new(testpb.TestAllExtensions)
-			proto.SetExtension(m, testpb.E_OptionalInt32, int32(32))
-			proto.SetExtension(m, testpb.E_OptionalInt64, int64(64))
-			proto.SetExtension(m, testpb.E_OptionalNestedMessage,
-				&testpb.TestAllExtensions_NestedMessage{
-					A:           proto.Int32(50),
-					Corecursive: m2,
-				},
-			)
-			proto.SetExtension(m, testpb.E_RepeatedFixed32, []uint32{1, 2, 3, 4, 5, 6})
-			return m
-		}(),
-	}, {
-		desc: "merge unknown fields",
-		dst: func() proto.Message {
-			m := new(testpb.TestAllTypes)
-			m.ProtoReflect().SetUnknown(pack.Message{
-				pack.Tag{Number: 50000, Type: pack.VarintType}, pack.Svarint(-5),
-			}.Marshal())
-			return m
-		}(),
-		src: func() proto.Message {
-			m := new(testpb.TestAllTypes)
-			m.ProtoReflect().SetUnknown(pack.Message{
-				pack.Tag{Number: 500000, Type: pack.VarintType}, pack.Svarint(-50),
-			}.Marshal())
-			return m
-		}(),
-		want: func() proto.Message {
-			m := new(testpb.TestAllTypes)
-			m.ProtoReflect().SetUnknown(pack.Message{
-				pack.Tag{Number: 50000, Type: pack.VarintType}, pack.Svarint(-5),
-				pack.Tag{Number: 500000, Type: pack.VarintType}, pack.Svarint(-50),
-			}.Marshal())
-			return m
-		}(),
-	}}
+				src := mt.New().Interface()
+				tt.src.Build(src.ProtoReflect())
 
-	for _, tt := range tests {
-		t.Run(tt.desc, func(t *testing.T) {
-			// Merge should be semantically equivalent to unmarshaling the
-			// encoded form of src into the current dst.
-			b1, err := proto.MarshalOptions{AllowPartial: true}.Marshal(tt.dst)
-			if err != nil {
-				t.Fatalf("Marshal(dst) error: %v", err)
-			}
-			b2, err := proto.MarshalOptions{AllowPartial: true}.Marshal(tt.src)
-			if err != nil {
-				t.Fatalf("Marshal(src) error: %v", err)
-			}
-			dst := tt.dst.ProtoReflect().New().Interface()
-			err = proto.UnmarshalOptions{AllowPartial: true}.Unmarshal(append(b1, b2...), dst)
-			if err != nil {
-				t.Fatalf("Unmarshal() error: %v", err)
-			}
-			if !proto.Equal(dst, tt.want) {
-				t.Fatalf("Unmarshal(Marshal(dst)+Marshal(src)) mismatch: got %v, want %v", dst, tt.want)
-			}
-
-			proto.Merge(tt.dst, tt.src)
-			if !proto.Equal(tt.dst, tt.want) {
-				t.Fatalf("Merge() mismatch:\n got %v\nwant %v", tt.dst, tt.want)
-			}
-			if tt.mutator != nil {
-				if !proto.Equal(tt.dst, tt.want) {
-					t.Fatalf("mutation observed in dest after modifying merge source:\n got %v\nwant %v", tt.dst, tt.want)
+				want := mt.New().Interface()
+				if tt.dst == nil && tt.want == nil {
+					tt.src.Build(want.ProtoReflect())
+				} else {
+					tt.want.Build(want.ProtoReflect())
 				}
-			}
-		})
+
+				// Merge should be semantically equivalent to unmarshaling the
+				// encoded form of src into the current dst.
+				b1, err := proto.MarshalOptions{AllowPartial: true}.Marshal(dst)
+				if err != nil {
+					t.Fatalf("Marshal(dst) error: %v", err)
+				}
+				b2, err := proto.MarshalOptions{AllowPartial: true}.Marshal(src)
+				if err != nil {
+					t.Fatalf("Marshal(src) error: %v", err)
+				}
+				unmarshaled := dst.ProtoReflect().New().Interface()
+				err = proto.UnmarshalOptions{AllowPartial: true}.Unmarshal(append(b1, b2...), unmarshaled)
+				if err != nil {
+					t.Fatalf("Unmarshal() error: %v", err)
+				}
+				if !proto.Equal(unmarshaled, want) {
+					t.Fatalf("Unmarshal(Marshal(dst)+Marshal(src)) mismatch:\n got %v\nwant %v\ndiff (-want,+got):\n%v", unmarshaled, want, cmp.Diff(want, unmarshaled, protocmp.Transform()))
+				}
+
+				// Test heterogeneous MessageTypes by merging into a
+				// dynamic message.
+				ddst := dynamicpb.NewMessage(mt.Descriptor())
+				tt.dst.Build(ddst.ProtoReflect())
+				proto.Merge(ddst, src)
+				if !proto.Equal(ddst, want) {
+					t.Fatalf("Merge() into dynamic message mismatch:\n got %v\nwant %v\ndiff (-want,+got):\n%v", ddst, want, cmp.Diff(want, ddst, protocmp.Transform()))
+				}
+
+				proto.Merge(dst, src)
+				if !proto.Equal(dst, want) {
+					t.Fatalf("Merge() mismatch:\n got %v\nwant %v\ndiff (-want,+got):\n%v", dst, want, cmp.Diff(want, dst, protocmp.Transform()))
+				}
+				mutateValue(protoreflect.ValueOfMessage(src.ProtoReflect()))
+				if !proto.Equal(dst, want) {
+					t.Fatalf("mutation observed after modifying source:\n got %v\nwant %v\ndiff (-want,+got):\n%v", dst, want, cmp.Diff(want, dst, protocmp.Transform()))
+				}
+
+			})
+		}
+	}
+}
+
+func TestMergeFromNil(t *testing.T) {
+	dst := &testpb.TestAllTypes{}
+	proto.Merge(dst, (*testpb.TestAllTypes)(nil))
+	if !proto.Equal(dst, &testpb.TestAllTypes{}) {
+		t.Errorf("destination should be empty after merging from nil message; got:\n%v", prototext.Format(dst))
 	}
 }
 
@@ -562,3 +842,64 @@
 		t.Errorf("Equal mismatch:\ngot  %v\nwant %v", got, want)
 	}
 }
+
+func TestClone(t *testing.T) {
+	want := &testpb.TestAllTypes{
+		OptionalInt32: proto.Int32(1),
+	}
+	got := proto.Clone(want).(*testpb.TestAllTypes)
+	if !proto.Equal(got, want) {
+		t.Errorf("Clone(src) != src:\n got %v\nwant %v", got, want)
+	}
+}
+
+// mutateValue changes a Value, returning a new value.
+//
+// For scalar values, it returns a value different from the input.
+// For Message, List, and Map values, it mutates the input and returns it.
+func mutateValue(v protoreflect.Value) protoreflect.Value {
+	switch v := v.Interface().(type) {
+	case bool:
+		return protoreflect.ValueOfBool(!v)
+	case protoreflect.EnumNumber:
+		return protoreflect.ValueOfEnum(v + 1)
+	case int32:
+		return protoreflect.ValueOfInt32(v + 1)
+	case int64:
+		return protoreflect.ValueOfInt64(v + 1)
+	case uint32:
+		return protoreflect.ValueOfUint32(v + 1)
+	case uint64:
+		return protoreflect.ValueOfUint64(v + 1)
+	case float32:
+		return protoreflect.ValueOfFloat32(v + 1)
+	case float64:
+		return protoreflect.ValueOfFloat64(v + 1)
+	case []byte:
+		for i := range v {
+			v[i]++
+		}
+		return protoreflect.ValueOfBytes(v)
+	case string:
+		return protoreflect.ValueOfString("_" + v)
+	case protoreflect.Message:
+		v.Range(func(fd protoreflect.FieldDescriptor, val protoreflect.Value) bool {
+			v.Set(fd, mutateValue(val))
+			return true
+		})
+		return protoreflect.ValueOfMessage(v)
+	case protoreflect.List:
+		for i := 0; i < v.Len(); i++ {
+			v.Set(i, mutateValue(v.Get(i)))
+		}
+		return protoreflect.ValueOfList(v)
+	case protoreflect.Map:
+		v.Range(func(mk protoreflect.MapKey, mv protoreflect.Value) bool {
+			v.Set(mk, mutateValue(mv))
+			return true
+		})
+		return protoreflect.ValueOfMap(v)
+	default:
+		panic(fmt.Sprintf("unknown value type %T", v))
+	}
+}
diff --git a/proto/testmessages_test.go b/proto/testmessages_test.go
index ca3ce3c..cfa3cd7 100644
--- a/proto/testmessages_test.go
+++ b/proto/testmessages_test.go
@@ -45,6 +45,22 @@
 	return messages
 }
 
+func templateMessages(messages ...proto.Message) []protoreflect.MessageType {
+	if len(messages) == 0 {
+		messages = []proto.Message{
+			(*testpb.TestAllTypes)(nil),
+			(*test3pb.TestAllTypes)(nil),
+			(*testpb.TestAllExtensions)(nil),
+		}
+	}
+	var out []protoreflect.MessageType
+	for _, m := range messages {
+		out = append(out, m.ProtoReflect().Type())
+	}
+	return out
+
+}
+
 var testValidMessages = []testProto{
 	{
 		desc:          "basic scalar types",
diff --git a/proto/weak_test.go b/proto/weak_test.go
index 15a18fb..0b582ba 100644
--- a/proto/weak_test.go
+++ b/proto/weak_test.go
@@ -9,6 +9,7 @@
 
 	"google.golang.org/protobuf/internal/encoding/pack"
 	"google.golang.org/protobuf/internal/flags"
+	"google.golang.org/protobuf/internal/protobuild"
 	"google.golang.org/protobuf/proto"
 
 	testpb "google.golang.org/protobuf/internal/testprotos/test"
@@ -19,6 +20,7 @@
 	if flags.ProtoLegacy {
 		testValidMessages = append(testValidMessages, testWeakValidMessages...)
 		testInvalidMessages = append(testInvalidMessages, testWeakInvalidMessages...)
+		testMerges = append(testMerges, testWeakMerges...)
 	}
 }
 
@@ -65,6 +67,36 @@
 	},
 }
 
+var testWeakMerges = []testMerge{
+	{
+		desc: "clone weak message",
+		src: protobuild.Message{
+			"weak_message1": protobuild.Message{
+				"a": 1,
+			},
+		},
+		types: []proto.Message{&testpb.TestWeak{}},
+	}, {
+		desc: "merge weak message",
+		dst: protobuild.Message{
+			"weak_message1": protobuild.Message{
+				"a": 1,
+			},
+		},
+		src: protobuild.Message{
+			"weak_message1": protobuild.Message{
+				"a": 2,
+			},
+		},
+		want: protobuild.Message{
+			"weak_message1": protobuild.Message{
+				"a": 2,
+			},
+		},
+		types: []proto.Message{&testpb.TestWeak{}},
+	},
+}
+
 func TestWeakNil(t *testing.T) {
 	if !flags.ProtoLegacy {
 		t.SkipNow()