Path: blob/main/vendor/github.com/google/go-cmp/cmp/options.go
2880 views
// Copyright 2017, The Go Authors. All rights reserved.1// Use of this source code is governed by a BSD-style2// license that can be found in the LICENSE file.34package cmp56import (7"fmt"8"reflect"9"regexp"10"strings"1112"github.com/google/go-cmp/cmp/internal/function"13)1415// Option configures for specific behavior of [Equal] and [Diff]. In particular,16// the fundamental Option functions ([Ignore], [Transformer], and [Comparer]),17// configure how equality is determined.18//19// The fundamental options may be composed with filters ([FilterPath] and20// [FilterValues]) to control the scope over which they are applied.21//22// The [github.com/google/go-cmp/cmp/cmpopts] package provides helper functions23// for creating options that may be used with [Equal] and [Diff].24type Option interface {25// filter applies all filters and returns the option that remains.26// Each option may only read s.curPath and call s.callTTBFunc.27//28// An Options is returned only if multiple comparers or transformers29// can apply simultaneously and will only contain values of those types30// or sub-Options containing values of those types.31filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption32}3334// applicableOption represents the following types:35//36// Fundamental: ignore | validator | *comparer | *transformer37// Grouping: Options38type applicableOption interface {39Option4041// apply executes the option, which may mutate s or panic.42apply(s *state, vx, vy reflect.Value)43}4445// coreOption represents the following types:46//47// Fundamental: ignore | validator | *comparer | *transformer48// Filters: *pathFilter | *valuesFilter49type coreOption interface {50Option51isCore()52}5354type core struct{}5556func (core) isCore() {}5758// Options is a list of [Option] values that also satisfies the [Option] interface.59// Helper comparison packages may return an Options value when packing multiple60// [Option] values into a single [Option]. When this package processes an Options,61// it will be implicitly expanded into a flat list.62//63// Applying a filter on an Options is equivalent to applying that same filter64// on all individual options held within.65type Options []Option6667func (opts Options) filter(s *state, t reflect.Type, vx, vy reflect.Value) (out applicableOption) {68for _, opt := range opts {69switch opt := opt.filter(s, t, vx, vy); opt.(type) {70case ignore:71return ignore{} // Only ignore can short-circuit evaluation72case validator:73out = validator{} // Takes precedence over comparer or transformer74case *comparer, *transformer, Options:75switch out.(type) {76case nil:77out = opt78case validator:79// Keep validator80case *comparer, *transformer, Options:81out = Options{out, opt} // Conflicting comparers or transformers82}83}84}85return out86}8788func (opts Options) apply(s *state, _, _ reflect.Value) {89const warning = "ambiguous set of applicable options"90const help = "consider using filters to ensure at most one Comparer or Transformer may apply"91var ss []string92for _, opt := range flattenOptions(nil, opts) {93ss = append(ss, fmt.Sprint(opt))94}95set := strings.Join(ss, "\n\t")96panic(fmt.Sprintf("%s at %#v:\n\t%s\n%s", warning, s.curPath, set, help))97}9899func (opts Options) String() string {100var ss []string101for _, opt := range opts {102ss = append(ss, fmt.Sprint(opt))103}104return fmt.Sprintf("Options{%s}", strings.Join(ss, ", "))105}106107// FilterPath returns a new [Option] where opt is only evaluated if filter f108// returns true for the current [Path] in the value tree.109//110// This filter is called even if a slice element or map entry is missing and111// provides an opportunity to ignore such cases. The filter function must be112// symmetric such that the filter result is identical regardless of whether the113// missing value is from x or y.114//115// The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or116// a previously filtered [Option].117func FilterPath(f func(Path) bool, opt Option) Option {118if f == nil {119panic("invalid path filter function")120}121if opt := normalizeOption(opt); opt != nil {122return &pathFilter{fnc: f, opt: opt}123}124return nil125}126127type pathFilter struct {128core129fnc func(Path) bool130opt Option131}132133func (f pathFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption {134if f.fnc(s.curPath) {135return f.opt.filter(s, t, vx, vy)136}137return nil138}139140func (f pathFilter) String() string {141return fmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(f.fnc)), f.opt)142}143144// FilterValues returns a new [Option] where opt is only evaluated if filter f,145// which is a function of the form "func(T, T) bool", returns true for the146// current pair of values being compared. If either value is invalid or147// the type of the values is not assignable to T, then this filter implicitly148// returns false.149//150// The filter function must be151// symmetric (i.e., agnostic to the order of the inputs) and152// deterministic (i.e., produces the same result when given the same inputs).153// If T is an interface, it is possible that f is called with two values with154// different concrete types that both implement T.155//156// The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or157// a previously filtered [Option].158func FilterValues(f interface{}, opt Option) Option {159v := reflect.ValueOf(f)160if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() {161panic(fmt.Sprintf("invalid values filter function: %T", f))162}163if opt := normalizeOption(opt); opt != nil {164vf := &valuesFilter{fnc: v, opt: opt}165if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {166vf.typ = ti167}168return vf169}170return nil171}172173type valuesFilter struct {174core175typ reflect.Type // T176fnc reflect.Value // func(T, T) bool177opt Option178}179180func (f valuesFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption {181if !vx.IsValid() || !vx.CanInterface() || !vy.IsValid() || !vy.CanInterface() {182return nil183}184if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) {185return f.opt.filter(s, t, vx, vy)186}187return nil188}189190func (f valuesFilter) String() string {191return fmt.Sprintf("FilterValues(%s, %v)", function.NameOf(f.fnc), f.opt)192}193194// Ignore is an [Option] that causes all comparisons to be ignored.195// This value is intended to be combined with [FilterPath] or [FilterValues].196// It is an error to pass an unfiltered Ignore option to [Equal].197func Ignore() Option { return ignore{} }198199type ignore struct{ core }200201func (ignore) isFiltered() bool { return false }202func (ignore) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { return ignore{} }203func (ignore) apply(s *state, _, _ reflect.Value) { s.report(true, reportByIgnore) }204func (ignore) String() string { return "Ignore()" }205206// validator is a sentinel Option type to indicate that some options could not207// be evaluated due to unexported fields, missing slice elements, or208// missing map entries. Both values are validator only for unexported fields.209type validator struct{ core }210211func (validator) filter(_ *state, _ reflect.Type, vx, vy reflect.Value) applicableOption {212if !vx.IsValid() || !vy.IsValid() {213return validator{}214}215if !vx.CanInterface() || !vy.CanInterface() {216return validator{}217}218return nil219}220func (validator) apply(s *state, vx, vy reflect.Value) {221// Implies missing slice element or map entry.222if !vx.IsValid() || !vy.IsValid() {223s.report(vx.IsValid() == vy.IsValid(), 0)224return225}226227// Unable to Interface implies unexported field without visibility access.228if !vx.CanInterface() || !vy.CanInterface() {229help := "consider using a custom Comparer; if you control the implementation of type, you can also consider using an Exporter, AllowUnexported, or cmpopts.IgnoreUnexported"230var name string231if t := s.curPath.Index(-2).Type(); t.Name() != "" {232// Named type with unexported fields.233name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name()) // e.g., "path/to/package".MyType234isProtoMessage := func(t reflect.Type) bool {235m, ok := reflect.PointerTo(t).MethodByName("ProtoReflect")236return ok && m.Type.NumIn() == 1 && m.Type.NumOut() == 1 &&237m.Type.Out(0).PkgPath() == "google.golang.org/protobuf/reflect/protoreflect" &&238m.Type.Out(0).Name() == "Message"239}240if isProtoMessage(t) {241help = `consider using "google.golang.org/protobuf/testing/protocmp".Transform to compare proto.Message types`242} else if _, ok := reflect.New(t).Interface().(error); ok {243help = "consider using cmpopts.EquateErrors to compare error values"244} else if t.Comparable() {245help = "consider using cmpopts.EquateComparable to compare comparable Go types"246}247} else {248// Unnamed type with unexported fields. Derive PkgPath from field.249var pkgPath string250for i := 0; i < t.NumField() && pkgPath == ""; i++ {251pkgPath = t.Field(i).PkgPath252}253name = fmt.Sprintf("%q.(%v)", pkgPath, t.String()) // e.g., "path/to/package".(struct { a int })254}255panic(fmt.Sprintf("cannot handle unexported field at %#v:\n\t%v\n%s", s.curPath, name, help))256}257258panic("not reachable")259}260261// identRx represents a valid identifier according to the Go specification.262const identRx = `[_\p{L}][_\p{L}\p{N}]*`263264var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`)265266// Transformer returns an [Option] that applies a transformation function that267// converts values of a certain type into that of another.268//269// The transformer f must be a function "func(T) R" that converts values of270// type T to those of type R and is implicitly filtered to input values271// assignable to T. The transformer must not mutate T in any way.272//273// To help prevent some cases of infinite recursive cycles applying the274// same transform to the output of itself (e.g., in the case where the275// input and output types are the same), an implicit filter is added such that276// a transformer is applicable only if that exact transformer is not already277// in the tail of the [Path] since the last non-[Transform] step.278// For situations where the implicit filter is still insufficient,279// consider using [github.com/google/go-cmp/cmp/cmpopts.AcyclicTransformer],280// which adds a filter to prevent the transformer from281// being recursively applied upon itself.282//283// The name is a user provided label that is used as the [Transform.Name] in the284// transformation [PathStep] (and eventually shown in the [Diff] output).285// The name must be a valid identifier or qualified identifier in Go syntax.286// If empty, an arbitrary name is used.287func Transformer(name string, f interface{}) Option {288v := reflect.ValueOf(f)289if !function.IsType(v.Type(), function.Transformer) || v.IsNil() {290panic(fmt.Sprintf("invalid transformer function: %T", f))291}292if name == "" {293name = function.NameOf(v)294if !identsRx.MatchString(name) {295name = "λ" // Lambda-symbol as placeholder name296}297} else if !identsRx.MatchString(name) {298panic(fmt.Sprintf("invalid name: %q", name))299}300tr := &transformer{name: name, fnc: reflect.ValueOf(f)}301if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {302tr.typ = ti303}304return tr305}306307type transformer struct {308core309name string310typ reflect.Type // T311fnc reflect.Value // func(T) R312}313314func (tr *transformer) isFiltered() bool { return tr.typ != nil }315316func (tr *transformer) filter(s *state, t reflect.Type, _, _ reflect.Value) applicableOption {317for i := len(s.curPath) - 1; i >= 0; i-- {318if t, ok := s.curPath[i].(Transform); !ok {319break // Hit most recent non-Transform step320} else if tr == t.trans {321return nil // Cannot directly use same Transform322}323}324if tr.typ == nil || t.AssignableTo(tr.typ) {325return tr326}327return nil328}329330func (tr *transformer) apply(s *state, vx, vy reflect.Value) {331step := Transform{&transform{pathStep{typ: tr.fnc.Type().Out(0)}, tr}}332vvx := s.callTRFunc(tr.fnc, vx, step)333vvy := s.callTRFunc(tr.fnc, vy, step)334step.vx, step.vy = vvx, vvy335s.compareAny(step)336}337338func (tr transformer) String() string {339return fmt.Sprintf("Transformer(%s, %s)", tr.name, function.NameOf(tr.fnc))340}341342// Comparer returns an [Option] that determines whether two values are equal343// to each other.344//345// The comparer f must be a function "func(T, T) bool" and is implicitly346// filtered to input values assignable to T. If T is an interface, it is347// possible that f is called with two values of different concrete types that348// both implement T.349//350// The equality function must be:351// - Symmetric: equal(x, y) == equal(y, x)352// - Deterministic: equal(x, y) == equal(x, y)353// - Pure: equal(x, y) does not modify x or y354func Comparer(f interface{}) Option {355v := reflect.ValueOf(f)356if !function.IsType(v.Type(), function.Equal) || v.IsNil() {357panic(fmt.Sprintf("invalid comparer function: %T", f))358}359cm := &comparer{fnc: v}360if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {361cm.typ = ti362}363return cm364}365366type comparer struct {367core368typ reflect.Type // T369fnc reflect.Value // func(T, T) bool370}371372func (cm *comparer) isFiltered() bool { return cm.typ != nil }373374func (cm *comparer) filter(_ *state, t reflect.Type, _, _ reflect.Value) applicableOption {375if cm.typ == nil || t.AssignableTo(cm.typ) {376return cm377}378return nil379}380381func (cm *comparer) apply(s *state, vx, vy reflect.Value) {382eq := s.callTTBFunc(cm.fnc, vx, vy)383s.report(eq, reportByFunc)384}385386func (cm comparer) String() string {387return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc))388}389390// Exporter returns an [Option] that specifies whether [Equal] is allowed to391// introspect into the unexported fields of certain struct types.392//393// Users of this option must understand that comparing on unexported fields394// from external packages is not safe since changes in the internal395// implementation of some external package may cause the result of [Equal]396// to unexpectedly change. However, it may be valid to use this option on types397// defined in an internal package where the semantic meaning of an unexported398// field is in the control of the user.399//400// In many cases, a custom [Comparer] should be used instead that defines401// equality as a function of the public API of a type rather than the underlying402// unexported implementation.403//404// For example, the [reflect.Type] documentation defines equality to be determined405// by the == operator on the interface (essentially performing a shallow pointer406// comparison) and most attempts to compare *[regexp.Regexp] types are interested407// in only checking that the regular expression strings are equal.408// Both of these are accomplished using [Comparer] options:409//410// Comparer(func(x, y reflect.Type) bool { return x == y })411// Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() })412//413// In other cases, the [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported]414// option can be used to ignore all unexported fields on specified struct types.415func Exporter(f func(reflect.Type) bool) Option {416return exporter(f)417}418419type exporter func(reflect.Type) bool420421func (exporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {422panic("not implemented")423}424425// AllowUnexported returns an [Option] that allows [Equal] to forcibly introspect426// unexported fields of the specified struct types.427//428// See [Exporter] for the proper use of this option.429func AllowUnexported(types ...interface{}) Option {430m := make(map[reflect.Type]bool)431for _, typ := range types {432t := reflect.TypeOf(typ)433if t.Kind() != reflect.Struct {434panic(fmt.Sprintf("invalid struct type: %T", typ))435}436m[t] = true437}438return exporter(func(t reflect.Type) bool { return m[t] })439}440441// Result represents the comparison result for a single node and442// is provided by cmp when calling Report (see [Reporter]).443type Result struct {444_ [0]func() // Make Result incomparable445flags resultFlags446}447448// Equal reports whether the node was determined to be equal or not.449// As a special case, ignored nodes are considered equal.450func (r Result) Equal() bool {451return r.flags&(reportEqual|reportByIgnore) != 0452}453454// ByIgnore reports whether the node is equal because it was ignored.455// This never reports true if [Result.Equal] reports false.456func (r Result) ByIgnore() bool {457return r.flags&reportByIgnore != 0458}459460// ByMethod reports whether the Equal method determined equality.461func (r Result) ByMethod() bool {462return r.flags&reportByMethod != 0463}464465// ByFunc reports whether a [Comparer] function determined equality.466func (r Result) ByFunc() bool {467return r.flags&reportByFunc != 0468}469470// ByCycle reports whether a reference cycle was detected.471func (r Result) ByCycle() bool {472return r.flags&reportByCycle != 0473}474475type resultFlags uint476477const (478_ resultFlags = (1 << iota) / 2479480reportEqual481reportUnequal482reportByIgnore483reportByMethod484reportByFunc485reportByCycle486)487488// Reporter is an [Option] that can be passed to [Equal]. When [Equal] traverses489// the value trees, it calls PushStep as it descends into each node in the490// tree and PopStep as it ascend out of the node. The leaves of the tree are491// either compared (determined to be equal or not equal) or ignored and reported492// as such by calling the Report method.493func Reporter(r interface {494// PushStep is called when a tree-traversal operation is performed.495// The PathStep itself is only valid until the step is popped.496// The PathStep.Values are valid for the duration of the entire traversal497// and must not be mutated.498//499// Equal always calls PushStep at the start to provide an operation-less500// PathStep used to report the root values.501//502// Within a slice, the exact set of inserted, removed, or modified elements503// is unspecified and may change in future implementations.504// The entries of a map are iterated through in an unspecified order.505PushStep(PathStep)506507// Report is called exactly once on leaf nodes to report whether the508// comparison identified the node as equal, unequal, or ignored.509// A leaf node is one that is immediately preceded by and followed by510// a pair of PushStep and PopStep calls.511Report(Result)512513// PopStep ascends back up the value tree.514// There is always a matching pop call for every push call.515PopStep()516}) Option {517return reporter{r}518}519520type reporter struct{ reporterIface }521type reporterIface interface {522PushStep(PathStep)523Report(Result)524PopStep()525}526527func (reporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {528panic("not implemented")529}530531// normalizeOption normalizes the input options such that all Options groups532// are flattened and groups with a single element are reduced to that element.533// Only coreOptions and Options containing coreOptions are allowed.534func normalizeOption(src Option) Option {535switch opts := flattenOptions(nil, Options{src}); len(opts) {536case 0:537return nil538case 1:539return opts[0]540default:541return opts542}543}544545// flattenOptions copies all options in src to dst as a flat list.546// Only coreOptions and Options containing coreOptions are allowed.547func flattenOptions(dst, src Options) Options {548for _, opt := range src {549switch opt := opt.(type) {550case nil:551continue552case Options:553dst = flattenOptions(dst, opt)554case coreOption:555dst = append(dst, opt)556default:557panic(fmt.Sprintf("invalid option type: %T", opt))558}559}560return dst561}562563564