aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/google/go-cmp/cmp/cmpopts
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2018-06-18 19:45:37 +0200
committerDmitry Vyukov <dvyukov@google.com>2018-06-18 19:45:37 +0200
commite79c9d2b4369454ed5a020b3e2dc6ae536afd815 (patch)
treeefff0cc9e959f0f10f89b85889d27bf9795c4bfd /vendor/github.com/google/go-cmp/cmp/cmpopts
parent27c5f59f504f562333e3cd5e715fea5cb69c396e (diff)
vendor: add github.com/google/go-cmp/cmp
This package simplifies diffing complex objects in tests.
Diffstat (limited to 'vendor/github.com/google/go-cmp/cmp/cmpopts')
-rw-r--r--vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go89
-rw-r--r--vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go148
-rw-r--r--vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go146
-rw-r--r--vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go46
-rw-r--r--vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go31
-rw-r--r--vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go182
6 files changed, 642 insertions, 0 deletions
diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go
new file mode 100644
index 000000000..cc39492cf
--- /dev/null
+++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go
@@ -0,0 +1,89 @@
+// 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 cmpopts provides common options for the cmp package.
+package cmpopts
+
+import (
+ "math"
+ "reflect"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+func equateAlways(_, _ interface{}) bool { return true }
+
+// EquateEmpty returns a Comparer option that determines all maps and slices
+// with a length of zero to be equal, regardless of whether they are nil.
+//
+// EquateEmpty can be used in conjuction with SortSlices and SortMaps.
+func EquateEmpty() cmp.Option {
+ return cmp.FilterValues(isEmpty, cmp.Comparer(equateAlways))
+}
+
+func isEmpty(x, y interface{}) bool {
+ vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
+ return (x != nil && y != nil && vx.Type() == vy.Type()) &&
+ (vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) &&
+ (vx.Len() == 0 && vy.Len() == 0)
+}
+
+// EquateApprox returns a Comparer option that determines float32 or float64
+// values to be equal if they are within a relative fraction or absolute margin.
+// This option is not used when either x or y is NaN or infinite.
+//
+// The fraction determines that the difference of two values must be within the
+// smaller fraction of the two values, while the margin determines that the two
+// values must be within some absolute margin.
+// To express only a fraction or only a margin, use 0 for the other parameter.
+// The fraction and margin must be non-negative.
+//
+// The mathematical expression used is equivalent to:
+// |x-y| ≤ max(fraction*min(|x|, |y|), margin)
+//
+// EquateApprox can be used in conjuction with EquateNaNs.
+func EquateApprox(fraction, margin float64) cmp.Option {
+ if margin < 0 || fraction < 0 || math.IsNaN(margin) || math.IsNaN(fraction) {
+ panic("margin or fraction must be a non-negative number")
+ }
+ a := approximator{fraction, margin}
+ return cmp.Options{
+ cmp.FilterValues(areRealF64s, cmp.Comparer(a.compareF64)),
+ cmp.FilterValues(areRealF32s, cmp.Comparer(a.compareF32)),
+ }
+}
+
+type approximator struct{ frac, marg float64 }
+
+func areRealF64s(x, y float64) bool {
+ return !math.IsNaN(x) && !math.IsNaN(y) && !math.IsInf(x, 0) && !math.IsInf(y, 0)
+}
+func areRealF32s(x, y float32) bool {
+ return areRealF64s(float64(x), float64(y))
+}
+func (a approximator) compareF64(x, y float64) bool {
+ relMarg := a.frac * math.Min(math.Abs(x), math.Abs(y))
+ return math.Abs(x-y) <= math.Max(a.marg, relMarg)
+}
+func (a approximator) compareF32(x, y float32) bool {
+ return a.compareF64(float64(x), float64(y))
+}
+
+// EquateNaNs returns a Comparer option that determines float32 and float64
+// NaN values to be equal.
+//
+// EquateNaNs can be used in conjuction with EquateApprox.
+func EquateNaNs() cmp.Option {
+ return cmp.Options{
+ cmp.FilterValues(areNaNsF64s, cmp.Comparer(equateAlways)),
+ cmp.FilterValues(areNaNsF32s, cmp.Comparer(equateAlways)),
+ }
+}
+
+func areNaNsF64s(x, y float64) bool {
+ return math.IsNaN(x) && math.IsNaN(y)
+}
+func areNaNsF32s(x, y float32) bool {
+ return areNaNsF64s(float64(x), float64(y))
+}
diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go
new file mode 100644
index 000000000..016891da3
--- /dev/null
+++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go
@@ -0,0 +1,148 @@
+// 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 cmpopts
+
+import (
+ "fmt"
+ "reflect"
+ "unicode"
+ "unicode/utf8"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+// IgnoreFields returns an Option that ignores exported fields of the
+// given names on a single struct type.
+// The struct type is specified by passing in a value of that type.
+//
+// The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a
+// specific sub-field that is embedded or nested within the parent struct.
+//
+// This does not handle unexported fields; use IgnoreUnexported instead.
+func IgnoreFields(typ interface{}, names ...string) cmp.Option {
+ sf := newStructFilter(typ, names...)
+ return cmp.FilterPath(sf.filter, cmp.Ignore())
+}
+
+// IgnoreTypes returns an Option that ignores all values assignable to
+// certain types, which are specified by passing in a value of each type.
+func IgnoreTypes(typs ...interface{}) cmp.Option {
+ tf := newTypeFilter(typs...)
+ return cmp.FilterPath(tf.filter, cmp.Ignore())
+}
+
+type typeFilter []reflect.Type
+
+func newTypeFilter(typs ...interface{}) (tf typeFilter) {
+ for _, typ := range typs {
+ t := reflect.TypeOf(typ)
+ if t == nil {
+ // This occurs if someone tries to pass in sync.Locker(nil)
+ panic("cannot determine type; consider using IgnoreInterfaces")
+ }
+ tf = append(tf, t)
+ }
+ return tf
+}
+func (tf typeFilter) filter(p cmp.Path) bool {
+ if len(p) < 1 {
+ return false
+ }
+ t := p[len(p)-1].Type()
+ for _, ti := range tf {
+ if t.AssignableTo(ti) {
+ return true
+ }
+ }
+ return false
+}
+
+// IgnoreInterfaces returns an Option that ignores all values or references of
+// values assignable to certain interface types. These interfaces are specified
+// by passing in an anonymous struct with the interface types embedded in it.
+// For example, to ignore sync.Locker, pass in struct{sync.Locker}{}.
+func IgnoreInterfaces(ifaces interface{}) cmp.Option {
+ tf := newIfaceFilter(ifaces)
+ return cmp.FilterPath(tf.filter, cmp.Ignore())
+}
+
+type ifaceFilter []reflect.Type
+
+func newIfaceFilter(ifaces interface{}) (tf ifaceFilter) {
+ t := reflect.TypeOf(ifaces)
+ if ifaces == nil || t.Name() != "" || t.Kind() != reflect.Struct {
+ panic("input must be an anonymous struct")
+ }
+ for i := 0; i < t.NumField(); i++ {
+ fi := t.Field(i)
+ switch {
+ case !fi.Anonymous:
+ panic("struct cannot have named fields")
+ case fi.Type.Kind() != reflect.Interface:
+ panic("embedded field must be an interface type")
+ case fi.Type.NumMethod() == 0:
+ // This matches everything; why would you ever want this?
+ panic("cannot ignore empty interface")
+ default:
+ tf = append(tf, fi.Type)
+ }
+ }
+ return tf
+}
+func (tf ifaceFilter) filter(p cmp.Path) bool {
+ if len(p) < 1 {
+ return false
+ }
+ t := p[len(p)-1].Type()
+ for _, ti := range tf {
+ if t.AssignableTo(ti) {
+ return true
+ }
+ if t.Kind() != reflect.Ptr && reflect.PtrTo(t).AssignableTo(ti) {
+ return true
+ }
+ }
+ return false
+}
+
+// IgnoreUnexported returns an Option that only ignores the immediate unexported
+// fields of a struct, including anonymous fields of unexported types.
+// In particular, unexported fields within the struct's exported fields
+// of struct types, including anonymous fields, will not be ignored unless the
+// type of the field itself is also passed to IgnoreUnexported.
+func IgnoreUnexported(typs ...interface{}) cmp.Option {
+ ux := newUnexportedFilter(typs...)
+ return cmp.FilterPath(ux.filter, cmp.Ignore())
+}
+
+type unexportedFilter struct{ m map[reflect.Type]bool }
+
+func newUnexportedFilter(typs ...interface{}) unexportedFilter {
+ ux := unexportedFilter{m: make(map[reflect.Type]bool)}
+ for _, typ := range typs {
+ t := reflect.TypeOf(typ)
+ if t == nil || t.Kind() != reflect.Struct {
+ panic(fmt.Sprintf("invalid struct type: %T", typ))
+ }
+ ux.m[t] = true
+ }
+ return ux
+}
+func (xf unexportedFilter) filter(p cmp.Path) bool {
+ if len(p) < 2 {
+ return false
+ }
+ sf, ok := p[len(p)-1].(cmp.StructField)
+ if !ok {
+ return false
+ }
+ return xf.m[p[len(p)-2].Type()] && !isExported(sf.Name())
+}
+
+// isExported reports whether the identifier is exported.
+func isExported(id string) bool {
+ r, _ := utf8.DecodeRuneInString(id)
+ return unicode.IsUpper(r)
+}
diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go
new file mode 100644
index 000000000..a566d240b
--- /dev/null
+++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go
@@ -0,0 +1,146 @@
+// 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 cmpopts
+
+import (
+ "fmt"
+ "reflect"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/internal/function"
+)
+
+// SortSlices returns a Transformer option that sorts all []V.
+// The less function must be of the form "func(T, T) bool" which is used to
+// sort any slice with element type V that is assignable to T.
+//
+// The less function must be:
+// • Deterministic: less(x, y) == less(x, y)
+// • Irreflexive: !less(x, x)
+// • Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
+//
+// The less function does not have to be "total". That is, if !less(x, y) and
+// !less(y, x) for two elements x and y, their relative order is maintained.
+//
+// SortSlices can be used in conjuction with EquateEmpty.
+func SortSlices(less interface{}) cmp.Option {
+ vf := reflect.ValueOf(less)
+ if !function.IsType(vf.Type(), function.Less) || vf.IsNil() {
+ panic(fmt.Sprintf("invalid less function: %T", less))
+ }
+ ss := sliceSorter{vf.Type().In(0), vf}
+ return cmp.FilterValues(ss.filter, cmp.Transformer("Sort", ss.sort))
+}
+
+type sliceSorter struct {
+ in reflect.Type // T
+ fnc reflect.Value // func(T, T) bool
+}
+
+func (ss sliceSorter) filter(x, y interface{}) bool {
+ vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
+ if !(x != nil && y != nil && vx.Type() == vy.Type()) ||
+ !(vx.Kind() == reflect.Slice && vx.Type().Elem().AssignableTo(ss.in)) ||
+ (vx.Len() <= 1 && vy.Len() <= 1) {
+ return false
+ }
+ // Check whether the slices are already sorted to avoid an infinite
+ // recursion cycle applying the same transform to itself.
+ ok1 := sliceIsSorted(x, func(i, j int) bool { return ss.less(vx, i, j) })
+ ok2 := sliceIsSorted(y, func(i, j int) bool { return ss.less(vy, i, j) })
+ return !ok1 || !ok2
+}
+func (ss sliceSorter) sort(x interface{}) interface{} {
+ src := reflect.ValueOf(x)
+ dst := reflect.MakeSlice(src.Type(), src.Len(), src.Len())
+ for i := 0; i < src.Len(); i++ {
+ dst.Index(i).Set(src.Index(i))
+ }
+ sortSliceStable(dst.Interface(), func(i, j int) bool { return ss.less(dst, i, j) })
+ ss.checkSort(dst)
+ return dst.Interface()
+}
+func (ss sliceSorter) checkSort(v reflect.Value) {
+ start := -1 // Start of a sequence of equal elements.
+ for i := 1; i < v.Len(); i++ {
+ if ss.less(v, i-1, i) {
+ // Check that first and last elements in v[start:i] are equal.
+ if start >= 0 && (ss.less(v, start, i-1) || ss.less(v, i-1, start)) {
+ panic(fmt.Sprintf("incomparable values detected: want equal elements: %v", v.Slice(start, i)))
+ }
+ start = -1
+ } else if start == -1 {
+ start = i
+ }
+ }
+}
+func (ss sliceSorter) less(v reflect.Value, i, j int) bool {
+ vx, vy := v.Index(i), v.Index(j)
+ return ss.fnc.Call([]reflect.Value{vx, vy})[0].Bool()
+}
+
+// SortMaps returns a Transformer option that flattens map[K]V types to be a
+// sorted []struct{K, V}. The less function must be of the form
+// "func(T, T) bool" which is used to sort any map with key K that is
+// assignable to T.
+//
+// Flattening the map into a slice has the property that cmp.Equal is able to
+// use Comparers on K or the K.Equal method if it exists.
+//
+// The less function must be:
+// • Deterministic: less(x, y) == less(x, y)
+// • Irreflexive: !less(x, x)
+// • Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
+// • Total: if x != y, then either less(x, y) or less(y, x)
+//
+// SortMaps can be used in conjuction with EquateEmpty.
+func SortMaps(less interface{}) cmp.Option {
+ vf := reflect.ValueOf(less)
+ if !function.IsType(vf.Type(), function.Less) || vf.IsNil() {
+ panic(fmt.Sprintf("invalid less function: %T", less))
+ }
+ ms := mapSorter{vf.Type().In(0), vf}
+ return cmp.FilterValues(ms.filter, cmp.Transformer("Sort", ms.sort))
+}
+
+type mapSorter struct {
+ in reflect.Type // T
+ fnc reflect.Value // func(T, T) bool
+}
+
+func (ms mapSorter) filter(x, y interface{}) bool {
+ vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
+ return (x != nil && y != nil && vx.Type() == vy.Type()) &&
+ (vx.Kind() == reflect.Map && vx.Type().Key().AssignableTo(ms.in)) &&
+ (vx.Len() != 0 || vy.Len() != 0)
+}
+func (ms mapSorter) sort(x interface{}) interface{} {
+ src := reflect.ValueOf(x)
+ outType := mapEntryType(src.Type())
+ dst := reflect.MakeSlice(reflect.SliceOf(outType), src.Len(), src.Len())
+ for i, k := range src.MapKeys() {
+ v := reflect.New(outType).Elem()
+ v.Field(0).Set(k)
+ v.Field(1).Set(src.MapIndex(k))
+ dst.Index(i).Set(v)
+ }
+ sortSlice(dst.Interface(), func(i, j int) bool { return ms.less(dst, i, j) })
+ ms.checkSort(dst)
+ return dst.Interface()
+}
+func (ms mapSorter) checkSort(v reflect.Value) {
+ for i := 1; i < v.Len(); i++ {
+ if !ms.less(v, i-1, i) {
+ panic(fmt.Sprintf("partial order detected: want %v < %v", v.Index(i-1), v.Index(i)))
+ }
+ }
+}
+func (ms mapSorter) less(v reflect.Value, i, j int) bool {
+ vx, vy := v.Index(i).Field(0), v.Index(j).Field(0)
+ if !hasReflectStructOf {
+ vx, vy = vx.Elem(), vy.Elem()
+ }
+ return ms.fnc.Call([]reflect.Value{vx, vy})[0].Bool()
+}
diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go
new file mode 100644
index 000000000..839b88ca4
--- /dev/null
+++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go
@@ -0,0 +1,46 @@
+// 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 !go1.8
+
+package cmpopts
+
+import (
+ "reflect"
+ "sort"
+)
+
+const hasReflectStructOf = false
+
+func mapEntryType(reflect.Type) reflect.Type {
+ return reflect.TypeOf(struct{ K, V interface{} }{})
+}
+
+func sliceIsSorted(slice interface{}, less func(i, j int) bool) bool {
+ return sort.IsSorted(reflectSliceSorter{reflect.ValueOf(slice), less})
+}
+func sortSlice(slice interface{}, less func(i, j int) bool) {
+ sort.Sort(reflectSliceSorter{reflect.ValueOf(slice), less})
+}
+func sortSliceStable(slice interface{}, less func(i, j int) bool) {
+ sort.Stable(reflectSliceSorter{reflect.ValueOf(slice), less})
+}
+
+type reflectSliceSorter struct {
+ slice reflect.Value
+ less func(i, j int) bool
+}
+
+func (ss reflectSliceSorter) Len() int {
+ return ss.slice.Len()
+}
+func (ss reflectSliceSorter) Less(i, j int) bool {
+ return ss.less(i, j)
+}
+func (ss reflectSliceSorter) Swap(i, j int) {
+ vi := ss.slice.Index(i).Interface()
+ vj := ss.slice.Index(j).Interface()
+ ss.slice.Index(i).Set(reflect.ValueOf(vj))
+ ss.slice.Index(j).Set(reflect.ValueOf(vi))
+}
diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go
new file mode 100644
index 000000000..8a59c0d38
--- /dev/null
+++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go
@@ -0,0 +1,31 @@
+// 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 go1.8
+
+package cmpopts
+
+import (
+ "reflect"
+ "sort"
+)
+
+const hasReflectStructOf = true
+
+func mapEntryType(t reflect.Type) reflect.Type {
+ return reflect.StructOf([]reflect.StructField{
+ {Name: "K", Type: t.Key()},
+ {Name: "V", Type: t.Elem()},
+ })
+}
+
+func sliceIsSorted(slice interface{}, less func(i, j int) bool) bool {
+ return sort.SliceIsSorted(slice, less)
+}
+func sortSlice(slice interface{}, less func(i, j int) bool) {
+ sort.Slice(slice, less)
+}
+func sortSliceStable(slice interface{}, less func(i, j int) bool) {
+ sort.SliceStable(slice, less)
+}
diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go
new file mode 100644
index 000000000..97f707983
--- /dev/null
+++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go
@@ -0,0 +1,182 @@
+// 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 cmpopts
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+// filterField returns a new Option where opt is only evaluated on paths that
+// include a specific exported field on a single struct type.
+// The struct type is specified by passing in a value of that type.
+//
+// The name may be a dot-delimited string (e.g., "Foo.Bar") to select a
+// specific sub-field that is embedded or nested within the parent struct.
+func filterField(typ interface{}, name string, opt cmp.Option) cmp.Option {
+ // TODO: This is currently unexported over concerns of how helper filters
+ // can be composed together easily.
+ // TODO: Add tests for FilterField.
+
+ sf := newStructFilter(typ, name)
+ return cmp.FilterPath(sf.filter, opt)
+}
+
+type structFilter struct {
+ t reflect.Type // The root struct type to match on
+ ft fieldTree // Tree of fields to match on
+}
+
+func newStructFilter(typ interface{}, names ...string) structFilter {
+ // TODO: Perhaps allow * as a special identifier to allow ignoring any
+ // number of path steps until the next field match?
+ // This could be useful when a concrete struct gets transformed into
+ // an anonymous struct where it is not possible to specify that by type,
+ // but the transformer happens to provide guarantees about the names of
+ // the transformed fields.
+
+ t := reflect.TypeOf(typ)
+ if t == nil || t.Kind() != reflect.Struct {
+ panic(fmt.Sprintf("%T must be a struct", typ))
+ }
+ var ft fieldTree
+ for _, name := range names {
+ cname, err := canonicalName(t, name)
+ if err != nil {
+ panic(fmt.Sprintf("%s: %v", strings.Join(cname, "."), err))
+ }
+ ft.insert(cname)
+ }
+ return structFilter{t, ft}
+}
+
+func (sf structFilter) filter(p cmp.Path) bool {
+ for i, ps := range p {
+ if ps.Type().AssignableTo(sf.t) && sf.ft.matchPrefix(p[i+1:]) {
+ return true
+ }
+ }
+ return false
+}
+
+// fieldTree represents a set of dot-separated identifiers.
+//
+// For example, inserting the following selectors:
+// Foo
+// Foo.Bar.Baz
+// Foo.Buzz
+// Nuka.Cola.Quantum
+//
+// Results in a tree of the form:
+// {sub: {
+// "Foo": {ok: true, sub: {
+// "Bar": {sub: {
+// "Baz": {ok: true},
+// }},
+// "Buzz": {ok: true},
+// }},
+// "Nuka": {sub: {
+// "Cola": {sub: {
+// "Quantum": {ok: true},
+// }},
+// }},
+// }}
+type fieldTree struct {
+ ok bool // Whether this is a specified node
+ sub map[string]fieldTree // The sub-tree of fields under this node
+}
+
+// insert inserts a sequence of field accesses into the tree.
+func (ft *fieldTree) insert(cname []string) {
+ if ft.sub == nil {
+ ft.sub = make(map[string]fieldTree)
+ }
+ if len(cname) == 0 {
+ ft.ok = true
+ return
+ }
+ sub := ft.sub[cname[0]]
+ sub.insert(cname[1:])
+ ft.sub[cname[0]] = sub
+}
+
+// matchPrefix reports whether any selector in the fieldTree matches
+// the start of path p.
+func (ft fieldTree) matchPrefix(p cmp.Path) bool {
+ for _, ps := range p {
+ switch ps := ps.(type) {
+ case cmp.StructField:
+ ft = ft.sub[ps.Name()]
+ if ft.ok {
+ return true
+ }
+ if len(ft.sub) == 0 {
+ return false
+ }
+ case cmp.Indirect:
+ default:
+ return false
+ }
+ }
+ return false
+}
+
+// canonicalName returns a list of identifiers where any struct field access
+// through an embedded field is expanded to include the names of the embedded
+// types themselves.
+//
+// For example, suppose field "Foo" is not directly in the parent struct,
+// but actually from an embedded struct of type "Bar". Then, the canonical name
+// of "Foo" is actually "Bar.Foo".
+//
+// Suppose field "Foo" is not directly in the parent struct, but actually
+// a field in two different embedded structs of types "Bar" and "Baz".
+// Then the selector "Foo" causes a panic since it is ambiguous which one it
+// refers to. The user must specify either "Bar.Foo" or "Baz.Foo".
+func canonicalName(t reflect.Type, sel string) ([]string, error) {
+ var name string
+ sel = strings.TrimPrefix(sel, ".")
+ if sel == "" {
+ return nil, fmt.Errorf("name must not be empty")
+ }
+ if i := strings.IndexByte(sel, '.'); i < 0 {
+ name, sel = sel, ""
+ } else {
+ name, sel = sel[:i], sel[i:]
+ }
+
+ // Type must be a struct or pointer to struct.
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+ if t.Kind() != reflect.Struct {
+ return nil, fmt.Errorf("%v must be a struct", t)
+ }
+
+ // Find the canonical name for this current field name.
+ // If the field exists in an embedded struct, then it will be expanded.
+ if !isExported(name) {
+ // Disallow unexported fields:
+ // * To discourage people from actually touching unexported fields
+ // * FieldByName is buggy (https://golang.org/issue/4876)
+ return []string{name}, fmt.Errorf("name must be exported")
+ }
+ sf, ok := t.FieldByName(name)
+ if !ok {
+ return []string{name}, fmt.Errorf("does not exist")
+ }
+ var ss []string
+ for i := range sf.Index {
+ ss = append(ss, t.FieldByIndex(sf.Index[:i+1]).Name)
+ }
+ if sel == "" {
+ return ss, nil
+ }
+ ssPost, err := canonicalName(sf.Type, sel)
+ return append(ss, ssPost...), err
+}