xref: /aosp_15_r20/external/go-cmp/cmp/cmpopts/ignore.go (revision 88d15eac089d7f20c739ff1001d56b91872b21a1)
1*88d15eacSSasha Smundak// Copyright 2017, The Go Authors. All rights reserved.
2*88d15eacSSasha Smundak// Use of this source code is governed by a BSD-style
3*88d15eacSSasha Smundak// license that can be found in the LICENSE file.
4*88d15eacSSasha Smundak
5*88d15eacSSasha Smundakpackage cmpopts
6*88d15eacSSasha Smundak
7*88d15eacSSasha Smundakimport (
8*88d15eacSSasha Smundak	"fmt"
9*88d15eacSSasha Smundak	"reflect"
10*88d15eacSSasha Smundak	"unicode"
11*88d15eacSSasha Smundak	"unicode/utf8"
12*88d15eacSSasha Smundak
13*88d15eacSSasha Smundak	"github.com/google/go-cmp/cmp"
14*88d15eacSSasha Smundak	"github.com/google/go-cmp/cmp/internal/function"
15*88d15eacSSasha Smundak)
16*88d15eacSSasha Smundak
17*88d15eacSSasha Smundak// IgnoreFields returns an Option that ignores fields of the
18*88d15eacSSasha Smundak// given names on a single struct type. It respects the names of exported fields
19*88d15eacSSasha Smundak// that are forwarded due to struct embedding.
20*88d15eacSSasha Smundak// The struct type is specified by passing in a value of that type.
21*88d15eacSSasha Smundak//
22*88d15eacSSasha Smundak// The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a
23*88d15eacSSasha Smundak// specific sub-field that is embedded or nested within the parent struct.
24*88d15eacSSasha Smundakfunc IgnoreFields(typ interface{}, names ...string) cmp.Option {
25*88d15eacSSasha Smundak	sf := newStructFilter(typ, names...)
26*88d15eacSSasha Smundak	return cmp.FilterPath(sf.filter, cmp.Ignore())
27*88d15eacSSasha Smundak}
28*88d15eacSSasha Smundak
29*88d15eacSSasha Smundak// IgnoreTypes returns an Option that ignores all values assignable to
30*88d15eacSSasha Smundak// certain types, which are specified by passing in a value of each type.
31*88d15eacSSasha Smundakfunc IgnoreTypes(typs ...interface{}) cmp.Option {
32*88d15eacSSasha Smundak	tf := newTypeFilter(typs...)
33*88d15eacSSasha Smundak	return cmp.FilterPath(tf.filter, cmp.Ignore())
34*88d15eacSSasha Smundak}
35*88d15eacSSasha Smundak
36*88d15eacSSasha Smundaktype typeFilter []reflect.Type
37*88d15eacSSasha Smundak
38*88d15eacSSasha Smundakfunc newTypeFilter(typs ...interface{}) (tf typeFilter) {
39*88d15eacSSasha Smundak	for _, typ := range typs {
40*88d15eacSSasha Smundak		t := reflect.TypeOf(typ)
41*88d15eacSSasha Smundak		if t == nil {
42*88d15eacSSasha Smundak			// This occurs if someone tries to pass in sync.Locker(nil)
43*88d15eacSSasha Smundak			panic("cannot determine type; consider using IgnoreInterfaces")
44*88d15eacSSasha Smundak		}
45*88d15eacSSasha Smundak		tf = append(tf, t)
46*88d15eacSSasha Smundak	}
47*88d15eacSSasha Smundak	return tf
48*88d15eacSSasha Smundak}
49*88d15eacSSasha Smundakfunc (tf typeFilter) filter(p cmp.Path) bool {
50*88d15eacSSasha Smundak	if len(p) < 1 {
51*88d15eacSSasha Smundak		return false
52*88d15eacSSasha Smundak	}
53*88d15eacSSasha Smundak	t := p.Last().Type()
54*88d15eacSSasha Smundak	for _, ti := range tf {
55*88d15eacSSasha Smundak		if t.AssignableTo(ti) {
56*88d15eacSSasha Smundak			return true
57*88d15eacSSasha Smundak		}
58*88d15eacSSasha Smundak	}
59*88d15eacSSasha Smundak	return false
60*88d15eacSSasha Smundak}
61*88d15eacSSasha Smundak
62*88d15eacSSasha Smundak// IgnoreInterfaces returns an Option that ignores all values or references of
63*88d15eacSSasha Smundak// values assignable to certain interface types. These interfaces are specified
64*88d15eacSSasha Smundak// by passing in an anonymous struct with the interface types embedded in it.
65*88d15eacSSasha Smundak// For example, to ignore sync.Locker, pass in struct{sync.Locker}{}.
66*88d15eacSSasha Smundakfunc IgnoreInterfaces(ifaces interface{}) cmp.Option {
67*88d15eacSSasha Smundak	tf := newIfaceFilter(ifaces)
68*88d15eacSSasha Smundak	return cmp.FilterPath(tf.filter, cmp.Ignore())
69*88d15eacSSasha Smundak}
70*88d15eacSSasha Smundak
71*88d15eacSSasha Smundaktype ifaceFilter []reflect.Type
72*88d15eacSSasha Smundak
73*88d15eacSSasha Smundakfunc newIfaceFilter(ifaces interface{}) (tf ifaceFilter) {
74*88d15eacSSasha Smundak	t := reflect.TypeOf(ifaces)
75*88d15eacSSasha Smundak	if ifaces == nil || t.Name() != "" || t.Kind() != reflect.Struct {
76*88d15eacSSasha Smundak		panic("input must be an anonymous struct")
77*88d15eacSSasha Smundak	}
78*88d15eacSSasha Smundak	for i := 0; i < t.NumField(); i++ {
79*88d15eacSSasha Smundak		fi := t.Field(i)
80*88d15eacSSasha Smundak		switch {
81*88d15eacSSasha Smundak		case !fi.Anonymous:
82*88d15eacSSasha Smundak			panic("struct cannot have named fields")
83*88d15eacSSasha Smundak		case fi.Type.Kind() != reflect.Interface:
84*88d15eacSSasha Smundak			panic("embedded field must be an interface type")
85*88d15eacSSasha Smundak		case fi.Type.NumMethod() == 0:
86*88d15eacSSasha Smundak			// This matches everything; why would you ever want this?
87*88d15eacSSasha Smundak			panic("cannot ignore empty interface")
88*88d15eacSSasha Smundak		default:
89*88d15eacSSasha Smundak			tf = append(tf, fi.Type)
90*88d15eacSSasha Smundak		}
91*88d15eacSSasha Smundak	}
92*88d15eacSSasha Smundak	return tf
93*88d15eacSSasha Smundak}
94*88d15eacSSasha Smundakfunc (tf ifaceFilter) filter(p cmp.Path) bool {
95*88d15eacSSasha Smundak	if len(p) < 1 {
96*88d15eacSSasha Smundak		return false
97*88d15eacSSasha Smundak	}
98*88d15eacSSasha Smundak	t := p.Last().Type()
99*88d15eacSSasha Smundak	for _, ti := range tf {
100*88d15eacSSasha Smundak		if t.AssignableTo(ti) {
101*88d15eacSSasha Smundak			return true
102*88d15eacSSasha Smundak		}
103*88d15eacSSasha Smundak		if t.Kind() != reflect.Ptr && reflect.PtrTo(t).AssignableTo(ti) {
104*88d15eacSSasha Smundak			return true
105*88d15eacSSasha Smundak		}
106*88d15eacSSasha Smundak	}
107*88d15eacSSasha Smundak	return false
108*88d15eacSSasha Smundak}
109*88d15eacSSasha Smundak
110*88d15eacSSasha Smundak// IgnoreUnexported returns an Option that only ignores the immediate unexported
111*88d15eacSSasha Smundak// fields of a struct, including anonymous fields of unexported types.
112*88d15eacSSasha Smundak// In particular, unexported fields within the struct's exported fields
113*88d15eacSSasha Smundak// of struct types, including anonymous fields, will not be ignored unless the
114*88d15eacSSasha Smundak// type of the field itself is also passed to IgnoreUnexported.
115*88d15eacSSasha Smundak//
116*88d15eacSSasha Smundak// Avoid ignoring unexported fields of a type which you do not control (i.e. a
117*88d15eacSSasha Smundak// type from another repository), as changes to the implementation of such types
118*88d15eacSSasha Smundak// may change how the comparison behaves. Prefer a custom Comparer instead.
119*88d15eacSSasha Smundakfunc IgnoreUnexported(typs ...interface{}) cmp.Option {
120*88d15eacSSasha Smundak	ux := newUnexportedFilter(typs...)
121*88d15eacSSasha Smundak	return cmp.FilterPath(ux.filter, cmp.Ignore())
122*88d15eacSSasha Smundak}
123*88d15eacSSasha Smundak
124*88d15eacSSasha Smundaktype unexportedFilter struct{ m map[reflect.Type]bool }
125*88d15eacSSasha Smundak
126*88d15eacSSasha Smundakfunc newUnexportedFilter(typs ...interface{}) unexportedFilter {
127*88d15eacSSasha Smundak	ux := unexportedFilter{m: make(map[reflect.Type]bool)}
128*88d15eacSSasha Smundak	for _, typ := range typs {
129*88d15eacSSasha Smundak		t := reflect.TypeOf(typ)
130*88d15eacSSasha Smundak		if t == nil || t.Kind() != reflect.Struct {
131*88d15eacSSasha Smundak			panic(fmt.Sprintf("%T must be a non-pointer struct", typ))
132*88d15eacSSasha Smundak		}
133*88d15eacSSasha Smundak		ux.m[t] = true
134*88d15eacSSasha Smundak	}
135*88d15eacSSasha Smundak	return ux
136*88d15eacSSasha Smundak}
137*88d15eacSSasha Smundakfunc (xf unexportedFilter) filter(p cmp.Path) bool {
138*88d15eacSSasha Smundak	sf, ok := p.Index(-1).(cmp.StructField)
139*88d15eacSSasha Smundak	if !ok {
140*88d15eacSSasha Smundak		return false
141*88d15eacSSasha Smundak	}
142*88d15eacSSasha Smundak	return xf.m[p.Index(-2).Type()] && !isExported(sf.Name())
143*88d15eacSSasha Smundak}
144*88d15eacSSasha Smundak
145*88d15eacSSasha Smundak// isExported reports whether the identifier is exported.
146*88d15eacSSasha Smundakfunc isExported(id string) bool {
147*88d15eacSSasha Smundak	r, _ := utf8.DecodeRuneInString(id)
148*88d15eacSSasha Smundak	return unicode.IsUpper(r)
149*88d15eacSSasha Smundak}
150*88d15eacSSasha Smundak
151*88d15eacSSasha Smundak// IgnoreSliceElements returns an Option that ignores elements of []V.
152*88d15eacSSasha Smundak// The discard function must be of the form "func(T) bool" which is used to
153*88d15eacSSasha Smundak// ignore slice elements of type V, where V is assignable to T.
154*88d15eacSSasha Smundak// Elements are ignored if the function reports true.
155*88d15eacSSasha Smundakfunc IgnoreSliceElements(discardFunc interface{}) cmp.Option {
156*88d15eacSSasha Smundak	vf := reflect.ValueOf(discardFunc)
157*88d15eacSSasha Smundak	if !function.IsType(vf.Type(), function.ValuePredicate) || vf.IsNil() {
158*88d15eacSSasha Smundak		panic(fmt.Sprintf("invalid discard function: %T", discardFunc))
159*88d15eacSSasha Smundak	}
160*88d15eacSSasha Smundak	return cmp.FilterPath(func(p cmp.Path) bool {
161*88d15eacSSasha Smundak		si, ok := p.Index(-1).(cmp.SliceIndex)
162*88d15eacSSasha Smundak		if !ok {
163*88d15eacSSasha Smundak			return false
164*88d15eacSSasha Smundak		}
165*88d15eacSSasha Smundak		if !si.Type().AssignableTo(vf.Type().In(0)) {
166*88d15eacSSasha Smundak			return false
167*88d15eacSSasha Smundak		}
168*88d15eacSSasha Smundak		vx, vy := si.Values()
169*88d15eacSSasha Smundak		if vx.IsValid() && vf.Call([]reflect.Value{vx})[0].Bool() {
170*88d15eacSSasha Smundak			return true
171*88d15eacSSasha Smundak		}
172*88d15eacSSasha Smundak		if vy.IsValid() && vf.Call([]reflect.Value{vy})[0].Bool() {
173*88d15eacSSasha Smundak			return true
174*88d15eacSSasha Smundak		}
175*88d15eacSSasha Smundak		return false
176*88d15eacSSasha Smundak	}, cmp.Ignore())
177*88d15eacSSasha Smundak}
178*88d15eacSSasha Smundak
179*88d15eacSSasha Smundak// IgnoreMapEntries returns an Option that ignores entries of map[K]V.
180*88d15eacSSasha Smundak// The discard function must be of the form "func(T, R) bool" which is used to
181*88d15eacSSasha Smundak// ignore map entries of type K and V, where K and V are assignable to T and R.
182*88d15eacSSasha Smundak// Entries are ignored if the function reports true.
183*88d15eacSSasha Smundakfunc IgnoreMapEntries(discardFunc interface{}) cmp.Option {
184*88d15eacSSasha Smundak	vf := reflect.ValueOf(discardFunc)
185*88d15eacSSasha Smundak	if !function.IsType(vf.Type(), function.KeyValuePredicate) || vf.IsNil() {
186*88d15eacSSasha Smundak		panic(fmt.Sprintf("invalid discard function: %T", discardFunc))
187*88d15eacSSasha Smundak	}
188*88d15eacSSasha Smundak	return cmp.FilterPath(func(p cmp.Path) bool {
189*88d15eacSSasha Smundak		mi, ok := p.Index(-1).(cmp.MapIndex)
190*88d15eacSSasha Smundak		if !ok {
191*88d15eacSSasha Smundak			return false
192*88d15eacSSasha Smundak		}
193*88d15eacSSasha Smundak		if !mi.Key().Type().AssignableTo(vf.Type().In(0)) || !mi.Type().AssignableTo(vf.Type().In(1)) {
194*88d15eacSSasha Smundak			return false
195*88d15eacSSasha Smundak		}
196*88d15eacSSasha Smundak		k := mi.Key()
197*88d15eacSSasha Smundak		vx, vy := mi.Values()
198*88d15eacSSasha Smundak		if vx.IsValid() && vf.Call([]reflect.Value{k, vx})[0].Bool() {
199*88d15eacSSasha Smundak			return true
200*88d15eacSSasha Smundak		}
201*88d15eacSSasha Smundak		if vy.IsValid() && vf.Call([]reflect.Value{k, vy})[0].Bool() {
202*88d15eacSSasha Smundak			return true
203*88d15eacSSasha Smundak		}
204*88d15eacSSasha Smundak		return false
205*88d15eacSSasha Smundak	}, cmp.Ignore())
206*88d15eacSSasha Smundak}
207