internal/impl: implement Vector fields

Generate functions for wrapping []T to implement protoreflect.Vector.
This implementation uses Go reflection instead to provide a single implementation
that can handle all Go slice types.

The test harness was greatly expanded to be able to test vectors (in addition
to messages and maps in the near future).

Change-Id: I0106c175f84a1e7e0a0a5b0e02e2489b70b0d177
Reviewed-on: https://go-review.googlesource.com/c/135339
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/internal/impl/message.go b/internal/impl/message.go
index f51526b..2a908c2 100644
--- a/internal/impl/message.go
+++ b/internal/impl/message.go
@@ -215,7 +215,7 @@
 func (m *message) UnknownFields() pref.UnknownFields {
 	return (*unknownFields)(m)
 }
-func (m *message) Unwrap() interface{} {
+func (m *message) Unwrap() interface{} { // TODO: unexport?
 	return m.p.asType(m.mi.goType.Elem()).Interface()
 }
 func (m *message) Interface() pref.ProtoMessage {
@@ -266,7 +266,7 @@
 		return
 	}
 	// TODO: Handle extension fields.
-	panic("invalid field")
+	panic(fmt.Sprintf("invalid field: %d", n))
 }
 func (fs *knownFields) Clear(n pref.FieldNumber) {
 	if fi := fs.mi.fields[n]; fi != nil {
@@ -274,14 +274,14 @@
 		return
 	}
 	// TODO: Handle extension fields.
-	panic("invalid field")
+	panic(fmt.Sprintf("invalid field: %d", n))
 }
 func (fs *knownFields) Mutable(n pref.FieldNumber) pref.Mutable {
 	if fi := fs.mi.fields[n]; fi != nil {
 		return fi.mutable(fs.p)
 	}
 	// TODO: Handle extension fields.
-	panic("invalid field")
+	panic(fmt.Sprintf("invalid field: %d", n))
 }
 func (fs *knownFields) Range(f func(pref.FieldNumber, pref.Value) bool) {
 	for n, fi := range fs.mi.fields {
diff --git a/internal/impl/message_field.go b/internal/impl/message_field.go
index 2d1c34c..aef3993 100644
--- a/internal/impl/message_field.go
+++ b/internal/impl/message_field.go
@@ -41,10 +41,79 @@
 }
 
 func fieldInfoForVector(fd pref.FieldDescriptor, fs reflect.StructField) fieldInfo {
-	// TODO: support vector fields.
-	panic(fmt.Sprintf("invalid field: %v", fd))
+	ft := fs.Type
+	if ft.Kind() != reflect.Slice {
+		panic(fmt.Sprintf("invalid type: got %v, want slice kind", ft))
+	}
+	conv := matchGoTypePBKind(ft.Elem(), fd.Kind())
+	fieldOffset := offsetOf(fs)
+	// TODO: Implement unsafe fast path?
+	return fieldInfo{
+		has: func(p pointer) bool {
+			rv := p.apply(fieldOffset).asType(fs.Type).Elem()
+			return rv.Len() > 0
+		},
+		get: func(p pointer) pref.Value {
+			rv := p.apply(fieldOffset).asType(fs.Type).Elem()
+			return pref.ValueOf(vectorReflect{rv, conv})
+		},
+		set: func(p pointer, v pref.Value) {
+			rv := p.apply(fieldOffset).asType(fs.Type).Elem()
+			rv.Set(v.Vector().(vectorReflect).v)
+		},
+		clear: func(p pointer) {
+			rv := p.apply(fieldOffset).asType(fs.Type).Elem()
+			rv.Set(reflect.Zero(rv.Type()))
+		},
+		mutable: func(p pointer) pref.Mutable {
+			rv := p.apply(fieldOffset).asType(fs.Type).Elem()
+			return vectorReflect{rv, conv}
+		},
+	}
 }
 
+type vectorReflect struct {
+	v    reflect.Value // addressable []T
+	conv converter
+}
+
+func (vs vectorReflect) Len() int {
+	return vs.v.Len()
+}
+func (vs vectorReflect) Get(i int) pref.Value {
+	return vs.conv.toPB(vs.v.Index(i))
+}
+func (vs vectorReflect) Set(i int, v pref.Value) {
+	vs.v.Index(i).Set(vs.conv.toGo(v))
+}
+func (vs vectorReflect) Append(v pref.Value) {
+	vs.v.Set(reflect.Append(vs.v, vs.conv.toGo(v)))
+}
+func (vs vectorReflect) Mutable(i int) pref.Mutable {
+	// Mutable is only valid for messages and panics for other kinds.
+	rv := vs.v.Index(i)
+	if rv.IsNil() {
+		pv := pref.ValueOf(vs.conv.newMessage())
+		rv.Set(vs.conv.toGo(pv))
+	}
+	return rv.Interface().(pref.Message)
+}
+func (vs vectorReflect) MutableAppend() pref.Mutable {
+	// MutableAppend is only valid for messages and panics for other kinds.
+	pv := pref.ValueOf(vs.conv.newMessage())
+	vs.v.Set(reflect.Append(vs.v, vs.conv.toGo(pv)))
+	return vs.v.Index(vs.Len() - 1).Interface().(pref.Message)
+}
+func (vs vectorReflect) Truncate(i int) {
+	vs.v.Set(vs.v.Slice(0, i))
+}
+func (vs vectorReflect) Unwrap() interface{} { // TODO: unexport?
+	return vs.v.Interface()
+}
+func (vs vectorReflect) ProtoMutable() {}
+
+var _ pref.Vector = vectorReflect{}
+
 var emptyBytes = reflect.ValueOf([]byte{})
 
 func fieldInfoForScalar(fd pref.FieldDescriptor, fs reflect.StructField) fieldInfo {
@@ -221,8 +290,9 @@
 // converter provides functions for converting to/from Go reflect.Value types
 // and protobuf protoreflect.Value types.
 type converter struct {
-	toPB func(reflect.Value) pref.Value
-	toGo func(pref.Value) reflect.Value
+	toPB       func(reflect.Value) pref.Value
+	toGo       func(pref.Value) reflect.Value
+	newMessage func() pref.Message
 }
 
 func makeScalarConverter(goType, pbType reflect.Type) converter {
diff --git a/internal/impl/message_test.go b/internal/impl/message_test.go
index c21c40e..c24fb04 100644
--- a/internal/impl/message_test.go
+++ b/internal/impl/message_test.go
@@ -5,10 +5,13 @@
 package impl
 
 import (
-	"reflect"
+	"fmt"
+	"math"
+	"strings"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
 
 	pref "github.com/golang/protobuf/v2/reflect/protoreflect"
 	ptype "github.com/golang/protobuf/v2/reflect/prototype"
@@ -22,6 +25,8 @@
 	return md
 }
 
+var V = pref.ValueOf
+
 type (
 	MyBool    bool
 	MyInt32   int32
@@ -32,266 +37,487 @@
 	MyFloat64 float64
 	MyString  string
 	MyBytes   []byte
+
+	NamedStrings []MyString
+	NamedBytes   []MyBytes
 )
 
-type ScalarProto2 struct {
-	Bool    *bool    `protobuf:"1"`
-	Int32   *int32   `protobuf:"2"`
-	Int64   *int64   `protobuf:"3"`
-	Uint32  *uint32  `protobuf:"4"`
-	Uint64  *uint64  `protobuf:"5"`
-	Float32 *float32 `protobuf:"6"`
-	Float64 *float64 `protobuf:"7"`
-	String  *string  `protobuf:"8"`
-	StringA []byte   `protobuf:"9"`
-	Bytes   []byte   `protobuf:"10"`
-	BytesA  *string  `protobuf:"11"`
+// List of test operations to perform on messages, vectors, or maps.
+type (
+	messageOp  interface{} // equalMessage | hasFields | getFields | setFields | clearFields | vectorFields | mapFields
+	messageOps []messageOp
 
-	MyBool    *MyBool    `protobuf:"12"`
-	MyInt32   *MyInt32   `protobuf:"13"`
-	MyInt64   *MyInt64   `protobuf:"14"`
-	MyUint32  *MyUint32  `protobuf:"15"`
-	MyUint64  *MyUint64  `protobuf:"16"`
-	MyFloat32 *MyFloat32 `protobuf:"17"`
-	MyFloat64 *MyFloat64 `protobuf:"18"`
-	MyString  *MyString  `protobuf:"19"`
-	MyStringA MyBytes    `protobuf:"20"`
-	MyBytes   MyBytes    `protobuf:"21"`
-	MyBytesA  *MyString  `protobuf:"22"`
+	vectorOp  interface{} // equalVector | lenVector | getVector | setVector | appendVector | truncVector
+	vectorOps []vectorOp
+
+	mapOp  interface{} // TODO
+	mapOps []mapOp     // TODO
+)
+
+// Test operations performed on a message.
+type (
+	equalMessage  pref.Message
+	hasFields     map[pref.FieldNumber]bool
+	getFields     map[pref.FieldNumber]pref.Value
+	setFields     map[pref.FieldNumber]pref.Value
+	clearFields   map[pref.FieldNumber]bool
+	vectorFields  map[pref.FieldNumber]vectorOps
+	mapFields     map[pref.FieldNumber]mapOps
+	messageFields map[pref.FieldNumber]messageOps
+	// TODO: Mutable, Range, ExtensionTypes
+)
+
+// Test operations performed on a vector.
+type (
+	equalVector  pref.Vector
+	lenVector    int
+	getVector    map[int]pref.Value
+	setVector    map[int]pref.Value
+	appendVector []pref.Value
+	truncVector  int
+	// TODO: Mutable, MutableAppend
+)
+
+func TestScalarProto2(t *testing.T) {
+	type ScalarProto2 struct {
+		Bool    *bool    `protobuf:"1"`
+		Int32   *int32   `protobuf:"2"`
+		Int64   *int64   `protobuf:"3"`
+		Uint32  *uint32  `protobuf:"4"`
+		Uint64  *uint64  `protobuf:"5"`
+		Float32 *float32 `protobuf:"6"`
+		Float64 *float64 `protobuf:"7"`
+		String  *string  `protobuf:"8"`
+		StringA []byte   `protobuf:"9"`
+		Bytes   []byte   `protobuf:"10"`
+		BytesA  *string  `protobuf:"11"`
+
+		MyBool    *MyBool    `protobuf:"12"`
+		MyInt32   *MyInt32   `protobuf:"13"`
+		MyInt64   *MyInt64   `protobuf:"14"`
+		MyUint32  *MyUint32  `protobuf:"15"`
+		MyUint64  *MyUint64  `protobuf:"16"`
+		MyFloat32 *MyFloat32 `protobuf:"17"`
+		MyFloat64 *MyFloat64 `protobuf:"18"`
+		MyString  *MyString  `protobuf:"19"`
+		MyStringA MyBytes    `protobuf:"20"`
+		MyBytes   MyBytes    `protobuf:"21"`
+		MyBytesA  *MyString  `protobuf:"22"`
+	}
+
+	mi := MessageType{Desc: mustMakeMessageDesc(ptype.StandaloneMessage{
+		Syntax:   pref.Proto2,
+		FullName: "ScalarProto2",
+		Fields: []ptype.Field{
+			{Name: "f1", Number: 1, Cardinality: pref.Optional, Kind: pref.BoolKind, Default: V(bool(true))},
+			{Name: "f2", Number: 2, Cardinality: pref.Optional, Kind: pref.Int32Kind, Default: V(int32(2))},
+			{Name: "f3", Number: 3, Cardinality: pref.Optional, Kind: pref.Int64Kind, Default: V(int64(3))},
+			{Name: "f4", Number: 4, Cardinality: pref.Optional, Kind: pref.Uint32Kind, Default: V(uint32(4))},
+			{Name: "f5", Number: 5, Cardinality: pref.Optional, Kind: pref.Uint64Kind, Default: V(uint64(5))},
+			{Name: "f6", Number: 6, Cardinality: pref.Optional, Kind: pref.FloatKind, Default: V(float32(6))},
+			{Name: "f7", Number: 7, Cardinality: pref.Optional, Kind: pref.DoubleKind, Default: V(float64(7))},
+			{Name: "f8", Number: 8, Cardinality: pref.Optional, Kind: pref.StringKind, Default: V(string("8"))},
+			{Name: "f9", Number: 9, Cardinality: pref.Optional, Kind: pref.StringKind, Default: V(string("9"))},
+			{Name: "f10", Number: 10, Cardinality: pref.Optional, Kind: pref.BytesKind, Default: V([]byte("10"))},
+			{Name: "f11", Number: 11, Cardinality: pref.Optional, Kind: pref.BytesKind, Default: V([]byte("11"))},
+
+			{Name: "f12", Number: 12, Cardinality: pref.Optional, Kind: pref.BoolKind, Default: V(bool(true))},
+			{Name: "f13", Number: 13, Cardinality: pref.Optional, Kind: pref.Int32Kind, Default: V(int32(13))},
+			{Name: "f14", Number: 14, Cardinality: pref.Optional, Kind: pref.Int64Kind, Default: V(int64(14))},
+			{Name: "f15", Number: 15, Cardinality: pref.Optional, Kind: pref.Uint32Kind, Default: V(uint32(15))},
+			{Name: "f16", Number: 16, Cardinality: pref.Optional, Kind: pref.Uint64Kind, Default: V(uint64(16))},
+			{Name: "f17", Number: 17, Cardinality: pref.Optional, Kind: pref.FloatKind, Default: V(float32(17))},
+			{Name: "f18", Number: 18, Cardinality: pref.Optional, Kind: pref.DoubleKind, Default: V(float64(18))},
+			{Name: "f19", Number: 19, Cardinality: pref.Optional, Kind: pref.StringKind, Default: V(string("19"))},
+			{Name: "f20", Number: 20, Cardinality: pref.Optional, Kind: pref.StringKind, Default: V(string("20"))},
+			{Name: "f21", Number: 21, Cardinality: pref.Optional, Kind: pref.BytesKind, Default: V([]byte("21"))},
+			{Name: "f22", Number: 22, Cardinality: pref.Optional, Kind: pref.BytesKind, Default: V([]byte("22"))},
+		},
+	})}
+
+	testMessage(t, nil, mi.MessageOf(&ScalarProto2{}), messageOps{
+		hasFields{
+			1: false, 2: false, 3: false, 4: false, 5: false, 6: false, 7: false, 8: false, 9: false, 10: false, 11: false,
+			12: false, 13: false, 14: false, 15: false, 16: false, 17: false, 18: false, 19: false, 20: false, 21: false, 22: false,
+		},
+		getFields{
+			1: V(bool(true)), 2: V(int32(2)), 3: V(int64(3)), 4: V(uint32(4)), 5: V(uint64(5)), 6: V(float32(6)), 7: V(float64(7)), 8: V(string("8")), 9: V(string("9")), 10: V([]byte("10")), 11: V([]byte("11")),
+			12: V(bool(true)), 13: V(int32(13)), 14: V(int64(14)), 15: V(uint32(15)), 16: V(uint64(16)), 17: V(float32(17)), 18: V(float64(18)), 19: V(string("19")), 20: V(string("20")), 21: V([]byte("21")), 22: V([]byte("22")),
+		},
+		setFields{
+			1: V(bool(false)), 2: V(int32(0)), 3: V(int64(0)), 4: V(uint32(0)), 5: V(uint64(0)), 6: V(float32(0)), 7: V(float64(0)), 8: V(string("")), 9: V(string("")), 10: V([]byte(nil)), 11: V([]byte(nil)),
+			12: V(bool(false)), 13: V(int32(0)), 14: V(int64(0)), 15: V(uint32(0)), 16: V(uint64(0)), 17: V(float32(0)), 18: V(float64(0)), 19: V(string("")), 20: V(string("")), 21: V([]byte(nil)), 22: V([]byte(nil)),
+		},
+		hasFields{
+			1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true, 8: true, 9: true, 10: true, 11: true,
+			12: true, 13: true, 14: true, 15: true, 16: true, 17: true, 18: true, 19: true, 20: true, 21: true, 22: true,
+		},
+		equalMessage(mi.MessageOf(&ScalarProto2{
+			new(bool), new(int32), new(int64), new(uint32), new(uint64), new(float32), new(float64), new(string), []byte{}, []byte{}, new(string),
+			new(MyBool), new(MyInt32), new(MyInt64), new(MyUint32), new(MyUint64), new(MyFloat32), new(MyFloat64), new(MyString), MyBytes{}, MyBytes{}, new(MyString),
+		})),
+		clearFields{
+			1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true, 8: true, 9: true, 10: true, 11: true,
+			12: true, 13: true, 14: true, 15: true, 16: true, 17: true, 18: true, 19: true, 20: true, 21: true, 22: true,
+		},
+		equalMessage(mi.MessageOf(&ScalarProto2{})),
+	})
 }
 
-var scalarProto2Desc = mustMakeMessageDesc(ptype.StandaloneMessage{
-	Syntax:   pref.Proto2,
-	FullName: "ScalarProto2",
-	Fields: []ptype.Field{
-		{Name: "f1", Number: 1, Cardinality: pref.Optional, Kind: pref.BoolKind, Default: pref.ValueOf(bool(true))},
-		{Name: "f2", Number: 2, Cardinality: pref.Optional, Kind: pref.Int32Kind, Default: pref.ValueOf(int32(2))},
-		{Name: "f3", Number: 3, Cardinality: pref.Optional, Kind: pref.Int64Kind, Default: pref.ValueOf(int64(3))},
-		{Name: "f4", Number: 4, Cardinality: pref.Optional, Kind: pref.Uint32Kind, Default: pref.ValueOf(uint32(4))},
-		{Name: "f5", Number: 5, Cardinality: pref.Optional, Kind: pref.Uint64Kind, Default: pref.ValueOf(uint64(5))},
-		{Name: "f6", Number: 6, Cardinality: pref.Optional, Kind: pref.FloatKind, Default: pref.ValueOf(float32(6))},
-		{Name: "f7", Number: 7, Cardinality: pref.Optional, Kind: pref.DoubleKind, Default: pref.ValueOf(float64(7))},
-		{Name: "f8", Number: 8, Cardinality: pref.Optional, Kind: pref.StringKind, Default: pref.ValueOf(string("8"))},
-		{Name: "f9", Number: 9, Cardinality: pref.Optional, Kind: pref.StringKind, Default: pref.ValueOf(string("9"))},
-		{Name: "f10", Number: 10, Cardinality: pref.Optional, Kind: pref.BytesKind, Default: pref.ValueOf([]byte("10"))},
-		{Name: "f11", Number: 11, Cardinality: pref.Optional, Kind: pref.BytesKind, Default: pref.ValueOf([]byte("11"))},
+func TestScalarProto3(t *testing.T) {
+	type ScalarProto3 struct {
+		Bool    bool    `protobuf:"1"`
+		Int32   int32   `protobuf:"2"`
+		Int64   int64   `protobuf:"3"`
+		Uint32  uint32  `protobuf:"4"`
+		Uint64  uint64  `protobuf:"5"`
+		Float32 float32 `protobuf:"6"`
+		Float64 float64 `protobuf:"7"`
+		String  string  `protobuf:"8"`
+		StringA []byte  `protobuf:"9"`
+		Bytes   []byte  `protobuf:"10"`
+		BytesA  string  `protobuf:"11"`
 
-		{Name: "f12", Number: 12, Cardinality: pref.Optional, Kind: pref.BoolKind, Default: pref.ValueOf(bool(true))},
-		{Name: "f13", Number: 13, Cardinality: pref.Optional, Kind: pref.Int32Kind, Default: pref.ValueOf(int32(13))},
-		{Name: "f14", Number: 14, Cardinality: pref.Optional, Kind: pref.Int64Kind, Default: pref.ValueOf(int64(14))},
-		{Name: "f15", Number: 15, Cardinality: pref.Optional, Kind: pref.Uint32Kind, Default: pref.ValueOf(uint32(15))},
-		{Name: "f16", Number: 16, Cardinality: pref.Optional, Kind: pref.Uint64Kind, Default: pref.ValueOf(uint64(16))},
-		{Name: "f17", Number: 17, Cardinality: pref.Optional, Kind: pref.FloatKind, Default: pref.ValueOf(float32(17))},
-		{Name: "f18", Number: 18, Cardinality: pref.Optional, Kind: pref.DoubleKind, Default: pref.ValueOf(float64(18))},
-		{Name: "f19", Number: 19, Cardinality: pref.Optional, Kind: pref.StringKind, Default: pref.ValueOf(string("19"))},
-		{Name: "f20", Number: 20, Cardinality: pref.Optional, Kind: pref.StringKind, Default: pref.ValueOf(string("20"))},
-		{Name: "f21", Number: 21, Cardinality: pref.Optional, Kind: pref.BytesKind, Default: pref.ValueOf([]byte("21"))},
-		{Name: "f22", Number: 22, Cardinality: pref.Optional, Kind: pref.BytesKind, Default: pref.ValueOf([]byte("22"))},
-	},
-})
+		MyBool    MyBool    `protobuf:"12"`
+		MyInt32   MyInt32   `protobuf:"13"`
+		MyInt64   MyInt64   `protobuf:"14"`
+		MyUint32  MyUint32  `protobuf:"15"`
+		MyUint64  MyUint64  `protobuf:"16"`
+		MyFloat32 MyFloat32 `protobuf:"17"`
+		MyFloat64 MyFloat64 `protobuf:"18"`
+		MyString  MyString  `protobuf:"19"`
+		MyStringA MyBytes   `protobuf:"20"`
+		MyBytes   MyBytes   `protobuf:"21"`
+		MyBytesA  MyString  `protobuf:"22"`
+	}
 
-type ScalarProto3 struct {
-	Bool    bool    `protobuf:"1"`
-	Int32   int32   `protobuf:"2"`
-	Int64   int64   `protobuf:"3"`
-	Uint32  uint32  `protobuf:"4"`
-	Uint64  uint64  `protobuf:"5"`
-	Float32 float32 `protobuf:"6"`
-	Float64 float64 `protobuf:"7"`
-	String  string  `protobuf:"8"`
-	StringA []byte  `protobuf:"9"`
-	Bytes   []byte  `protobuf:"10"`
-	BytesA  string  `protobuf:"11"`
+	mi := MessageType{Desc: mustMakeMessageDesc(ptype.StandaloneMessage{
+		Syntax:   pref.Proto3,
+		FullName: "ScalarProto3",
+		Fields: []ptype.Field{
+			{Name: "f1", Number: 1, Cardinality: pref.Optional, Kind: pref.BoolKind},
+			{Name: "f2", Number: 2, Cardinality: pref.Optional, Kind: pref.Int32Kind},
+			{Name: "f3", Number: 3, Cardinality: pref.Optional, Kind: pref.Int64Kind},
+			{Name: "f4", Number: 4, Cardinality: pref.Optional, Kind: pref.Uint32Kind},
+			{Name: "f5", Number: 5, Cardinality: pref.Optional, Kind: pref.Uint64Kind},
+			{Name: "f6", Number: 6, Cardinality: pref.Optional, Kind: pref.FloatKind},
+			{Name: "f7", Number: 7, Cardinality: pref.Optional, Kind: pref.DoubleKind},
+			{Name: "f8", Number: 8, Cardinality: pref.Optional, Kind: pref.StringKind},
+			{Name: "f9", Number: 9, Cardinality: pref.Optional, Kind: pref.StringKind},
+			{Name: "f10", Number: 10, Cardinality: pref.Optional, Kind: pref.BytesKind},
+			{Name: "f11", Number: 11, Cardinality: pref.Optional, Kind: pref.BytesKind},
 
-	MyBool    MyBool    `protobuf:"12"`
-	MyInt32   MyInt32   `protobuf:"13"`
-	MyInt64   MyInt64   `protobuf:"14"`
-	MyUint32  MyUint32  `protobuf:"15"`
-	MyUint64  MyUint64  `protobuf:"16"`
-	MyFloat32 MyFloat32 `protobuf:"17"`
-	MyFloat64 MyFloat64 `protobuf:"18"`
-	MyString  MyString  `protobuf:"19"`
-	MyStringA MyBytes   `protobuf:"20"`
-	MyBytes   MyBytes   `protobuf:"21"`
-	MyBytesA  MyString  `protobuf:"22"`
+			{Name: "f12", Number: 12, Cardinality: pref.Optional, Kind: pref.BoolKind},
+			{Name: "f13", Number: 13, Cardinality: pref.Optional, Kind: pref.Int32Kind},
+			{Name: "f14", Number: 14, Cardinality: pref.Optional, Kind: pref.Int64Kind},
+			{Name: "f15", Number: 15, Cardinality: pref.Optional, Kind: pref.Uint32Kind},
+			{Name: "f16", Number: 16, Cardinality: pref.Optional, Kind: pref.Uint64Kind},
+			{Name: "f17", Number: 17, Cardinality: pref.Optional, Kind: pref.FloatKind},
+			{Name: "f18", Number: 18, Cardinality: pref.Optional, Kind: pref.DoubleKind},
+			{Name: "f19", Number: 19, Cardinality: pref.Optional, Kind: pref.StringKind},
+			{Name: "f20", Number: 20, Cardinality: pref.Optional, Kind: pref.StringKind},
+			{Name: "f21", Number: 21, Cardinality: pref.Optional, Kind: pref.BytesKind},
+			{Name: "f22", Number: 22, Cardinality: pref.Optional, Kind: pref.BytesKind},
+		},
+	})}
+
+	testMessage(t, nil, mi.MessageOf(&ScalarProto3{}), messageOps{
+		hasFields{
+			1: false, 2: false, 3: false, 4: false, 5: false, 6: false, 7: false, 8: false, 9: false, 10: false, 11: false,
+			12: false, 13: false, 14: false, 15: false, 16: false, 17: false, 18: false, 19: false, 20: false, 21: false, 22: false,
+		},
+		getFields{
+			1: V(bool(false)), 2: V(int32(0)), 3: V(int64(0)), 4: V(uint32(0)), 5: V(uint64(0)), 6: V(float32(0)), 7: V(float64(0)), 8: V(string("")), 9: V(string("")), 10: V([]byte(nil)), 11: V([]byte(nil)),
+			12: V(bool(false)), 13: V(int32(0)), 14: V(int64(0)), 15: V(uint32(0)), 16: V(uint64(0)), 17: V(float32(0)), 18: V(float64(0)), 19: V(string("")), 20: V(string("")), 21: V([]byte(nil)), 22: V([]byte(nil)),
+		},
+		setFields{
+			1: V(bool(false)), 2: V(int32(0)), 3: V(int64(0)), 4: V(uint32(0)), 5: V(uint64(0)), 6: V(float32(0)), 7: V(float64(0)), 8: V(string("")), 9: V(string("")), 10: V([]byte(nil)), 11: V([]byte(nil)),
+			12: V(bool(false)), 13: V(int32(0)), 14: V(int64(0)), 15: V(uint32(0)), 16: V(uint64(0)), 17: V(float32(0)), 18: V(float64(0)), 19: V(string("")), 20: V(string("")), 21: V([]byte(nil)), 22: V([]byte(nil)),
+		},
+		hasFields{
+			1: false, 2: false, 3: false, 4: false, 5: false, 6: false, 7: false, 8: false, 9: false, 10: false, 11: false,
+			12: false, 13: false, 14: false, 15: false, 16: false, 17: false, 18: false, 19: false, 20: false, 21: false, 22: false,
+		},
+		equalMessage(mi.MessageOf(&ScalarProto3{})),
+		setFields{
+			1: V(bool(true)), 2: V(int32(2)), 3: V(int64(3)), 4: V(uint32(4)), 5: V(uint64(5)), 6: V(float32(6)), 7: V(float64(7)), 8: V(string("8")), 9: V(string("9")), 10: V([]byte("10")), 11: V([]byte("11")),
+			12: V(bool(true)), 13: V(int32(13)), 14: V(int64(14)), 15: V(uint32(15)), 16: V(uint64(16)), 17: V(float32(17)), 18: V(float64(18)), 19: V(string("19")), 20: V(string("20")), 21: V([]byte("21")), 22: V([]byte("22")),
+		},
+		hasFields{
+			1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true, 8: true, 9: true, 10: true, 11: true,
+			12: true, 13: true, 14: true, 15: true, 16: true, 17: true, 18: true, 19: true, 20: true, 21: true, 22: true,
+		},
+		equalMessage(mi.MessageOf(&ScalarProto3{
+			true, 2, 3, 4, 5, 6, 7, "8", []byte("9"), []byte("10"), "11",
+			true, 13, 14, 15, 16, 17, 18, "19", []byte("20"), []byte("21"), "22",
+		})),
+		clearFields{
+			1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true, 8: true, 9: true, 10: true, 11: true,
+			12: true, 13: true, 14: true, 15: true, 16: true, 17: true, 18: true, 19: true, 20: true, 21: true, 22: true,
+		},
+		equalMessage(mi.MessageOf(&ScalarProto3{})),
+	})
 }
 
-var scalarProto3Desc = mustMakeMessageDesc(ptype.StandaloneMessage{
-	Syntax:   pref.Proto3,
-	FullName: "ScalarProto3",
-	Fields: []ptype.Field{
-		{Name: "f1", Number: 1, Cardinality: pref.Optional, Kind: pref.BoolKind},
-		{Name: "f2", Number: 2, Cardinality: pref.Optional, Kind: pref.Int32Kind},
-		{Name: "f3", Number: 3, Cardinality: pref.Optional, Kind: pref.Int64Kind},
-		{Name: "f4", Number: 4, Cardinality: pref.Optional, Kind: pref.Uint32Kind},
-		{Name: "f5", Number: 5, Cardinality: pref.Optional, Kind: pref.Uint64Kind},
-		{Name: "f6", Number: 6, Cardinality: pref.Optional, Kind: pref.FloatKind},
-		{Name: "f7", Number: 7, Cardinality: pref.Optional, Kind: pref.DoubleKind},
-		{Name: "f8", Number: 8, Cardinality: pref.Optional, Kind: pref.StringKind},
-		{Name: "f9", Number: 9, Cardinality: pref.Optional, Kind: pref.StringKind},
-		{Name: "f10", Number: 10, Cardinality: pref.Optional, Kind: pref.BytesKind},
-		{Name: "f11", Number: 11, Cardinality: pref.Optional, Kind: pref.BytesKind},
+func TestRepeatedScalars(t *testing.T) {
+	type RepeatedScalars struct {
+		Bools    []bool    `protobuf:"1"`
+		Int32s   []int32   `protobuf:"2"`
+		Int64s   []int64   `protobuf:"3"`
+		Uint32s  []uint32  `protobuf:"4"`
+		Uint64s  []uint64  `protobuf:"5"`
+		Float32s []float32 `protobuf:"6"`
+		Float64s []float64 `protobuf:"7"`
+		Strings  []string  `protobuf:"8"`
+		StringsA [][]byte  `protobuf:"9"`
+		Bytes    [][]byte  `protobuf:"10"`
+		BytesA   []string  `protobuf:"11"`
 
-		{Name: "f12", Number: 12, Cardinality: pref.Optional, Kind: pref.BoolKind},
-		{Name: "f13", Number: 13, Cardinality: pref.Optional, Kind: pref.Int32Kind},
-		{Name: "f14", Number: 14, Cardinality: pref.Optional, Kind: pref.Int64Kind},
-		{Name: "f15", Number: 15, Cardinality: pref.Optional, Kind: pref.Uint32Kind},
-		{Name: "f16", Number: 16, Cardinality: pref.Optional, Kind: pref.Uint64Kind},
-		{Name: "f17", Number: 17, Cardinality: pref.Optional, Kind: pref.FloatKind},
-		{Name: "f18", Number: 18, Cardinality: pref.Optional, Kind: pref.DoubleKind},
-		{Name: "f19", Number: 19, Cardinality: pref.Optional, Kind: pref.StringKind},
-		{Name: "f20", Number: 20, Cardinality: pref.Optional, Kind: pref.StringKind},
-		{Name: "f21", Number: 21, Cardinality: pref.Optional, Kind: pref.BytesKind},
-		{Name: "f22", Number: 22, Cardinality: pref.Optional, Kind: pref.BytesKind},
-	},
-})
+		MyStrings1 []MyString `protobuf:"12"`
+		MyStrings2 []MyBytes  `protobuf:"13"`
+		MyBytes1   []MyBytes  `protobuf:"14"`
+		MyBytes2   []MyString `protobuf:"15"`
 
-func TestKnownFields(t *testing.T) {
-	V := pref.ValueOf
-	type (
-		// has checks that each field matches the list.
-		hasOp []bool
-		// get checks that each field returns values matching the list.
-		getOp []pref.Value
-		// set calls set on each field with the given value in the list.
-		setOp []pref.Value
-		// clear calls clear on each field.
-		clearOp []bool
-		// equal checks that the current message equals the provided value.
-		equalOp struct{ want interface{} }
+		MyStrings3 NamedStrings `protobuf:"16"`
+		MyStrings4 NamedBytes   `protobuf:"17"`
+		MyBytes3   NamedBytes   `protobuf:"18"`
+		MyBytes4   NamedStrings `protobuf:"19"`
+	}
 
-		testOp interface{} // has | get | set | clear | equal
-	)
+	mi := MessageType{Desc: mustMakeMessageDesc(ptype.StandaloneMessage{
+		Syntax:   pref.Proto2,
+		FullName: "RepeatedScalars",
+		Fields: []ptype.Field{
+			{Name: "f1", Number: 1, Cardinality: pref.Repeated, Kind: pref.BoolKind},
+			{Name: "f2", Number: 2, Cardinality: pref.Repeated, Kind: pref.Int32Kind},
+			{Name: "f3", Number: 3, Cardinality: pref.Repeated, Kind: pref.Int64Kind},
+			{Name: "f4", Number: 4, Cardinality: pref.Repeated, Kind: pref.Uint32Kind},
+			{Name: "f5", Number: 5, Cardinality: pref.Repeated, Kind: pref.Uint64Kind},
+			{Name: "f6", Number: 6, Cardinality: pref.Repeated, Kind: pref.FloatKind},
+			{Name: "f7", Number: 7, Cardinality: pref.Repeated, Kind: pref.DoubleKind},
+			{Name: "f8", Number: 8, Cardinality: pref.Repeated, Kind: pref.StringKind},
+			{Name: "f9", Number: 9, Cardinality: pref.Repeated, Kind: pref.StringKind},
+			{Name: "f10", Number: 10, Cardinality: pref.Repeated, Kind: pref.BytesKind},
+			{Name: "f11", Number: 11, Cardinality: pref.Repeated, Kind: pref.BytesKind},
 
-	tests := []struct {
-		structType  reflect.Type
-		messageDesc pref.MessageDescriptor
-		testOps     []testOp
-	}{{
-		structType:  reflect.TypeOf(ScalarProto2{}),
-		messageDesc: scalarProto2Desc,
-		testOps: []testOp{
-			hasOp([]bool{
-				false, false, false, false, false, false, false, false, false, false, false,
-				false, false, false, false, false, false, false, false, false, false, false,
-			}),
-			getOp([]pref.Value{
-				V(bool(true)), V(int32(2)), V(int64(3)), V(uint32(4)), V(uint64(5)), V(float32(6)), V(float64(7)), V(string("8")), V(string("9")), V([]byte("10")), V([]byte("11")),
-				V(bool(true)), V(int32(13)), V(int64(14)), V(uint32(15)), V(uint64(16)), V(float32(17)), V(float64(18)), V(string("19")), V(string("20")), V([]byte("21")), V([]byte("22")),
-			}),
-			setOp([]pref.Value{
-				V(bool(false)), V(int32(0)), V(int64(0)), V(uint32(0)), V(uint64(0)), V(float32(0)), V(float64(0)), V(string("")), V(string("")), V([]byte(nil)), V([]byte(nil)),
-				V(bool(false)), V(int32(0)), V(int64(0)), V(uint32(0)), V(uint64(0)), V(float32(0)), V(float64(0)), V(string("")), V(string("")), V([]byte(nil)), V([]byte(nil)),
-			}),
-			hasOp([]bool{
-				true, true, true, true, true, true, true, true, true, true, true,
-				true, true, true, true, true, true, true, true, true, true, true,
-			}),
-			equalOp{&ScalarProto2{
-				new(bool), new(int32), new(int64), new(uint32), new(uint64), new(float32), new(float64), new(string), []byte{}, []byte{}, new(string),
-				new(MyBool), new(MyInt32), new(MyInt64), new(MyUint32), new(MyUint64), new(MyFloat32), new(MyFloat64), new(MyString), MyBytes{}, MyBytes{}, new(MyString),
-			}},
-			clearOp([]bool{
-				true, true, true, true, true, true, true, true, true, true, true,
-				true, true, true, true, true, true, true, true, true, true, true,
-			}),
-			equalOp{&ScalarProto2{}},
+			{Name: "f12", Number: 12, Cardinality: pref.Repeated, Kind: pref.StringKind},
+			{Name: "f13", Number: 13, Cardinality: pref.Repeated, Kind: pref.StringKind},
+			{Name: "f14", Number: 14, Cardinality: pref.Repeated, Kind: pref.BytesKind},
+			{Name: "f15", Number: 15, Cardinality: pref.Repeated, Kind: pref.BytesKind},
+
+			{Name: "f16", Number: 16, Cardinality: pref.Repeated, Kind: pref.StringKind},
+			{Name: "f17", Number: 17, Cardinality: pref.Repeated, Kind: pref.StringKind},
+			{Name: "f18", Number: 18, Cardinality: pref.Repeated, Kind: pref.BytesKind},
+			{Name: "f19", Number: 19, Cardinality: pref.Repeated, Kind: pref.BytesKind},
 		},
-	}, {
-		structType:  reflect.TypeOf(ScalarProto3{}),
-		messageDesc: scalarProto3Desc,
-		testOps: []testOp{
-			hasOp([]bool{
-				false, false, false, false, false, false, false, false, false, false, false,
-				false, false, false, false, false, false, false, false, false, false, false,
-			}),
-			getOp([]pref.Value{
-				V(bool(false)), V(int32(0)), V(int64(0)), V(uint32(0)), V(uint64(0)), V(float32(0)), V(float64(0)), V(string("")), V(string("")), V([]byte(nil)), V([]byte(nil)),
-				V(bool(false)), V(int32(0)), V(int64(0)), V(uint32(0)), V(uint64(0)), V(float32(0)), V(float64(0)), V(string("")), V(string("")), V([]byte(nil)), V([]byte(nil)),
-			}),
-			setOp([]pref.Value{
-				V(bool(false)), V(int32(0)), V(int64(0)), V(uint32(0)), V(uint64(0)), V(float32(0)), V(float64(0)), V(string("")), V(string("")), V([]byte(nil)), V([]byte(nil)),
-				V(bool(false)), V(int32(0)), V(int64(0)), V(uint32(0)), V(uint64(0)), V(float32(0)), V(float64(0)), V(string("")), V(string("")), V([]byte(nil)), V([]byte(nil)),
-			}),
-			hasOp([]bool{
-				false, false, false, false, false, false, false, false, false, false, false,
-				false, false, false, false, false, false, false, false, false, false, false,
-			}),
-			equalOp{&ScalarProto3{}},
-			setOp([]pref.Value{
-				V(bool(true)), V(int32(2)), V(int64(3)), V(uint32(4)), V(uint64(5)), V(float32(6)), V(float64(7)), V(string("8")), V(string("9")), V([]byte("10")), V([]byte("11")),
-				V(bool(true)), V(int32(13)), V(int64(14)), V(uint32(15)), V(uint64(16)), V(float32(17)), V(float64(18)), V(string("19")), V(string("20")), V([]byte("21")), V([]byte("22")),
-			}),
-			hasOp([]bool{
-				true, true, true, true, true, true, true, true, true, true, true,
-				true, true, true, true, true, true, true, true, true, true, true,
-			}),
-			equalOp{&ScalarProto3{
-				true, 2, 3, 4, 5, 6, 7, "8", []byte("9"), []byte("10"), "11",
-				true, 13, 14, 15, 16, 17, 18, "19", []byte("20"), []byte("21"), "22",
-			}},
-			clearOp([]bool{
-				true, true, true, true, true, true, true, true, true, true, true,
-				true, true, true, true, true, true, true, true, true, true, true,
-			}),
-			equalOp{&ScalarProto3{}},
+	})}
+
+	empty := mi.MessageOf(&RepeatedScalars{})
+	emptyFS := empty.KnownFields()
+
+	want := mi.MessageOf(&RepeatedScalars{
+		Bools:    []bool{true, false, true},
+		Int32s:   []int32{2, math.MinInt32, math.MaxInt32},
+		Int64s:   []int64{3, math.MinInt64, math.MaxInt64},
+		Uint32s:  []uint32{4, math.MaxUint32 / 2, math.MaxUint32},
+		Uint64s:  []uint64{5, math.MaxUint64 / 2, math.MaxUint64},
+		Float32s: []float32{6, math.SmallestNonzeroFloat32, float32(math.NaN()), math.MaxFloat32},
+		Float64s: []float64{7, math.SmallestNonzeroFloat64, float64(math.NaN()), math.MaxFloat64},
+		Strings:  []string{"8", "", "eight"},
+		StringsA: [][]byte{[]byte("9"), nil, []byte("nine")},
+		Bytes:    [][]byte{[]byte("10"), nil, []byte("ten")},
+		BytesA:   []string{"11", "", "eleven"},
+
+		MyStrings1: []MyString{"12", "", "twelve"},
+		MyStrings2: []MyBytes{[]byte("13"), nil, []byte("thirteen")},
+		MyBytes1:   []MyBytes{[]byte("14"), nil, []byte("fourteen")},
+		MyBytes2:   []MyString{"15", "", "fifteen"},
+
+		MyStrings3: NamedStrings{"16", "", "sixteen"},
+		MyStrings4: NamedBytes{[]byte("17"), nil, []byte("seventeen")},
+		MyBytes3:   NamedBytes{[]byte("18"), nil, []byte("eighteen")},
+		MyBytes4:   NamedStrings{"19", "", "nineteen"},
+	})
+	wantFS := want.KnownFields()
+
+	testMessage(t, nil, mi.MessageOf(&RepeatedScalars{}), messageOps{
+		hasFields{1: false, 2: false, 3: false, 4: false, 5: false, 6: false, 7: false, 8: false, 9: false, 10: false, 11: false, 12: false, 13: false, 14: false, 15: false, 16: false, 17: false, 18: false, 19: false},
+		getFields{1: emptyFS.Get(1), 3: emptyFS.Get(3), 5: emptyFS.Get(5), 7: emptyFS.Get(7), 9: emptyFS.Get(9), 11: emptyFS.Get(11), 13: emptyFS.Get(13), 15: emptyFS.Get(15), 17: emptyFS.Get(17), 19: emptyFS.Get(19)},
+		setFields{1: wantFS.Get(1), 3: wantFS.Get(3), 5: wantFS.Get(5), 7: wantFS.Get(7), 9: wantFS.Get(9), 11: wantFS.Get(11), 13: wantFS.Get(13), 15: wantFS.Get(15), 17: wantFS.Get(17), 19: wantFS.Get(19)},
+		vectorFields{
+			2: {
+				lenVector(0),
+				appendVector{V(int32(2)), V(int32(math.MinInt32)), V(int32(math.MaxInt32))},
+				getVector{0: V(int32(2)), 1: V(int32(math.MinInt32)), 2: V(int32(math.MaxInt32))},
+				equalVector(wantFS.Get(2).Vector()),
+			},
+			4: {
+				appendVector{V(uint32(0)), V(uint32(0)), V(uint32(0))},
+				setVector{0: V(uint32(4)), 1: V(uint32(math.MaxUint32 / 2)), 2: V(uint32(math.MaxUint32))},
+				lenVector(3),
+			},
+			6: {
+				appendVector{V(float32(6)), V(float32(math.SmallestNonzeroFloat32)), V(float32(math.NaN())), V(float32(math.MaxFloat32))},
+				equalVector(wantFS.Get(6).Vector()),
+			},
+			8: {
+				appendVector{V(""), V(""), V(""), V(""), V(""), V("")},
+				lenVector(6),
+				setVector{0: V("8"), 2: V("eight")},
+				truncVector(3),
+				equalVector(wantFS.Get(8).Vector()),
+			},
+			10: {
+				appendVector{V([]byte(nil)), V([]byte(nil))},
+				setVector{0: V([]byte("10"))},
+				appendVector{V([]byte("wrong"))},
+				setVector{2: V([]byte("ten"))},
+				equalVector(wantFS.Get(10).Vector()),
+			},
+			12: {
+				appendVector{V("12"), V("wrong"), V("twelve")},
+				setVector{1: V("")},
+				equalVector(wantFS.Get(12).Vector()),
+			},
+			14: {
+				appendVector{V([]byte("14")), V([]byte(nil)), V([]byte("fourteen"))},
+				equalVector(wantFS.Get(14).Vector()),
+			},
+			16: {
+				appendVector{V("16"), V(""), V("sixteen"), V("extra")},
+				truncVector(3),
+				equalVector(wantFS.Get(16).Vector()),
+			},
+			18: {
+				appendVector{V([]byte("18")), V([]byte(nil)), V([]byte("eighteen"))},
+				equalVector(wantFS.Get(18).Vector()),
+			},
 		},
-	}}
+		hasFields{1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true, 8: true, 9: true, 10: true, 11: true, 12: true, 13: true, 14: true, 15: true, 16: true, 17: true, 18: true, 19: true},
+		equalMessage(want),
+		clearFields{1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true, 8: true, 9: true, 10: true, 11: true, 12: true, 13: true, 14: true, 15: true, 16: true, 17: true, 18: true, 19: true},
+		equalMessage(mi.MessageOf(&RepeatedScalars{})),
+	})
+}
 
-	for _, tt := range tests {
-		t.Run(tt.structType.Name(), func(t *testing.T) {
-			mi := MessageType{Desc: tt.messageDesc}
+// TODO: Need to test singular and repeated messages
 
-			// Test the field functions.
-			p := reflect.New(tt.structType).Interface()
-			m := mi.MessageOf(p)
-			fs := m.KnownFields()
-			for i, op := range tt.testOps {
-				switch op := op.(type) {
-				case hasOp:
-					got := map[pref.FieldNumber]bool{}
-					want := map[pref.FieldNumber]bool{}
-					for j, ok := range op {
-						n := pref.FieldNumber(j + 1)
-						got[n] = fs.Has(n)
-						want[n] = ok
-					}
-					if diff := cmp.Diff(want, got); diff != "" {
-						t.Errorf("operation %d, has mismatch (-want, +got):\n%s", i, diff)
-					}
-				case getOp:
-					got := map[pref.FieldNumber]pref.Value{}
-					want := map[pref.FieldNumber]pref.Value{}
-					for j, v := range op {
-						n := pref.FieldNumber(j + 1)
-						got[n] = fs.Get(n)
-						want[n] = v
-					}
-					xformValue := cmp.Transformer("", func(v pref.Value) interface{} {
-						return v.Interface()
-					})
-					if diff := cmp.Diff(want, got, xformValue); diff != "" {
-						t.Errorf("operation %d, get mismatch (-want, +got):\n%s", i, diff)
-					}
-				case setOp:
-					for j, v := range op {
-						n := pref.FieldNumber(j + 1)
-						fs.Set(n, v)
-					}
-				case clearOp:
-					for j, ok := range op {
-						n := pref.FieldNumber(j + 1)
-						if ok {
-							fs.Clear(n)
-						}
-					}
-				case equalOp:
-					got := m.(interface{ Unwrap() interface{} }).Unwrap()
-					if diff := cmp.Diff(op.want, got); diff != "" {
-						t.Errorf("operation %d, equal mismatch (-want, +got):\n%s", i, diff)
-					}
+var cmpOpts = cmp.Options{
+	cmp.Transformer("UnwrapValue", func(v pref.Value) interface{} {
+		return v.Interface()
+	}),
+	cmp.Transformer("UnwrapMessage", func(m pref.Message) interface{} {
+		v := m.Interface()
+		if v, ok := v.(interface{ Unwrap() interface{} }); ok {
+			return v.Unwrap()
+		}
+		return v
+	}),
+	cmp.Transformer("UnwrapVector", func(v pref.Vector) interface{} {
+		return v.(interface{ Unwrap() interface{} }).Unwrap()
+	}),
+	cmp.Transformer("UnwrapMap", func(m pref.Map) interface{} {
+		return m.(interface{ Unwrap() interface{} }).Unwrap()
+	}),
+	cmpopts.EquateNaNs(),
+}
+
+func testMessage(t *testing.T, p path, m pref.Message, tt messageOps) {
+	fs := m.KnownFields()
+	for i, op := range tt {
+		p.Push(i)
+		switch op := op.(type) {
+		case equalMessage:
+			if diff := cmp.Diff(op, m, cmpOpts); diff != "" {
+				t.Errorf("operation %v, message mismatch (-want, +got):\n%s", p, diff)
+			}
+		case hasFields:
+			got := map[pref.FieldNumber]bool{}
+			want := map[pref.FieldNumber]bool(op)
+			for n := range want {
+				got[n] = fs.Has(n)
+			}
+			if diff := cmp.Diff(want, got); diff != "" {
+				t.Errorf("operation %v, KnownFields.Has mismatch (-want, +got):\n%s", p, diff)
+			}
+		case getFields:
+			got := map[pref.FieldNumber]pref.Value{}
+			want := map[pref.FieldNumber]pref.Value(op)
+			for n := range want {
+				got[n] = fs.Get(n)
+			}
+			if diff := cmp.Diff(want, got, cmpOpts); diff != "" {
+				t.Errorf("operation %v, KnownFields.Get mismatch (-want, +got):\n%s", p, diff)
+			}
+		case setFields:
+			for n, v := range op {
+				fs.Set(n, v)
+			}
+		case clearFields:
+			for n, ok := range op {
+				if ok {
+					fs.Clear(n)
 				}
 			}
-		})
+		case vectorFields:
+			for n, tt := range op {
+				p.Push(int(n))
+				testVectors(t, p, fs.Mutable(n).(pref.Vector), tt)
+				p.Pop()
+			}
+		default:
+			t.Fatalf("operation %v, invalid operation: %T", p, op)
+		}
+		p.Pop()
 	}
 }
+
+func testVectors(t *testing.T, p path, v pref.Vector, tt vectorOps) {
+	for i, op := range tt {
+		p.Push(i)
+		switch op := op.(type) {
+		case equalVector:
+			if diff := cmp.Diff(op, v, cmpOpts); diff != "" {
+				t.Errorf("operation %v, vector mismatch (-want, +got):\n%s", p, diff)
+			}
+		case lenVector:
+			if got, want := v.Len(), int(op); got != want {
+				t.Errorf("operation %v, Vector.Len = %d, want %d", p, got, want)
+			}
+		case getVector:
+			got := map[int]pref.Value{}
+			want := map[int]pref.Value(op)
+			for n := range want {
+				got[n] = v.Get(n)
+			}
+			if diff := cmp.Diff(want, got, cmpOpts); diff != "" {
+				t.Errorf("operation %v, Vector.Get mismatch (-want, +got):\n%s", p, diff)
+			}
+		case setVector:
+			for n, e := range op {
+				v.Set(n, e)
+			}
+		case appendVector:
+			for _, e := range op {
+				v.Append(e)
+			}
+		case truncVector:
+			v.Truncate(int(op))
+		default:
+			t.Fatalf("operation %v, invalid operation: %T", p, op)
+		}
+		p.Pop()
+	}
+}
+
+type path []int
+
+func (p *path) Push(i int) { *p = append(*p, i) }
+func (p *path) Pop()       { *p = (*p)[:len(*p)-1] }
+func (p path) String() string {
+	var ss []string
+	for _, i := range p {
+		ss = append(ss, fmt.Sprint(i))
+	}
+	return strings.Join(ss, ".")
+}