testing/protocmp: add Filter options

This CL adds the following helper options:
	func FilterEnum(protoreflect.Enum, cmp.Option) cmp.Option
	func FilterMessage(proto.Message, cmp.Option) cmp.Option
	func FilterField(proto.Message, protoreflect.Name, cmp.Option) cmp.Option
	func FilterOneof(proto.Message, protoreflect.Name, cmp.Option) cmp.Option
	func FilterDescriptor(protoreflect.Descriptor, cmp.Option) cmp.Option

There is primarily exposing pre-existing functionality that the Ignore options
were already depending on to operate.

Change-Id: I44edf2ffa07de980a9ad3284525bfe3b45428d74
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/207177
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/testing/protocmp/util.go b/testing/protocmp/util.go
index 7a0b49b..b9ccae4 100644
--- a/testing/protocmp/util.go
+++ b/testing/protocmp/util.go
@@ -22,8 +22,90 @@
 	messageReflectType = reflect.TypeOf(Message{})
 )
 
+// FilterEnum filters opt to only be applicable on standalone Enums,
+// singular fields of enums, list fields of enums, or map fields of enum values,
+// where the enum is the same type as the specified enum.
+//
+// The Go type of the last path step may be an:
+//	• Enum for singular fields, elements of a repeated field,
+//	values of a map field, or standalone Enums
+//	• []Enum for list fields
+//	• map[K]Enum for map fields
+//	• interface{} for a Message map entry value
+//
+// This must be used in conjunction with Transform.
+func FilterEnum(enum protoreflect.Enum, opt cmp.Option) cmp.Option {
+	return FilterDescriptor(enum.Descriptor(), opt)
+}
+
+// FilterMessage filters opt to only be applicable on standalone Messages,
+// singular fields of messages, list fields of messages, or map fields of
+// message values, where the message is the same type as the specified message.
+//
+// The Go type of the last path step may be an:
+//	• Message for singular fields, elements of a repeated field,
+//	values of a map field, or standalone Messages
+//	• []Message for list fields
+//	• map[K]Message for map fields
+//	• interface{} for a Message map entry value
+//
+// This must be used in conjunction with Transform.
+func FilterMessage(message proto.Message, opt cmp.Option) cmp.Option {
+	return FilterDescriptor(message.ProtoReflect().Descriptor(), opt)
+}
+
+// FilterField filters opt to only be applicable on the specified field
+// in the message. It panics if a field of the given name does not exist.
+//
+// The Go type of the last path step may be an:
+//	• T for singular fields
+//	• []T for list fields
+//	• map[K]T for map fields
+//	• interface{} for a Message map entry value
+//
+// This must be used in conjunction with Transform.
+func FilterField(message proto.Message, name protoreflect.Name, opt cmp.Option) cmp.Option {
+	md := message.ProtoReflect().Descriptor()
+	return FilterDescriptor(mustFindFieldDescriptor(md, name), opt)
+}
+
+// FilterOneof filters opt to only be applicable on all fields within the
+// specified oneof in the message. It panics if a oneof of the given name
+// does not exist.
+//
+// The Go type of the last path step may be an:
+//	• T for singular fields
+//	• []T for list fields
+//	• map[K]T for map fields
+//	• interface{} for a Message map entry value
+//
+// This must be used in conjunction with Transform.
+func FilterOneof(message proto.Message, name protoreflect.Name, opt cmp.Option) cmp.Option {
+	md := message.ProtoReflect().Descriptor()
+	return FilterDescriptor(mustFindOneofDescriptor(md, name), opt)
+}
+
+// FilterDescriptor ignores the specified descriptor.
+//
+// The following descriptor types may be specified:
+//	• protoreflect.EnumDescriptor
+//	• protoreflect.MessageDescriptor
+//	• protoreflect.FieldDescriptor
+//	• protoreflect.OneofDescriptor
+//
+// For the behavior of each, see the corresponding filter function.
+// Since this filter accepts a protoreflect.FieldDescriptor, it can be used
+// to also filter for extension fields as a protoreflect.ExtensionDescriptor
+// is just an alias to protoreflect.FieldDescriptor.
+//
+// This must be used in conjunction with Transform.
+func FilterDescriptor(desc protoreflect.Descriptor, opt cmp.Option) cmp.Option {
+	f := newNameFilters(desc)
+	return cmp.FilterPath(f.Filter, opt)
+}
+
 // IgnoreEnums ignores all enums of the specified types.
-// See IgnoreDescriptors with regard to EnumDescriptors for more information.
+// It is equivalent to FilterEnum(enum, cmp.Ignore()) for each enum.
 //
 // This must be used in conjunction with Transform.
 func IgnoreEnums(enums ...protoreflect.Enum) cmp.Option {
@@ -35,7 +117,7 @@
 }
 
 // IgnoreMessages ignores all messages of the specified types.
-// See IgnoreDescriptors with regard to MessageDescriptors for more information.
+// It is equivalent to FilterMessage(message, cmp.Ignore()) for each message.
 //
 // This must be used in conjunction with Transform.
 func IgnoreMessages(messages ...proto.Message) cmp.Option {
@@ -46,9 +128,9 @@
 	return IgnoreDescriptors(ds...)
 }
 
-// IgnoreFields ignores the specified fields in messages of type m.
-// This panics if a field of the given name does not exist.
-// See IgnoreDescriptors with regard to FieldDescriptors for more information.
+// IgnoreFields ignores the specified fields in the specified message.
+// It is equivalent to FilterField(message, name, cmp.Ignore()) for each field
+// in the message.
 //
 // This must be used in conjunction with Transform.
 func IgnoreFields(message proto.Message, names ...protoreflect.Name) cmp.Option {
@@ -60,9 +142,9 @@
 	return IgnoreDescriptors(ds...)
 }
 
-// IgnoreOneofs ignores fields in the specified oneofs in messages of type m.
-// This panics if a oneof of the given name does not exist.
-// See IgnoreDescriptors with regard to OneofDescriptors for more information.
+// IgnoreOneofs ignores fields of the specified oneofs in the specified message.
+// It is equivalent to FilterOneof(message, name, cmp.Ignore()) for each oneof
+// in the message.
 //
 // This must be used in conjunction with Transform.
 func IgnoreOneofs(message proto.Message, names ...protoreflect.Name) cmp.Option {
@@ -75,24 +157,7 @@
 }
 
 // IgnoreDescriptors ignores the specified set of descriptors.
-// The following descriptor types may be specified:
-//
-// • EnumDescriptor: Enums of this type or messages containing singular fields,
-// list fields, or map fields with enum values of this type are ignored.
-// Enums are matched based on their full name.
-//
-// • MessageDescriptor: Messages of this type or messages containing
-// singular fields, list fields, or map fields with message values of this type
-// are ignored. Messages are matched based on their full name.
-//
-// • ExtensionDescriptor: Extensions fields that match the given descriptor
-// by full name are ignored.
-//
-// • FieldDescriptor: Message fields that match the given descriptor
-// by full name are ignored.
-//
-// • OneofDescriptor: Message fields that match the set of fields in the given
-// oneof descriptor by full name are ignored.
+// It is equivalent to FilterDescriptor(desc, cmp.Ignore()) for each descriptor.
 //
 // This must be used in conjunction with Transform.
 func IgnoreDescriptors(descs ...protoreflect.Descriptor) cmp.Option {
@@ -192,6 +257,12 @@
 }
 
 func (f *nameFilters) filterFields(p cmp.Path) bool {
+	// Trim off trailing type-assertions so that the filter can match on the
+	// concrete value held within an interface value.
+	if _, ok := p.Last().(cmp.TypeAssertion); ok {
+		p = p[:len(p)-1]
+	}
+
 	// Filter for Message maps.
 	mi, ok := p.Index(-1).(cmp.MapIndex)
 	if !ok {
diff --git a/testing/protocmp/util_test.go b/testing/protocmp/util_test.go
index 1206592..04e0d1a 100644
--- a/testing/protocmp/util_test.go
+++ b/testing/protocmp/util_test.go
@@ -368,8 +368,6 @@
 		y:    &testpb.TestAllTypes{RepeatedForeignMessage: []*testpb.ForeignMessage{{}, {}, nil, {}, {C: proto.Int32(5)}, {}}},
 		opts: cmp.Options{Transform(), IgnoreEmptyMessages()},
 		want: true,
-
-		// TODO
 	}, {
 		x:    &testpb.TestAllTypes{MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{}},
 		y:    &testpb.TestAllTypes{MapStringNestedMessage: nil},
@@ -531,6 +529,436 @@
 		want: false,
 	}}...)
 
+	// Test FilterEnum.
+	tests = append(tests, []test{{
+		x:    &testpb.TestAllTypes{OptionalNestedEnum: testpb.TestAllTypes_FOO.Enum()},
+		y:    &testpb.TestAllTypes{OptionalNestedEnum: testpb.TestAllTypes_BAR.Enum()},
+		opts: cmp.Options{Transform()},
+		want: false,
+	}, {
+		x: &testpb.TestAllTypes{OptionalNestedEnum: testpb.TestAllTypes_FOO.Enum()},
+		y: &testpb.TestAllTypes{OptionalNestedEnum: testpb.TestAllTypes_BAR.Enum()},
+		opts: cmp.Options{
+			Transform(),
+			FilterEnum(testpb.ForeignEnum(0), cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: false, // mismatching filter type
+	}, {
+		x: &testpb.TestAllTypes{OptionalNestedEnum: testpb.TestAllTypes_FOO.Enum()},
+		y: &testpb.TestAllTypes{OptionalNestedEnum: testpb.TestAllTypes_BAR.Enum()},
+		opts: cmp.Options{
+			Transform(),
+			FilterEnum(testpb.TestAllTypes_NestedEnum(0), cmp.Comparer(func(x, y int) bool { return true })),
+		},
+		want: false, // matching filter type, but mismatching comparer type
+	}, {
+		x: &testpb.TestAllTypes{OptionalNestedEnum: testpb.TestAllTypes_FOO.Enum()},
+		y: &testpb.TestAllTypes{OptionalNestedEnum: testpb.TestAllTypes_BAR.Enum()},
+		opts: cmp.Options{
+			Transform(),
+			FilterEnum(testpb.TestAllTypes_NestedEnum(0), cmp.Comparer(func(x, y testpb.TestAllTypes_NestedEnum) bool { return true })),
+		},
+		want: false, // matching filter type, but mismatching comparer type
+	}, {
+		x: &testpb.TestAllTypes{OptionalNestedEnum: testpb.TestAllTypes_FOO.Enum()},
+		y: &testpb.TestAllTypes{OptionalNestedEnum: testpb.TestAllTypes_BAR.Enum()},
+		opts: cmp.Options{
+			Transform(),
+			FilterEnum(testpb.TestAllTypes_NestedEnum(0), cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: true,
+	}, {
+		x: &testpb.TestAllTypes{OptionalNestedEnum: testpb.TestAllTypes_FOO.Enum()},
+		y: &testpb.TestAllTypes{OptionalNestedEnum: testpb.TestAllTypes_BAR.Enum()},
+		opts: cmp.Options{
+			Transform(),
+			FilterEnum(testpb.TestAllTypes_NestedEnum(0), cmp.Comparer(func(x, y Enum) bool { return true })),
+		},
+		want: true,
+	}, {
+		x:    &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_FOO}},
+		y:    &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_BAR}},
+		opts: cmp.Options{Transform()},
+		want: false,
+	}, {
+		x: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_FOO}},
+		y: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_BAR}},
+		opts: cmp.Options{
+			Transform(),
+			FilterEnum(testpb.ForeignEnum(0), cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: false, // mismatching filter type
+	}, {
+		x: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_FOO}},
+		y: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_BAR}},
+		opts: cmp.Options{
+			Transform(),
+			FilterEnum(testpb.TestAllTypes_NestedEnum(0), cmp.Comparer(func(x, y int) bool { return true })),
+		},
+		want: false, // matching filter type, but mismatching comparer type
+	}, {
+		x: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_FOO}},
+		y: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_BAR}},
+		opts: cmp.Options{
+			Transform(),
+			FilterEnum(testpb.TestAllTypes_NestedEnum(0), cmp.Comparer(func(x, y []testpb.TestAllTypes_NestedEnum) bool { return true })),
+		},
+		want: false, // matching filter type, but mismatching comparer type
+	}, {
+		x: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_FOO}},
+		y: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_BAR}},
+		opts: cmp.Options{
+			Transform(),
+			FilterEnum(testpb.TestAllTypes_NestedEnum(0), cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: true,
+	}, {
+		x: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_FOO}},
+		y: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_BAR}},
+		opts: cmp.Options{
+			Transform(),
+			FilterEnum(testpb.TestAllTypes_NestedEnum(0), cmp.Comparer(func(x, y []Enum) bool { return true })),
+		},
+		want: true,
+	}, {
+		x:    &testpb.TestAllTypes{MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{"k": testpb.TestAllTypes_FOO}},
+		y:    &testpb.TestAllTypes{MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{"k": testpb.TestAllTypes_BAR}},
+		opts: cmp.Options{Transform()},
+		want: false,
+	}, {
+		x: &testpb.TestAllTypes{MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{"k": testpb.TestAllTypes_FOO}},
+		y: &testpb.TestAllTypes{MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{"k": testpb.TestAllTypes_BAR}},
+		opts: cmp.Options{
+			Transform(),
+			FilterEnum(testpb.ForeignEnum(0), cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: false, // mismatching filter type
+	}, {
+		x: &testpb.TestAllTypes{MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{"k": testpb.TestAllTypes_FOO}},
+		y: &testpb.TestAllTypes{MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{"k": testpb.TestAllTypes_BAR}},
+		opts: cmp.Options{
+			Transform(),
+			FilterEnum(testpb.TestAllTypes_NestedEnum(0), cmp.Comparer(func(x, y int) bool { return true })),
+		},
+		want: false, // matching filter type, but mismatching comparer type
+	}, {
+		x: &testpb.TestAllTypes{MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{"k": testpb.TestAllTypes_FOO}},
+		y: &testpb.TestAllTypes{MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{"k": testpb.TestAllTypes_BAR}},
+		opts: cmp.Options{
+			Transform(),
+			FilterEnum(testpb.TestAllTypes_NestedEnum(0), cmp.Comparer(func(x, y map[string]testpb.TestAllTypes_NestedEnum) bool { return true })),
+		},
+		want: false, // matching filter type, but mismatching comparer type
+	}, {
+		x: &testpb.TestAllTypes{MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{"k": testpb.TestAllTypes_FOO}},
+		y: &testpb.TestAllTypes{MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{"k": testpb.TestAllTypes_BAR}},
+		opts: cmp.Options{
+			Transform(),
+			FilterEnum(testpb.TestAllTypes_NestedEnum(0), cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: true,
+	}, {
+		x: &testpb.TestAllTypes{MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{"k": testpb.TestAllTypes_FOO}},
+		y: &testpb.TestAllTypes{MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{"k": testpb.TestAllTypes_BAR}},
+		opts: cmp.Options{
+			Transform(),
+			FilterEnum(testpb.TestAllTypes_NestedEnum(0), cmp.Comparer(func(x, y map[string]Enum) bool { return true })),
+		},
+		want: true,
+	}}...)
+
+	// Test FilterMessage.
+	tests = append(tests, []test{{
+		x:    &testpb.TestAllTypes{OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{A: proto.Int32(1)}},
+		y:    &testpb.TestAllTypes{OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{A: proto.Int32(2)}},
+		opts: cmp.Options{Transform()},
+		want: false,
+	}, {
+		x: &testpb.TestAllTypes{OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{A: proto.Int32(1)}},
+		y: &testpb.TestAllTypes{OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{A: proto.Int32(2)}},
+		opts: cmp.Options{
+			Transform(),
+			FilterMessage(new(testpb.TestAllExtensions), cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: false, // mismatching filter type
+	}, {
+		x: &testpb.TestAllTypes{OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{A: proto.Int32(1)}},
+		y: &testpb.TestAllTypes{OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{A: proto.Int32(2)}},
+		opts: cmp.Options{
+			Transform(),
+			FilterMessage(new(testpb.TestAllTypes_NestedMessage), cmp.Comparer(func(x, y int) bool { return true })),
+		},
+		want: false, // matching filter type, but mismatching comparer type
+	}, {
+		x: &testpb.TestAllTypes{OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{A: proto.Int32(1)}},
+		y: &testpb.TestAllTypes{OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{A: proto.Int32(2)}},
+		opts: cmp.Options{
+			Transform(),
+			FilterMessage(new(testpb.TestAllTypes_NestedMessage), cmp.Comparer(func(x, y *testpb.TestAllTypes_NestedMessage) bool { return true })),
+		},
+		want: false, // matching filter type, but mismatching comparer type
+	}, {
+		x: &testpb.TestAllTypes{OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{A: proto.Int32(1)}},
+		y: &testpb.TestAllTypes{OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{A: proto.Int32(2)}},
+		opts: cmp.Options{
+			Transform(),
+			FilterMessage(new(testpb.TestAllTypes_NestedMessage), cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: true,
+	}, {
+		x: &testpb.TestAllTypes{OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{A: proto.Int32(1)}},
+		y: &testpb.TestAllTypes{OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{A: proto.Int32(2)}},
+		opts: cmp.Options{
+			Transform(),
+			FilterMessage(new(testpb.TestAllTypes_NestedMessage), cmp.Comparer(func(x, y Message) bool { return true })),
+		},
+		want: true,
+	}, {
+		x:    &testpb.TestAllTypes{RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{{A: proto.Int32(1)}}},
+		y:    &testpb.TestAllTypes{RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{{A: proto.Int32(2)}}},
+		opts: cmp.Options{Transform()},
+		want: false,
+	}, {
+		x: &testpb.TestAllTypes{RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{{A: proto.Int32(1)}}},
+		y: &testpb.TestAllTypes{RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{{A: proto.Int32(2)}}},
+		opts: cmp.Options{
+			Transform(),
+			FilterMessage(new(testpb.TestAllExtensions), cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: false, // mismatching filter type
+	}, {
+		x: &testpb.TestAllTypes{RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{{A: proto.Int32(1)}}},
+		y: &testpb.TestAllTypes{RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{{A: proto.Int32(2)}}},
+		opts: cmp.Options{
+			Transform(),
+			FilterMessage(new(testpb.TestAllTypes_NestedMessage), cmp.Comparer(func(x, y int) bool { return true })),
+		},
+		want: false, // matching filter type, but mismatching comparer type
+	}, {
+		x: &testpb.TestAllTypes{RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{{A: proto.Int32(1)}}},
+		y: &testpb.TestAllTypes{RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{{A: proto.Int32(2)}}},
+		opts: cmp.Options{
+			Transform(),
+			FilterMessage(new(testpb.TestAllTypes_NestedMessage), cmp.Comparer(func(x, y []*testpb.TestAllTypes_NestedMessage) bool { return true })),
+		},
+		want: false, // matching filter type, but mismatching comparer type
+	}, {
+		x: &testpb.TestAllTypes{RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{{A: proto.Int32(1)}}},
+		y: &testpb.TestAllTypes{RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{{A: proto.Int32(2)}}},
+		opts: cmp.Options{
+			Transform(),
+			FilterMessage(new(testpb.TestAllTypes_NestedMessage), cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: true,
+	}, {
+		x: &testpb.TestAllTypes{RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{{A: proto.Int32(1)}}},
+		y: &testpb.TestAllTypes{RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{{A: proto.Int32(2)}}},
+		opts: cmp.Options{
+			Transform(),
+			FilterMessage(new(testpb.TestAllTypes_NestedMessage), cmp.Comparer(func(x, y []Message) bool { return true })),
+		},
+		want: true,
+	}, {
+		x:    &testpb.TestAllTypes{MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{"k": {A: proto.Int32(1)}}},
+		y:    &testpb.TestAllTypes{MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{"k": {A: proto.Int32(2)}}},
+		opts: cmp.Options{Transform()},
+		want: false,
+	}, {
+		x: &testpb.TestAllTypes{MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{"k": {A: proto.Int32(1)}}},
+		y: &testpb.TestAllTypes{MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{"k": {A: proto.Int32(2)}}},
+		opts: cmp.Options{
+			Transform(),
+			FilterMessage(new(testpb.TestAllExtensions), cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: false, // mismatching filter type
+	}, {
+		x: &testpb.TestAllTypes{MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{"k": {A: proto.Int32(1)}}},
+		y: &testpb.TestAllTypes{MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{"k": {A: proto.Int32(2)}}},
+		opts: cmp.Options{
+			Transform(),
+			FilterMessage(new(testpb.TestAllTypes_NestedMessage), cmp.Comparer(func(x, y int) bool { return true })),
+		},
+		want: false, // matching filter type, but mismatching comparer type
+	}, {
+		x: &testpb.TestAllTypes{MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{"k": {A: proto.Int32(1)}}},
+		y: &testpb.TestAllTypes{MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{"k": {A: proto.Int32(2)}}},
+		opts: cmp.Options{
+			Transform(),
+			FilterMessage(new(testpb.TestAllTypes_NestedMessage), cmp.Comparer(func(x, y map[string]*testpb.TestAllTypes_NestedMessage) bool { return true })),
+		},
+		want: false, // matching filter type, but mismatching comparer type
+	}, {
+		x: &testpb.TestAllTypes{MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{"k": {A: proto.Int32(1)}}},
+		y: &testpb.TestAllTypes{MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{"k": {A: proto.Int32(2)}}},
+		opts: cmp.Options{
+			Transform(),
+			FilterMessage(new(testpb.TestAllTypes_NestedMessage), cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: true,
+	}, {
+		x: &testpb.TestAllTypes{MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{"k": {A: proto.Int32(1)}}},
+		y: &testpb.TestAllTypes{MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{"k": {A: proto.Int32(2)}}},
+		opts: cmp.Options{
+			Transform(),
+			FilterMessage(new(testpb.TestAllTypes_NestedMessage), cmp.Comparer(func(x, y map[string]Message) bool { return true })),
+		},
+		want: true,
+	}}...)
+
+	// Test FilterField.
+	tests = append(tests, []test{{
+		x:    &testpb.TestAllTypes{OptionalInt32: proto.Int32(1)},
+		y:    &testpb.TestAllTypes{OptionalInt32: proto.Int32(2)},
+		opts: cmp.Options{Transform()},
+		want: false,
+	}, {
+		x: &testpb.TestAllTypes{OptionalInt32: proto.Int32(1)},
+		y: &testpb.TestAllTypes{OptionalInt32: proto.Int32(2)},
+		opts: cmp.Options{
+			Transform(),
+			FilterField(new(testpb.TestAllTypes), "optional_int64", cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: false, // mismatching filter name
+	}, {
+		x: &testpb.TestAllTypes{OptionalInt32: proto.Int32(1)},
+		y: &testpb.TestAllTypes{OptionalInt32: proto.Int32(2)},
+		opts: cmp.Options{
+			Transform(),
+			FilterField(new(testpb.TestAllTypes), "optional_int32", cmp.Comparer(func(x, y int64) bool { return true })),
+		},
+		want: false, // matching filter name, but mismatching comparer type
+	}, {
+		x: &testpb.TestAllTypes{OptionalInt32: proto.Int32(1)},
+		y: &testpb.TestAllTypes{OptionalInt32: proto.Int32(2)},
+		opts: cmp.Options{
+			Transform(),
+			FilterField(new(testpb.TestAllTypes), "optional_int32", cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: true,
+	}, {
+		x: &testpb.TestAllTypes{OptionalInt32: proto.Int32(1)},
+		y: &testpb.TestAllTypes{OptionalInt32: proto.Int32(2)},
+		opts: cmp.Options{
+			Transform(),
+			FilterField(new(testpb.TestAllTypes), "optional_int32", cmp.Comparer(func(x, y int32) bool { return true })),
+		},
+		want: true,
+	}, {
+		x:    &testpb.TestAllTypes{RepeatedInt32: []int32{1}},
+		y:    &testpb.TestAllTypes{RepeatedInt32: []int32{2}},
+		opts: cmp.Options{Transform()},
+		want: false,
+	}, {
+		x: &testpb.TestAllTypes{RepeatedInt32: []int32{1}},
+		y: &testpb.TestAllTypes{RepeatedInt32: []int32{2}},
+		opts: cmp.Options{
+			Transform(),
+			FilterField(new(testpb.TestAllTypes), "repeated_int64", cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: false, // mismatching filter name
+	}, {
+		x: &testpb.TestAllTypes{RepeatedInt32: []int32{1}},
+		y: &testpb.TestAllTypes{RepeatedInt32: []int32{2}},
+		opts: cmp.Options{
+			Transform(),
+			FilterField(new(testpb.TestAllTypes), "repeated_int32", cmp.Comparer(func(x, y []int64) bool { return true })),
+		},
+		want: false, // matching filter name, but mismatching comparer type
+	}, {
+		x: &testpb.TestAllTypes{RepeatedInt32: []int32{1}},
+		y: &testpb.TestAllTypes{RepeatedInt32: []int32{2}},
+		opts: cmp.Options{
+			Transform(),
+			FilterField(new(testpb.TestAllTypes), "repeated_int32", cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: true,
+	}, {
+		x: &testpb.TestAllTypes{RepeatedInt32: []int32{1}},
+		y: &testpb.TestAllTypes{RepeatedInt32: []int32{2}},
+		opts: cmp.Options{
+			Transform(),
+			FilterField(new(testpb.TestAllTypes), "repeated_int32", cmp.Comparer(func(x, y []int32) bool { return true })),
+		},
+		want: true,
+	}, {
+		x:    &testpb.TestAllTypes{MapInt32Int32: map[int32]int32{1: 1}},
+		y:    &testpb.TestAllTypes{MapInt32Int32: map[int32]int32{2: 2}},
+		opts: cmp.Options{Transform()},
+		want: false,
+	}, {
+		x: &testpb.TestAllTypes{MapInt32Int32: map[int32]int32{1: 1}},
+		y: &testpb.TestAllTypes{MapInt32Int32: map[int32]int32{2: 2}},
+		opts: cmp.Options{
+			Transform(),
+			FilterField(new(testpb.TestAllTypes), "map_int64_int64", cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: false, // mismatching filter name
+	}, {
+		x: &testpb.TestAllTypes{MapInt32Int32: map[int32]int32{1: 1}},
+		y: &testpb.TestAllTypes{MapInt32Int32: map[int32]int32{2: 2}},
+		opts: cmp.Options{
+			Transform(),
+			FilterField(new(testpb.TestAllTypes), "map_int32_int32", cmp.Comparer(func(x, y map[int64]int64) bool { return true })),
+		},
+		want: false, // matching filter name, but mismatching comparer type
+	}, {
+		x: &testpb.TestAllTypes{MapInt32Int32: map[int32]int32{1: 1}},
+		y: &testpb.TestAllTypes{MapInt32Int32: map[int32]int32{2: 2}},
+		opts: cmp.Options{
+			Transform(),
+			FilterField(new(testpb.TestAllTypes), "map_int32_int32", cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: true,
+	}, {
+		x: &testpb.TestAllTypes{MapInt32Int32: map[int32]int32{1: 1}},
+		y: &testpb.TestAllTypes{MapInt32Int32: map[int32]int32{2: 2}},
+		opts: cmp.Options{
+			Transform(),
+			FilterField(new(testpb.TestAllTypes), "map_int32_int32", cmp.Comparer(func(x, y map[int32]int32) bool { return true })),
+		},
+		want: true,
+	}}...)
+
+	// Test FilterOneof
+	tests = append(tests, []test{{
+		x:    &testpb.TestAllTypes{OneofField: &testpb.TestAllTypes_OneofUint32{1}},
+		y:    &testpb.TestAllTypes{OneofField: &testpb.TestAllTypes_OneofUint32{2}},
+		opts: cmp.Options{Transform()},
+		want: false,
+	}, {
+		x: &testpb.TestAllTypes{OneofField: &testpb.TestAllTypes_OneofUint32{1}},
+		y: &testpb.TestAllTypes{OneofField: &testpb.TestAllTypes_OneofUint32{2}},
+		opts: cmp.Options{
+			Transform(),
+			FilterOneof(new(testpb.TestAllTypes), "oneof_optional", cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: false, // mismatching filter name
+	}, {
+		x: &testpb.TestAllTypes{OneofField: &testpb.TestAllTypes_OneofUint32{1}},
+		y: &testpb.TestAllTypes{OneofField: &testpb.TestAllTypes_OneofUint32{2}},
+		opts: cmp.Options{
+			Transform(),
+			FilterOneof(new(testpb.TestAllTypes), "oneof_field", cmp.Comparer(func(x, y string) bool { return true })),
+		},
+		want: false, // matching filter name, but mismatching comparer type
+	}, {
+		x: &testpb.TestAllTypes{OneofField: &testpb.TestAllTypes_OneofUint32{1}},
+		y: &testpb.TestAllTypes{OneofField: &testpb.TestAllTypes_OneofUint32{2}},
+		opts: cmp.Options{
+			Transform(),
+			FilterOneof(new(testpb.TestAllTypes), "oneof_field", cmp.Comparer(func(x, y uint32) bool { return true })),
+		},
+		want: true,
+	}, {
+		x: &testpb.TestAllTypes{OneofField: &testpb.TestAllTypes_OneofUint32{1}},
+		y: &testpb.TestAllTypes{OneofField: &testpb.TestAllTypes_OneofUint32{2}},
+		opts: cmp.Options{
+			Transform(),
+			FilterOneof(new(testpb.TestAllTypes), "oneof_field", cmp.Comparer(func(x, y interface{}) bool { return true })),
+		},
+		want: true,
+	}}...)
+
 	for _, tt := range tests {
 		t.Run("", func(t *testing.T) {
 			got := cmp.Equal(tt.x, tt.y, tt.opts)