// 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 file.package cmpimport ()// Option configures for specific behavior of [Equal] and [Diff]. 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.//// The [github.com/google/go-cmp/cmp/cmpopts] package provides helper functions// for creating options that may be used with [Equal] and [Diff].typeOptioninterface {// filter applies all filters and returns the option that remains. // Each option may only read s.curPath and call s.callTTBFunc. // // An Options is returned only if multiple comparers or transformers // can apply simultaneously and will only contain values of those types // or sub-Options containing values of those types.filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption}// applicableOption represents the following types://// Fundamental: ignore | validator | *comparer | *transformer// Grouping: OptionstypeapplicableOptioninterface {Option// apply executes the option, which may mutate s or panic.apply(s *state, vx, vy reflect.Value)}// coreOption represents the following types://// Fundamental: ignore | validator | *comparer | *transformer// Filters: *pathFilter | *valuesFiltertypecoreOptioninterface {OptionisCore()}typecorestruct{}func (core) () {}// 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.typeOptions []Optionfunc ( Options) ( *state, reflect.Type, , reflect.Value) ( applicableOption) {for , := range {switch := .filter(, , , ); .(type) {caseignore:returnignore{} // Only ignore can short-circuit evaluationcasevalidator: = validator{} // Takes precedence over comparer or transformercase *comparer, *transformer, Options:switch .(type) {casenil: = casevalidator:// Keep validatorcase *comparer, *transformer, Options: = Options{, } // Conflicting comparers or transformers } } }return}func ( Options) ( *state, , reflect.Value) {const = "ambiguous set of applicable options"const = "consider using filters to ensure at most one Comparer or Transformer may apply"var []stringfor , := rangeflattenOptions(nil, ) { = append(, fmt.Sprint()) } := strings.Join(, "\n\t")panic(fmt.Sprintf("%s at %#v:\n\t%s\n%s", , .curPath, , ))}func ( Options) () string {var []stringfor , := range { = append(, fmt.Sprint()) }returnfmt.Sprintf("Options{%s}", strings.Join(, ", "))}// FilterPath returns a new [Option] where opt is only evaluated if filter f// returns true for the current [Path] in the value tree.//// This filter is called even if a slice element or map entry is missing and// provides an opportunity to ignore such cases. The filter function must be// symmetric such that the filter result is identical regardless of whether the// missing value is from x or y.//// The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or// a previously filtered [Option].func ( func(Path) bool, Option) Option {if == nil {panic("invalid path filter function") }if := normalizeOption(); != nil {return &pathFilter{fnc: , opt: } }returnnil}typepathFilterstruct {corefncfunc(Path) booloptOption}func ( pathFilter) ( *state, reflect.Type, , reflect.Value) applicableOption {if .fnc(.curPath) {return .opt.filter(, , , ) }returnnil}func ( pathFilter) () string {returnfmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(.fnc)), .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 either value is invalid or// 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 ( interface{}, Option) Option { := reflect.ValueOf()if !function.IsType(.Type(), function.ValueFilter) || .IsNil() {panic(fmt.Sprintf("invalid values filter function: %T", )) }if := normalizeOption(); != nil { := &valuesFilter{fnc: , opt: }if := .Type().In(0); .Kind() != reflect.Interface || .NumMethod() > 0 { .typ = }return }returnnil}typevaluesFilterstruct {coretypreflect.Type// Tfncreflect.Value// func(T, T) booloptOption}func ( valuesFilter) ( *state, reflect.Type, , reflect.Value) applicableOption {if !.IsValid() || !.CanInterface() || !.IsValid() || !.CanInterface() {returnnil }if (.typ == nil || .AssignableTo(.typ)) && .callTTBFunc(.fnc, , ) {return .opt.filter(, , , ) }returnnil}func ( valuesFilter) () string {returnfmt.Sprintf("FilterValues(%s, %v)", function.NameOf(.fnc), .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 () Option { returnignore{} }typeignorestruct{ core }func (ignore) () bool { returnfalse }func (ignore) ( *state, reflect.Type, , reflect.Value) applicableOption { returnignore{} }func (ignore) ( *state, , reflect.Value) { .report(true, reportByIgnore) }func (ignore) () string { return"Ignore()" }// validator is a sentinel Option type to indicate that some options could not// be evaluated due to unexported fields, missing slice elements, or// missing map entries. Both values are validator only for unexported fields.typevalidatorstruct{ core }func (validator) ( *state, reflect.Type, , reflect.Value) applicableOption {if !.IsValid() || !.IsValid() {returnvalidator{} }if !.CanInterface() || !.CanInterface() {returnvalidator{} }returnnil}func (validator) ( *state, , reflect.Value) {// Implies missing slice element or map entry.if !.IsValid() || !.IsValid() { .report(.IsValid() == .IsValid(), 0)return }// Unable to Interface implies unexported field without visibility access.if !.CanInterface() || !.CanInterface() { := "consider using a custom Comparer; if you control the implementation of type, you can also consider using an Exporter, AllowUnexported, or cmpopts.IgnoreUnexported"varstringif := .curPath.Index(-2).Type(); .Name() != "" {// Named type with unexported fields. = fmt.Sprintf("%q.%v", .PkgPath(), .Name()) // e.g., "path/to/package".MyType := func( reflect.Type) bool { , := reflect.PointerTo().MethodByName("ProtoReflect")return && .Type.NumIn() == 1 && .Type.NumOut() == 1 && .Type.Out(0).PkgPath() == "google.golang.org/protobuf/reflect/protoreflect" && .Type.Out(0).Name() == "Message" }if () { = `consider using "google.golang.org/protobuf/testing/protocmp".Transform to compare proto.Message types` } elseif , := reflect.New().Interface().(error); { = "consider using cmpopts.EquateErrors to compare error values" } elseif .Comparable() { = "consider using cmpopts.EquateComparable to compare comparable Go types" } } else {// Unnamed type with unexported fields. Derive PkgPath from field.varstringfor := 0; < .NumField() && == ""; ++ { = .Field().PkgPath } = fmt.Sprintf("%q.(%v)", , .String()) // e.g., "path/to/package".(struct { a int }) }panic(fmt.Sprintf("cannot handle unexported field at %#v:\n\t%v\n%s", .curPath, , )) }panic("not reachable")}// identRx represents a valid identifier according to the Go specification.constidentRx = `[_\p{L}][_\p{L}\p{N}]*`varidentsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`)// 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.//// To help prevent some cases of infinite recursive cycles applying the// same transform to the output of itself (e.g., in the case where the// input and output types are the same), an implicit filter is added such that// a transformer is applicable only if that exact transformer is not already// in the tail of the [Path] since the last non-[Transform] step.// For situations where the implicit filter is still insufficient,// consider using [github.com/google/go-cmp/cmp/cmpopts.AcyclicTransformer],// which adds a filter to prevent the transformer from// being recursively applied upon itself.//// The name is a user provided label that is used as the [Transform.Name] in the// transformation [PathStep] (and eventually shown in the [Diff] output).// The name must be a valid identifier or qualified identifier in Go syntax.// If empty, an arbitrary name is used.func ( string, interface{}) Option { := reflect.ValueOf()if !function.IsType(.Type(), function.Transformer) || .IsNil() {panic(fmt.Sprintf("invalid transformer function: %T", )) }if == "" { = function.NameOf()if !identsRx.MatchString() { = "λ"// Lambda-symbol as placeholder name } } elseif !identsRx.MatchString() {panic(fmt.Sprintf("invalid name: %q", )) } := &transformer{name: , fnc: reflect.ValueOf()}if := .Type().In(0); .Kind() != reflect.Interface || .NumMethod() > 0 { .typ = }return}typetransformerstruct {corenamestringtypreflect.Type// Tfncreflect.Value// func(T) R}func ( *transformer) () bool { return .typ != nil }func ( *transformer) ( *state, reflect.Type, , reflect.Value) applicableOption {for := len(.curPath) - 1; >= 0; -- {if , := .curPath[].(Transform); ! {break// Hit most recent non-Transform step } elseif == .trans {returnnil// Cannot directly use same Transform } }if .typ == nil || .AssignableTo(.typ) {return }returnnil}func ( *transformer) ( *state, , reflect.Value) { := Transform{&transform{pathStep{typ: .fnc.Type().Out(0)}, }} := .callTRFunc(.fnc, , ) := .callTRFunc(.fnc, , ) .vx, .vy = , .compareAny()}func ( transformer) () string {returnfmt.Sprintf("Transformer(%s, %s)", .name, function.NameOf(.fnc))}// 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 yfunc ( interface{}) Option { := reflect.ValueOf()if !function.IsType(.Type(), function.Equal) || .IsNil() {panic(fmt.Sprintf("invalid comparer function: %T", )) } := &comparer{fnc: }if := .Type().In(0); .Kind() != reflect.Interface || .NumMethod() > 0 { .typ = }return}typecomparerstruct {coretypreflect.Type// Tfncreflect.Value// func(T, T) bool}func ( *comparer) () bool { return .typ != nil }func ( *comparer) ( *state, reflect.Type, , reflect.Value) applicableOption {if .typ == nil || .AssignableTo(.typ) {return }returnnil}func ( *comparer) ( *state, , reflect.Value) { := .callTTBFunc(.fnc, , ) .report(, reportByFunc)}func ( comparer) () string {returnfmt.Sprintf("Comparer(%s)", function.NameOf(.fnc))}// Exporter returns an [Option] that specifies whether [Equal] is allowed to// introspect into the unexported fields of certain struct types.//// 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.//// In many 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 [Comparer] options://// Comparer(func(x, y reflect.Type) bool { return x == y })// Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() })//// In other cases, the [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported]// option can be used to ignore all unexported fields on specified struct types.func ( func(reflect.Type) bool) Option {returnexporter()}typeexporterfunc(reflect.Type) boolfunc (exporter) ( *state, reflect.Type, , reflect.Value) applicableOption {panic("not implemented")}// AllowUnexported returns an [Option] that allows [Equal] to forcibly introspect// unexported fields of the specified struct types.//// See [Exporter] for the proper use of this option.func ( ...interface{}) Option { := make(map[reflect.Type]bool)for , := range { := reflect.TypeOf()if .Kind() != reflect.Struct {panic(fmt.Sprintf("invalid struct type: %T", )) } [] = true }returnexporter(func( reflect.Type) bool { return [] })}// Result represents the comparison result for a single node and// is provided by cmp when calling Report (see [Reporter]).typeResultstruct { _ [0]func() // Make Result incomparableflagsresultFlags}// Equal reports whether the node was determined to be equal or not.// As a special case, ignored nodes are considered equal.func ( Result) () bool {return .flags&(reportEqual|reportByIgnore) != 0}// ByIgnore reports whether the node is equal because it was ignored.// This never reports true if [Result.Equal] reports false.func ( Result) () bool {return .flags&reportByIgnore != 0}// ByMethod reports whether the Equal method determined equality.func ( Result) () bool {return .flags&reportByMethod != 0}// ByFunc reports whether a [Comparer] function determined equality.func ( Result) () bool {return .flags&reportByFunc != 0}// ByCycle reports whether a reference cycle was detected.func ( Result) () bool {return .flags&reportByCycle != 0}typeresultFlagsuintconst ( _ resultFlags = (1 << iota) / 2reportEqualreportUnequalreportByIgnorereportByMethodreportByFuncreportByCycle)// Reporter is an [Option] that can be passed to [Equal]. When [Equal] traverses// the value trees, it calls PushStep as it descends into each node in the// tree and PopStep as it ascend out of the node. The leaves of the tree are// either compared (determined to be equal or not equal) or ignored and reported// as such by calling the Report method.func ( interface {// PushStep is called when a tree-traversal operation is performed. // The PathStep itself is only valid until the step is popped. // The PathStep.Values are valid for the duration of the entire traversal // and must not be mutated. // // Equal always calls PushStep at the start to provide an operation-less // PathStep used to report the root values. // // Within a slice, the exact set of inserted, removed, or modified elements // is unspecified and may change in future implementations. // The entries of a map are iterated through in an unspecified order. (PathStep)// Report is called exactly once on leaf nodes to report whether the // comparison identified the node as equal, unequal, or ignored. // A leaf node is one that is immediately preceded by and followed by // a pair of PushStep and PopStep calls. (Result)// PopStep ascends back up the value tree. // There is always a matching pop call for every push call. ()}) Option {returnreporter{}}typereporterstruct{ reporterIface }typereporterIfaceinterface {PushStep(PathStep)Report(Result)PopStep()}func (reporter) ( *state, reflect.Type, , reflect.Value) applicableOption {panic("not implemented")}// normalizeOption normalizes the input options such that all Options groups// are flattened and groups with a single element are reduced to that element.// Only coreOptions and Options containing coreOptions are allowed.func ( Option) Option {switch := flattenOptions(nil, Options{}); len() {case0:returnnilcase1:return [0]default:return }}// flattenOptions copies all options in src to dst as a flat list.// Only coreOptions and Options containing coreOptions are allowed.func (, Options) Options {for , := range {switch opt := .(type) {casenil:continuecaseOptions: = (, )casecoreOption: = append(, )default:panic(fmt.Sprintf("invalid option type: %T", )) } }return}
The pages are generated with Goldsv0.7.6. (GOOS=linux GOARCH=amd64)