Refactor option evaluation logic (#32)

The previous implementation of options had a single "option" type
that was used to represent either an Ignore, Comparer, or Transformer
and all of the filters relevant to each of them.

We refactor this logic by creating a new type to represent each of
the fundamental options and filtering options. Construction of
filtered options is now as simple as wrapping the input option with
the appropriate filter type.

Evaluation of filters now takes a top-down two-step approach where
1. We start with the set of all options, and recursively apply the filters
to create the "applicable" set S.
2. We apply the set S.

Both steps are represented as the filter and apply methods on
each of the core options.

This approach has the following advantages:
* It is faster because the same filter that was applied to multiple
options now only needs to execute once.
* It more closely matches the documented algorithm in cmp.Equal.
* It allows for easier extension of the API to add new fundamental
option types.
diff --git a/cmp/compare.go b/cmp/compare.go
index 177fc10..5527f01 100644
--- a/cmp/compare.go
+++ b/cmp/compare.go
@@ -54,11 +54,10 @@
 // If at least one Ignore exists in S, then the comparison is ignored.
 // If the number of Transformer and Comparer options in S is greater than one,
 // then Equal panics because it is ambiguous which option to use.
-// If S contains a single Transformer, then apply that transformer on the
-// current values and recursively call Equal on the transformed output values.
-// If S contains a single Comparer, then use that Comparer to determine whether
-// the current values are equal or not.
-// Otherwise, S is empty and evaluation proceeds to the next rule.
+// If S contains a single Transformer, then use that to transform the current
+// values and recursively call Equal on the output values.
+// If S contains a single Comparer, then use that to compare the current values.
+// Otherwise, evaluation proceeds to the next rule.
 //
 // • If the values have an Equal method of the form "(T) Equal(T) bool" or
 // "(T) Equal(I) bool" where T is assignable to I, then use the result of
@@ -119,8 +118,7 @@
 
 	// These fields, once set by processOption, will not change.
 	exporters map[reflect.Type]bool // Set of structs with unexported field visibility
-	optsIgn   []option              // List of all ignore options without value filters
-	opts      []option              // List of all other options
+	opts      Options               // List of all fundamental and filter options
 }
 
 func newState(opts []Option) *state {
@@ -128,22 +126,24 @@
 	for _, opt := range opts {
 		s.processOption(opt)
 	}
-	// Move Ignore options to the front so that they are evaluated first.
-	for i, j := 0, 0; i < len(s.opts); i++ {
-		if s.opts[i].op == nil {
-			s.opts[i], s.opts[j] = s.opts[j], s.opts[i]
-			j++
-		}
-	}
 	return s
 }
 
 func (s *state) processOption(opt Option) {
 	switch opt := opt.(type) {
+	case nil:
 	case Options:
 		for _, o := range opt {
 			s.processOption(o)
 		}
+	case coreOption:
+		type filtered interface {
+			isFiltered() bool
+		}
+		if fopt, ok := opt.(filtered); ok && !fopt.isFiltered() {
+			panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt))
+		}
+		s.opts = append(s.opts, opt)
 	case visibleStructs:
 		if s.exporters == nil {
 			s.exporters = make(map[reflect.Type]bool)
@@ -151,15 +151,6 @@
 		for t := range opt {
 			s.exporters[t] = true
 		}
-	case option:
-		if opt.typeFilter == nil && len(opt.pathFilters)+len(opt.valueFilters) == 0 {
-			panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt))
-		}
-		if opt.op == nil && len(opt.valueFilters) == 0 {
-			s.optsIgn = append(s.optsIgn, opt)
-		} else {
-			s.opts = append(s.opts, opt)
-		}
 	case reporter:
 		if s.reporter != nil {
 			panic("difference reporter already registered")
@@ -205,9 +196,10 @@
 		s.curPath.push(&pathStep{typ: t})
 		defer s.curPath.pop()
 	}
+	vx, vy = s.tryExporting(vx, vy)
 
 	// Rule 1: Check whether an option applies on this node in the value tree.
-	if s.tryOptions(&vx, &vy, t) {
+	if s.tryOptions(vx, vy, t) {
 		return
 	}
 
@@ -284,92 +276,39 @@
 	}
 }
 
-// tryOptions iterates through all of the options and evaluates whether any
-// of them can be applied. This may modify the underlying values vx and vy
-// if an unexported field is being forcibly exported.
-func (s *state) tryOptions(vx, vy *reflect.Value, t reflect.Type) bool {
-	// Try all ignore options that do not depend on the value first.
-	// This avoids possible panics when processing unexported fields.
-	for _, opt := range s.optsIgn {
-		var v reflect.Value // Dummy value; should never be used
-		if s.applyFilters(v, v, t, opt) {
-			return true // Ignore option applied
-		}
-	}
-
-	// Since the values must be used after this point, verify that the values
-	// are either exported or can be forcibly exported.
+func (s *state) tryExporting(vx, vy reflect.Value) (reflect.Value, reflect.Value) {
 	if sf, ok := s.curPath[len(s.curPath)-1].(*structField); ok && sf.unexported {
-		if !sf.force {
-			const help = "consider using AllowUnexported or cmpopts.IgnoreUnexported"
-			panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help))
+		if sf.force {
+			// Use unsafe pointer arithmetic to get read-write access to an
+			// unexported field in the struct.
+			vx = unsafeRetrieveField(sf.pvx, sf.field)
+			vy = unsafeRetrieveField(sf.pvy, sf.field)
+		} else {
+			// We are not allowed to export the value, so invalidate them
+			// so that tryOptions can panic later if not explicitly ignored.
+			vx = nothing
+			vy = nothing
 		}
+	}
+	return vx, vy
+}
 
-		// Use unsafe pointer arithmetic to get read-write access to an
-		// unexported field in the struct.
-		*vx = unsafeRetrieveField(sf.pvx, sf.field)
-		*vy = unsafeRetrieveField(sf.pvy, sf.field)
+func (s *state) tryOptions(vx, vy reflect.Value, t reflect.Type) bool {
+	// If there were no FilterValues, we will not detect invalid inputs,
+	// so manually check for them and append invalid if necessary.
+	// We still evaluate the options since an ignore can override invalid.
+	opts := s.opts
+	if !vx.IsValid() || !vy.IsValid() {
+		opts = Options{opts, invalid{}}
 	}
 
-	// Try all other options now.
-	optIdx := -1 // Index of Option to apply
-	for i, opt := range s.opts {
-		if !s.applyFilters(*vx, *vy, t, opt) {
-			continue
-		}
-		if opt.op == nil {
-			return true // Ignored comparison
-		}
-		if optIdx >= 0 {
-			panic(fmt.Sprintf("ambiguous set of options at %#v\n\n%v\n\n%v\n", s.curPath, s.opts[optIdx], opt))
-		}
-		optIdx = i
-	}
-	if optIdx >= 0 {
-		s.applyOption(*vx, *vy, t, s.opts[optIdx])
-		return true
+	// Evaluate all filters and apply the remaining options.
+	if opt := opts.filter(s, vx, vy, t); opt != nil {
+		return opt.apply(s, vx, vy)
 	}
 	return false
 }
 
-func (s *state) applyFilters(vx, vy reflect.Value, t reflect.Type, opt option) bool {
-	if opt.typeFilter != nil {
-		if !t.AssignableTo(opt.typeFilter) {
-			return false
-		}
-	}
-	for _, f := range opt.pathFilters {
-		if !f(s.curPath) {
-			return false
-		}
-	}
-	for _, f := range opt.valueFilters {
-		if !t.AssignableTo(f.in) || !s.callTTBFunc(f.fnc, vx, vy) {
-			return false
-		}
-	}
-	return true
-}
-
-func (s *state) applyOption(vx, vy reflect.Value, t reflect.Type, opt option) {
-	switch op := opt.op.(type) {
-	case *transformer:
-		// Update path before calling the Transformer so that dynamic checks
-		// will use the updated path.
-		s.curPath.push(&transform{pathStep{op.fnc.Type().Out(0)}, op})
-		defer s.curPath.pop()
-
-		vx = s.callTRFunc(op.fnc, vx)
-		vy = s.callTRFunc(op.fnc, vy)
-		s.compareAny(vx, vy)
-		return
-	case *comparer:
-		eq := s.callTTBFunc(op.fnc, vx, vy)
-		s.report(eq, vx, vy)
-		return
-	}
-}
-
 func (s *state) tryMethod(vx, vy reflect.Value, t reflect.Type) bool {
 	// Check if this type even has an Equal method.
 	m, ok := t.MethodByName("Equal")
diff --git a/cmp/compare_test.go b/cmp/compare_test.go
index a0c003e..36a4ecf 100644
--- a/cmp/compare_test.go
+++ b/cmp/compare_test.go
@@ -113,7 +113,7 @@
 			cmp.Comparer(func(x, y int) bool { return true }),
 			cmp.Transformer("", func(x int) float64 { return float64(x) }),
 		},
-		wantPanic: "ambiguous set of options",
+		wantPanic: "ambiguous set of applicable options",
 	}, {
 		label: label,
 		x:     1,
@@ -380,7 +380,7 @@
 			cmp.Transformer("", func(in int) int { return in / 2 }),
 			cmp.Transformer("", func(in int) int { return in }),
 		},
-		wantPanic: "ambiguous set of options",
+		wantPanic: "ambiguous set of applicable options",
 	}, {
 		label: label,
 		x:     []int{0, -5, 0, -1},
diff --git a/cmp/options.go b/cmp/options.go
index 0498a55..a4e159a 100644
--- a/cmp/options.go
+++ b/cmp/options.go
@@ -23,11 +23,38 @@
 // The cmp/cmpopts package provides helper functions for creating options that
 // may be used with Equal and Diff.
 type Option interface {
-	// Prevent Option from being equivalent to interface{}, which provides
-	// a small type checking benefit by preventing Equal(opt, x, y).
-	option()
+	// filter applies all filters and returns the option that remains.
+	// Each option may only read s.curPath and call s.callTTBFunc.
+	//
+	// An Options is returned only if multiple comparers or transformers
+	// can apply simultaneously and will only contain values of those types
+	// or sub-Options containing values of those types.
+	filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption
 }
 
+// applicableOption represents the following types:
+//	Fundamental: ignore | invalid | *comparer | *transformer
+//	Grouping:    Options
+type applicableOption interface {
+	Option
+
+	// apply executes the option and reports whether the option was applied.
+	// Each option may mutate s.
+	apply(s *state, vx, vy reflect.Value) bool
+}
+
+// coreOption represents the following types:
+//	Fundamental: ignore | invalid | *comparer | *transformer
+//	Filters:     *pathFilter | *valuesFilter
+type coreOption interface {
+	Option
+	isCore()
+}
+
+type core struct{}
+
+func (core) isCore() {}
+
 // Options is a list of Option values that also satisfies the Option interface.
 // Helper comparison packages may return an Options value when packing multiple
 // Option values into a single Option. When this package processes an Options,
@@ -37,79 +64,44 @@
 // on all individual options held within.
 type Options []Option
 
-func (Options) option() {}
-
-type (
-	pathFilter  func(Path) bool
-	valueFilter struct {
-		in  reflect.Type  // T
-		fnc reflect.Value // func(T, T) bool
-	}
-)
-
-type option struct {
-	typeFilter   reflect.Type
-	pathFilters  []pathFilter
-	valueFilters []valueFilter
-
-	// op is the operation to perform. If nil, then this acts as an ignore.
-	op interface{} // nil | *transformer | *comparer
-}
-
-func (option) option() {}
-
-func (o option) String() string {
-	// TODO: Add information about the caller?
-	// TODO: Maintain the order that filters were added?
-
-	var ss []string
-	switch op := o.op.(type) {
-	case *transformer:
-		fn := getFuncName(op.fnc.Pointer())
-		ss = append(ss, fmt.Sprintf("Transformer(%s, %s)", op.name, fn))
-	case *comparer:
-		fn := getFuncName(op.fnc.Pointer())
-		ss = append(ss, fmt.Sprintf("Comparer(%s)", fn))
-	default:
-		ss = append(ss, "Ignore()")
-	}
-
-	for _, f := range o.pathFilters {
-		fn := getFuncName(reflect.ValueOf(f).Pointer())
-		ss = append(ss, fmt.Sprintf("FilterPath(%s)", fn))
-	}
-	for _, f := range o.valueFilters {
-		fn := getFuncName(f.fnc.Pointer())
-		ss = append(ss, fmt.Sprintf("FilterValues(%s)", fn))
-	}
-	return strings.Join(ss, "\n\t")
-}
-
-// getFuncName returns a short function name from the pointer.
-// The string parsing logic works up until Go1.9.
-func getFuncName(p uintptr) string {
-	fnc := runtime.FuncForPC(p)
-	if fnc == nil {
-		return "<unknown>"
-	}
-	name := fnc.Name() // E.g., "long/path/name/mypkg.(mytype).(long/path/name/mypkg.myfunc)-fm"
-	if strings.HasSuffix(name, ")-fm") || strings.HasSuffix(name, ")·fm") {
-		// Strip the package name from method name.
-		name = strings.TrimSuffix(name, ")-fm")
-		name = strings.TrimSuffix(name, ")·fm")
-		if i := strings.LastIndexByte(name, '('); i >= 0 {
-			methodName := name[i+1:] // E.g., "long/path/name/mypkg.myfunc"
-			if j := strings.LastIndexByte(methodName, '.'); j >= 0 {
-				methodName = methodName[j+1:] // E.g., "myfunc"
+func (opts Options) filter(s *state, vx, vy reflect.Value, t reflect.Type) (out applicableOption) {
+	for _, opt := range opts {
+		switch opt := opt.filter(s, vx, vy, t); opt.(type) {
+		case ignore:
+			return ignore{} // Only ignore can short-circuit evaluation
+		case invalid:
+			out = invalid{} // Takes precedence over comparer or transformer
+		case *comparer, *transformer, Options:
+			switch out.(type) {
+			case nil:
+				out = opt
+			case invalid:
+				// Keep invalid
+			case *comparer, *transformer, Options:
+				out = Options{out, opt} // Conflicting comparers or transformers
 			}
-			name = name[:i] + methodName // E.g., "long/path/name/mypkg.(mytype)." + "myfunc"
 		}
 	}
-	if i := strings.LastIndexByte(name, '/'); i >= 0 {
-		// Strip the package name.
-		name = name[i+1:] // E.g., "mypkg.(mytype).myfunc"
+	return out
+}
+
+func (opts Options) apply(s *state, _, _ reflect.Value) bool {
+	const warning = "ambiguous set of applicable options"
+	const help = "consider using filters to ensure at most one Comparer or Transformer may apply"
+	var ss []string
+	for _, opt := range flattenOptions(nil, opts) {
+		ss = append(ss, fmt.Sprint(opt))
 	}
-	return name
+	set := strings.Join(ss, "\n\t")
+	panic(fmt.Sprintf("%s at %#v:\n\t%s\n%s", warning, s.curPath, set, help))
+}
+
+func (opts Options) String() string {
+	var ss []string
+	for _, opt := range opts {
+		ss = append(ss, fmt.Sprint(opt))
+	}
+	return fmt.Sprintf("Options{%s}", strings.Join(ss, ", "))
 }
 
 // FilterPath returns a new Option where opt is only evaluated if filter f
@@ -121,20 +113,28 @@
 	if f == nil {
 		panic("invalid path filter function")
 	}
-	switch opt := opt.(type) {
-	case Options:
-		var opts []Option
-		for _, o := range opt {
-			opts = append(opts, FilterPath(f, o)) // Append to slice copy
-		}
-		return Options(opts)
-	case option:
-		n := len(opt.pathFilters)
-		opt.pathFilters = append(opt.pathFilters[:n:n], f) // Append to copy
-		return opt
-	default:
-		panic(fmt.Sprintf("unknown option type: %T", opt))
+	if opt := normalizeOption(opt); opt != nil {
+		return &pathFilter{fnc: f, opt: opt}
 	}
+	return nil
+}
+
+type pathFilter struct {
+	core
+	fnc func(Path) bool
+	opt Option
+}
+
+func (f pathFilter) filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption {
+	if f.fnc(s.curPath) {
+		return f.opt.filter(s, vx, vy, t)
+	}
+	return nil
+}
+
+func (f pathFilter) String() string {
+	fn := getFuncName(reflect.ValueOf(f.fnc).Pointer())
+	return fmt.Sprintf("FilterPath(%s, %v)", fn, f.opt)
 }
 
 // FilterValues returns a new Option where opt is only evaluated if filter f,
@@ -155,28 +155,58 @@
 	if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() {
 		panic(fmt.Sprintf("invalid values filter function: %T", f))
 	}
-	switch opt := opt.(type) {
-	case Options:
-		var opts []Option
-		for _, o := range opt {
-			opts = append(opts, FilterValues(f, o)) // Append to slice copy
+	if opt := normalizeOption(opt); opt != nil {
+		vf := &valuesFilter{fnc: v, opt: opt}
+		if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
+			vf.typ = ti
 		}
-		return Options(opts)
-	case option:
-		n := len(opt.valueFilters)
-		vf := valueFilter{v.Type().In(0), v}
-		opt.valueFilters = append(opt.valueFilters[:n:n], vf) // Append to copy
-		return opt
-	default:
-		panic(fmt.Sprintf("unknown option type: %T", opt))
+		return vf
 	}
+	return nil
+}
+
+type valuesFilter struct {
+	core
+	typ reflect.Type  // T
+	fnc reflect.Value // func(T, T) bool
+	opt Option
+}
+
+func (f valuesFilter) filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption {
+	if !vx.IsValid() || !vy.IsValid() {
+		return invalid{}
+	}
+	if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) {
+		return f.opt.filter(s, vx, vy, t)
+	}
+	return nil
+}
+
+func (f valuesFilter) String() string {
+	fn := getFuncName(f.fnc.Pointer())
+	return fmt.Sprintf("FilterValues(%s, %v)", fn, f.opt)
 }
 
 // Ignore is an Option that causes all comparisons to be ignored.
 // This value is intended to be combined with FilterPath or FilterValues.
 // It is an error to pass an unfiltered Ignore option to Equal.
-func Ignore() Option {
-	return option{}
+func Ignore() Option { return ignore{} }
+
+type ignore struct{ core }
+
+func (ignore) isFiltered() bool                                                     { return false }
+func (ignore) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return ignore{} }
+func (ignore) apply(_ *state, _, _ reflect.Value) bool                              { return true }
+func (ignore) String() string                                                       { return "Ignore()" }
+
+// invalid is a sentinel Option type to indicate that some options could not
+// be evaluated due to unexported fields.
+type invalid struct{ core }
+
+func (invalid) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return invalid{} }
+func (invalid) apply(s *state, _, _ reflect.Value) bool {
+	const help = "consider using AllowUnexported or cmpopts.IgnoreUnexported"
+	panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help))
 }
 
 // Transformer returns an Option that applies a transformation function that
@@ -202,18 +232,45 @@
 	if !isValid(name) {
 		panic(fmt.Sprintf("invalid name: %q", name))
 	}
-	opt := option{op: &transformer{name, reflect.ValueOf(f)}}
+	tr := &transformer{name: name, fnc: reflect.ValueOf(f)}
 	if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
-		opt.typeFilter = ti
+		tr.typ = ti
 	}
-	return opt
+	return tr
 }
 
 type transformer struct {
+	core
 	name string
+	typ  reflect.Type  // T
 	fnc  reflect.Value // func(T) R
 }
 
+func (tr *transformer) isFiltered() bool { return tr.typ != nil }
+
+func (tr *transformer) filter(_ *state, _, _ reflect.Value, t reflect.Type) applicableOption {
+	if tr.typ == nil || t.AssignableTo(tr.typ) {
+		return tr
+	}
+	return nil
+}
+
+func (tr *transformer) apply(s *state, vx, vy reflect.Value) bool {
+	// Update path before calling the Transformer so that dynamic checks
+	// will use the updated path.
+	s.curPath.push(&transform{pathStep{tr.fnc.Type().Out(0)}, tr})
+	defer s.curPath.pop()
+
+	vx = s.callTRFunc(tr.fnc, vx)
+	vy = s.callTRFunc(tr.fnc, vy)
+	s.compareAny(vx, vy)
+	return true
+}
+
+func (tr transformer) String() string {
+	return fmt.Sprintf("Transformer(%s, %s)", tr.name, getFuncName(tr.fnc.Pointer()))
+}
+
 // Comparer returns an Option that determines whether two values are equal
 // to each other.
 //
@@ -231,17 +288,38 @@
 	if !function.IsType(v.Type(), function.Equal) || v.IsNil() {
 		panic(fmt.Sprintf("invalid comparer function: %T", f))
 	}
-	opt := option{op: &comparer{v}}
+	cm := &comparer{fnc: v}
 	if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
-		opt.typeFilter = ti
+		cm.typ = ti
 	}
-	return opt
+	return cm
 }
 
 type comparer struct {
+	core
+	typ reflect.Type  // T
 	fnc reflect.Value // func(T, T) bool
 }
 
+func (cm *comparer) isFiltered() bool { return cm.typ != nil }
+
+func (cm *comparer) filter(_ *state, _, _ reflect.Value, t reflect.Type) applicableOption {
+	if cm.typ == nil || t.AssignableTo(cm.typ) {
+		return cm
+	}
+	return nil
+}
+
+func (cm *comparer) apply(s *state, vx, vy reflect.Value) bool {
+	eq := s.callTTBFunc(cm.fnc, vx, vy)
+	s.report(eq, vx, vy)
+	return true
+}
+
+func (cm comparer) String() string {
+	return fmt.Sprintf("Comparer(%s)", getFuncName(cm.fnc.Pointer()))
+}
+
 // AllowUnexported returns an Option that forcibly allows operations on
 // unexported fields in certain structs, which are specified by passing in a
 // value of each struct type.
@@ -285,7 +363,9 @@
 
 type visibleStructs map[reflect.Type]bool
 
-func (visibleStructs) option() {}
+func (visibleStructs) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption {
+	panic("not implemented")
+}
 
 // reporter is an Option that configures how differences are reported.
 type reporter interface {
@@ -305,3 +385,62 @@
 	// which is possible with maps and slices.
 	Report(x, y reflect.Value, eq bool, p Path)
 }
+
+// normalizeOption normalizes the input options such that all Options groups
+// are flattened and groups with a single element are reduced to that element.
+// Only coreOptions and Options containing coreOptions are allowed.
+func normalizeOption(src Option) Option {
+	switch opts := flattenOptions(nil, Options{src}); len(opts) {
+	case 0:
+		return nil
+	case 1:
+		return opts[0]
+	default:
+		return opts
+	}
+}
+
+// flattenOptions copies all options in src to dst as a flat list.
+// Only coreOptions and Options containing coreOptions are allowed.
+func flattenOptions(dst, src Options) Options {
+	for _, opt := range src {
+		switch opt := opt.(type) {
+		case nil:
+			continue
+		case Options:
+			dst = flattenOptions(dst, opt)
+		case coreOption:
+			dst = append(dst, opt)
+		default:
+			panic(fmt.Sprintf("invalid option type: %T", opt))
+		}
+	}
+	return dst
+}
+
+// getFuncName returns a short function name from the pointer.
+// The string parsing logic works up until Go1.9.
+func getFuncName(p uintptr) string {
+	fnc := runtime.FuncForPC(p)
+	if fnc == nil {
+		return "<unknown>"
+	}
+	name := fnc.Name() // E.g., "long/path/name/mypkg.(mytype).(long/path/name/mypkg.myfunc)-fm"
+	if strings.HasSuffix(name, ")-fm") || strings.HasSuffix(name, ")·fm") {
+		// Strip the package name from method name.
+		name = strings.TrimSuffix(name, ")-fm")
+		name = strings.TrimSuffix(name, ")·fm")
+		if i := strings.LastIndexByte(name, '('); i >= 0 {
+			methodName := name[i+1:] // E.g., "long/path/name/mypkg.myfunc"
+			if j := strings.LastIndexByte(methodName, '.'); j >= 0 {
+				methodName = methodName[j+1:] // E.g., "myfunc"
+			}
+			name = name[:i] + methodName // E.g., "long/path/name/mypkg.(mytype)." + "myfunc"
+		}
+	}
+	if i := strings.LastIndexByte(name, '/'); i >= 0 {
+		// Strip the package name.
+		name = name[i+1:] // E.g., "mypkg.(mytype).myfunc"
+	}
+	return name
+}
diff --git a/cmp/options_test.go b/cmp/options_test.go
index 2bde798..009b524 100644
--- a/cmp/options_test.go
+++ b/cmp/options_test.go
@@ -130,7 +130,7 @@
 		label:     "FilterPath",
 		fnc:       FilterPath,
 		args:      []interface{}{func(Path) bool { return true }, &defaultReporter{}},
-		wantPanic: "unknown option type",
+		wantPanic: "invalid option type",
 	}, {
 		label: "FilterPath",
 		fnc:   FilterPath,
@@ -139,7 +139,7 @@
 		label:     "FilterPath",
 		fnc:       FilterPath,
 		args:      []interface{}{func(Path) bool { return true }, Options{Ignore(), &defaultReporter{}}},
-		wantPanic: "unknown option type",
+		wantPanic: "invalid option type",
 	}, {
 		label:     "FilterValues",
 		fnc:       FilterValues,
@@ -172,7 +172,7 @@
 		label:     "FilterValues",
 		fnc:       FilterValues,
 		args:      []interface{}{func(int, int) bool { return true }, &defaultReporter{}},
-		wantPanic: "unknown option type",
+		wantPanic: "invalid option type",
 	}, {
 		label: "FilterValues",
 		fnc:   FilterValues,
@@ -181,7 +181,7 @@
 		label:     "FilterValues",
 		fnc:       FilterValues,
 		args:      []interface{}{func(int, int) bool { return true }, Options{Ignore(), &defaultReporter{}}},
-		wantPanic: "unknown option type",
+		wantPanic: "invalid option type",
 	}}
 
 	for _, tt := range tests {