Add package cmp for performing equality of Go values

The API of the package is as follows:
    func Equal(x, y interface{}, opts ...Option) bool
    func Diff(x, y interface{}, opts ...Option) string

    type Option interface{ ... }
        func Ignore() Option
        func Comparer(f interface{}) Option
        func Transformer(name string, f interface{}) Option

        func FilterPath(f func(Path) bool, opt Option) Option
        func FilterValues(f interface{}, opt Option) Option

        func AllowUnexported(typs ...interface{}) Option
    type Options []Option

    type Path []PathStep
    type PathStep interface{ ... }

    type Indirect interface{ ... }
    type StructField interface{ ... }
    type MapIndex interface{ ... }
    type SliceIndex interface{ ... }
    type TypeAssertion interface{ ... }
    type Transform interface{ ... }

See the package docs in compare.go for a high-level view of this package.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..ae319c7
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,23 @@
+# How to Contribute
+
+We'd love to accept your patches and contributions to this project. There are
+just a few small guidelines you need to follow.
+
+## Contributor License Agreement
+
+Contributions to this project must be accompanied by a Contributor License
+Agreement. You (or your employer) retain the copyright to your contribution,
+this simply gives us permission to use and redistribute your contributions as
+part of the project. Head over to <https://cla.developers.google.com/> to see
+your current agreements on file or to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already submitted one
+(even if it was for a different project), you probably don't need to do it
+again.
+
+## Code reviews
+
+All submissions, including submissions by project members, require review. We
+use GitHub pull requests for this purpose. Consult
+[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
+information on using pull requests.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..32017f8
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2017 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b78f72f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,39 @@
+# Package for equality of Go values
+
+[![GoDoc](https://godoc.org/github.com/google/go-cmp/cmp?status.svg)][godoc]
+
+This package is intended to be a more powerful and safer alternative to
+`reflect.DeepEqual` for comparing whether two values are semantically equal.
+
+The primary features of cmp are:
+
+* When the default behavior of equality does not suit the needs of the test,
+  custom equality functions can override the equality operation.
+  For example, an equality function may report floats as equal so long as they
+  are within some tolerance of each other.
+
+* Types that have an `Equal` method may use that method to determine equality.
+  This allows package authors to determine the equality operation for the types
+  that they define.
+
+* If no custom equality functions are used and no `Equal` method is defined,
+  equality is determined by recursively comparing the primitive kinds on both
+  values, much like `reflect.DeepEqual`. Unlike `reflect.DeepEqual`, unexported
+  fields are not compared; they result in panics unless suppressed by using
+  an `Ignore` option.
+
+This is not an official Google product.
+
+[godoc]: https://godoc.org/github.com/google/go-cmp/cmp
+
+## Install
+
+```
+go get -u github.com/google/go-cmp/cmp
+```
+
+## License
+
+BSD - See [LICENSE][license] file
+
+[license]: https://github.com/google/go-cmp/blob/master/LICENSE
diff --git a/cmp/LICENSE b/cmp/LICENSE
new file mode 100644
index 0000000..32017f8
--- /dev/null
+++ b/cmp/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2017 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/cmp/compare.go b/cmp/compare.go
new file mode 100644
index 0000000..0d8ef77
--- /dev/null
+++ b/cmp/compare.go
@@ -0,0 +1,514 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+// Package cmp determines equality of values.
+//
+// This package is intended to be a more powerful and safer alternative to
+// reflect.DeepEqual for comparing whether two values are semantically equal.
+//
+// The primary features of cmp are:
+//
+// • When the default behavior of equality does not suit the needs of the test,
+// custom equality functions can override the equality operation.
+// For example, an equality function may report floats as equal so long as they
+// are within some tolerance of each other.
+//
+// • Types that have an Equal method may use that method to determine equality.
+// This allows package authors to determine the equality operation for the types
+// that they define.
+//
+// • If no custom equality functions are used and no Equal method is defined,
+// equality is determined by recursively comparing the primitive kinds on both
+// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
+// fields are not compared; they result in panics unless suppressed by using
+// an Ignore option.
+package cmp
+
+import (
+	"fmt"
+	"reflect"
+	"sort"
+)
+
+// BUG: Maps with keys containing NaN values cannot be properly compared due to
+// the reflection package's inability to retrieve such entries. Equal will panic
+// anytime it comes across a NaN key, but this behavior may change.
+//
+// See https://golang.org/issue/11104 for more details.
+
+// Equal reports whether x and y are equal by recursively applying the
+// following rules in the given order to x and y and all of their sub-values:
+//
+// • If two values are not of the same type, then they are never equal
+// and the overall result is false.
+//
+// • Let S be the set of all Ignore, Transformer, and Comparer options that
+// remain after applying all path filters, value filters, and type filters.
+// 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 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
+// x.Equal(y). Otherwise, no such method exists and evaluation proceeds to
+// the next rule.
+//
+// • Lastly, try to compare x and y based on their basic kinds.
+// Simple kinds like booleans, integers, floats, complex numbers, strings, and
+// channels are compared using the equivalent of the == operator in Go.
+// Functions are only equal if they are both nil, otherwise they are unequal.
+// Pointers are equal if the underlying values they point to are also equal.
+// Interfaces are equal if their underlying concrete values are also equal.
+//
+// Structs are equal if all of their fields are equal. If a struct contains
+// unexported fields, Equal panics unless the AllowUnexported option is used or
+// an Ignore option ignores that field.
+// Slices and arrays are equal if they have the same length and the elements
+// at each index are equal.
+// Maps are equal if their keys are exactly equal (according to the == operator)
+// and the corresponding elements for each key are equal. To specify a custom
+// comparison for map keys, use a Transformer to convert the map to a
+// corresponding slice type.
+func Equal(x, y interface{}, opts ...Option) bool {
+	s := newState(opts)
+	s.compareAny(reflect.ValueOf(x), reflect.ValueOf(y))
+	return s.eq
+}
+
+// Diff returns a human-readable report of the differences between two values.
+// It returns an empty string if and only if Equal returns true for the same
+// input values and options. The output string will use the "-" symbol to
+// indicate elements removed from x, and the "+" symbol to indicate elements
+// added to y.
+//
+// Do not depend on this output being stable.
+func Diff(x, y interface{}, opts ...Option) string {
+	r := new(defaultReporter)
+	opts = append(opts[:len(opts):len(opts)], r) // Force copy when appending
+	eq := Equal(x, y, opts...)
+	d := r.String()
+	if (d == "") != eq {
+		panic("inconsistent difference and equality results")
+	}
+	return d
+}
+
+type state struct {
+	eq      bool // Current result of comparison
+	curPath Path // The current path in the value tree
+
+	// dsCheck tracks the state needed to periodically perform checks that
+	// user provided func(T, T) bool functions are symmetric and deterministic.
+	//
+	// Checks occur every Nth function call, where N is a triangular number:
+	//	0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ...
+	// See https://en.wikipedia.org/wiki/Triangular_number
+	//
+	// This sequence ensures that the cost of checks drops significantly as
+	// the number of functions calls grows larger.
+	dsCheck struct{ curr, next int }
+
+	// 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
+	reporter  reporter              // Optional reporter used for difference formatting
+}
+
+func newState(opts []Option) *state {
+	s := &state{eq: true}
+	for _, opt := range opts {
+		s.processOption(opt)
+	}
+	// Sort options such that Ignore options are evaluated first.
+	sort.SliceStable(s.opts, func(i, j int) bool {
+		return s.opts[i].op == nil && s.opts[j].op != nil
+	})
+	return s
+}
+
+func (s *state) processOption(opt Option) {
+	switch opt := opt.(type) {
+	case Options:
+		for _, o := range opt {
+			s.processOption(o)
+		}
+	case visibleStructs:
+		if s.exporters == nil {
+			s.exporters = make(map[reflect.Type]bool)
+		}
+		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")
+		}
+		s.reporter = opt
+	default:
+		panic(fmt.Sprintf("unknown option %T", opt))
+	}
+}
+
+func (s *state) compareAny(vx, vy reflect.Value) {
+	// TODO: Support cyclic data structures.
+
+	// Rule 0: Differing types are never equal.
+	if !vx.IsValid() || !vy.IsValid() {
+		s.report(vx.IsValid() == vy.IsValid(), vx, vy)
+		return
+	}
+	if vx.Type() != vy.Type() {
+		s.report(false, vx, vy) // Possible for path to be empty
+		return
+	}
+	t := vx.Type()
+	if len(s.curPath) == 0 {
+		s.curPath.push(&pathStep{typ: t})
+	}
+
+	// Rule 1: Check whether an option applies on this node in the value tree.
+	if s.tryOptions(&vx, &vy, t) {
+		return
+	}
+
+	// Rule 2: Check whether the type has a valid Equal method.
+	if s.tryMethod(vx, vy, t) {
+		return
+	}
+
+	// Rule 3: Recursively descend into each value's underlying kind.
+	switch t.Kind() {
+	case reflect.Bool:
+		s.report(vx.Bool() == vy.Bool(), vx, vy)
+		return
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		s.report(vx.Int() == vy.Int(), vx, vy)
+		return
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		s.report(vx.Uint() == vy.Uint(), vx, vy)
+		return
+	case reflect.Float32, reflect.Float64:
+		s.report(vx.Float() == vy.Float(), vx, vy)
+		return
+	case reflect.Complex64, reflect.Complex128:
+		s.report(vx.Complex() == vy.Complex(), vx, vy)
+		return
+	case reflect.String:
+		s.report(vx.String() == vy.String(), vx, vy)
+		return
+	case reflect.Chan, reflect.UnsafePointer:
+		s.report(vx.Pointer() == vy.Pointer(), vx, vy)
+		return
+	case reflect.Func:
+		s.report(vx.IsNil() && vy.IsNil(), vx, vy)
+		return
+	case reflect.Ptr:
+		if vx.IsNil() || vy.IsNil() {
+			s.report(vx.IsNil() && vy.IsNil(), vx, vy)
+			return
+		}
+		s.curPath.push(&indirect{pathStep{t.Elem()}})
+		defer s.curPath.pop()
+		s.compareAny(vx.Elem(), vy.Elem())
+		return
+	case reflect.Interface:
+		if vx.IsNil() || vy.IsNil() {
+			s.report(vx.IsNil() && vy.IsNil(), vx, vy)
+			return
+		}
+		if vx.Elem().Type() != vy.Elem().Type() {
+			s.report(false, vx.Elem(), vy.Elem())
+			return
+		}
+		s.curPath.push(&typeAssertion{pathStep{vx.Elem().Type()}})
+		defer s.curPath.pop()
+		s.compareAny(vx.Elem(), vy.Elem())
+		return
+	case reflect.Slice:
+		if vx.IsNil() || vy.IsNil() {
+			s.report(vx.IsNil() && vy.IsNil(), vx, vy)
+			return
+		}
+		fallthrough
+	case reflect.Array:
+		s.compareArray(vx, vy, t)
+		return
+	case reflect.Map:
+		s.compareMap(vx, vy, t)
+		return
+	case reflect.Struct:
+		s.compareStruct(vx, vy, t)
+		return
+	default:
+		panic(fmt.Sprintf("%v kind not handled", t.Kind()))
+	}
+}
+
+// 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.
+	if sf, ok := s.curPath[len(s.curPath)-1].(*structField); ok && sf.unexported {
+		if !sf.force {
+			panic(fmt.Sprintf("cannot handle unexported field: %#v", s.curPath))
+		}
+
+		// 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)
+	}
+
+	// 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
+	}
+	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.callFunc(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:
+		vx = op.fnc.Call([]reflect.Value{vx})[0]
+		vy = op.fnc.Call([]reflect.Value{vy})[0]
+		s.curPath.push(&transform{pathStep{op.fnc.Type().Out(0)}, op})
+		defer s.curPath.pop()
+		s.compareAny(vx, vy)
+		return
+	case *comparer:
+		eq := s.callFunc(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")
+	ft := functionType(m.Type)
+	if !ok || (ft != equalFunc && ft != equalIfaceFunc) {
+		return false
+	}
+
+	eq := s.callFunc(m.Func, vx, vy)
+	s.report(eq, vx, vy)
+	return true
+}
+
+func (s *state) callFunc(f, x, y reflect.Value) bool {
+	got := f.Call([]reflect.Value{x, y})[0].Bool()
+	if s.dsCheck.curr == s.dsCheck.next {
+		// Swapping the input arguments is sufficient to check that
+		// f is symmetric and deterministic.
+		want := f.Call([]reflect.Value{y, x})[0].Bool()
+		if got != want {
+			fn := getFuncName(f.Pointer())
+			panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", fn))
+		}
+		s.dsCheck.curr = 0
+		s.dsCheck.next++
+	}
+	s.dsCheck.curr++
+	return got
+}
+
+func (s *state) compareArray(vx, vy reflect.Value, t reflect.Type) {
+	step := &sliceIndex{pathStep{t.Elem()}, 0}
+	s.curPath.push(step)
+	defer s.curPath.pop()
+
+	// Regardless of the lengths, we always try to compare the elements.
+	// If one slice is longer, we will report the elements of the longer
+	// slice as different (relative to an invalid reflect.Value).
+	nmin := vx.Len()
+	if nmin > vy.Len() {
+		nmin = vy.Len()
+	}
+	for i := 0; i < nmin; i++ {
+		step.key = i
+		s.compareAny(vx.Index(i), vy.Index(i))
+	}
+	for i := nmin; i < vx.Len(); i++ {
+		step.key = i
+		s.report(false, vx.Index(i), reflect.Value{})
+	}
+	for i := nmin; i < vy.Len(); i++ {
+		step.key = i
+		s.report(false, reflect.Value{}, vy.Index(i))
+	}
+}
+
+func (s *state) compareMap(vx, vy reflect.Value, t reflect.Type) {
+	if vx.IsNil() || vy.IsNil() {
+		s.report(vx.IsNil() && vy.IsNil(), vx, vy)
+		return
+	}
+
+	// We combine and sort the two map keys so that we can perform the
+	// comparisons in a deterministic order.
+	step := &mapIndex{pathStep: pathStep{t.Elem()}}
+	s.curPath.push(step)
+	defer s.curPath.pop()
+	for _, k := range sortKeys(append(vx.MapKeys(), vy.MapKeys()...)) {
+		step.key = k
+		vvx := vx.MapIndex(k)
+		vvy := vy.MapIndex(k)
+		switch {
+		case vvx.IsValid() && vvy.IsValid():
+			s.compareAny(vvx, vvy)
+		case vvx.IsValid() && !vvy.IsValid():
+			s.report(false, vvx, reflect.Value{})
+		case !vvx.IsValid() && vvy.IsValid():
+			s.report(false, reflect.Value{}, vvy)
+		default:
+			// It is possible for both vvx and vvy to be invalid if the
+			// key contained a NaN value in it. There is no way in
+			// reflection to be able to retrieve these values.
+			// See https://golang.org/issue/11104
+			panic(fmt.Sprintf("%#v has map key with NaNs", s.curPath))
+		}
+	}
+}
+
+func (s *state) compareStruct(vx, vy reflect.Value, t reflect.Type) {
+	var vax, vay reflect.Value // Addressable versions of vx and vy
+
+	step := &structField{}
+	s.curPath.push(step)
+	defer s.curPath.pop()
+	for i := 0; i < t.NumField(); i++ {
+		vvx := vx.Field(i)
+		vvy := vy.Field(i)
+		step.typ = t.Field(i).Type
+		step.name = t.Field(i).Name
+		step.idx = i
+		step.unexported = !isExported(step.name)
+		if step.unexported {
+			// Defer checking of unexported fields until later to give an
+			// Ignore a chance to ignore the field.
+			if !vax.IsValid() || !vay.IsValid() {
+				// For unsafeRetrieveField to work, the parent struct must
+				// be addressable. Create a new copy of the values if
+				// necessary to make them addressable.
+				vax = makeAddressable(vx)
+				vay = makeAddressable(vy)
+			}
+			step.force = s.exporters[t]
+			step.pvx = vax
+			step.pvy = vay
+			step.field = t.Field(i)
+		}
+		s.compareAny(vvx, vvy)
+	}
+}
+
+// report records the result of a single comparison.
+// It also calls Report if any reporter is registered.
+func (s *state) report(eq bool, vx, vy reflect.Value) {
+	s.eq = s.eq && eq
+	if s.reporter != nil {
+		s.reporter.Report(vx, vy, eq, s.curPath)
+	}
+}
+
+// makeAddressable returns a value that is always addressable.
+// It returns the input verbatim if it is already addressable,
+// otherwise it creates a new value and returns an addressable copy.
+func makeAddressable(v reflect.Value) reflect.Value {
+	if v.CanAddr() {
+		return v
+	}
+	vc := reflect.New(v.Type()).Elem()
+	vc.Set(v)
+	return vc
+}
+
+type funcType int
+
+const (
+	invalidFunc     funcType    = iota
+	equalFunc                   // func(T, T) bool
+	equalIfaceFunc              // func(T, I) bool
+	transformFunc               // func(T) R
+	valueFilterFunc = equalFunc // func(T, T) bool
+)
+
+var boolType = reflect.TypeOf(true)
+
+// functionType identifies which type of function signature this is.
+func functionType(t reflect.Type) funcType {
+	if t == nil || t.Kind() != reflect.Func || t.IsVariadic() {
+		return invalidFunc
+	}
+	ni, no := t.NumIn(), t.NumOut()
+	switch {
+	case ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType:
+		return equalFunc // or valueFilterFunc
+	case ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType:
+		return equalIfaceFunc
+	case ni == 1 && no == 1:
+		return transformFunc
+	default:
+		return invalidFunc
+	}
+}
diff --git a/cmp/compare_test.go b/cmp/compare_test.go
new file mode 100644
index 0000000..ba6a7e1
--- /dev/null
+++ b/cmp/compare_test.go
@@ -0,0 +1,1761 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package cmp_test
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"math/rand"
+	"reflect"
+	"regexp"
+	"sort"
+	"strings"
+	"sync"
+	"testing"
+	"time"
+	"unicode"
+	"unicode/utf8"
+
+	"github.com/google/go-cmp/cmp"
+	pb "github.com/google/go-cmp/cmp/internal/testprotos"
+	ts "github.com/google/go-cmp/cmp/internal/teststructs"
+)
+
+var now = time.Now()
+var boolType = reflect.TypeOf(true)
+var mutexType = reflect.TypeOf(sync.Mutex{})
+
+func intPtr(n int) *int { return &n }
+
+func equalRegexp(x, y *regexp.Regexp) bool {
+	if x == nil || y == nil {
+		return x == nil && y == nil
+	}
+	return x.String() == y.String()
+}
+
+func IgnoreUnexported(typs ...interface{}) cmp.Option {
+	m := make(map[reflect.Type]bool)
+	for _, typ := range typs {
+		t := reflect.TypeOf(typ)
+		if t.Kind() != reflect.Struct {
+			panic(fmt.Sprintf("invalid struct type: %T", typ))
+		}
+		m[t] = true
+	}
+	return cmp.FilterPath(func(p cmp.Path) bool {
+		if len(p) < 2 {
+			return false
+		}
+		sf, ok := p[len(p)-1].(cmp.StructField)
+		if !ok {
+			return false
+		}
+		return m[p[len(p)-2].Type()] && !isExported(sf.Name())
+	}, cmp.Ignore())
+}
+
+func isExported(id string) bool {
+	r, _ := utf8.DecodeRuneInString(id)
+	return unicode.IsUpper(r)
+}
+
+type test struct {
+	label     string       // Test description
+	x, y      interface{}  // Input values to compare
+	opts      []cmp.Option // Input options
+	wantDiff  string       // The exact difference string
+	wantPanic string       // Sub-string of an expected panic message
+}
+
+func TestDiff(t *testing.T) {
+	var tests []test
+	tests = append(tests, comparerTests()...)
+	tests = append(tests, transformerTests()...)
+	tests = append(tests, embeddedTests()...)
+	tests = append(tests, methodTests()...)
+	tests = append(tests, project1Tests()...)
+	tests = append(tests, project2Tests()...)
+	tests = append(tests, project3Tests()...)
+	tests = append(tests, project4Tests()...)
+
+	for _, tt := range tests {
+		t.Run(tt.label, func(t *testing.T) {
+			var gotDiff, gotPanic string
+			func() {
+				defer func() {
+					if ex := recover(); ex != nil {
+						if s, ok := ex.(string); ok {
+							gotPanic = s
+						} else {
+							panic(ex)
+						}
+					}
+				}()
+				gotDiff = cmp.Diff(tt.x, tt.y, tt.opts...)
+			}()
+			if tt.wantPanic == "" {
+				if gotPanic != "" {
+					t.Fatalf("unexpected panic message: %s", gotPanic)
+				}
+				if strings.TrimSpace(gotDiff) != strings.TrimSpace(tt.wantDiff) {
+					t.Fatalf("difference message:\ngot:\n%s\nwant:\n%s", gotDiff, tt.wantDiff)
+				}
+			} else {
+				if !strings.Contains(gotPanic, tt.wantPanic) {
+					t.Fatalf("panic message:\ngot:  %s\nwant: %s", gotPanic, tt.wantPanic)
+				}
+			}
+		})
+	}
+}
+
+func comparerTests() []test {
+	const label = "Comparer"
+
+	return []test{{
+		label:    label,
+		x:        1,
+		y:        1,
+		wantDiff: "",
+	}, {
+		label:     label,
+		x:         1,
+		y:         1,
+		opts:      []cmp.Option{cmp.Ignore()},
+		wantPanic: "cannot use an unfiltered option",
+	}, {
+		label:     label,
+		x:         1,
+		y:         1,
+		opts:      []cmp.Option{cmp.Comparer(func(_, _ interface{}) bool { return true })},
+		wantPanic: "cannot use an unfiltered option",
+	}, {
+		label:     label,
+		x:         1,
+		y:         1,
+		opts:      []cmp.Option{cmp.Transformer("", func(x interface{}) interface{} { return x })},
+		wantPanic: "cannot use an unfiltered option",
+	}, {
+		label: label,
+		x:     1,
+		y:     1,
+		opts: []cmp.Option{
+			cmp.Comparer(func(x, y int) bool { return true }),
+			cmp.Transformer("", func(x int) float64 { return float64(x) }),
+		},
+		wantPanic: "ambiguous set of options",
+	}, {
+		label: label,
+		x:     1,
+		y:     1,
+		opts: []cmp.Option{
+			cmp.FilterPath(func(p cmp.Path) bool {
+				return len(p) > 0 && p[len(p)-1].Type().Kind() == reflect.Int
+			}, cmp.Options{cmp.Ignore(), cmp.Ignore(), cmp.Ignore()}),
+			cmp.Comparer(func(x, y int) bool { return true }),
+			cmp.Transformer("", func(x int) float64 { return float64(x) }),
+		},
+	}, {
+		label:     label,
+		opts:      []cmp.Option{struct{ cmp.Option }{}},
+		wantPanic: "unknown option",
+	}, {
+		label:    label,
+		x:        struct{ A, B, C int }{1, 2, 3},
+		y:        struct{ A, B, C int }{1, 2, 3},
+		wantDiff: "",
+	}, {
+		label:    label,
+		x:        struct{ A, B, C int }{1, 2, 3},
+		y:        struct{ A, B, C int }{1, 2, 4},
+		wantDiff: "root.C:\n\t-: 3\n\t+: 4\n",
+	}, {
+		label:     label,
+		x:         struct{ a, b, c int }{1, 2, 3},
+		y:         struct{ a, b, c int }{1, 2, 4},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label,
+		x:     &struct{ A *int }{intPtr(4)},
+		y:     &struct{ A *int }{intPtr(4)},
+	}, {
+		label:    label,
+		x:        &struct{ A *int }{intPtr(4)},
+		y:        &struct{ A *int }{intPtr(5)},
+		wantDiff: "*root.A:\n\t-: 4\n\t+: 5\n",
+	}, {
+		label: label,
+		x:     &struct{ A *int }{intPtr(4)},
+		y:     &struct{ A *int }{intPtr(5)},
+		opts: []cmp.Option{
+			cmp.Comparer(func(x, y int) bool { return true }),
+		},
+	}, {
+		label: label,
+		x:     &struct{ A *int }{intPtr(4)},
+		y:     &struct{ A *int }{intPtr(5)},
+		opts: []cmp.Option{
+			cmp.Comparer(func(x, y *int) bool { return x != nil && y != nil }),
+		},
+	}, {
+		label: label,
+		x:     &struct{ R *bytes.Buffer }{},
+		y:     &struct{ R *bytes.Buffer }{},
+	}, {
+		label:    label,
+		x:        &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
+		y:        &struct{ R *bytes.Buffer }{},
+		wantDiff: "root.R:\n\t-: \"\"\n\t+: <nil>\n",
+	}, {
+		label: label,
+		x:     &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
+		y:     &struct{ R *bytes.Buffer }{},
+		opts: []cmp.Option{
+			cmp.Comparer(func(x, y io.Reader) bool { return true }),
+		},
+	}, {
+		label:     label,
+		x:         &struct{ R bytes.Buffer }{},
+		y:         &struct{ R bytes.Buffer }{},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label,
+		x:     &struct{ R bytes.Buffer }{},
+		y:     &struct{ R bytes.Buffer }{},
+		opts: []cmp.Option{
+			cmp.Comparer(func(x, y io.Reader) bool { return true }),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label,
+		x:     &struct{ R bytes.Buffer }{},
+		y:     &struct{ R bytes.Buffer }{},
+		opts: []cmp.Option{
+			cmp.Transformer("Ref", func(x bytes.Buffer) *bytes.Buffer { return &x }),
+			cmp.Comparer(func(x, y io.Reader) bool { return true }),
+		},
+	}, {
+		label:     label,
+		x:         []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
+		y:         []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label:    label,
+		x:        []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
+		y:        []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
+		opts:     []cmp.Option{cmp.Comparer(equalRegexp)},
+		wantDiff: "",
+	}, {
+		label:    label,
+		x:        []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
+		y:        []*regexp.Regexp{nil, regexp.MustCompile("a*b*d*")},
+		opts:     []cmp.Option{cmp.Comparer(equalRegexp)},
+		wantDiff: "{[]*regexp.Regexp}[1]:\n\t-: \"a*b*c*\"\n\t+: \"a*b*d*\"\n",
+	}, {
+		label: label,
+		x: func() ***int {
+			a := 0
+			b := &a
+			c := &b
+			return &c
+		}(),
+		y: func() ***int {
+			a := 0
+			b := &a
+			c := &b
+			return &c
+		}(),
+	}, {
+		label: label,
+		x: func() ***int {
+			a := 0
+			b := &a
+			c := &b
+			return &c
+		}(),
+		y: func() ***int {
+			a := 1
+			b := &a
+			c := &b
+			return &c
+		}(),
+		wantDiff: `
+***{***int}:
+	-: 0
+	+: 1`,
+	}, {
+		label: label,
+		x:     []int{1, 2, 3, 4, 5}[:3],
+		y:     []int{1, 2, 3},
+	}, {
+		label: label,
+		x:     struct{ fmt.Stringer }{bytes.NewBufferString("hello")},
+		y:     struct{ fmt.Stringer }{regexp.MustCompile("hello")},
+		opts:  []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
+	}, {
+		label: label,
+		x:     struct{ fmt.Stringer }{bytes.NewBufferString("hello")},
+		y:     struct{ fmt.Stringer }{regexp.MustCompile("hello2")},
+		opts:  []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
+		wantDiff: `
+root:
+	-: "hello"
+	+: "hello2"`,
+	}, {
+		label: label,
+		x:     make([]int, 1000),
+		y:     make([]int, 1000),
+		opts: []cmp.Option{
+			cmp.Comparer(func(_, _ int) bool {
+				return rand.Intn(2) == 0
+			}),
+		},
+		wantPanic: "non-deterministic or non-symmetric function detected",
+	}, {
+		label: label,
+		x:     make([]int, 1000),
+		y:     make([]int, 1000),
+		opts: []cmp.Option{
+			cmp.FilterValues(func(_, _ int) bool {
+				return rand.Intn(2) == 0
+			}, cmp.Ignore()),
+		},
+		wantPanic: "non-deterministic or non-symmetric function detected",
+	}}
+}
+
+func transformerTests() []test {
+	const label = "Transformer/"
+
+	return []test{{
+		label: label,
+		x:     uint8(0),
+		y:     uint8(1),
+		opts: []cmp.Option{
+			cmp.Transformer("", func(in uint8) uint16 { return uint16(in) }),
+			cmp.Transformer("", func(in uint16) uint32 { return uint32(in) }),
+			cmp.Transformer("", func(in uint32) uint64 { return uint64(in) }),
+		},
+		wantDiff: `
+λ(λ(λ({uint8}))):
+	-: 0x00
+	+: 0x01`,
+	}, {
+		label: label,
+		x:     0,
+		y:     1,
+		opts: []cmp.Option{
+			cmp.Transformer("", func(in int) int { return in / 2 }),
+			cmp.Transformer("", func(in int) int { return in }),
+		},
+		wantPanic: "ambiguous set of options",
+	}, {
+		label: label,
+		x:     []int{0, -5, 0, -1},
+		y:     []int{1, 3, 0, -5},
+		opts: []cmp.Option{
+			cmp.FilterValues(
+				func(x, y int) bool { return x+y >= 0 },
+				cmp.Transformer("", func(in int) int64 { return int64(in / 2) }),
+			),
+			cmp.FilterValues(
+				func(x, y int) bool { return x+y < 0 },
+				cmp.Transformer("", func(in int) int64 { return int64(in) }),
+			),
+		},
+		wantDiff: `
+λ({[]int}[1]):
+	-: -5
+	+: 3
+λ({[]int}[3]):
+	-: -1
+	+: -5`,
+	}, {
+		label: label,
+		x:     0,
+		y:     1,
+		opts: []cmp.Option{
+			cmp.Transformer("", func(in int) interface{} {
+				if in == 0 {
+					return "string"
+				}
+				return in
+			}),
+		},
+		wantDiff: `
+λ({int}):
+	-: "string"
+	+: 1`,
+	}}
+}
+
+func embeddedTests() []test {
+	const label = "EmbeddedStruct/"
+
+	privateStruct := *new(ts.ParentStructA).PrivateStruct()
+
+	createStructA := func(i int) ts.ParentStructA {
+		s := ts.ParentStructA{}
+		s.PrivateStruct().Public = 1 + i
+		s.PrivateStruct().SetPrivate(2 + i)
+		return s
+	}
+
+	createStructB := func(i int) ts.ParentStructB {
+		s := ts.ParentStructB{}
+		s.PublicStruct.Public = 1 + i
+		s.PublicStruct.SetPrivate(2 + i)
+		return s
+	}
+
+	createStructC := func(i int) ts.ParentStructC {
+		s := ts.ParentStructC{}
+		s.PrivateStruct().Public = 1 + i
+		s.PrivateStruct().SetPrivate(2 + i)
+		s.Public = 3 + i
+		s.SetPrivate(4 + i)
+		return s
+	}
+
+	createStructD := func(i int) ts.ParentStructD {
+		s := ts.ParentStructD{}
+		s.PublicStruct.Public = 1 + i
+		s.PublicStruct.SetPrivate(2 + i)
+		s.Public = 3 + i
+		s.SetPrivate(4 + i)
+		return s
+	}
+
+	createStructE := func(i int) ts.ParentStructE {
+		s := ts.ParentStructE{}
+		s.PrivateStruct().Public = 1 + i
+		s.PrivateStruct().SetPrivate(2 + i)
+		s.PublicStruct.Public = 3 + i
+		s.PublicStruct.SetPrivate(4 + i)
+		return s
+	}
+
+	createStructF := func(i int) ts.ParentStructF {
+		s := ts.ParentStructF{}
+		s.PrivateStruct().Public = 1 + i
+		s.PrivateStruct().SetPrivate(2 + i)
+		s.PublicStruct.Public = 3 + i
+		s.PublicStruct.SetPrivate(4 + i)
+		s.Public = 5 + i
+		s.SetPrivate(6 + i)
+		return s
+	}
+
+	createStructG := func(i int) *ts.ParentStructG {
+		s := ts.NewParentStructG()
+		s.PrivateStruct().Public = 1 + i
+		s.PrivateStruct().SetPrivate(2 + i)
+		return s
+	}
+
+	createStructH := func(i int) *ts.ParentStructH {
+		s := ts.NewParentStructH()
+		s.PublicStruct.Public = 1 + i
+		s.PublicStruct.SetPrivate(2 + i)
+		return s
+	}
+
+	createStructI := func(i int) *ts.ParentStructI {
+		s := ts.NewParentStructI()
+		s.PrivateStruct().Public = 1 + i
+		s.PrivateStruct().SetPrivate(2 + i)
+		s.PublicStruct.Public = 3 + i
+		s.PublicStruct.SetPrivate(4 + i)
+		return s
+	}
+
+	createStructJ := func(i int) *ts.ParentStructJ {
+		s := ts.NewParentStructJ()
+		s.PrivateStruct().Public = 1 + i
+		s.PrivateStruct().SetPrivate(2 + i)
+		s.PublicStruct.Public = 3 + i
+		s.PublicStruct.SetPrivate(4 + i)
+		s.Private().Public = 5 + i
+		s.Private().SetPrivate(6 + i)
+		s.Public.Public = 7 + i
+		s.Public.SetPrivate(8 + i)
+		return s
+	}
+
+	return []test{{
+		label:     label + "ParentStructA",
+		x:         ts.ParentStructA{},
+		y:         ts.ParentStructA{},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructA",
+		x:     ts.ParentStructA{},
+		y:     ts.ParentStructA{},
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructA{}),
+		},
+	}, {
+		label: label + "ParentStructA",
+		x:     createStructA(0),
+		y:     createStructA(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructA{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructA",
+		x:     createStructA(0),
+		y:     createStructA(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
+		},
+	}, {
+		label: label + "ParentStructA",
+		x:     createStructA(0),
+		y:     createStructA(1),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
+		},
+		wantDiff: `
+{teststructs.ParentStructA}.privateStruct.Public:
+	-: 1
+	+: 2
+{teststructs.ParentStructA}.privateStruct.private:
+	-: 2
+	+: 3`,
+	}, {
+		label: label + "ParentStructB",
+		x:     ts.ParentStructB{},
+		y:     ts.ParentStructB{},
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructB{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructB",
+		x:     ts.ParentStructB{},
+		y:     ts.ParentStructB{},
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructB{}),
+			IgnoreUnexported(ts.PublicStruct{}),
+		},
+	}, {
+		label: label + "ParentStructB",
+		x:     createStructB(0),
+		y:     createStructB(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructB{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructB",
+		x:     createStructB(0),
+		y:     createStructB(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
+		},
+	}, {
+		label: label + "ParentStructB",
+		x:     createStructB(0),
+		y:     createStructB(1),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
+		},
+		wantDiff: `
+{teststructs.ParentStructB}.PublicStruct.Public:
+	-: 1
+	+: 2
+{teststructs.ParentStructB}.PublicStruct.private:
+	-: 2
+	+: 3`,
+	}, {
+		label:     label + "ParentStructC",
+		x:         ts.ParentStructC{},
+		y:         ts.ParentStructC{},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructC",
+		x:     ts.ParentStructC{},
+		y:     ts.ParentStructC{},
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructC{}),
+		},
+	}, {
+		label: label + "ParentStructC",
+		x:     createStructC(0),
+		y:     createStructC(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructC{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructC",
+		x:     createStructC(0),
+		y:     createStructC(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
+		},
+	}, {
+		label: label + "ParentStructC",
+		x:     createStructC(0),
+		y:     createStructC(1),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
+		},
+		wantDiff: `
+{teststructs.ParentStructC}.privateStruct.Public:
+	-: 1
+	+: 2
+{teststructs.ParentStructC}.privateStruct.private:
+	-: 2
+	+: 3
+{teststructs.ParentStructC}.Public:
+	-: 3
+	+: 4
+{teststructs.ParentStructC}.private:
+	-: 4
+	+: 5`,
+	}, {
+		label: label + "ParentStructD",
+		x:     ts.ParentStructD{},
+		y:     ts.ParentStructD{},
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructD{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructD",
+		x:     ts.ParentStructD{},
+		y:     ts.ParentStructD{},
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructD{}),
+			IgnoreUnexported(ts.PublicStruct{}),
+		},
+	}, {
+		label: label + "ParentStructD",
+		x:     createStructD(0),
+		y:     createStructD(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructD{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructD",
+		x:     createStructD(0),
+		y:     createStructD(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
+		},
+	}, {
+		label: label + "ParentStructD",
+		x:     createStructD(0),
+		y:     createStructD(1),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
+		},
+		wantDiff: `
+{teststructs.ParentStructD}.PublicStruct.Public:
+	-: 1
+	+: 2
+{teststructs.ParentStructD}.PublicStruct.private:
+	-: 2
+	+: 3
+{teststructs.ParentStructD}.Public:
+	-: 3
+	+: 4
+{teststructs.ParentStructD}.private:
+	-: 4
+	+: 5`,
+	}, {
+		label: label + "ParentStructE",
+		x:     ts.ParentStructE{},
+		y:     ts.ParentStructE{},
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructE{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructE",
+		x:     ts.ParentStructE{},
+		y:     ts.ParentStructE{},
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructE{}),
+			IgnoreUnexported(ts.PublicStruct{}),
+		},
+	}, {
+		label: label + "ParentStructE",
+		x:     createStructE(0),
+		y:     createStructE(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructE{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructE",
+		x:     createStructE(0),
+		y:     createStructE(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructE",
+		x:     createStructE(0),
+		y:     createStructE(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
+		},
+	}, {
+		label: label + "ParentStructE",
+		x:     createStructE(0),
+		y:     createStructE(1),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
+		},
+		wantDiff: `
+{teststructs.ParentStructE}.privateStruct.Public:
+	-: 1
+	+: 2
+{teststructs.ParentStructE}.privateStruct.private:
+	-: 2
+	+: 3
+{teststructs.ParentStructE}.PublicStruct.Public:
+	-: 3
+	+: 4
+{teststructs.ParentStructE}.PublicStruct.private:
+	-: 4
+	+: 5`,
+	}, {
+		label: label + "ParentStructF",
+		x:     ts.ParentStructF{},
+		y:     ts.ParentStructF{},
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructF{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructF",
+		x:     ts.ParentStructF{},
+		y:     ts.ParentStructF{},
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructF{}),
+			IgnoreUnexported(ts.PublicStruct{}),
+		},
+	}, {
+		label: label + "ParentStructF",
+		x:     createStructF(0),
+		y:     createStructF(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructF{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructF",
+		x:     createStructF(0),
+		y:     createStructF(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructF",
+		x:     createStructF(0),
+		y:     createStructF(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
+		},
+	}, {
+		label: label + "ParentStructF",
+		x:     createStructF(0),
+		y:     createStructF(1),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
+		},
+		wantDiff: `
+{teststructs.ParentStructF}.privateStruct.Public:
+	-: 1
+	+: 2
+{teststructs.ParentStructF}.privateStruct.private:
+	-: 2
+	+: 3
+{teststructs.ParentStructF}.PublicStruct.Public:
+	-: 3
+	+: 4
+{teststructs.ParentStructF}.PublicStruct.private:
+	-: 4
+	+: 5
+{teststructs.ParentStructF}.Public:
+	-: 5
+	+: 6
+{teststructs.ParentStructF}.private:
+	-: 6
+	+: 7`,
+	}, {
+		label:     label + "ParentStructG",
+		x:         ts.ParentStructG{},
+		y:         ts.ParentStructG{},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructG",
+		x:     ts.ParentStructG{},
+		y:     ts.ParentStructG{},
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructG{}),
+		},
+	}, {
+		label: label + "ParentStructG",
+		x:     createStructG(0),
+		y:     createStructG(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructG{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructG",
+		x:     createStructG(0),
+		y:     createStructG(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
+		},
+	}, {
+		label: label + "ParentStructG",
+		x:     createStructG(0),
+		y:     createStructG(1),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
+		},
+		wantDiff: `
+{*teststructs.ParentStructG}.privateStruct.Public:
+	-: 1
+	+: 2
+{*teststructs.ParentStructG}.privateStruct.private:
+	-: 2
+	+: 3`,
+	}, {
+		label: label + "ParentStructH",
+		x:     ts.ParentStructH{},
+		y:     ts.ParentStructH{},
+	}, {
+		label:     label + "ParentStructH",
+		x:         createStructH(0),
+		y:         createStructH(0),
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructH",
+		x:     ts.ParentStructH{},
+		y:     ts.ParentStructH{},
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructH{}),
+		},
+	}, {
+		label: label + "ParentStructH",
+		x:     createStructH(0),
+		y:     createStructH(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructH{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructH",
+		x:     createStructH(0),
+		y:     createStructH(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
+		},
+	}, {
+		label: label + "ParentStructH",
+		x:     createStructH(0),
+		y:     createStructH(1),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
+		},
+		wantDiff: `
+{*teststructs.ParentStructH}.PublicStruct.Public:
+	-: 1
+	+: 2
+{*teststructs.ParentStructH}.PublicStruct.private:
+	-: 2
+	+: 3`,
+	}, {
+		label:     label + "ParentStructI",
+		x:         ts.ParentStructI{},
+		y:         ts.ParentStructI{},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructI",
+		x:     ts.ParentStructI{},
+		y:     ts.ParentStructI{},
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructI{}),
+		},
+	}, {
+		label: label + "ParentStructI",
+		x:     createStructI(0),
+		y:     createStructI(0),
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructI{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructI",
+		x:     createStructI(0),
+		y:     createStructI(0),
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructI{}, ts.PublicStruct{}),
+		},
+	}, {
+		label: label + "ParentStructI",
+		x:     createStructI(0),
+		y:     createStructI(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructI{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructI",
+		x:     createStructI(0),
+		y:     createStructI(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
+		},
+	}, {
+		label: label + "ParentStructI",
+		x:     createStructI(0),
+		y:     createStructI(1),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
+		},
+		wantDiff: `
+{*teststructs.ParentStructI}.privateStruct.Public:
+	-: 1
+	+: 2
+{*teststructs.ParentStructI}.privateStruct.private:
+	-: 2
+	+: 3
+{*teststructs.ParentStructI}.PublicStruct.Public:
+	-: 3
+	+: 4
+{*teststructs.ParentStructI}.PublicStruct.private:
+	-: 4
+	+: 5`,
+	}, {
+		label:     label + "ParentStructJ",
+		x:         ts.ParentStructJ{},
+		y:         ts.ParentStructJ{},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructJ",
+		x:     ts.ParentStructJ{},
+		y:     ts.ParentStructJ{},
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructJ{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructJ",
+		x:     ts.ParentStructJ{},
+		y:     ts.ParentStructJ{},
+		opts: []cmp.Option{
+			IgnoreUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
+		},
+	}, {
+		label: label + "ParentStructJ",
+		x:     createStructJ(0),
+		y:     createStructJ(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
+		},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label + "ParentStructJ",
+		x:     createStructJ(0),
+		y:     createStructJ(0),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
+		},
+	}, {
+		label: label + "ParentStructJ",
+		x:     createStructJ(0),
+		y:     createStructJ(1),
+		opts: []cmp.Option{
+			cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
+		},
+		wantDiff: `
+{*teststructs.ParentStructJ}.privateStruct.Public:
+	-: 1
+	+: 2
+{*teststructs.ParentStructJ}.privateStruct.private:
+	-: 2
+	+: 3
+{*teststructs.ParentStructJ}.PublicStruct.Public:
+	-: 3
+	+: 4
+{*teststructs.ParentStructJ}.PublicStruct.private:
+	-: 4
+	+: 5
+{*teststructs.ParentStructJ}.Public.Public:
+	-: 7
+	+: 8
+{*teststructs.ParentStructJ}.Public.private:
+	-: 8
+	+: 9
+{*teststructs.ParentStructJ}.private.Public:
+	-: 5
+	+: 6
+{*teststructs.ParentStructJ}.private.private:
+	-: 6
+	+: 7`,
+	}}
+}
+
+func methodTests() []test {
+	const label = "EqualMethod/"
+
+	// A common mistake that the Equal method is on a pointer receiver,
+	// but only a non-pointer value is present in the struct.
+	// A transform can be used to forcibly reference the value.
+	derefTransform := cmp.FilterPath(func(p cmp.Path) bool {
+		if len(p) == 0 {
+			return false
+		}
+		t := p[len(p)-1].Type()
+		if _, ok := t.MethodByName("Equal"); ok || t.Kind() == reflect.Ptr {
+			return false
+		}
+		if m, ok := reflect.PtrTo(t).MethodByName("Equal"); ok {
+			tf := m.Func.Type()
+			return !tf.IsVariadic() && tf.NumIn() == 2 && tf.NumOut() == 1 &&
+				tf.In(0).AssignableTo(tf.In(1)) && tf.Out(0) == boolType
+		}
+		return false
+	}, cmp.Transformer("Ref", func(x interface{}) interface{} {
+		v := reflect.ValueOf(x)
+		vp := reflect.New(v.Type())
+		vp.Elem().Set(v)
+		return vp.Interface()
+	}))
+
+	// For each of these types, there is an Equal method defined, which always
+	// returns true, while the underlying data are fundamentally different.
+	// Since the method should be called, these are expected to be equal.
+	return []test{{
+		label: label + "StructA",
+		x:     ts.StructA{"NotEqual"},
+		y:     ts.StructA{"not_equal"},
+	}, {
+		label: label + "StructA",
+		x:     &ts.StructA{"NotEqual"},
+		y:     &ts.StructA{"not_equal"},
+	}, {
+		label: label + "StructB",
+		x:     ts.StructB{"NotEqual"},
+		y:     ts.StructB{"not_equal"},
+		wantDiff: `
+{teststructs.StructB}.X:
+	-: "NotEqual"
+	+: "not_equal"`,
+	}, {
+		label: label + "StructB",
+		x:     ts.StructB{"NotEqual"},
+		y:     ts.StructB{"not_equal"},
+		opts:  []cmp.Option{derefTransform},
+	}, {
+		label: label + "StructB",
+		x:     &ts.StructB{"NotEqual"},
+		y:     &ts.StructB{"not_equal"},
+	}, {
+		label: label + "StructC",
+		x:     ts.StructC{"NotEqual"},
+		y:     ts.StructC{"not_equal"},
+	}, {
+		label: label + "StructC",
+		x:     &ts.StructC{"NotEqual"},
+		y:     &ts.StructC{"not_equal"},
+	}, {
+		label: label + "StructD",
+		x:     ts.StructD{"NotEqual"},
+		y:     ts.StructD{"not_equal"},
+		wantDiff: `
+{teststructs.StructD}.X:
+	-: "NotEqual"
+	+: "not_equal"`,
+	}, {
+		label: label + "StructD",
+		x:     ts.StructD{"NotEqual"},
+		y:     ts.StructD{"not_equal"},
+		opts:  []cmp.Option{derefTransform},
+	}, {
+		label: label + "StructD",
+		x:     &ts.StructD{"NotEqual"},
+		y:     &ts.StructD{"not_equal"},
+	}, {
+		label: label + "StructE",
+		x:     ts.StructE{"NotEqual"},
+		y:     ts.StructE{"not_equal"},
+		wantDiff: `
+{teststructs.StructE}.X:
+	-: "NotEqual"
+	+: "not_equal"`,
+	}, {
+		label: label + "StructE",
+		x:     ts.StructE{"NotEqual"},
+		y:     ts.StructE{"not_equal"},
+		opts:  []cmp.Option{derefTransform},
+	}, {
+		label: label + "StructE",
+		x:     &ts.StructE{"NotEqual"},
+		y:     &ts.StructE{"not_equal"},
+	}, {
+		label: label + "StructF",
+		x:     ts.StructF{"NotEqual"},
+		y:     ts.StructF{"not_equal"},
+		wantDiff: `
+{teststructs.StructF}.X:
+	-: "NotEqual"
+	+: "not_equal"`,
+	}, {
+		label: label + "StructF",
+		x:     &ts.StructF{"NotEqual"},
+		y:     &ts.StructF{"not_equal"},
+	}, {
+		label: label + "StructA1",
+		x:     ts.StructA1{ts.StructA{"NotEqual"}, "equal"},
+		y:     ts.StructA1{ts.StructA{"not_equal"}, "equal"},
+	}, {
+		label:    label + "StructA1",
+		x:        ts.StructA1{ts.StructA{"NotEqual"}, "NotEqual"},
+		y:        ts.StructA1{ts.StructA{"not_equal"}, "not_equal"},
+		wantDiff: "{teststructs.StructA1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
+	}, {
+		label: label + "StructA1",
+		x:     &ts.StructA1{ts.StructA{"NotEqual"}, "equal"},
+		y:     &ts.StructA1{ts.StructA{"not_equal"}, "equal"},
+	}, {
+		label:    label + "StructA1",
+		x:        &ts.StructA1{ts.StructA{"NotEqual"}, "NotEqual"},
+		y:        &ts.StructA1{ts.StructA{"not_equal"}, "not_equal"},
+		wantDiff: "{*teststructs.StructA1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
+	}, {
+		label: label + "StructB1",
+		x:     ts.StructB1{ts.StructB{"NotEqual"}, "equal"},
+		y:     ts.StructB1{ts.StructB{"not_equal"}, "equal"},
+		opts:  []cmp.Option{derefTransform},
+	}, {
+		label:    label + "StructB1",
+		x:        ts.StructB1{ts.StructB{"NotEqual"}, "NotEqual"},
+		y:        ts.StructB1{ts.StructB{"not_equal"}, "not_equal"},
+		opts:     []cmp.Option{derefTransform},
+		wantDiff: "{teststructs.StructB1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
+	}, {
+		label: label + "StructB1",
+		x:     &ts.StructB1{ts.StructB{"NotEqual"}, "equal"},
+		y:     &ts.StructB1{ts.StructB{"not_equal"}, "equal"},
+		opts:  []cmp.Option{derefTransform},
+	}, {
+		label:    label + "StructB1",
+		x:        &ts.StructB1{ts.StructB{"NotEqual"}, "NotEqual"},
+		y:        &ts.StructB1{ts.StructB{"not_equal"}, "not_equal"},
+		opts:     []cmp.Option{derefTransform},
+		wantDiff: "{*teststructs.StructB1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
+	}, {
+		label: label + "StructC1",
+		x:     ts.StructC1{ts.StructC{"NotEqual"}, "NotEqual"},
+		y:     ts.StructC1{ts.StructC{"not_equal"}, "not_equal"},
+	}, {
+		label: label + "StructC1",
+		x:     &ts.StructC1{ts.StructC{"NotEqual"}, "NotEqual"},
+		y:     &ts.StructC1{ts.StructC{"not_equal"}, "not_equal"},
+	}, {
+		label: label + "StructD1",
+		x:     ts.StructD1{ts.StructD{"NotEqual"}, "NotEqual"},
+		y:     ts.StructD1{ts.StructD{"not_equal"}, "not_equal"},
+		wantDiff: `
+{teststructs.StructD1}.StructD.X:
+	-: "NotEqual"
+	+: "not_equal"
+{teststructs.StructD1}.X:
+	-: "NotEqual"
+	+: "not_equal"`,
+	}, {
+		label: label + "StructD1",
+		x:     ts.StructD1{ts.StructD{"NotEqual"}, "NotEqual"},
+		y:     ts.StructD1{ts.StructD{"not_equal"}, "not_equal"},
+		opts:  []cmp.Option{derefTransform},
+	}, {
+		label: label + "StructD1",
+		x:     &ts.StructD1{ts.StructD{"NotEqual"}, "NotEqual"},
+		y:     &ts.StructD1{ts.StructD{"not_equal"}, "not_equal"},
+	}, {
+		label: label + "StructE1",
+		x:     ts.StructE1{ts.StructE{"NotEqual"}, "NotEqual"},
+		y:     ts.StructE1{ts.StructE{"not_equal"}, "not_equal"},
+		wantDiff: `
+{teststructs.StructE1}.StructE.X:
+	-: "NotEqual"
+	+: "not_equal"
+{teststructs.StructE1}.X:
+	-: "NotEqual"
+	+: "not_equal"`,
+	}, {
+		label: label + "StructE1",
+		x:     ts.StructE1{ts.StructE{"NotEqual"}, "NotEqual"},
+		y:     ts.StructE1{ts.StructE{"not_equal"}, "not_equal"},
+		opts:  []cmp.Option{derefTransform},
+	}, {
+		label: label + "StructE1",
+		x:     &ts.StructE1{ts.StructE{"NotEqual"}, "NotEqual"},
+		y:     &ts.StructE1{ts.StructE{"not_equal"}, "not_equal"},
+	}, {
+		label: label + "StructF1",
+		x:     ts.StructF1{ts.StructF{"NotEqual"}, "NotEqual"},
+		y:     ts.StructF1{ts.StructF{"not_equal"}, "not_equal"},
+		wantDiff: `
+{teststructs.StructF1}.StructF.X:
+	-: "NotEqual"
+	+: "not_equal"
+{teststructs.StructF1}.X:
+	-: "NotEqual"
+	+: "not_equal"`,
+	}, {
+		label: label + "StructF1",
+		x:     &ts.StructF1{ts.StructF{"NotEqual"}, "NotEqual"},
+		y:     &ts.StructF1{ts.StructF{"not_equal"}, "not_equal"},
+	}, {
+		label: label + "StructA2",
+		x:     ts.StructA2{&ts.StructA{"NotEqual"}, "equal"},
+		y:     ts.StructA2{&ts.StructA{"not_equal"}, "equal"},
+	}, {
+		label:    label + "StructA2",
+		x:        ts.StructA2{&ts.StructA{"NotEqual"}, "NotEqual"},
+		y:        ts.StructA2{&ts.StructA{"not_equal"}, "not_equal"},
+		wantDiff: "{teststructs.StructA2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
+	}, {
+		label: label + "StructA2",
+		x:     &ts.StructA2{&ts.StructA{"NotEqual"}, "equal"},
+		y:     &ts.StructA2{&ts.StructA{"not_equal"}, "equal"},
+	}, {
+		label:    label + "StructA2",
+		x:        &ts.StructA2{&ts.StructA{"NotEqual"}, "NotEqual"},
+		y:        &ts.StructA2{&ts.StructA{"not_equal"}, "not_equal"},
+		wantDiff: "{*teststructs.StructA2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
+	}, {
+		label: label + "StructB2",
+		x:     ts.StructB2{&ts.StructB{"NotEqual"}, "equal"},
+		y:     ts.StructB2{&ts.StructB{"not_equal"}, "equal"},
+	}, {
+		label:    label + "StructB2",
+		x:        ts.StructB2{&ts.StructB{"NotEqual"}, "NotEqual"},
+		y:        ts.StructB2{&ts.StructB{"not_equal"}, "not_equal"},
+		wantDiff: "{teststructs.StructB2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
+	}, {
+		label: label + "StructB2",
+		x:     &ts.StructB2{&ts.StructB{"NotEqual"}, "equal"},
+		y:     &ts.StructB2{&ts.StructB{"not_equal"}, "equal"},
+	}, {
+		label:    label + "StructB2",
+		x:        &ts.StructB2{&ts.StructB{"NotEqual"}, "NotEqual"},
+		y:        &ts.StructB2{&ts.StructB{"not_equal"}, "not_equal"},
+		wantDiff: "{*teststructs.StructB2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
+	}, {
+		label: label + "StructC2",
+		x:     ts.StructC2{&ts.StructC{"NotEqual"}, "NotEqual"},
+		y:     ts.StructC2{&ts.StructC{"not_equal"}, "not_equal"},
+	}, {
+		label: label + "StructC2",
+		x:     &ts.StructC2{&ts.StructC{"NotEqual"}, "NotEqual"},
+		y:     &ts.StructC2{&ts.StructC{"not_equal"}, "not_equal"},
+	}, {
+		label: label + "StructD2",
+		x:     ts.StructD2{&ts.StructD{"NotEqual"}, "NotEqual"},
+		y:     ts.StructD2{&ts.StructD{"not_equal"}, "not_equal"},
+	}, {
+		label: label + "StructD2",
+		x:     &ts.StructD2{&ts.StructD{"NotEqual"}, "NotEqual"},
+		y:     &ts.StructD2{&ts.StructD{"not_equal"}, "not_equal"},
+	}, {
+		label: label + "StructE2",
+		x:     ts.StructE2{&ts.StructE{"NotEqual"}, "NotEqual"},
+		y:     ts.StructE2{&ts.StructE{"not_equal"}, "not_equal"},
+	}, {
+		label: label + "StructE2",
+		x:     &ts.StructE2{&ts.StructE{"NotEqual"}, "NotEqual"},
+		y:     &ts.StructE2{&ts.StructE{"not_equal"}, "not_equal"},
+	}, {
+		label: label + "StructF2",
+		x:     ts.StructF2{&ts.StructF{"NotEqual"}, "NotEqual"},
+		y:     ts.StructF2{&ts.StructF{"not_equal"}, "not_equal"},
+	}, {
+		label: label + "StructF2",
+		x:     &ts.StructF2{&ts.StructF{"NotEqual"}, "NotEqual"},
+		y:     &ts.StructF2{&ts.StructF{"not_equal"}, "not_equal"},
+	}, {
+		label:    label + "StructNo",
+		x:        ts.StructNo{"NotEqual"},
+		y:        ts.StructNo{"not_equal"},
+		wantDiff: "{teststructs.StructNo}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
+	}, {
+		label: label + "AssignA",
+		x:     ts.AssignA(func() int { return 0 }),
+		y:     ts.AssignA(func() int { return 1 }),
+	}, {
+		label: label + "AssignB",
+		x:     ts.AssignB(struct{ A int }{0}),
+		y:     ts.AssignB(struct{ A int }{1}),
+	}, {
+		label: label + "AssignC",
+		x:     ts.AssignC(make(chan bool)),
+		y:     ts.AssignC(make(chan bool)),
+	}, {
+		label: label + "AssignD",
+		x:     ts.AssignD(make(chan bool)),
+		y:     ts.AssignD(make(chan bool)),
+	}}
+}
+
+func project1Tests() []test {
+	const label = "Project1"
+
+	ignoreUnexported := IgnoreUnexported(
+		ts.EagleImmutable{},
+		ts.DreamerImmutable{},
+		ts.SlapImmutable{},
+		ts.GoatImmutable{},
+		ts.DonkeyImmutable{},
+		ts.LoveRadius{},
+		ts.SummerLove{},
+		ts.SummerLoveSummary{},
+	)
+
+	createEagle := func() ts.Eagle {
+		return ts.Eagle{
+			Name:   "eagle",
+			Hounds: []string{"buford", "tannen"},
+			Desc:   "some description",
+			Dreamers: []ts.Dreamer{{}, {
+				Name: "dreamer2",
+				Animal: []interface{}{
+					ts.Goat{
+						Target: "corporation",
+						Immutable: &ts.GoatImmutable{
+							ID:      "southbay",
+							State:   (*pb.Goat_States)(intPtr(5)),
+							Started: now,
+						},
+					},
+					ts.Donkey{},
+				},
+				Amoeba: 53,
+			}},
+			Slaps: []ts.Slap{{
+				Name: "slapID",
+				Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}},
+				Immutable: &ts.SlapImmutable{
+					ID:       "immutableSlap",
+					MildSlap: true,
+					Started:  now,
+					LoveRadius: &ts.LoveRadius{
+						Summer: &ts.SummerLove{
+							Summary: &ts.SummerLoveSummary{
+								Devices:    []string{"foo", "bar", "baz"},
+								ChangeType: []pb.SummerType{1, 2, 3},
+							},
+						},
+					},
+				},
+			}},
+			Immutable: &ts.EagleImmutable{
+				ID:          "eagleID",
+				Birthday:    now,
+				MissingCall: (*pb.Eagle_MissingCalls)(intPtr(55)),
+			},
+		}
+	}
+
+	return []test{{
+		label: label,
+		x: ts.Eagle{Slaps: []ts.Slap{{
+			Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}},
+		}}},
+		y: ts.Eagle{Slaps: []ts.Slap{{
+			Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}},
+		}}},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label,
+		x: ts.Eagle{Slaps: []ts.Slap{{
+			Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}},
+		}}},
+		y: ts.Eagle{Slaps: []ts.Slap{{
+			Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}},
+		}}},
+		opts:     []cmp.Option{cmp.Comparer(pb.Equal)},
+		wantDiff: "",
+	}, {
+		label: label,
+		x: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
+			Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}},
+		}}},
+		y: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
+			Args: &pb.MetaData{Stringer: pb.Stringer{"metadata2"}},
+		}}},
+		opts:     []cmp.Option{cmp.Comparer(pb.Equal)},
+		wantDiff: "{teststructs.Eagle}.Slaps[4].Args:\n\t-: \"metadata\"\n\t+: \"metadata2\"\n",
+	}, {
+		label: label,
+		x:     createEagle(),
+		y:     createEagle(),
+		opts:  []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
+	}, {
+		label: label,
+		x: func() ts.Eagle {
+			eg := createEagle()
+			eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.ID = "southbay2"
+			eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.State = (*pb.Goat_States)(intPtr(6))
+			eg.Slaps[0].Immutable.MildSlap = false
+			return eg
+		}(),
+		y: func() ts.Eagle {
+			eg := createEagle()
+			devs := eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices
+			eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices = devs[:1]
+			return eg
+		}(),
+		opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
+		wantDiff: `
+{teststructs.Eagle}.Dreamers[1].Animal[0].(teststructs.Goat).Immutable.ID:
+	-: "southbay2"
+	+: "southbay"
+*{teststructs.Eagle}.Dreamers[1].Animal[0].(teststructs.Goat).Immutable.State:
+	-: 6
+	+: 5
+{teststructs.Eagle}.Slaps[0].Immutable.MildSlap:
+	-: false
+	+: true
+{teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[1]:
+	-: "bar"
+	+: <non-existent>
+{teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[2]:
+	-: "baz"
+	+: <non-existent>`,
+	}}
+}
+
+func project2Tests() []test {
+	const label = "Project2"
+
+	sortGerms := cmp.FilterValues(func(x, y []*pb.Germ) bool {
+		ok1 := sort.SliceIsSorted(x, func(i, j int) bool {
+			return x[i].String() < x[j].String()
+		})
+		ok2 := sort.SliceIsSorted(y, func(i, j int) bool {
+			return y[i].String() < y[j].String()
+		})
+		return !ok1 || !ok2
+	}, cmp.Transformer("Sort", func(in []*pb.Germ) []*pb.Germ {
+		out := append([]*pb.Germ(nil), in...) // Make copy
+		sort.Slice(out, func(i, j int) bool {
+			return out[i].String() < out[j].String()
+		})
+		return out
+	}))
+
+	equalDish := cmp.Comparer(func(x, y *ts.Dish) bool {
+		if x == nil || y == nil {
+			return x == nil && y == nil
+		}
+		px, err1 := x.Proto()
+		py, err2 := y.Proto()
+		if err1 != nil || err2 != nil {
+			return err1 == err2
+		}
+		return pb.Equal(px, py)
+	})
+
+	createBatch := func() ts.GermBatch {
+		return ts.GermBatch{
+			DirtyGerms: map[int32][]*pb.Germ{
+				17: []*pb.Germ{
+					&pb.Germ{Stringer: pb.Stringer{"germ1"}},
+				},
+				18: []*pb.Germ{
+					&pb.Germ{Stringer: pb.Stringer{"germ2"}},
+					&pb.Germ{Stringer: pb.Stringer{"germ3"}},
+					&pb.Germ{Stringer: pb.Stringer{"germ4"}},
+				},
+			},
+			GermMap: map[int32]*pb.Germ{
+				13: &pb.Germ{Stringer: pb.Stringer{"germ13"}},
+				21: &pb.Germ{Stringer: pb.Stringer{"germ21"}},
+			},
+			DishMap: map[int32]*ts.Dish{
+				0: ts.CreateDish(nil, io.EOF),
+				1: ts.CreateDish(nil, io.ErrUnexpectedEOF),
+				2: ts.CreateDish(&pb.Dish{Stringer: pb.Stringer{"dish"}}, nil),
+			},
+			HasPreviousResult: true,
+			DirtyID:           10,
+			GermStrain:        421,
+			InfectedAt:        now,
+		}
+	}
+
+	return []test{{
+		label:     label,
+		x:         createBatch(),
+		y:         createBatch(),
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label,
+		x:     createBatch(),
+		y:     createBatch(),
+		opts:  []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
+	}, {
+		label: label,
+		x:     createBatch(),
+		y: func() ts.GermBatch {
+			gb := createBatch()
+			s := gb.DirtyGerms[18]
+			s[0], s[1], s[2] = s[1], s[2], s[0]
+			return gb
+		}(),
+		opts: []cmp.Option{cmp.Comparer(pb.Equal), equalDish},
+		wantDiff: `
+{teststructs.GermBatch}.DirtyGerms[18][0]:
+	-: "germ2"
+	+: "germ3"
+{teststructs.GermBatch}.DirtyGerms[18][1]:
+	-: "germ3"
+	+: "germ4"
+{teststructs.GermBatch}.DirtyGerms[18][2]:
+	-: "germ4"
+	+: "germ2"`,
+	}, {
+		label: label,
+		x:     createBatch(),
+		y: func() ts.GermBatch {
+			gb := createBatch()
+			s := gb.DirtyGerms[18]
+			s[0], s[1], s[2] = s[1], s[2], s[0]
+			return gb
+		}(),
+		opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
+	}, {
+		label: label,
+		x: func() ts.GermBatch {
+			gb := createBatch()
+			delete(gb.DirtyGerms, 17)
+			gb.DishMap[1] = nil
+			return gb
+		}(),
+		y: func() ts.GermBatch {
+			gb := createBatch()
+			gb.DirtyGerms[18] = gb.DirtyGerms[18][:2]
+			gb.GermStrain = 22
+			return gb
+		}(),
+		opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
+		wantDiff: `
+{teststructs.GermBatch}.DirtyGerms[17]:
+	-: <non-existent>
+	+: []*testprotos.Germ{"germ1"}
+{teststructs.GermBatch}.DirtyGerms[18][2]:
+	-: "germ4"
+	+: <non-existent>
+{teststructs.GermBatch}.DishMap[1]:
+	-: (*teststructs.Dish)(nil)
+	+: &teststructs.Dish{err: &errors.errorString{s: "unexpected EOF"}}
+{teststructs.GermBatch}.GermStrain:
+	-: 421
+	+: 22`,
+	}}
+}
+
+func project3Tests() []test {
+	const label = "Project3"
+
+	allowVisibility := cmp.AllowUnexported(ts.Dirt{})
+
+	ignoreLocker := cmp.FilterPath(func(p cmp.Path) bool {
+		return len(p) > 0 && p[len(p)-1].Type() == mutexType
+	}, cmp.Ignore())
+
+	transformProtos := cmp.Transformer("", func(x pb.Dirt) *pb.Dirt {
+		return &x
+	})
+
+	equalTable := cmp.Comparer(func(x, y ts.Table) bool {
+		tx, ok1 := x.(*ts.MockTable)
+		ty, ok2 := y.(*ts.MockTable)
+		if !ok1 || !ok2 {
+			panic("table type must be MockTable")
+		}
+		return cmp.Equal(tx.State(), ty.State())
+	})
+
+	createDirt := func() (d ts.Dirt) {
+		d.SetTable(ts.CreateMockTable([]string{"a", "b", "c"}))
+		d.SetTimestamp(12345)
+		d.Discord = 554
+		d.Proto = pb.Dirt{Stringer: pb.Stringer{"proto"}}
+		d.SetWizard(map[string]*pb.Wizard{
+			"harry": &pb.Wizard{Stringer: pb.Stringer{"potter"}},
+			"albus": &pb.Wizard{Stringer: pb.Stringer{"dumbledore"}},
+		})
+		d.SetLastTime(54321)
+		return d
+	}
+
+	return []test{{
+		label:     label,
+		x:         createDirt(),
+		y:         createDirt(),
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label:     label,
+		x:         createDirt(),
+		y:         createDirt(),
+		opts:      []cmp.Option{allowVisibility, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label,
+		x:     createDirt(),
+		y:     createDirt(),
+		opts:  []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
+	}, {
+		label: label,
+		x: func() ts.Dirt {
+			d := createDirt()
+			d.SetTable(ts.CreateMockTable([]string{"a", "c"}))
+			d.Proto = pb.Dirt{Stringer: pb.Stringer{"blah"}}
+			return d
+		}(),
+		y: func() ts.Dirt {
+			d := createDirt()
+			d.Discord = 500
+			d.SetWizard(map[string]*pb.Wizard{
+				"harry": &pb.Wizard{Stringer: pb.Stringer{"otter"}},
+			})
+			return d
+		}(),
+		opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
+		wantDiff: `
+{teststructs.Dirt}.table:
+	-: &teststructs.MockTable{state: []string{"a", "c"}}
+	+: &teststructs.MockTable{state: []string{"a", "b", "c"}}
+{teststructs.Dirt}.Discord:
+	-: 554
+	+: 500
+λ({teststructs.Dirt}.Proto):
+	-: "blah"
+	+: "proto"
+{teststructs.Dirt}.wizard["albus"]:
+	-: "dumbledore"
+	+: <non-existent>
+{teststructs.Dirt}.wizard["harry"]:
+	-: "potter"
+	+: "otter"`,
+	}}
+}
+
+func project4Tests() []test {
+	const label = "Project4"
+
+	allowVisibility := cmp.AllowUnexported(
+		ts.Cartel{},
+		ts.Headquarter{},
+		ts.Poison{},
+	)
+
+	transformProtos := cmp.Transformer("", func(x pb.Restrictions) *pb.Restrictions {
+		return &x
+	})
+
+	createCartel := func() ts.Cartel {
+		var p ts.Poison
+		p.SetPoisonType(5)
+		p.SetExpiration(now)
+		p.SetManufactuer("acme")
+
+		var hq ts.Headquarter
+		hq.SetID(5)
+		hq.SetLocation("moon")
+		hq.SetSubDivisions([]string{"alpha", "bravo", "charlie"})
+		hq.SetMetaData(&pb.MetaData{Stringer: pb.Stringer{"metadata"}})
+		hq.SetPublicMessage([]byte{1, 2, 3, 4, 5})
+		hq.SetHorseBack("abcdef")
+		hq.SetStatus(44)
+
+		var c ts.Cartel
+		c.Headquarter = hq
+		c.SetSource("mars")
+		c.SetCreationTime(now)
+		c.SetBoss("al capone")
+		c.SetPoisons([]*ts.Poison{&p})
+
+		return c
+	}
+
+	return []test{{
+		label:     label,
+		x:         createCartel(),
+		y:         createCartel(),
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label:     label,
+		x:         createCartel(),
+		y:         createCartel(),
+		opts:      []cmp.Option{allowVisibility, cmp.Comparer(pb.Equal)},
+		wantPanic: "cannot handle unexported field",
+	}, {
+		label: label,
+		x:     createCartel(),
+		y:     createCartel(),
+		opts:  []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
+	}, {
+		label: label,
+		x: func() ts.Cartel {
+			d := createCartel()
+			var p1, p2 ts.Poison
+			p1.SetPoisonType(1)
+			p1.SetExpiration(now)
+			p1.SetManufactuer("acme")
+			p2.SetPoisonType(2)
+			p2.SetManufactuer("acme2")
+			d.SetPoisons([]*ts.Poison{&p1, &p2})
+			return d
+		}(),
+		y: func() ts.Cartel {
+			d := createCartel()
+			d.SetSubDivisions([]string{"bravo", "charlie"})
+			d.SetPublicMessage([]byte{1, 2, 4, 3, 5})
+			return d
+		}(),
+		opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
+		wantDiff: `
+{teststructs.Cartel}.Headquarter.subDivisions[0]:
+	-: "alpha"
+	+: "bravo"
+{teststructs.Cartel}.Headquarter.subDivisions[1]:
+	-: "bravo"
+	+: "charlie"
+{teststructs.Cartel}.Headquarter.subDivisions[2]:
+	-: "charlie"
+	+: <non-existent>
+{teststructs.Cartel}.Headquarter.publicMessage[2]:
+	-: 0x03
+	+: 0x04
+{teststructs.Cartel}.Headquarter.publicMessage[3]:
+	-: 0x04
+	+: 0x03
+{teststructs.Cartel}.poisons[0].poisonType:
+	-: 1
+	+: 5
+{teststructs.Cartel}.poisons[1]:
+	-: &teststructs.Poison{poisonType: 2, manufactuer: "acme2"}
+	+: <non-existent>`,
+	}}
+}
diff --git a/cmp/example_test.go b/cmp/example_test.go
new file mode 100644
index 0000000..a01f4ad
--- /dev/null
+++ b/cmp/example_test.go
@@ -0,0 +1,302 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package cmp_test
+
+import (
+	"fmt"
+	"math"
+	"reflect"
+	"sort"
+	"strings"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+// Approximate equality for floats can be handled by defining a custom
+// comparer on floats that determines two values to be equal if they are within
+// some range of each other.
+func ExampleOption_approximateFloats() {
+	// This Comparer only operates on float64.
+	// To handle float32s, either define a similar function for that type
+	// or use a Transformer to convert float32s into float64s.
+	opt := cmp.Comparer(func(x, y float64) bool {
+		delta := math.Abs(x - y)
+		mean := math.Abs(x+y) / 2.0
+		return delta/mean < 0.00001
+	})
+
+	x := []float64{1.0, 1.1, 1.2, math.Pi}
+	y := []float64{1.0, 1.1, 1.2, 3.14159265359} // Accurate enough to Pi
+	z := []float64{1.0, 1.1, 1.2, 3.1415}        // Diverges too far from Pi
+
+	fmt.Println(cmp.Equal(x, y, opt))
+	fmt.Println(cmp.Equal(y, z, opt))
+	fmt.Println(cmp.Equal(z, x, opt))
+
+	// Output:
+	// true
+	// false
+	// false
+}
+
+// Normal floating-point arithmetic defines == to be false when comparing
+// NaN with itself. In certain cases, this is not the desired property.
+func ExampleOption_equalNaNs() {
+	// This Comparer only operates on float64.
+	// To handle float32s, either define a similar function for that type
+	// or use a Transformer to convert float32s into float64s.
+	opt := cmp.Comparer(func(x, y float64) bool {
+		return (math.IsNaN(x) && math.IsNaN(y)) || x == y
+	})
+
+	x := []float64{1.0, math.NaN(), math.E, -0.0, +0.0}
+	y := []float64{1.0, math.NaN(), math.E, -0.0, +0.0}
+	z := []float64{1.0, math.NaN(), math.Pi, -0.0, +0.0} // Pi constant instead of E
+
+	fmt.Println(cmp.Equal(x, y, opt))
+	fmt.Println(cmp.Equal(y, z, opt))
+	fmt.Println(cmp.Equal(z, x, opt))
+
+	// Output:
+	// true
+	// false
+	// false
+}
+
+// To have floating-point comparisons combine both properties of NaN being
+// equal to itself and also approximate equality of values, filters are needed
+// to restrict the scope of the comparison so that they are composable.
+func ExampleOption_equalNaNsAndApproximateFloats() {
+	alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
+
+	opts := cmp.Options{
+		// This option declares that a float64 comparison is equal only if
+		// both inputs are NaN.
+		cmp.FilterValues(func(x, y float64) bool {
+			return math.IsNaN(x) && math.IsNaN(y)
+		}, alwaysEqual),
+
+		// This option declares approximate equality on float64s only if
+		// both inputs are not NaN.
+		cmp.FilterValues(func(x, y float64) bool {
+			return !math.IsNaN(x) && !math.IsNaN(y)
+		}, cmp.Comparer(func(x, y float64) bool {
+			delta := math.Abs(x - y)
+			mean := math.Abs(x+y) / 2.0
+			return delta/mean < 0.00001
+		})),
+	}
+
+	x := []float64{math.NaN(), 1.0, 1.1, 1.2, math.Pi}
+	y := []float64{math.NaN(), 1.0, 1.1, 1.2, 3.14159265359} // Accurate enough to Pi
+	z := []float64{math.NaN(), 1.0, 1.1, 1.2, 3.1415}        // Diverges too far from Pi
+
+	fmt.Println(cmp.Equal(x, y, opts))
+	fmt.Println(cmp.Equal(y, z, opts))
+	fmt.Println(cmp.Equal(z, x, opts))
+
+	// Output:
+	// true
+	// false
+	// false
+}
+
+// Sometimes, an empty map or slice is considered equal to an allocated one
+// of zero length.
+func ExampleOption_equalEmpty() {
+	alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
+
+	// This option handles slices and maps of any type.
+	opt := cmp.FilterValues(func(x, y interface{}) bool {
+		vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
+		return (vx.IsValid() && vy.IsValid() && vx.Type() == vy.Type()) &&
+			(vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) &&
+			(vx.Len() == 0 && vy.Len() == 0)
+	}, alwaysEqual)
+
+	type S struct {
+		A []int
+		B map[string]bool
+	}
+	x := S{nil, make(map[string]bool, 100)}
+	y := S{make([]int, 0, 200), nil}
+	z := S{[]int{0}, nil} // []int has a single element (i.e., not empty)
+
+	fmt.Println(cmp.Equal(x, y, opt))
+	fmt.Println(cmp.Equal(y, z, opt))
+	fmt.Println(cmp.Equal(z, x, opt))
+
+	// Output:
+	// true
+	// false
+	// false
+}
+
+// Equal compares map keys using Go's == operator. To use Equal itself on
+// map keys, transform the map into something else, like a slice of
+// key-value pairs.
+func ExampleOption_transformMap() {
+	type KV struct {
+		K time.Time
+		V string
+	}
+	// This transformer flattens the map as a slice of sorted key-value pairs.
+	// We can now safely rely on the Time.Equal to be used for equality.
+	trans := cmp.Transformer("", func(m map[time.Time]string) (s []KV) {
+		for k, v := range m {
+			s = append(s, KV{k, v})
+		}
+		sort.Slice(s, func(i, j int) bool {
+			return s[i].K.Before(s[j].K)
+		})
+		return s
+	})
+
+	t1 := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
+	t2 := time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)
+	t3 := time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC)
+
+	x := map[time.Time]string{
+		t1.In(time.UTC): "0th birthday",
+		t2.In(time.UTC): "1st birthday",
+		t3.In(time.UTC): "2nd birthday",
+	}
+	y := map[time.Time]string{
+		t1.In(time.Local): "0th birthday",
+		t2.In(time.Local): "1st birthday",
+		t3.In(time.Local): "2nd birthday",
+	}
+	z := map[time.Time]string{
+		time.Now(): "a long long",
+		time.Now(): "time ago",
+		time.Now(): "in a galaxy far far away",
+	}
+
+	fmt.Println(cmp.Equal(x, y, trans))
+	fmt.Println(cmp.Equal(y, z, trans))
+	fmt.Println(cmp.Equal(z, x, trans))
+
+	// Output:
+	// true
+	// false
+	// false
+}
+
+// Two slices may be considered equal if they have the same elements,
+// regardless of the order that they appear in. Transformations can be used
+// to sort the slice.
+func ExampleOption_sortedSlice() {
+	// This Transformer sorts a []int.
+	// Since the transformer transforms []int into []int, there is problem where
+	// this is recursively applied forever. To prevent this, use a FilterValues
+	// to first check for the condition upon which the transformer ought to apply.
+	trans := cmp.FilterValues(func(x, y []int) bool {
+		return !sort.IntsAreSorted(x) || !sort.IntsAreSorted(y)
+	}, cmp.Transformer("Sort", func(in []int) []int {
+		out := append([]int(nil), in...) // Copy input to avoid mutating it
+		sort.Ints(out)
+		return out
+	}))
+
+	x := struct{ Ints []int }{[]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}
+	y := struct{ Ints []int }{[]int{2, 8, 0, 9, 6, 1, 4, 7, 3, 5}}
+	z := struct{ Ints []int }{[]int{0, 0, 1, 2, 3, 4, 5, 6, 7, 8}}
+
+	fmt.Println(cmp.Equal(x, y, trans))
+	fmt.Println(cmp.Equal(y, z, trans))
+	fmt.Println(cmp.Equal(z, x, trans))
+
+	// Output:
+	// true
+	// false
+	// false
+}
+
+type otherString string
+
+func (x otherString) Equal(y otherString) bool {
+	return strings.ToLower(string(x)) == strings.ToLower(string(y))
+}
+
+// If the Equal method defined on a type is not suitable, the type can be be
+// dynamically transformed to be stripped of the Equal method (or any method
+// for that matter).
+func ExampleOption_avoidEqualMethod() {
+	// Suppose otherString.Equal performs a case-insensitive equality,
+	// which is too loose for our needs.
+	// We can avoid the methods of otherString by declaring a new type.
+	type myString otherString
+
+	// This transformer converts otherString to myString, allowing Equal to use
+	// other Options to determine equality.
+	trans := cmp.Transformer("", func(in otherString) myString {
+		return myString(in)
+	})
+
+	x := []otherString{"foo", "bar", "baz"}
+	y := []otherString{"fOO", "bAr", "Baz"} // Same as before, but with different case
+
+	fmt.Println(cmp.Equal(x, y))        // Equal because of case-insensitivity
+	fmt.Println(cmp.Equal(x, y, trans)) // Not equal because of more exact equality
+
+	// Output:
+	// true
+	// false
+}
+
+func roundF64(z float64) float64 {
+	if z < 0 {
+		return math.Ceil(z - 0.5)
+	}
+	return math.Floor(z + 0.5)
+}
+
+// The complex numbers complex64 and complex128 can really just be decomposed
+// into a pair of float32 or float64 values. It would be convenient to be able
+// define only a single comparator on float64 and have float32, complex64, and
+// complex128 all be able to use that comparator. Transformations can be used
+// to handle this.
+func ExampleOption_transformComplex() {
+	opts := []cmp.Option{
+		// This transformer decomposes complex128 into a pair of float64s.
+		cmp.Transformer("T1", func(in complex128) (out struct{ Real, Imag float64 }) {
+			out.Real, out.Imag = real(in), imag(in)
+			return out
+		}),
+		// This transformer converts complex64 to complex128 to allow the
+		// above transform to take effect.
+		cmp.Transformer("T2", func(in complex64) complex128 {
+			return complex128(in)
+		}),
+		// This transformer converts float32 to float64.
+		cmp.Transformer("T3", func(in float32) float64 {
+			return float64(in)
+		}),
+		// This equality function compares float64s as rounded integers.
+		cmp.Comparer(func(x, y float64) bool {
+			return roundF64(x) == roundF64(y)
+		}),
+	}
+
+	x := []interface{}{
+		complex128(3.0), complex64(5.1 + 2.9i), float32(-1.2), float64(12.3),
+	}
+	y := []interface{}{
+		complex128(3.1), complex64(4.9 + 3.1i), float32(-1.3), float64(11.7),
+	}
+	z := []interface{}{
+		complex128(3.8), complex64(4.9 + 3.1i), float32(-1.3), float64(11.7),
+	}
+
+	fmt.Println(cmp.Equal(x, y, opts...))
+	fmt.Println(cmp.Equal(y, z, opts...))
+	fmt.Println(cmp.Equal(z, x, opts...))
+
+	// Output:
+	// true
+	// false
+	// false
+}
diff --git a/cmp/internal/testprotos/protos.go b/cmp/internal/testprotos/protos.go
new file mode 100644
index 0000000..120c8b0
--- /dev/null
+++ b/cmp/internal/testprotos/protos.go
@@ -0,0 +1,116 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package testprotos
+
+func Equal(x, y Message) bool {
+	if x == nil || y == nil {
+		return x == nil && y == nil
+	}
+	return x.String() == y.String()
+}
+
+type Message interface {
+	Proto()
+	String() string
+}
+
+type proto interface {
+	Proto()
+}
+
+type notComparable struct {
+	unexportedField func()
+}
+
+type Stringer struct{ X string }
+
+func (s *Stringer) String() string { return s.X }
+
+// Project1 protocol buffers
+type (
+	Eagle_States         int
+	Eagle_MissingCalls   int
+	Dreamer_States       int
+	Dreamer_MissingCalls int
+	Slap_States          int
+	Goat_States          int
+	Donkey_States        int
+	SummerType           int
+
+	Eagle struct {
+		proto
+		notComparable
+		Stringer
+	}
+	Dreamer struct {
+		proto
+		notComparable
+		Stringer
+	}
+	Slap struct {
+		proto
+		notComparable
+		Stringer
+	}
+	Goat struct {
+		proto
+		notComparable
+		Stringer
+	}
+	Donkey struct {
+		proto
+		notComparable
+		Stringer
+	}
+)
+
+// Project2 protocol buffers
+type (
+	Germ struct {
+		proto
+		notComparable
+		Stringer
+	}
+	Dish struct {
+		proto
+		notComparable
+		Stringer
+	}
+)
+
+// Project3 protocol buffers
+type (
+	Dirt struct {
+		proto
+		notComparable
+		Stringer
+	}
+	Wizard struct {
+		proto
+		notComparable
+		Stringer
+	}
+	Sadistic struct {
+		proto
+		notComparable
+		Stringer
+	}
+)
+
+// Project4 protocol buffers
+type (
+	HoneyStatus int
+	PoisonType  int
+	MetaData    struct {
+		proto
+		notComparable
+		Stringer
+	}
+	Restrictions struct {
+		proto
+		notComparable
+		Stringer
+	}
+)
diff --git a/cmp/internal/teststructs/project1.go b/cmp/internal/teststructs/project1.go
new file mode 100644
index 0000000..1999e38
--- /dev/null
+++ b/cmp/internal/teststructs/project1.go
@@ -0,0 +1,267 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package teststructs
+
+import (
+	"time"
+
+	pb "github.com/google/go-cmp/cmp/internal/testprotos"
+)
+
+// This is an sanitized example of equality from a real use-case.
+// The original equality function was as follows:
+/*
+func equalEagle(x, y Eagle) bool {
+	if x.Name != y.Name &&
+		!reflect.DeepEqual(x.Hounds, y.Hounds) &&
+		x.Desc != y.Desc &&
+		x.DescLong != y.DescLong &&
+		x.Prong != y.Prong &&
+		x.StateGoverner != y.StateGoverner &&
+		x.PrankRating != y.PrankRating &&
+		x.FunnyPrank != y.FunnyPrank &&
+		!pb.Equal(x.Immutable.Proto(), y.Immutable.Proto()) {
+		return false
+	}
+
+	if len(x.Dreamers) != len(y.Dreamers) {
+		return false
+	}
+	for i := range x.Dreamers {
+		if !equalDreamer(x.Dreamers[i], y.Dreamers[i]) {
+			return false
+		}
+	}
+	if len(x.Slaps) != len(y.Slaps) {
+		return false
+	}
+	for i := range x.Slaps {
+		if !equalSlap(x.Slaps[i], y.Slaps[i]) {
+			return false
+		}
+	}
+	return true
+}
+func equalDreamer(x, y Dreamer) bool {
+	if x.Name != y.Name ||
+		x.Desc != y.Desc ||
+		x.DescLong != y.DescLong ||
+		x.ContSlapsInterval != y.ContSlapsInterval ||
+		x.Ornamental != y.Ornamental ||
+		x.Amoeba != y.Amoeba ||
+		x.Heroes != y.Heroes ||
+		x.FloppyDisk != y.FloppyDisk ||
+		x.MightiestDuck != y.MightiestDuck ||
+		x.FunnyPrank != y.FunnyPrank ||
+		!pb.Equal(x.Immutable.Proto(), y.Immutable.Proto()) {
+
+		return false
+	}
+	if len(x.Animal) != len(y.Animal) {
+		return false
+	}
+	for i := range x.Animal {
+		vx := x.Animal[i]
+		vy := y.Animal[i]
+		if reflect.TypeOf(x.Animal) != reflect.TypeOf(y.Animal) {
+			return false
+		}
+		switch vx.(type) {
+		case Goat:
+			if !equalGoat(vx.(Goat), vy.(Goat)) {
+				return false
+			}
+		case Donkey:
+			if !equalDonkey(vx.(Donkey), vy.(Donkey)) {
+				return false
+			}
+		default:
+			panic(fmt.Sprintf("unknown type: %T", vx))
+		}
+	}
+	if len(x.PreSlaps) != len(y.PreSlaps) {
+		return false
+	}
+	for i := range x.PreSlaps {
+		if !equalSlap(x.PreSlaps[i], y.PreSlaps[i]) {
+			return false
+		}
+	}
+	if len(x.ContSlaps) != len(y.ContSlaps) {
+		return false
+	}
+	for i := range x.ContSlaps {
+		if !equalSlap(x.ContSlaps[i], y.ContSlaps[i]) {
+			return false
+		}
+	}
+	return true
+}
+func equalSlap(x, y Slap) bool {
+	return x.Name == y.Name &&
+		x.Desc == y.Desc &&
+		x.DescLong == y.DescLong &&
+		pb.Equal(x.Args, y.Args) &&
+		x.Tense == y.Tense &&
+		x.Interval == y.Interval &&
+		x.Homeland == y.Homeland &&
+		x.FunnyPrank == y.FunnyPrank &&
+		pb.Equal(x.Immutable.Proto(), y.Immutable.Proto())
+}
+func equalGoat(x, y Goat) bool {
+	if x.Target != y.Target ||
+		x.FunnyPrank != y.FunnyPrank ||
+		!pb.Equal(x.Immutable.Proto(), y.Immutable.Proto()) {
+		return false
+	}
+	if len(x.Slaps) != len(y.Slaps) {
+		return false
+	}
+	for i := range x.Slaps {
+		if !equalSlap(x.Slaps[i], y.Slaps[i]) {
+			return false
+		}
+	}
+	return true
+}
+func equalDonkey(x, y Donkey) bool {
+	return x.Pause == y.Pause &&
+		x.Sleep == y.Sleep &&
+		x.FunnyPrank == y.FunnyPrank &&
+		pb.Equal(x.Immutable.Proto(), y.Immutable.Proto())
+}
+*/
+
+type Eagle struct {
+	Name          string
+	Hounds        []string
+	Desc          string
+	DescLong      string
+	Dreamers      []Dreamer
+	Prong         int64
+	Slaps         []Slap
+	StateGoverner string
+	PrankRating   string
+	FunnyPrank    string
+	Immutable     *EagleImmutable
+}
+
+type EagleImmutable struct {
+	ID          string
+	State       *pb.Eagle_States
+	MissingCall *pb.Eagle_MissingCalls
+	Birthday    time.Time
+	Death       time.Time
+	Started     time.Time
+	LastUpdate  time.Time
+	Creator     string
+	empty       bool
+}
+
+type Dreamer struct {
+	Name              string
+	Desc              string
+	DescLong          string
+	PreSlaps          []Slap
+	ContSlaps         []Slap
+	ContSlapsInterval int32
+	Animal            []interface{} // Could be either Goat or Donkey
+	Ornamental        bool
+	Amoeba            int64
+	Heroes            int32
+	FloppyDisk        int32
+	MightiestDuck     bool
+	FunnyPrank        string
+	Immutable         *DreamerImmutable
+}
+
+type DreamerImmutable struct {
+	ID          string
+	State       *pb.Dreamer_States
+	MissingCall *pb.Dreamer_MissingCalls
+	Calls       int32
+	Started     time.Time
+	Stopped     time.Time
+	LastUpdate  time.Time
+	empty       bool
+}
+
+type Slap struct {
+	Name       string
+	Desc       string
+	DescLong   string
+	Args       pb.Message
+	Tense      int32
+	Interval   int32
+	Homeland   uint32
+	FunnyPrank string
+	Immutable  *SlapImmutable
+}
+
+type SlapImmutable struct {
+	ID          string
+	Out         pb.Message
+	MildSlap    bool
+	PrettyPrint string
+	State       *pb.Slap_States
+	Started     time.Time
+	Stopped     time.Time
+	LastUpdate  time.Time
+	LoveRadius  *LoveRadius
+	empty       bool
+}
+
+type Goat struct {
+	Target     string
+	Slaps      []Slap
+	FunnyPrank string
+	Immutable  *GoatImmutable
+}
+
+type GoatImmutable struct {
+	ID         string
+	State      *pb.Goat_States
+	Started    time.Time
+	Stopped    time.Time
+	LastUpdate time.Time
+	empty      bool
+}
+type Donkey struct {
+	Pause      bool
+	Sleep      int32
+	FunnyPrank string
+	Immutable  *DonkeyImmutable
+}
+
+type DonkeyImmutable struct {
+	ID         string
+	State      *pb.Donkey_States
+	Started    time.Time
+	Stopped    time.Time
+	LastUpdate time.Time
+	empty      bool
+}
+
+type LoveRadius struct {
+	Summer *SummerLove
+	empty  bool
+}
+
+type SummerLove struct {
+	Summary *SummerLoveSummary
+	empty   bool
+}
+
+type SummerLoveSummary struct {
+	Devices    []string
+	ChangeType []pb.SummerType
+	empty      bool
+}
+
+func (EagleImmutable) Proto() *pb.Eagle     { return nil }
+func (DreamerImmutable) Proto() *pb.Dreamer { return nil }
+func (SlapImmutable) Proto() *pb.Slap       { return nil }
+func (GoatImmutable) Proto() *pb.Goat       { return nil }
+func (DonkeyImmutable) Proto() *pb.Donkey   { return nil }
diff --git a/cmp/internal/teststructs/project2.go b/cmp/internal/teststructs/project2.go
new file mode 100644
index 0000000..536592b
--- /dev/null
+++ b/cmp/internal/teststructs/project2.go
@@ -0,0 +1,74 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package teststructs
+
+import (
+	"time"
+
+	pb "github.com/google/go-cmp/cmp/internal/testprotos"
+)
+
+// This is an sanitized example of equality from a real use-case.
+// The original equality function was as follows:
+/*
+func equalBatch(b1, b2 *GermBatch) bool {
+	for _, b := range []*GermBatch{b1, b2} {
+		for _, l := range b.DirtyGerms {
+			sort.Slice(l, func(i, j int) bool { return l[i].String() < l[j].String() })
+		}
+		for _, l := range b.CleanGerms {
+			sort.Slice(l, func(i, j int) bool { return l[i].String() < l[j].String() })
+		}
+	}
+	if !pb.DeepEqual(b1.DirtyGerms, b2.DirtyGerms) ||
+		!pb.DeepEqual(b1.CleanGerms, b2.CleanGerms) ||
+		!pb.DeepEqual(b1.GermMap, b2.GermMap) {
+		return false
+	}
+	if len(b1.DishMap) != len(b2.DishMap) {
+		return false
+	}
+	for id := range b1.DishMap {
+		kpb1, err1 := b1.DishMap[id].Proto()
+		kpb2, err2 := b2.DishMap[id].Proto()
+		if !pb.Equal(kpb1, kpb2) || !reflect.DeepEqual(err1, err2) {
+			return false
+		}
+	}
+	return b1.HasPreviousResult == b2.HasPreviousResult &&
+		b1.DirtyID == b2.DirtyID &&
+		b1.CleanID == b2.CleanID &&
+		b1.GermStrain == b2.GermStrain &&
+		b1.TotalDirtyGerms == b2.TotalDirtyGerms &&
+		b1.InfectedAt.Equal(b2.InfectedAt)
+}
+*/
+
+type GermBatch struct {
+	DirtyGerms, CleanGerms map[int32][]*pb.Germ
+	GermMap                map[int32]*pb.Germ
+	DishMap                map[int32]*Dish
+	HasPreviousResult      bool
+	DirtyID, CleanID       int32
+	GermStrain             int32
+	TotalDirtyGerms        int
+	InfectedAt             time.Time
+}
+
+type Dish struct {
+	pb  *pb.Dish
+	err error
+}
+
+func CreateDish(m *pb.Dish, err error) *Dish {
+	return &Dish{pb: m, err: err}
+}
+
+func (d *Dish) Proto() (*pb.Dish, error) {
+	if d.err != nil {
+		return nil, d.err
+	}
+	return d.pb, nil
+}
diff --git a/cmp/internal/teststructs/project3.go b/cmp/internal/teststructs/project3.go
new file mode 100644
index 0000000..00c252e
--- /dev/null
+++ b/cmp/internal/teststructs/project3.go
@@ -0,0 +1,77 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package teststructs
+
+import (
+	"sync"
+
+	pb "github.com/google/go-cmp/cmp/internal/testprotos"
+)
+
+// This is an sanitized example of equality from a real use-case.
+// The original equality function was as follows:
+/*
+func equalDirt(x, y *Dirt) bool {
+	if !reflect.DeepEqual(x.table, y.table) ||
+		!reflect.DeepEqual(x.ts, y.ts) ||
+		x.Discord != y.Discord ||
+		!pb.Equal(&x.Proto, &y.Proto) ||
+		len(x.wizard) != len(y.wizard) ||
+		len(x.sadistic) != len(y.sadistic) ||
+		x.lastTime != y.lastTime {
+		return false
+	}
+	for k, vx := range x.wizard {
+		vy, ok := y.wizard[k]
+		if !ok || !pb.Equal(vx, vy) {
+			return false
+		}
+	}
+	for k, vx := range x.sadistic {
+		vy, ok := y.sadistic[k]
+		if !ok || !pb.Equal(vx, vy) {
+			return false
+		}
+	}
+	return true
+}
+*/
+
+type Dirt struct {
+	table    Table // Always concrete type of MockTable
+	ts       Timestamp
+	Discord  DiscordState
+	Proto    pb.Dirt
+	wizard   map[string]*pb.Wizard
+	sadistic map[string]*pb.Sadistic
+	lastTime int64
+	mu       sync.Mutex
+}
+
+type DiscordState int
+
+type Timestamp int64
+
+func (d *Dirt) SetTable(t Table)                      { d.table = t }
+func (d *Dirt) SetTimestamp(t Timestamp)              { d.ts = t }
+func (d *Dirt) SetWizard(m map[string]*pb.Wizard)     { d.wizard = m }
+func (d *Dirt) SetSadistic(m map[string]*pb.Sadistic) { d.sadistic = m }
+func (d *Dirt) SetLastTime(t int64)                   { d.lastTime = t }
+
+type Table interface {
+	Operation1() error
+	Operation2() error
+	Operation3() error
+}
+
+type MockTable struct {
+	state []string
+}
+
+func CreateMockTable(s []string) *MockTable { return &MockTable{s} }
+func (mt *MockTable) Operation1() error     { return nil }
+func (mt *MockTable) Operation2() error     { return nil }
+func (mt *MockTable) Operation3() error     { return nil }
+func (mt *MockTable) State() []string       { return mt.state }
diff --git a/cmp/internal/teststructs/project4.go b/cmp/internal/teststructs/project4.go
new file mode 100644
index 0000000..9b50d73
--- /dev/null
+++ b/cmp/internal/teststructs/project4.go
@@ -0,0 +1,142 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package teststructs
+
+import (
+	"time"
+
+	pb "github.com/google/go-cmp/cmp/internal/testprotos"
+)
+
+// This is an sanitized example of equality from a real use-case.
+// The original equality function was as follows:
+/*
+func equalCartel(x, y Cartel) bool {
+	if !(equalHeadquarter(x.Headquarter, y.Headquarter) &&
+		x.Source() == y.Source() &&
+		x.CreationDate().Equal(y.CreationDate()) &&
+		x.Boss() == y.Boss() &&
+		x.LastCrimeDate().Equal(y.LastCrimeDate())) {
+		return false
+	}
+	if len(x.Poisons()) != len(y.Poisons()) {
+		return false
+	}
+	for i := range x.Poisons() {
+		if !equalPoison(*x.Poisons()[i], *y.Poisons()[i]) {
+			return false
+		}
+	}
+	return true
+}
+func equalHeadquarter(x, y Headquarter) bool {
+	xr, yr := x.Restrictions(), y.Restrictions()
+	return x.ID() == y.ID() &&
+		x.Location() == y.Location() &&
+		reflect.DeepEqual(x.SubDivisions(), y.SubDivisions()) &&
+		x.IncorporatedDate().Equal(y.IncorporatedDate()) &&
+		pb.Equal(x.MetaData(), y.MetaData()) &&
+		bytes.Equal(x.PrivateMessage(), y.PrivateMessage()) &&
+		bytes.Equal(x.PublicMessage(), y.PublicMessage()) &&
+		x.HorseBack() == y.HorseBack() &&
+		x.Rattle() == y.Rattle() &&
+		x.Convulsion() == y.Convulsion() &&
+		x.Expansion() == y.Expansion() &&
+		x.Status() == y.Status() &&
+		pb.Equal(&xr, &yr) &&
+		x.CreationTime().Equal(y.CreationTime())
+}
+func equalPoison(x, y Poison) bool {
+	return x.PoisonType() == y.PoisonType() &&
+		x.Expiration().Equal(y.Expiration()) &&
+		x.Manufactuer() == y.Manufactuer() &&
+		x.Potency() == y.Potency()
+}
+*/
+
+type Cartel struct {
+	Headquarter
+	source        string
+	creationDate  time.Time
+	boss          string
+	lastCrimeDate time.Time
+	poisons       []*Poison
+}
+
+func (p Cartel) Source() string           { return p.source }
+func (p Cartel) CreationDate() time.Time  { return p.creationDate }
+func (p Cartel) Boss() string             { return p.boss }
+func (p Cartel) LastCrimeDate() time.Time { return p.lastCrimeDate }
+func (p Cartel) Poisons() []*Poison       { return p.poisons }
+
+func (p *Cartel) SetSource(x string)           { p.source = x }
+func (p *Cartel) SetCreationDate(x time.Time)  { p.creationDate = x }
+func (p *Cartel) SetBoss(x string)             { p.boss = x }
+func (p *Cartel) SetLastCrimeDate(x time.Time) { p.lastCrimeDate = x }
+func (p *Cartel) SetPoisons(x []*Poison)       { p.poisons = x }
+
+type Headquarter struct {
+	id               uint64
+	location         string
+	subDivisions     []string
+	incorporatedDate time.Time
+	metaData         *pb.MetaData
+	privateMessage   []byte
+	publicMessage    []byte
+	horseBack        string
+	rattle           string
+	convulsion       bool
+	expansion        uint64
+	status           pb.HoneyStatus
+	restrictions     pb.Restrictions
+	creationTime     time.Time
+}
+
+func (hq Headquarter) ID() uint64                    { return hq.id }
+func (hq Headquarter) Location() string              { return hq.location }
+func (hq Headquarter) SubDivisions() []string        { return hq.subDivisions }
+func (hq Headquarter) IncorporatedDate() time.Time   { return hq.incorporatedDate }
+func (hq Headquarter) MetaData() *pb.MetaData        { return hq.metaData }
+func (hq Headquarter) PrivateMessage() []byte        { return hq.privateMessage }
+func (hq Headquarter) PublicMessage() []byte         { return hq.publicMessage }
+func (hq Headquarter) HorseBack() string             { return hq.horseBack }
+func (hq Headquarter) Rattle() string                { return hq.rattle }
+func (hq Headquarter) Convulsion() bool              { return hq.convulsion }
+func (hq Headquarter) Expansion() uint64             { return hq.expansion }
+func (hq Headquarter) Status() pb.HoneyStatus        { return hq.status }
+func (hq Headquarter) Restrictions() pb.Restrictions { return hq.restrictions }
+func (hq Headquarter) CreationTime() time.Time       { return hq.creationTime }
+
+func (hq *Headquarter) SetID(x uint64)                    { hq.id = x }
+func (hq *Headquarter) SetLocation(x string)              { hq.location = x }
+func (hq *Headquarter) SetSubDivisions(x []string)        { hq.subDivisions = x }
+func (hq *Headquarter) SetIncorporatedDate(x time.Time)   { hq.incorporatedDate = x }
+func (hq *Headquarter) SetMetaData(x *pb.MetaData)        { hq.metaData = x }
+func (hq *Headquarter) SetPrivateMessage(x []byte)        { hq.privateMessage = x }
+func (hq *Headquarter) SetPublicMessage(x []byte)         { hq.publicMessage = x }
+func (hq *Headquarter) SetHorseBack(x string)             { hq.horseBack = x }
+func (hq *Headquarter) SetRattle(x string)                { hq.rattle = x }
+func (hq *Headquarter) SetConvulsion(x bool)              { hq.convulsion = x }
+func (hq *Headquarter) SetExpansion(x uint64)             { hq.expansion = x }
+func (hq *Headquarter) SetStatus(x pb.HoneyStatus)        { hq.status = x }
+func (hq *Headquarter) SetRestrictions(x pb.Restrictions) { hq.restrictions = x }
+func (hq *Headquarter) SetCreationTime(x time.Time)       { hq.creationTime = x }
+
+type Poison struct {
+	poisonType  pb.PoisonType
+	expiration  time.Time
+	manufactuer string
+	potency     int
+}
+
+func (p Poison) PoisonType() pb.PoisonType { return p.poisonType }
+func (p Poison) Expiration() time.Time     { return p.expiration }
+func (p Poison) Manufactuer() string       { return p.manufactuer }
+func (p Poison) Potency() int              { return p.potency }
+
+func (p *Poison) SetPoisonType(x pb.PoisonType) { p.poisonType = x }
+func (p *Poison) SetExpiration(x time.Time)     { p.expiration = x }
+func (p *Poison) SetManufactuer(x string)       { p.manufactuer = x }
+func (p *Poison) SetPotency(x int)              { p.potency = x }
diff --git a/cmp/internal/teststructs/structs.go b/cmp/internal/teststructs/structs.go
new file mode 100644
index 0000000..6b4d2a7
--- /dev/null
+++ b/cmp/internal/teststructs/structs.go
@@ -0,0 +1,197 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package teststructs
+
+type InterfaceA interface {
+	InterfaceA()
+}
+
+type (
+	StructA struct{ X string } // Equal method on value receiver
+	StructB struct{ X string } // Equal method on pointer receiver
+	StructC struct{ X string } // Equal method (with interface argument) on value receiver
+	StructD struct{ X string } // Equal method (with interface argument) on pointer receiver
+	StructE struct{ X string } // Equal method (with interface argument on value receiver) on pointer receiver
+	StructF struct{ X string } // Equal method (with interface argument on pointer receiver) on value receiver
+
+	// These embed the above types as a value.
+	StructA1 struct {
+		StructA
+		X string
+	}
+	StructB1 struct {
+		StructB
+		X string
+	}
+	StructC1 struct {
+		StructC
+		X string
+	}
+	StructD1 struct {
+		StructD
+		X string
+	}
+	StructE1 struct {
+		StructE
+		X string
+	}
+	StructF1 struct {
+		StructF
+		X string
+	}
+
+	// These embed the above types as a pointer.
+	StructA2 struct {
+		*StructA
+		X string
+	}
+	StructB2 struct {
+		*StructB
+		X string
+	}
+	StructC2 struct {
+		*StructC
+		X string
+	}
+	StructD2 struct {
+		*StructD
+		X string
+	}
+	StructE2 struct {
+		*StructE
+		X string
+	}
+	StructF2 struct {
+		*StructF
+		X string
+	}
+
+	StructNo struct{ X string } // Equal method (with interface argument) on non-satisfying receiver
+
+	AssignA func() int
+	AssignB struct{ A int }
+	AssignC chan bool
+	AssignD <-chan bool
+)
+
+func (x StructA) Equal(y StructA) bool     { return true }
+func (x *StructB) Equal(y *StructB) bool   { return true }
+func (x StructC) Equal(y InterfaceA) bool  { return true }
+func (x StructC) InterfaceA()              {}
+func (x *StructD) Equal(y InterfaceA) bool { return true }
+func (x *StructD) InterfaceA()             {}
+func (x *StructE) Equal(y InterfaceA) bool { return true }
+func (x StructE) InterfaceA()              {}
+func (x StructF) Equal(y InterfaceA) bool  { return true }
+func (x *StructF) InterfaceA()             {}
+func (x StructNo) Equal(y InterfaceA) bool { return true }
+
+func (x AssignA) Equal(y func() int) bool      { return true }
+func (x AssignB) Equal(y struct{ A int }) bool { return true }
+func (x AssignC) Equal(y chan bool) bool       { return true }
+func (x AssignD) Equal(y <-chan bool) bool     { return true }
+
+var _ = func(
+	a StructA, b StructB, c StructC, d StructD, e StructE, f StructF,
+	ap *StructA, bp *StructB, cp *StructC, dp *StructD, ep *StructE, fp *StructF,
+	a1 StructA1, b1 StructB1, c1 StructC1, d1 StructD1, e1 StructE1, f1 StructF1,
+	a2 StructA2, b2 StructB2, c2 StructC2, d2 StructD2, e2 StructE2, f2 StructF1,
+) {
+	a.Equal(a)
+	b.Equal(&b)
+	c.Equal(c)
+	d.Equal(&d)
+	e.Equal(e)
+	f.Equal(&f)
+
+	ap.Equal(*ap)
+	bp.Equal(bp)
+	cp.Equal(*cp)
+	dp.Equal(dp)
+	ep.Equal(*ep)
+	fp.Equal(fp)
+
+	a1.Equal(a1.StructA)
+	b1.Equal(&b1.StructB)
+	c1.Equal(c1)
+	d1.Equal(&d1)
+	e1.Equal(e1)
+	f1.Equal(&f1)
+
+	a2.Equal(*a2.StructA)
+	b2.Equal(b2.StructB)
+	c2.Equal(c2)
+	d2.Equal(&d2)
+	e2.Equal(e2)
+	f2.Equal(&f2)
+}
+
+type (
+	privateStruct struct{ Public, private int }
+	PublicStruct  struct{ Public, private int }
+	ParentStructA struct{ privateStruct }
+	ParentStructB struct{ PublicStruct }
+	ParentStructC struct {
+		privateStruct
+		Public, private int
+	}
+	ParentStructD struct {
+		PublicStruct
+		Public, private int
+	}
+	ParentStructE struct {
+		privateStruct
+		PublicStruct
+	}
+	ParentStructF struct {
+		privateStruct
+		PublicStruct
+		Public, private int
+	}
+	ParentStructG struct {
+		*privateStruct
+	}
+	ParentStructH struct {
+		*PublicStruct
+	}
+	ParentStructI struct {
+		*privateStruct
+		*PublicStruct
+	}
+	ParentStructJ struct {
+		*privateStruct
+		*PublicStruct
+		Public  PublicStruct
+		private privateStruct
+	}
+)
+
+func NewParentStructG() *ParentStructG {
+	return &ParentStructG{new(privateStruct)}
+}
+func NewParentStructH() *ParentStructH {
+	return &ParentStructH{new(PublicStruct)}
+}
+func NewParentStructI() *ParentStructI {
+	return &ParentStructI{new(privateStruct), new(PublicStruct)}
+}
+func NewParentStructJ() *ParentStructJ {
+	return &ParentStructJ{
+		privateStruct: new(privateStruct), PublicStruct: new(PublicStruct),
+	}
+}
+func (s *privateStruct) SetPrivate(i int)              { s.private = i }
+func (s *PublicStruct) SetPrivate(i int)               { s.private = i }
+func (s *ParentStructC) SetPrivate(i int)              { s.private = i }
+func (s *ParentStructD) SetPrivate(i int)              { s.private = i }
+func (s *ParentStructF) SetPrivate(i int)              { s.private = i }
+func (s *ParentStructA) PrivateStruct() *privateStruct { return &s.privateStruct }
+func (s *ParentStructC) PrivateStruct() *privateStruct { return &s.privateStruct }
+func (s *ParentStructE) PrivateStruct() *privateStruct { return &s.privateStruct }
+func (s *ParentStructF) PrivateStruct() *privateStruct { return &s.privateStruct }
+func (s *ParentStructG) PrivateStruct() *privateStruct { return s.privateStruct }
+func (s *ParentStructI) PrivateStruct() *privateStruct { return s.privateStruct }
+func (s *ParentStructJ) PrivateStruct() *privateStruct { return s.privateStruct }
+func (s *ParentStructJ) Private() *privateStruct       { return &s.private }
diff --git a/cmp/options.go b/cmp/options.go
new file mode 100644
index 0000000..f69a066
--- /dev/null
+++ b/cmp/options.go
@@ -0,0 +1,296 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package cmp
+
+import (
+	"fmt"
+	"reflect"
+	"runtime"
+	"strings"
+)
+
+// Option configures for specific behavior of Diff and Equal. In particular,
+// the fundamental Option functions (Ignore, Transformer, and Comparer),
+// configure how equality is determined.
+//
+// The fundamental options may be composed with filters (FilterPath and
+// FilterValues) to control the scope over which they are applied.
+type Option interface {
+	// Prevent Option from being equivalent to interface{}, which provides
+	// a small type checking benefit by preventing Equal(opt, x, y).
+	option()
+}
+
+// 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,
+// it will be implicitly expanded into a flat list.
+//
+// Applying a filter on an Options is equivalent to applying that same filter
+// 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"
+			}
+			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
+}
+
+// FilterPath returns a new Option where opt is only evaluated if filter f
+// returns true for the current Path in the value tree.
+//
+// The option passed in may be an Ignore, Transformer, Comparer, Options, or
+// a previously filtered Option.
+func FilterPath(f func(Path) bool, opt Option) Option {
+	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))
+	}
+}
+
+// FilterValues returns a new Option where opt is only evaluated if filter f,
+// which is a function of the form "func(T, T) bool", returns true for the
+// current pair of values being compared. If the type of the values is not
+// assignable to T, then this filter implicitly returns false.
+//
+// The filter function must be
+// symmetric (i.e., agnostic to the order of the inputs) and
+// deterministic (i.e., produces the same result when given the same inputs).
+// If T is an interface, it is possible that f is called with two values with
+// different concrete types that both implement T.
+//
+// The option passed in may be an Ignore, Transformer, Comparer, Options, or
+// a previously filtered Option.
+func FilterValues(f interface{}, opt Option) Option {
+	v := reflect.ValueOf(f)
+	if functionType(v.Type()) != valueFilterFunc || 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
+		}
+		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))
+	}
+}
+
+// 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{}
+}
+
+// Transformer returns an Option that applies a transformation function that
+// converts values of a certain type into that of another.
+//
+// The transformer f must be a function "func(T) R" that converts values of
+// type T to those of type R and is implicitly filtered to input values
+// assignable to T. The transformer must not mutate T in any way.
+// If T and R are the same type, an additional filter must be applied to
+// act as the base case to prevent an infinite recursion applying the same
+// transform to itself (see the SortedSlice example).
+//
+// The name is a user provided label that is used as the Transform.Name in the
+// transformation PathStep. If empty, an arbitrary name is used.
+func Transformer(name string, f interface{}) Option {
+	v := reflect.ValueOf(f)
+	if functionType(v.Type()) != transformFunc || v.IsNil() {
+		panic(fmt.Sprintf("invalid transformer function: %T", f))
+	}
+	if name == "" {
+		name = "λ" // Lambda-symbol as place-holder for anonymous transformer
+	}
+	if !isValid(name) {
+		panic(fmt.Sprintf("invalid name: %q", name))
+	}
+	opt := option{op: &transformer{name, reflect.ValueOf(f)}}
+	if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
+		opt.typeFilter = ti
+	}
+	return opt
+}
+
+type transformer struct {
+	name string
+	fnc  reflect.Value // func(T) R
+}
+
+// Comparer returns an Option that determines whether two values are equal
+// to each other.
+//
+// The comparer f must be a function "func(T, T) bool" and is implicitly
+// filtered to input values assignable to T. If T is an interface, it is
+// possible that f is called with two values of different concrete types that
+// both implement T.
+//
+// The equality function must be:
+//	• Symmetric: equal(x, y) == equal(y, x)
+//	• Deterministic: equal(x, y) == equal(x, y)
+//	• Pure: equal(x, y) does not modify x or y
+func Comparer(f interface{}) Option {
+	v := reflect.ValueOf(f)
+	if functionType(v.Type()) != equalFunc || v.IsNil() {
+		panic(fmt.Sprintf("invalid comparer function: %T", f))
+	}
+	opt := option{op: &comparer{v}}
+	if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
+		opt.typeFilter = ti
+	}
+	return opt
+}
+
+type comparer struct {
+	fnc reflect.Value // func(T, T) bool
+}
+
+// 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.
+//
+// Users of this option must understand that comparing on unexported fields
+// from external packages is not safe since changes in the internal
+// implementation of some external package may cause the result of Equal
+// to unexpectedly change. However, it may be valid to use this option on types
+// defined in an internal package where the semantic meaning of an unexported
+// field is in the control of the user.
+//
+// For most cases, a custom Comparer should be used instead that defines
+// equality as a function of the public API of a type rather than the underlying
+// unexported implementation.
+//
+// For example, the reflect.Type documentation defines equality to be determined
+// by the == operator on the interface (essentially performing a shallow pointer
+// comparison) and most attempts to compare *regexp.Regexp types are interested
+// in only checking that the regular expression strings are equal.
+// Both of these are accomplished using Comparers:
+//	Comparer(func(x, y reflect.Type) bool { return x == y })
+//	Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() })
+//
+// NOTE: This feature is experimental and may be removed!
+func AllowUnexported(types ...interface{}) Option {
+	m := make(map[reflect.Type]bool)
+	for _, typ := range types {
+		t := reflect.TypeOf(typ)
+		if t.Kind() != reflect.Struct {
+			panic(fmt.Sprintf("invalid struct type: %T", typ))
+		}
+		m[t] = true
+	}
+	return visibleStructs(m)
+}
+
+type visibleStructs map[reflect.Type]bool
+
+func (visibleStructs) option() {}
+
+// reporter is an Option that configures how differences are reported.
+//
+// TODO: Not exported yet, see concerns in defaultReporter.Report.
+type reporter interface {
+	Option
+
+	// Report is called for every comparison made and will be provided with
+	// the two values being compared, the equality result, and the
+	// current path in the value tree. It is possible for x or y to be an
+	// invalid reflect.Value if one of the values is non-existent;
+	// which is possible with maps and slices.
+	Report(x, y reflect.Value, eq bool, p Path)
+
+	// TODO: Perhaps add PushStep and PopStep and change Report to only accept
+	// a PathStep instead of the full-path? This change allows us to provide
+	// better output closer to what pretty.Compare is able to achieve.
+}
diff --git a/cmp/options_test.go b/cmp/options_test.go
new file mode 100644
index 0000000..76b76a0
--- /dev/null
+++ b/cmp/options_test.go
@@ -0,0 +1,217 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package cmp
+
+import (
+	"io"
+	"reflect"
+	"strings"
+	"testing"
+
+	ts "github.com/google/go-cmp/cmp/internal/teststructs"
+)
+
+// Test that the creation of Option values with non-sensible inputs produces
+// a run-time panic with a decent error message
+func TestOptionPanic(t *testing.T) {
+	type myBool bool
+	tests := []struct {
+		label     string        // Test description
+		fnc       interface{}   // Option function to call
+		args      []interface{} // Arguments to pass in
+		wantPanic string        // Expected panic message
+	}{{
+		label: "AllowUnexported",
+		fnc:   AllowUnexported,
+		args:  []interface{}{},
+	}, {
+		label:     "AllowUnexported",
+		fnc:       AllowUnexported,
+		args:      []interface{}{1},
+		wantPanic: "invalid struct type",
+	}, {
+		label: "AllowUnexported",
+		fnc:   AllowUnexported,
+		args:  []interface{}{ts.StructA{}},
+	}, {
+		label: "AllowUnexported",
+		fnc:   AllowUnexported,
+		args:  []interface{}{ts.StructA{}, ts.StructB{}, ts.StructA{}},
+	}, {
+		label:     "AllowUnexported",
+		fnc:       AllowUnexported,
+		args:      []interface{}{ts.StructA{}, &ts.StructB{}, ts.StructA{}},
+		wantPanic: "invalid struct type",
+	}, {
+		label:     "Comparer",
+		fnc:       Comparer,
+		args:      []interface{}{5},
+		wantPanic: "invalid comparer function",
+	}, {
+		label: "Comparer",
+		fnc:   Comparer,
+		args:  []interface{}{func(x, y interface{}) bool { return true }},
+	}, {
+		label: "Comparer",
+		fnc:   Comparer,
+		args:  []interface{}{func(x, y io.Reader) bool { return true }},
+	}, {
+		label:     "Comparer",
+		fnc:       Comparer,
+		args:      []interface{}{func(x, y io.Reader) myBool { return true }},
+		wantPanic: "invalid comparer function",
+	}, {
+		label:     "Comparer",
+		fnc:       Comparer,
+		args:      []interface{}{func(x string, y interface{}) bool { return true }},
+		wantPanic: "invalid comparer function",
+	}, {
+		label:     "Comparer",
+		fnc:       Comparer,
+		args:      []interface{}{(func(int, int) bool)(nil)},
+		wantPanic: "invalid comparer function",
+	}, {
+		label:     "Transformer",
+		fnc:       Transformer,
+		args:      []interface{}{"", 0},
+		wantPanic: "invalid transformer function",
+	}, {
+		label: "Transformer",
+		fnc:   Transformer,
+		args:  []interface{}{"", func(int) int { return 0 }},
+	}, {
+		label: "Transformer",
+		fnc:   Transformer,
+		args:  []interface{}{"", func(bool) bool { return true }},
+	}, {
+		label: "Transformer",
+		fnc:   Transformer,
+		args:  []interface{}{"", func(int) bool { return true }},
+	}, {
+		label:     "Transformer",
+		fnc:       Transformer,
+		args:      []interface{}{"", func(int, int) bool { return true }},
+		wantPanic: "invalid transformer function",
+	}, {
+		label:     "Transformer",
+		fnc:       Transformer,
+		args:      []interface{}{"", (func(int) uint)(nil)},
+		wantPanic: "invalid transformer function",
+	}, {
+		label: "Transformer",
+		fnc:   Transformer,
+		args:  []interface{}{"Func", func(Path) Path { return nil }},
+	}, {
+		label: "Transformer",
+		fnc:   Transformer,
+		args:  []interface{}{"世界", func(int) bool { return true }},
+	}, {
+		label:     "Transformer",
+		fnc:       Transformer,
+		args:      []interface{}{"/*", func(int) bool { return true }},
+		wantPanic: "invalid name",
+	}, {
+		label:     "Transformer",
+		fnc:       Transformer,
+		args:      []interface{}{"_", func(int) bool { return true }},
+		wantPanic: "invalid name",
+	}, {
+		label:     "FilterPath",
+		fnc:       FilterPath,
+		args:      []interface{}{(func(Path) bool)(nil), Ignore()},
+		wantPanic: "invalid path filter function",
+	}, {
+		label: "FilterPath",
+		fnc:   FilterPath,
+		args:  []interface{}{func(Path) bool { return true }, Ignore()},
+	}, {
+		label:     "FilterPath",
+		fnc:       FilterPath,
+		args:      []interface{}{func(Path) bool { return true }, &defaultReporter{}},
+		wantPanic: "unknown option type",
+	}, {
+		label: "FilterPath",
+		fnc:   FilterPath,
+		args:  []interface{}{func(Path) bool { return true }, Options{Ignore(), Ignore()}},
+	}, {
+		label:     "FilterPath",
+		fnc:       FilterPath,
+		args:      []interface{}{func(Path) bool { return true }, Options{Ignore(), &defaultReporter{}}},
+		wantPanic: "unknown option type",
+	}, {
+		label:     "FilterValues",
+		fnc:       FilterValues,
+		args:      []interface{}{0, Ignore()},
+		wantPanic: "invalid values filter function",
+	}, {
+		label: "FilterValues",
+		fnc:   FilterValues,
+		args:  []interface{}{func(x, y int) bool { return true }, Ignore()},
+	}, {
+		label: "FilterValues",
+		fnc:   FilterValues,
+		args:  []interface{}{func(x, y interface{}) bool { return true }, Ignore()},
+	}, {
+		label:     "FilterValues",
+		fnc:       FilterValues,
+		args:      []interface{}{func(x, y interface{}) myBool { return true }, Ignore()},
+		wantPanic: "invalid values filter function",
+	}, {
+		label:     "FilterValues",
+		fnc:       FilterValues,
+		args:      []interface{}{func(x io.Reader, y interface{}) bool { return true }, Ignore()},
+		wantPanic: "invalid values filter function",
+	}, {
+		label:     "FilterValues",
+		fnc:       FilterValues,
+		args:      []interface{}{(func(int, int) bool)(nil), Ignore()},
+		wantPanic: "invalid values filter function",
+	}, {
+		label:     "FilterValues",
+		fnc:       FilterValues,
+		args:      []interface{}{func(int, int) bool { return true }, &defaultReporter{}},
+		wantPanic: "unknown option type",
+	}, {
+		label: "FilterValues",
+		fnc:   FilterValues,
+		args:  []interface{}{func(int, int) bool { return true }, Options{Ignore(), Ignore()}},
+	}, {
+		label:     "FilterValues",
+		fnc:       FilterValues,
+		args:      []interface{}{func(int, int) bool { return true }, Options{Ignore(), &defaultReporter{}}},
+		wantPanic: "unknown option type",
+	}}
+
+	for _, tt := range tests {
+		t.Run(tt.label, func(t *testing.T) {
+			var gotPanic string
+			func() {
+				defer func() {
+					if ex := recover(); ex != nil {
+						if s, ok := ex.(string); ok {
+							gotPanic = s
+						} else {
+							panic(ex)
+						}
+					}
+				}()
+				var vargs []reflect.Value
+				for _, arg := range tt.args {
+					vargs = append(vargs, reflect.ValueOf(arg))
+				}
+				reflect.ValueOf(tt.fnc).Call(vargs)
+			}()
+			if tt.wantPanic == "" {
+				if gotPanic != "" {
+					t.Fatalf("unexpected panic message: %s", gotPanic)
+				}
+			} else {
+				if !strings.Contains(gotPanic, tt.wantPanic) {
+					t.Fatalf("panic message:\ngot:  %s\nwant: %s", gotPanic, tt.wantPanic)
+				}
+			}
+		})
+	}
+}
diff --git a/cmp/path.go b/cmp/path.go
new file mode 100644
index 0000000..ae6af40
--- /dev/null
+++ b/cmp/path.go
@@ -0,0 +1,249 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package cmp
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+	"unicode"
+	"unicode/utf8"
+)
+
+type (
+	// Path is a list of PathSteps describing the sequence of operations to get
+	// from some root type to the current position in the value tree.
+	// The first Path element is always an operation-less PathStep that exists
+	// simply to identify the initial type.
+	//
+	// When traversing structs with embedded structs, the embedded struct will
+	// always be accessed as a field before traversing the fields of the
+	// embedded struct themselves. That is, an exported field from the
+	// embedded struct will never be accessed directly from the parent struct.
+	Path []PathStep
+
+	// PathStep is a union-type for specific operations to traverse
+	// a value's tree structure. Users of this package never need to implement
+	// these types as values of this type will be returned by this package.
+	PathStep interface {
+		String() string
+		Type() reflect.Type // Resulting type after performing the path step
+		isPathStep()
+	}
+
+	// SliceIndex is an index operation on a slice or array at some index Key.
+	SliceIndex interface {
+		PathStep
+		Key() int
+		isSliceIndex()
+	}
+	// MapIndex is an index operation on a map at some index Key.
+	MapIndex interface {
+		PathStep
+		Key() reflect.Value
+		isMapIndex()
+	}
+	// TypeAssertion represents a type assertion on an interface.
+	TypeAssertion interface {
+		PathStep
+		isTypeAssertion()
+	}
+	// StructField represents a struct field access on a field called Name.
+	StructField interface {
+		PathStep
+		Name() string
+		Index() int
+		isStructField()
+	}
+	// Indirect represents pointer indirection on the parent type.
+	Indirect interface {
+		PathStep
+		isIndirect()
+	}
+	// Transform is a transformation from the parent type to the current type.
+	Transform interface {
+		PathStep
+		Name() string
+		Func() reflect.Value
+		isTransform()
+	}
+)
+
+func (pa *Path) push(s PathStep) {
+	*pa = append(*pa, s)
+}
+
+func (pa *Path) pop() {
+	*pa = (*pa)[:len(*pa)-1]
+}
+
+// String returns the simplified path to a node.
+// The simplified path only contains struct field accesses.
+//
+// For example:
+//	MyMap.MySlices.MyField
+func (pa Path) String() string {
+	var ss []string
+	for _, s := range pa {
+		if _, ok := s.(*structField); ok {
+			ss = append(ss, s.String())
+		}
+	}
+	return strings.TrimPrefix(strings.Join(ss, ""), ".")
+}
+
+// GoString returns the path to a specific node using Go syntax.
+//
+// For example:
+//	(*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField
+func (pa Path) GoString() string {
+	var ssPre, ssPost []string
+	var numIndirect int
+	for i, s := range pa {
+		var nextStep PathStep
+		if i+1 < len(pa) {
+			nextStep = pa[i+1]
+		}
+		switch s := s.(type) {
+		case *indirect:
+			numIndirect++
+			pPre, pPost := "(", ")"
+			switch nextStep.(type) {
+			case *indirect:
+				continue // Next step is indirection, so let them batch up
+			case *structField:
+				numIndirect-- // Automatic indirection on struct fields
+			case nil:
+				pPre, pPost = "", "" // Last step; no need for parenthesis
+			}
+			if numIndirect > 0 {
+				ssPre = append(ssPre, pPre+strings.Repeat("*", numIndirect))
+				ssPost = append(ssPost, pPost)
+			}
+			numIndirect = 0
+			continue
+		case *transform:
+			ssPre = append(ssPre, s.trans.name+"(")
+			ssPost = append(ssPost, ")")
+			continue
+		case *typeAssertion:
+			// Elide type assertions immediately following a transform to
+			// prevent overly verbose path printouts.
+			// Some transforms return interface{} because of Go's lack of
+			// generics, but typically take in and return the exact same
+			// concrete type. Other times, the transform creates an anonymous
+			// struct, which will be very verbose to print.
+			if _, ok := nextStep.(*transform); ok {
+				continue
+			}
+		}
+		ssPost = append(ssPost, s.String())
+	}
+	for i, j := 0, len(ssPre)-1; i < j; i, j = i+1, j-1 {
+		ssPre[i], ssPre[j] = ssPre[j], ssPre[i]
+	}
+	return strings.Join(ssPre, "") + strings.Join(ssPost, "")
+}
+
+type (
+	pathStep struct {
+		typ reflect.Type
+	}
+
+	sliceIndex struct {
+		pathStep
+		key int
+	}
+	mapIndex struct {
+		pathStep
+		key reflect.Value
+	}
+	typeAssertion struct {
+		pathStep
+	}
+	structField struct {
+		pathStep
+		name string
+		idx  int
+
+		// These fields are used for forcibly accessing an unexported field.
+		// pvx, pvy, and field are only valid if unexported is true.
+		unexported bool
+		force      bool                // Forcibly allow visibility
+		pvx, pvy   reflect.Value       // Parent values
+		field      reflect.StructField // Field information
+	}
+	indirect struct {
+		pathStep
+	}
+	transform struct {
+		pathStep
+		trans *transformer
+	}
+)
+
+func (ps pathStep) Type() reflect.Type { return ps.typ }
+func (ps pathStep) String() string {
+	s := ps.typ.String()
+	if s == "" || strings.ContainsAny(s, "{}\n") {
+		return "root" // Type too simple or complex to print
+	}
+	return "{" + s + "}"
+}
+
+func (si sliceIndex) String() string    { return fmt.Sprintf("[%d]", si.key) }
+func (mi mapIndex) String() string      { return fmt.Sprintf("[%#v]", mi.key) }
+func (ta typeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) }
+func (sf structField) String() string   { return fmt.Sprintf(".%s", sf.name) }
+func (in indirect) String() string      { return "*" }
+func (tf transform) String() string     { return fmt.Sprintf("%s()", tf.trans.name) }
+
+func (si sliceIndex) Key() int           { return si.key }
+func (mi mapIndex) Key() reflect.Value   { return mi.key }
+func (sf structField) Name() string      { return sf.name }
+func (sf structField) Index() int        { return sf.idx }
+func (tf transform) Name() string        { return tf.trans.name }
+func (tf transform) Func() reflect.Value { return tf.trans.fnc }
+
+func (pathStep) isPathStep()           {}
+func (sliceIndex) isSliceIndex()       {}
+func (mapIndex) isMapIndex()           {}
+func (typeAssertion) isTypeAssertion() {}
+func (structField) isStructField()     {}
+func (indirect) isIndirect()           {}
+func (transform) isTransform()         {}
+
+var (
+	_ SliceIndex    = sliceIndex{}
+	_ MapIndex      = mapIndex{}
+	_ TypeAssertion = typeAssertion{}
+	_ StructField   = structField{}
+	_ Indirect      = indirect{}
+	_ Transform     = transform{}
+
+	_ PathStep = sliceIndex{}
+	_ PathStep = mapIndex{}
+	_ PathStep = typeAssertion{}
+	_ PathStep = structField{}
+	_ PathStep = indirect{}
+	_ PathStep = transform{}
+)
+
+// isExported reports whether the identifier is exported.
+func isExported(id string) bool {
+	r, _ := utf8.DecodeRuneInString(id)
+	return unicode.IsUpper(r)
+}
+
+// isValid reports whether the identifier is valid.
+// Empty and underscore-only strings are not valid.
+func isValid(id string) bool {
+	ok := id != "" && id != "_"
+	for j, c := range id {
+		ok = ok && (j > 0 || !unicode.IsDigit(c))
+		ok = ok && (c == '_' || unicode.IsLetter(c) || unicode.IsDigit(c))
+	}
+	return ok
+}
diff --git a/cmp/reporter.go b/cmp/reporter.go
new file mode 100644
index 0000000..85cf977
--- /dev/null
+++ b/cmp/reporter.go
@@ -0,0 +1,399 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package cmp
+
+import (
+	"fmt"
+	"math"
+	"reflect"
+	"sort"
+	"strings"
+)
+
+// TODO: Can we leave the interface for a reporter here in the cmp package
+// and somehow extract the implementation of defaultReporter into cmp/report?
+
+type defaultReporter struct {
+	Option
+	diffs  []string // List of differences, possibly truncated
+	ndiffs int      // Total number of differences
+	nbytes int      // Number of bytes in diffs
+	nlines int      // Number of lines in diffs
+}
+
+var _ reporter = (*defaultReporter)(nil)
+
+func (r *defaultReporter) Report(x, y reflect.Value, eq bool, p Path) {
+	// TODO: Is there a way to nicely print added/modified/removed elements
+	// from a slice? This will most certainly require support from the
+	// equality logic, but what would be the right API for this?
+	//
+	// The current API is equivalent to a Hamming distance for measuring the
+	// difference between two sequences of symbols. That is, the only operation
+	// we can represent is substitution. The new API would need to handle a
+	// Levenshtein distance, such that insertions, deletions, and substitutions
+	// are permitted. Furthermore, this will require an algorithm for computing
+	// the edit distance. Unfortunately, the time complexity for a minimal
+	// edit distance algorithm is not much better than O(n^2).
+	// There are approximations for the algorithm that can run much faster.
+	// See literature on computing Levenshtein distance.
+	//
+	// Passing in a pair of x and y is actually good for representing insertion
+	// and deletion by the fact that x or y may be an invalid value. However,
+	// we may need to pass in two paths px and py, to indicate the paths
+	// relative to x and y. Alternative, since we only perform the Levenshtein
+	// distance on slices, maybe we alter the SliceIndex type to record
+	// two different indexes.
+
+	// TODO: Perhaps we should coalesce differences on primitive kinds
+	// together if the number of differences exceeds some ratio.
+	// For example, comparing two SHA256s leads to many byte differences.
+
+	if eq {
+		// TODO: Maybe print some equal results for context?
+		return // Ignore equal results
+	}
+	const maxBytes = 4096
+	const maxLines = 256
+	r.ndiffs++
+	if r.nbytes < maxBytes && r.nlines < maxLines {
+		sx := prettyPrint(x, true)
+		sy := prettyPrint(y, true)
+		if sx == sy {
+			// Use of Stringer is not helpful, so rely on more exact formatting.
+			sx = prettyPrint(x, false)
+			sy = prettyPrint(y, false)
+		}
+		s := fmt.Sprintf("%#v:\n\t-: %s\n\t+: %s\n", p, sx, sy)
+		r.diffs = append(r.diffs, s)
+		r.nbytes += len(s)
+		r.nlines += strings.Count(s, "\n")
+	}
+}
+
+func (r *defaultReporter) String() string {
+	s := strings.Join(r.diffs, "")
+	if r.ndiffs == len(r.diffs) {
+		return s
+	}
+	return fmt.Sprintf("%s... %d more differences ...", s, len(r.diffs)-r.ndiffs)
+}
+
+var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
+
+func prettyPrint(v reflect.Value, useStringer bool) string {
+	return formatAny(v, formatConfig{useStringer, true, true, true}, nil)
+}
+
+type formatConfig struct {
+	useStringer    bool // Should the String method be used if available?
+	printType      bool // Should we print the type before the value?
+	followPointers bool // Should we recursively follow pointers?
+	realPointers   bool // Should we print the real address of pointers?
+}
+
+// formatAny prints the value v in a pretty formatted manner.
+// This is similar to fmt.Sprintf("%+v", v) except this:
+//	* Prints the type unless it can be elided.
+//	* Avoids printing struct fields that are zero.
+//	* Prints a nil-slice as being nil, not empty.
+//	* Prints map entries in deterministic order.
+func formatAny(v reflect.Value, conf formatConfig, visited map[uintptr]bool) string {
+	// TODO: Should this be a multi-line printout in certain situations?
+
+	if !v.IsValid() {
+		return "<non-existent>"
+	}
+	if conf.useStringer && v.Type().Implements(stringerIface) {
+		if v.Kind() == reflect.Ptr && v.IsNil() {
+			return "<nil>"
+		}
+		return fmt.Sprintf("%q", v.Interface().(fmt.Stringer).String())
+	}
+
+	switch v.Kind() {
+	case reflect.Bool:
+		return fmt.Sprint(v.Bool())
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return fmt.Sprint(v.Int())
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		if v.Type().PkgPath() == "" || v.Kind() == reflect.Uintptr {
+			return formatHex(v.Uint()) // Unnamed uints are usually bytes or words
+		}
+		return fmt.Sprint(v.Uint()) // Named uints are usually enumerations
+	case reflect.Float32, reflect.Float64:
+		return fmt.Sprint(v.Float())
+	case reflect.Complex64, reflect.Complex128:
+		return fmt.Sprint(v.Complex())
+	case reflect.String:
+		return fmt.Sprintf("%q", v)
+	case reflect.UnsafePointer, reflect.Chan, reflect.Func:
+		return formatPointer(v, conf)
+	case reflect.Ptr:
+		if v.IsNil() {
+			if conf.printType {
+				return fmt.Sprintf("(%v)(nil)", v.Type())
+			}
+			return "<nil>"
+		}
+		if visited[v.Pointer()] || !conf.followPointers {
+			return formatPointer(v, conf)
+		}
+		visited = insertPointer(visited, v.Pointer())
+		return "&" + formatAny(v.Elem(), conf, visited)
+	case reflect.Interface:
+		if v.IsNil() {
+			if conf.printType {
+				return fmt.Sprintf("%v(nil)", v.Type())
+			}
+			return "<nil>"
+		}
+		return formatAny(v.Elem(), conf, visited)
+	case reflect.Slice:
+		if v.IsNil() {
+			if conf.printType {
+				return fmt.Sprintf("%v(nil)", v.Type())
+			}
+			return "<nil>"
+		}
+		if visited[v.Pointer()] {
+			return formatPointer(v, conf)
+		}
+		visited = insertPointer(visited, v.Pointer())
+		fallthrough
+	case reflect.Array:
+		var ss []string
+		subConf := conf
+		subConf.printType = v.Type().Elem().Kind() == reflect.Interface
+		for i := 0; i < v.Len(); i++ {
+			s := formatAny(v.Index(i), subConf, visited)
+			ss = append(ss, s)
+		}
+		s := "{" + strings.Join(ss, ", ") + "}"
+		if conf.printType {
+			return v.Type().String() + s
+		}
+		return s
+	case reflect.Map:
+		if v.IsNil() {
+			if conf.printType {
+				return fmt.Sprintf("%v(nil)", v.Type())
+			}
+			return "<nil>"
+		}
+		if visited[v.Pointer()] {
+			return formatPointer(v, conf)
+		}
+		visited = insertPointer(visited, v.Pointer())
+
+		var ss []string
+		subConf := conf
+		subConf.printType = v.Type().Elem().Kind() == reflect.Interface
+		for _, k := range sortKeys(v.MapKeys()) {
+			sk := formatAny(k, formatConfig{realPointers: conf.realPointers}, visited)
+			sv := formatAny(v.MapIndex(k), subConf, visited)
+			ss = append(ss, fmt.Sprintf("%s: %s", sk, sv))
+		}
+		s := "{" + strings.Join(ss, ", ") + "}"
+		if conf.printType {
+			return v.Type().String() + s
+		}
+		return s
+	case reflect.Struct:
+		var ss []string
+		subConf := conf
+		subConf.printType = true
+		for i := 0; i < v.NumField(); i++ {
+			vv := v.Field(i)
+			if isZero(vv) {
+				continue // Elide zero value fields
+			}
+			name := v.Type().Field(i).Name
+			subConf.useStringer = conf.useStringer && isExported(name)
+			s := formatAny(vv, subConf, visited)
+			ss = append(ss, fmt.Sprintf("%s: %s", name, s))
+		}
+		s := "{" + strings.Join(ss, ", ") + "}"
+		if conf.printType {
+			return v.Type().String() + s
+		}
+		return s
+	default:
+		panic(fmt.Sprintf("%v kind not handled", v.Kind()))
+	}
+}
+
+func formatPointer(v reflect.Value, conf formatConfig) string {
+	p := v.Pointer()
+	if !conf.realPointers {
+		p = 0 // For deterministic printing purposes
+	}
+	s := formatHex(uint64(p))
+	if conf.printType {
+		return fmt.Sprintf("(%v)(%s)", v.Type(), s)
+	}
+	return s
+}
+
+func formatHex(u uint64) string {
+	var f string
+	switch {
+	case u <= 0xff:
+		f = "0x%02x"
+	case u <= 0xffff:
+		f = "0x%04x"
+	case u <= 0xffffff:
+		f = "0x%06x"
+	case u <= 0xffffffff:
+		f = "0x%08x"
+	case u <= 0xffffffffff:
+		f = "0x%010x"
+	case u <= 0xffffffffffff:
+		f = "0x%012x"
+	case u <= 0xffffffffffffff:
+		f = "0x%014x"
+	case u <= 0xffffffffffffffff:
+		f = "0x%016x"
+	}
+	return fmt.Sprintf(f, u)
+}
+
+// insertPointer insert p into m, allocating m if necessary.
+func insertPointer(m map[uintptr]bool, p uintptr) map[uintptr]bool {
+	if m == nil {
+		m = make(map[uintptr]bool)
+	}
+	m[p] = true
+	return m
+}
+
+// isZero reports whether v is the zero value.
+// This does not rely on Interface and so can be used on unexported fields.
+func isZero(v reflect.Value) bool {
+	switch v.Kind() {
+	case reflect.Bool:
+		return v.Bool() == false
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return v.Int() == 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return v.Uint() == 0
+	case reflect.Float32, reflect.Float64:
+		return v.Float() == 0
+	case reflect.Complex64, reflect.Complex128:
+		return v.Complex() == 0
+	case reflect.String:
+		return v.String() == ""
+	case reflect.UnsafePointer:
+		return v.Pointer() == 0
+	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
+		return v.IsNil()
+	case reflect.Array:
+		for i := 0; i < v.Len(); i++ {
+			if !isZero(v.Index(i)) {
+				return false
+			}
+		}
+		return true
+	case reflect.Struct:
+		for i := 0; i < v.NumField(); i++ {
+			if !isZero(v.Field(i)) {
+				return false
+			}
+		}
+		return true
+	}
+	return false
+}
+
+// isLess is a generic function for sorting arbitrary map keys.
+// The inputs must be of the same type and must be comparable.
+func isLess(x, y reflect.Value) bool {
+	switch x.Type().Kind() {
+	case reflect.Bool:
+		return !x.Bool() && y.Bool()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return x.Int() < y.Int()
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return x.Uint() < y.Uint()
+	case reflect.Float32, reflect.Float64:
+		fx, fy := x.Float(), y.Float()
+		return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy)
+	case reflect.Complex64, reflect.Complex128:
+		cx, cy := x.Complex(), y.Complex()
+		rx, ix, ry, iy := real(cx), imag(cx), real(cy), imag(cy)
+		if rx == ry || (math.IsNaN(rx) && math.IsNaN(ry)) {
+			return ix < iy || math.IsNaN(ix) && !math.IsNaN(iy)
+		}
+		return rx < ry || math.IsNaN(rx) && !math.IsNaN(ry)
+	case reflect.Ptr, reflect.UnsafePointer, reflect.Chan:
+		return x.Pointer() < y.Pointer()
+	case reflect.String:
+		return x.String() < y.String()
+	case reflect.Array:
+		for i := 0; i < x.Len(); i++ {
+			if isLess(x.Index(i), y.Index(i)) {
+				return true
+			}
+			if isLess(y.Index(i), x.Index(i)) {
+				return false
+			}
+		}
+		return false
+	case reflect.Struct:
+		for i := 0; i < x.NumField(); i++ {
+			if isLess(x.Field(i), y.Field(i)) {
+				return true
+			}
+			if isLess(y.Field(i), x.Field(i)) {
+				return false
+			}
+		}
+		return false
+	case reflect.Interface:
+		vx, vy := x.Elem(), y.Elem()
+		if !vx.IsValid() || !vy.IsValid() {
+			return !vx.IsValid() && vy.IsValid()
+		}
+		tx, ty := vx.Type(), vy.Type()
+		if tx == ty {
+			return isLess(x.Elem(), y.Elem())
+		}
+		if tx.Kind() != ty.Kind() {
+			return vx.Kind() < vy.Kind()
+		}
+		if tx.String() != ty.String() {
+			return tx.String() < ty.String()
+		}
+		if tx.PkgPath() != ty.PkgPath() {
+			return tx.PkgPath() < ty.PkgPath()
+		}
+		// This can happen in rare situations, so we fallback to just comparing
+		// the unique pointer for a reflect.Type. This guarantees deterministic
+		// ordering within a program, but it is obviously not stable.
+		return reflect.ValueOf(vx.Type()).Pointer() < reflect.ValueOf(vy.Type()).Pointer()
+	default:
+		// Must be Func, Map, or Slice; which are not comparable.
+		panic(fmt.Sprintf("%T is not comparable", x.Type()))
+	}
+}
+
+// sortKey sorts a list of map keys, deduplicating keys if necessary.
+func sortKeys(vs []reflect.Value) []reflect.Value {
+	if len(vs) == 0 {
+		return vs
+	}
+
+	// Sort the map keys
+	sort.Slice(vs, func(i, j int) bool { return isLess(vs[i], vs[j]) })
+
+	// Deduplicate keys (fails for NaNs).
+	vs2 := vs[:1]
+	for _, v := range vs[1:] {
+		if v.Interface() != vs2[len(vs2)-1].Interface() {
+			vs2 = append(vs2, v)
+		}
+	}
+	return vs2
+}
diff --git a/cmp/reporter_test.go b/cmp/reporter_test.go
new file mode 100644
index 0000000..7310077
--- /dev/null
+++ b/cmp/reporter_test.go
@@ -0,0 +1,227 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package cmp
+
+import (
+	"bytes"
+	"io"
+	"math"
+	"reflect"
+	"testing"
+)
+
+func TestFormatAny(t *testing.T) {
+	type key struct {
+		a int
+		b string
+		c chan bool
+	}
+
+	tests := []struct {
+		in   interface{}
+		want string
+	}{{
+		in:   []int{},
+		want: "[]int{}",
+	}, {
+		in:   []int(nil),
+		want: "[]int(nil)",
+	}, {
+		in:   []int{1, 2, 3, 4, 5},
+		want: "[]int{1, 2, 3, 4, 5}",
+	}, {
+		in:   []interface{}{1, true, "hello", struct{ A, B int }{1, 2}},
+		want: "[]interface {}{1, true, \"hello\", struct { A int; B int }{A: 1, B: 2}}",
+	}, {
+		in:   []struct{ A, B int }{{1, 2}, {0, 4}, {}},
+		want: "[]struct { A int; B int }{{A: 1, B: 2}, {B: 4}, {}}",
+	}, {
+		in:   map[*int]string{new(int): "hello"},
+		want: "map[*int]string{0x00: \"hello\"}",
+	}, {
+		in:   map[key]string{{}: "hello"},
+		want: "map[cmp.key]string{{}: \"hello\"}",
+	}, {
+		in:   map[key]string{{a: 5, b: "key", c: make(chan bool)}: "hello"},
+		want: "map[cmp.key]string{{a: 5, b: \"key\", c: (chan bool)(0x00)}: \"hello\"}",
+	}, {
+		in:   map[io.Reader]string{new(bytes.Reader): "hello"},
+		want: "map[io.Reader]string{0x00: \"hello\"}",
+	}, {
+		in: func() interface{} {
+			var a = []interface{}{nil}
+			a[0] = a
+			return a
+		}(),
+		want: "[]interface {}{([]interface {})(0x00)}",
+	}, {
+		in: func() interface{} {
+			type A *A
+			var a A
+			a = &a
+			return a
+		}(),
+		want: "&(cmp.A)(0x00)",
+	}, {
+		in: func() interface{} {
+			type A map[*A]A
+			a := make(A)
+			a[&a] = a
+			return a
+		}(),
+		want: "cmp.A{0x00: 0x00}",
+	}, {
+		in: func() interface{} {
+			var a [2]interface{}
+			a[0] = &a
+			return a
+		}(),
+		want: "[2]interface {}{&[2]interface {}{(*[2]interface {})(0x00), interface {}(nil)}, interface {}(nil)}",
+	}}
+
+	for i, tt := range tests {
+		got := formatAny(reflect.ValueOf(tt.in), formatConfig{true, true, true, false}, nil)
+		if got != tt.want {
+			t.Errorf("test %d, pretty print:\ngot  %q\nwant %q", i, got, tt.want)
+		}
+	}
+}
+
+func TestSortKeys(t *testing.T) {
+	type (
+		MyString string
+		MyArray  [2]int
+		MyStruct struct {
+			A MyString
+			B MyArray
+			C chan float64
+		}
+		EmptyStruct struct{}
+	)
+
+	opts := []Option{
+		Comparer(func(x, y float64) bool {
+			if math.IsNaN(x) && math.IsNaN(y) {
+				return true
+			}
+			return x == y
+		}),
+		Comparer(func(x, y complex128) bool {
+			rx, ix, ry, iy := real(x), imag(x), real(y), imag(y)
+			if math.IsNaN(rx) && math.IsNaN(ry) {
+				rx, ry = 0, 0
+			}
+			if math.IsNaN(ix) && math.IsNaN(iy) {
+				ix, iy = 0, 0
+			}
+			return rx == ry && ix == iy
+		}),
+		Comparer(func(x, y chan bool) bool { return true }),
+		Comparer(func(x, y chan int) bool { return true }),
+		Comparer(func(x, y chan float64) bool { return true }),
+		Comparer(func(x, y chan interface{}) bool { return true }),
+		Comparer(func(x, y *int) bool { return true }),
+	}
+
+	tests := []struct {
+		in   map[interface{}]bool // Set of keys to sort
+		want []interface{}
+	}{{
+		in:   map[interface{}]bool{1: true, 2: true, 3: true},
+		want: []interface{}{1, 2, 3},
+	}, {
+		in: map[interface{}]bool{
+			nil:                    true,
+			true:                   true,
+			false:                  true,
+			-5:                     true,
+			-55:                    true,
+			-555:                   true,
+			uint(1):                true,
+			uint(11):               true,
+			uint(111):              true,
+			"abc":                  true,
+			"abcd":                 true,
+			"abcde":                true,
+			"foo":                  true,
+			"bar":                  true,
+			MyString("abc"):        true,
+			MyString("abcd"):       true,
+			MyString("abcde"):      true,
+			new(int):               true,
+			new(int):               true,
+			make(chan bool):        true,
+			make(chan bool):        true,
+			make(chan int):         true,
+			make(chan interface{}): true,
+			math.Inf(+1):           true,
+			math.Inf(-1):           true,
+			1.2345:                 true,
+			12.345:                 true,
+			123.45:                 true,
+			1234.5:                 true,
+			0 + 0i:                 true,
+			1 + 0i:                 true,
+			2 + 0i:                 true,
+			0 + 1i:                 true,
+			0 + 2i:                 true,
+			0 + 3i:                 true,
+			[2]int{2, 3}:           true,
+			[2]int{4, 0}:           true,
+			[2]int{2, 4}:           true,
+			MyArray([2]int{2, 4}):  true,
+			EmptyStruct{}:          true,
+			MyStruct{
+				"bravo", [2]int{2, 3}, make(chan float64),
+			}: true,
+			MyStruct{
+				"alpha", [2]int{3, 3}, make(chan float64),
+			}: true,
+		},
+		want: []interface{}{
+			nil, false, true,
+			-555, -55, -5, uint(1), uint(11), uint(111),
+			math.Inf(-1), 1.2345, 12.345, 123.45, 1234.5, math.Inf(+1),
+			(0 + 0i), (0 + 1i), (0 + 2i), (0 + 3i), (1 + 0i), (2 + 0i),
+			[2]int{2, 3}, [2]int{2, 4}, [2]int{4, 0}, MyArray([2]int{2, 4}),
+			make(chan bool), make(chan bool), make(chan int), make(chan interface{}),
+			new(int), new(int),
+			MyString("abc"), MyString("abcd"), MyString("abcde"), "abc", "abcd", "abcde", "bar", "foo",
+			EmptyStruct{},
+			MyStruct{"alpha", [2]int{3, 3}, make(chan float64)},
+			MyStruct{"bravo", [2]int{2, 3}, make(chan float64)},
+		},
+	}, {
+		// NaN values cannot be properly deduplicated.
+		// This is okay since map entries with NaN in the keys cannot be
+		// retrieved anyways.
+		in: map[interface{}]bool{
+			math.NaN():                      true,
+			math.NaN():                      true,
+			complex(0, math.NaN()):          true,
+			complex(0, math.NaN()):          true,
+			complex(math.NaN(), 0):          true,
+			complex(math.NaN(), 0):          true,
+			complex(math.NaN(), math.NaN()): true,
+		},
+		want: []interface{}{
+			math.NaN(), math.NaN(), math.NaN(), math.NaN(),
+			complex(math.NaN(), math.NaN()), complex(math.NaN(), math.NaN()),
+			complex(math.NaN(), 0), complex(math.NaN(), 0), complex(math.NaN(), 0), complex(math.NaN(), 0),
+			complex(0, math.NaN()), complex(0, math.NaN()), complex(0, math.NaN()), complex(0, math.NaN()),
+		},
+	}}
+
+	for i, tt := range tests {
+		keys := append(reflect.ValueOf(tt.in).MapKeys(), reflect.ValueOf(tt.in).MapKeys()...)
+		var got []interface{}
+		for _, k := range sortKeys(keys) {
+			got = append(got, k.Interface())
+		}
+		if !Equal(got, tt.want, opts...) {
+			t.Errorf("test %d, output mismatch:\ngot  %#v\nwant %#v", i, got, tt.want)
+		}
+	}
+}
diff --git a/cmp/unsafe_panic.go b/cmp/unsafe_panic.go
new file mode 100644
index 0000000..79e8aaa
--- /dev/null
+++ b/cmp/unsafe_panic.go
@@ -0,0 +1,13 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+// +build appengine js
+
+package cmp
+
+import "reflect"
+
+func unsafeRetrieveField(reflect.Value, reflect.StructField) reflect.Value {
+	panic("unsafeRetrieveField is not implemented on appengine or gopherjs")
+}
diff --git a/cmp/unsafe_reflect.go b/cmp/unsafe_reflect.go
new file mode 100644
index 0000000..4b04bca
--- /dev/null
+++ b/cmp/unsafe_reflect.go
@@ -0,0 +1,21 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+// +build !appengine,!js
+
+package cmp
+
+import (
+	"reflect"
+	"unsafe"
+)
+
+// unsafeRetrieveField uses unsafe to forcibly retrieve any field from a struct
+// such that the value has read-write permissions.
+//
+// The parent struct, v, must be addressable, while f must be a StructField
+// describing the field to retrieve.
+func unsafeRetrieveField(v reflect.Value, f reflect.StructField) reflect.Value {
+	return reflect.NewAt(f.Type, unsafe.Pointer(v.UnsafeAddr()+f.Offset)).Elem()
+}