2018-07-12 02:58:56 +02:00
// 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"
2019-05-28 03:35:42 +02:00
"regexp"
2018-07-12 02:58:56 +02:00
"strings"
"github.com/google/go-cmp/cmp/internal/function"
)
// 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 cmp/cmpopts package provides helper functions for creating options that
// may be used with Equal and Diff.
type Option interface {
// 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.
2019-05-28 03:35:42 +02:00
filter ( s * state , t reflect . Type , vx , vy reflect . Value ) applicableOption
2018-07-12 02:58:56 +02:00
}
// applicableOption represents the following types:
2019-05-28 03:35:42 +02:00
// Fundamental: ignore | validator | *comparer | *transformer
2018-07-12 02:58:56 +02:00
// Grouping: Options
type applicableOption interface {
Option
// apply executes the option, which may mutate s or panic.
apply ( s * state , vx , vy reflect . Value )
}
// coreOption represents the following types:
2019-05-28 03:35:42 +02:00
// Fundamental: ignore | validator | *comparer | *transformer
2018-07-12 02:58:56 +02:00
// Filters: *pathFilter | *valuesFilter
type coreOption interface {
Option
isCore ( )
}
type core struct { }
func ( core ) isCore ( ) { }
// 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
2019-05-28 03:35:42 +02:00
func ( opts Options ) filter ( s * state , t reflect . Type , vx , vy reflect . Value ) ( out applicableOption ) {
2018-07-12 02:58:56 +02:00
for _ , opt := range opts {
2019-05-28 03:35:42 +02:00
switch opt := opt . filter ( s , t , vx , vy ) ; opt . ( type ) {
2018-07-12 02:58:56 +02:00
case ignore :
return ignore { } // Only ignore can short-circuit evaluation
2019-05-28 03:35:42 +02:00
case validator :
out = validator { } // Takes precedence over comparer or transformer
2018-07-12 02:58:56 +02:00
case * comparer , * transformer , Options :
switch out . ( type ) {
case nil :
out = opt
2019-05-28 03:35:42 +02:00
case validator :
// Keep validator
2018-07-12 02:58:56 +02:00
case * comparer , * transformer , Options :
out = Options { out , opt } // Conflicting comparers or transformers
}
}
}
return out
}
func ( opts Options ) apply ( s * state , _ , _ reflect . Value ) {
const warning = "ambiguous set of applicable options"
const help = "consider using filters to ensure at most one Comparer or Transformer may apply"
var ss [ ] string
for _ , opt := range flattenOptions ( nil , opts ) {
ss = append ( ss , fmt . Sprint ( opt ) )
}
set := strings . Join ( ss , "\n\t" )
panic ( fmt . Sprintf ( "%s at %#v:\n\t%s\n%s" , warning , s . curPath , set , help ) )
}
func ( opts Options ) String ( ) string {
var ss [ ] string
for _ , opt := range opts {
ss = append ( ss , fmt . Sprint ( opt ) )
}
return fmt . Sprintf ( "Options{%s}" , strings . Join ( ss , ", " ) )
}
// FilterPath returns a new Option where opt is only evaluated if filter f
// returns true for the current Path in the value tree.
//
2019-05-28 03:35:42 +02:00
// 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.
//
2018-07-12 02:58:56 +02:00
// 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" )
}
if opt := normalizeOption ( opt ) ; opt != nil {
return & pathFilter { fnc : f , opt : opt }
}
return nil
}
type pathFilter struct {
core
fnc func ( Path ) bool
opt Option
}
2019-05-28 03:35:42 +02:00
func ( f pathFilter ) filter ( s * state , t reflect . Type , vx , vy reflect . Value ) applicableOption {
2018-07-12 02:58:56 +02:00
if f . fnc ( s . curPath ) {
2019-05-28 03:35:42 +02:00
return f . opt . filter ( s , t , vx , vy )
2018-07-12 02:58:56 +02:00
}
return nil
}
func ( f pathFilter ) String ( ) string {
2019-05-28 03:35:42 +02:00
return fmt . Sprintf ( "FilterPath(%s, %v)" , function . NameOf ( reflect . ValueOf ( f . fnc ) ) , f . opt )
2018-07-12 02:58:56 +02:00
}
// 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
2019-05-28 03:35:42 +02:00
// 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.
2018-07-12 02:58:56 +02:00
//
// 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 ! function . IsType ( v . Type ( ) , function . ValueFilter ) || v . IsNil ( ) {
panic ( fmt . Sprintf ( "invalid values filter function: %T" , f ) )
}
if opt := normalizeOption ( opt ) ; opt != nil {
vf := & valuesFilter { fnc : v , opt : opt }
if ti := v . Type ( ) . In ( 0 ) ; ti . Kind ( ) != reflect . Interface || ti . NumMethod ( ) > 0 {
vf . typ = ti
}
return vf
}
return nil
}
type valuesFilter struct {
core
typ reflect . Type // T
fnc reflect . Value // func(T, T) bool
opt Option
}
2019-05-28 03:35:42 +02:00
func ( f valuesFilter ) filter ( s * state , t reflect . Type , vx , vy reflect . Value ) applicableOption {
if ! vx . IsValid ( ) || ! vx . CanInterface ( ) || ! vy . IsValid ( ) || ! vy . CanInterface ( ) {
return nil
2018-07-12 02:58:56 +02:00
}
if ( f . typ == nil || t . AssignableTo ( f . typ ) ) && s . callTTBFunc ( f . fnc , vx , vy ) {
2019-05-28 03:35:42 +02:00
return f . opt . filter ( s , t , vx , vy )
2018-07-12 02:58:56 +02:00
}
return nil
}
func ( f valuesFilter ) String ( ) string {
2019-05-28 03:35:42 +02:00
return fmt . Sprintf ( "FilterValues(%s, %v)" , function . NameOf ( f . fnc ) , f . opt )
2018-07-12 02:58:56 +02:00
}
// 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 ignore { } }
type ignore struct { core }
func ( ignore ) isFiltered ( ) bool { return false }
2019-05-28 03:35:42 +02:00
func ( ignore ) filter ( _ * state , _ reflect . Type , _ , _ reflect . Value ) applicableOption { return ignore { } }
func ( ignore ) apply ( s * state , _ , _ reflect . Value ) { s . report ( true , reportByIgnore ) }
2018-07-12 02:58:56 +02:00
func ( ignore ) String ( ) string { return "Ignore()" }
2019-05-28 03:35:42 +02:00
// 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.
type validator struct { core }
func ( validator ) filter ( _ * state , _ reflect . Type , vx , vy reflect . Value ) applicableOption {
if ! vx . IsValid ( ) || ! vy . IsValid ( ) {
return validator { }
}
if ! vx . CanInterface ( ) || ! vy . CanInterface ( ) {
return validator { }
}
return nil
}
func ( validator ) apply ( s * state , vx , vy reflect . Value ) {
// Implies missing slice element or map entry.
if ! vx . IsValid ( ) || ! vy . IsValid ( ) {
s . report ( vx . IsValid ( ) == vy . IsValid ( ) , 0 )
return
}
// Unable to Interface implies unexported field without visibility access.
if ! vx . CanInterface ( ) || ! vy . CanInterface ( ) {
const help = "consider using a custom Comparer; if you control the implementation of type, you can also consider AllowUnexported or cmpopts.IgnoreUnexported"
panic ( fmt . Sprintf ( "cannot handle unexported field: %#v\n%s" , s . curPath , help ) )
}
2018-07-12 02:58:56 +02:00
2019-05-28 03:35:42 +02:00
panic ( "not reachable" )
2018-07-12 02:58:56 +02:00
}
2019-05-28 03:35:42 +02:00
// identRx represents a valid identifier according to the Go specification.
const identRx = ` [_\p { L}][_\p { L}\p { N}]* `
var identsRx = regexp . MustCompile ( ` ^ ` + identRx + ` (\. ` + identRx + ` )*$ ` )
2018-07-12 02:58:56 +02:00
// 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.
2019-05-28 03:35:42 +02:00
// For situations where the implicit filter is still insufficient,
// consider using cmpopts.AcyclicTransformer, which adds a filter
// to prevent the transformer from being recursively applied upon itself.
2018-07-12 02:58:56 +02:00
//
// The name is a user provided label that is used as the Transform.Name in the
2019-05-28 03:35:42 +02:00
// 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.
2018-07-12 02:58:56 +02:00
func Transformer ( name string , f interface { } ) Option {
v := reflect . ValueOf ( f )
if ! function . IsType ( v . Type ( ) , function . Transformer ) || v . IsNil ( ) {
panic ( fmt . Sprintf ( "invalid transformer function: %T" , f ) )
}
if name == "" {
2019-05-28 03:35:42 +02:00
name = function . NameOf ( v )
if ! identsRx . MatchString ( name ) {
name = "λ" // Lambda-symbol as placeholder name
}
} else if ! identsRx . MatchString ( name ) {
2018-07-12 02:58:56 +02:00
panic ( fmt . Sprintf ( "invalid name: %q" , name ) )
}
tr := & transformer { name : name , fnc : reflect . ValueOf ( f ) }
if ti := v . Type ( ) . In ( 0 ) ; ti . Kind ( ) != reflect . Interface || ti . NumMethod ( ) > 0 {
tr . typ = ti
}
return tr
}
type transformer struct {
core
name string
typ reflect . Type // T
fnc reflect . Value // func(T) R
}
func ( tr * transformer ) isFiltered ( ) bool { return tr . typ != nil }
2019-05-28 03:35:42 +02:00
func ( tr * transformer ) filter ( s * state , t reflect . Type , _ , _ reflect . Value ) applicableOption {
2018-07-12 02:58:56 +02:00
for i := len ( s . curPath ) - 1 ; i >= 0 ; i -- {
2019-05-28 03:35:42 +02:00
if t , ok := s . curPath [ i ] . ( Transform ) ; ! ok {
2018-07-12 02:58:56 +02:00
break // Hit most recent non-Transform step
} else if tr == t . trans {
return nil // Cannot directly use same Transform
}
}
if tr . typ == nil || t . AssignableTo ( tr . typ ) {
return tr
}
return nil
}
func ( tr * transformer ) apply ( s * state , vx , vy reflect . Value ) {
2019-05-28 03:35:42 +02:00
step := Transform { & transform { pathStep { typ : tr . fnc . Type ( ) . Out ( 0 ) } , tr } }
vvx := s . callTRFunc ( tr . fnc , vx , step )
vvy := s . callTRFunc ( tr . fnc , vy , step )
step . vx , step . vy = vvx , vvy
s . compareAny ( step )
2018-07-12 02:58:56 +02:00
}
func ( tr transformer ) String ( ) string {
2019-05-28 03:35:42 +02:00
return fmt . Sprintf ( "Transformer(%s, %s)" , tr . name , function . NameOf ( tr . fnc ) )
2018-07-12 02:58:56 +02:00
}
// 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 ! function . IsType ( v . Type ( ) , function . Equal ) || v . IsNil ( ) {
panic ( fmt . Sprintf ( "invalid comparer function: %T" , f ) )
}
cm := & comparer { fnc : v }
if ti := v . Type ( ) . In ( 0 ) ; ti . Kind ( ) != reflect . Interface || ti . NumMethod ( ) > 0 {
cm . typ = ti
}
return cm
}
type comparer struct {
core
typ reflect . Type // T
fnc reflect . Value // func(T, T) bool
}
func ( cm * comparer ) isFiltered ( ) bool { return cm . typ != nil }
2019-05-28 03:35:42 +02:00
func ( cm * comparer ) filter ( _ * state , t reflect . Type , _ , _ reflect . Value ) applicableOption {
2018-07-12 02:58:56 +02:00
if cm . typ == nil || t . AssignableTo ( cm . typ ) {
return cm
}
return nil
}
func ( cm * comparer ) apply ( s * state , vx , vy reflect . Value ) {
eq := s . callTTBFunc ( cm . fnc , vx , vy )
2019-05-28 03:35:42 +02:00
s . report ( eq , reportByFunc )
2018-07-12 02:58:56 +02:00
}
func ( cm comparer ) String ( ) string {
2019-05-28 03:35:42 +02:00
return fmt . Sprintf ( "Comparer(%s)" , function . NameOf ( cm . fnc ) )
2018-07-12 02:58:56 +02:00
}
// 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.
//
2019-05-28 03:35:42 +02:00
// In many cases, a custom Comparer should be used instead that defines
2018-07-12 02:58:56 +02:00
// 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() })
//
// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore
// all unexported fields on specified struct types.
func AllowUnexported ( types ... interface { } ) Option {
if ! supportAllowUnexported {
panic ( "AllowUnexported is not supported on purego builds, Google App Engine Standard, or GopherJS" )
}
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
2019-05-28 03:35:42 +02:00
func ( visibleStructs ) filter ( _ * state , _ reflect . Type , _ , _ reflect . Value ) applicableOption {
2018-07-12 02:58:56 +02:00
panic ( "not implemented" )
}
2019-05-28 03:35:42 +02:00
// Result represents the comparison result for a single node and
// is provided by cmp when calling Result (see Reporter).
type Result struct {
_ [ 0 ] func ( ) // Make Result incomparable
flags resultFlags
}
// Equal reports whether the node was determined to be equal or not.
// As a special case, ignored nodes are considered equal.
func ( r Result ) Equal ( ) bool {
return r . flags & ( reportEqual | reportByIgnore ) != 0
}
// ByIgnore reports whether the node is equal because it was ignored.
// This never reports true if Equal reports false.
func ( r Result ) ByIgnore ( ) bool {
return r . flags & reportByIgnore != 0
}
// ByMethod reports whether the Equal method determined equality.
func ( r Result ) ByMethod ( ) bool {
return r . flags & reportByMethod != 0
}
// ByFunc reports whether a Comparer function determined equality.
func ( r Result ) ByFunc ( ) bool {
return r . flags & reportByFunc != 0
}
type resultFlags uint
const (
_ resultFlags = ( 1 << iota ) / 2
reportEqual
reportUnequal
reportByIgnore
reportByMethod
reportByFunc
)
// 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 Reporter ( r 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.
2018-07-12 02:58:56 +02:00
//
2019-05-28 03:35:42 +02:00
// 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.
PushStep ( 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.
Report ( Result )
// PopStep ascends back up the value tree.
// There is always a matching pop call for every push call.
PopStep ( )
} ) Option {
return reporter { r }
}
2018-07-12 02:58:56 +02:00
2019-05-28 03:35:42 +02:00
type reporter struct { reporterIface }
type reporterIface interface {
PushStep ( PathStep )
Report ( Result )
PopStep ( )
}
2018-07-12 02:58:56 +02:00
2019-05-28 03:35:42 +02:00
func ( reporter ) filter ( _ * state , _ reflect . Type , _ , _ reflect . Value ) applicableOption {
panic ( "not implemented" )
2018-07-12 02:58:56 +02:00
}
// 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 normalizeOption ( src Option ) Option {
switch opts := flattenOptions ( nil , Options { src } ) ; len ( opts ) {
case 0 :
return nil
case 1 :
return opts [ 0 ]
default :
return opts
}
}
// flattenOptions copies all options in src to dst as a flat list.
// Only coreOptions and Options containing coreOptions are allowed.
func flattenOptions ( dst , src Options ) Options {
for _ , opt := range src {
switch opt := opt . ( type ) {
case nil :
continue
case Options :
dst = flattenOptions ( dst , opt )
case coreOption :
dst = append ( dst , opt )
default :
panic ( fmt . Sprintf ( "invalid option type: %T" , opt ) )
}
}
return dst
}