@@ -54,11 +54,10 @@ var nothing = reflect.Value{}
5454// If at least one Ignore exists in S, then the comparison is ignored.
5555// If the number of Transformer and Comparer options in S is greater than one,
5656// then Equal panics because it is ambiguous which option to use.
57- // If S contains a single Transformer, then apply that transformer on the
58- // current values and recursively call Equal on the transformed output values.
59- // If S contains a single Comparer, then use that Comparer to determine whether
60- // the current values are equal or not.
61- // Otherwise, S is empty and evaluation proceeds to the next rule.
57+ // If S contains a single Transformer, then use that to transform the current
58+ // values and recursively call Equal on the output values.
59+ // If S contains a single Comparer, then use that to compare the current values.
60+ // Otherwise, evaluation proceeds to the next rule.
6261//
6362// • If the values have an Equal method of the form "(T) Equal(T) bool" or
6463// "(T) Equal(I) bool" where T is assignable to I, then use the result of
@@ -119,47 +118,39 @@ type state struct {
119118
120119// These fields, once set by processOption, will not change.
121120exporters map [reflect.Type ]bool // Set of structs with unexported field visibility
122- optsIgn []option // List of all ignore options without value filters
123- opts []option // List of all other options
121+ opts Options // List of all fundamental and filter options
124122}
125123
126124func newState (opts []Option ) * state {
127125s := new (state )
128126for _ , opt := range opts {
129127s .processOption (opt )
130128}
131- // Move Ignore options to the front so that they are evaluated first.
132- for i , j := 0 , 0 ; i < len (s .opts ); i ++ {
133- if s .opts [i ].op == nil {
134- s .opts [i ], s .opts [j ] = s .opts [j ], s .opts [i ]
135- j ++
136- }
137- }
138129return s
139130}
140131
141132func (s * state ) processOption (opt Option ) {
142133switch opt := opt .(type ) {
134+ case nil :
143135case Options :
144136for _ , o := range opt {
145137s .processOption (o )
146138}
139+ case coreOption :
140+ type filtered interface {
141+ isFiltered () bool
142+ }
143+ if fopt , ok := opt .(filtered ); ok && ! fopt .isFiltered () {
144+ panic (fmt .Sprintf ("cannot use an unfiltered option: %v" , opt ))
145+ }
146+ s .opts = append (s .opts , opt )
147147case visibleStructs :
148148if s .exporters == nil {
149149s .exporters = make (map [reflect.Type ]bool )
150150}
151151for t := range opt {
152152s .exporters [t ] = true
153153}
154- case option :
155- if opt .typeFilter == nil && len (opt .pathFilters )+ len (opt .valueFilters ) == 0 {
156- panic (fmt .Sprintf ("cannot use an unfiltered option: %v" , opt ))
157- }
158- if opt .op == nil && len (opt .valueFilters ) == 0 {
159- s .optsIgn = append (s .optsIgn , opt )
160- } else {
161- s .opts = append (s .opts , opt )
162- }
163154case reporter :
164155if s .reporter != nil {
165156panic ("difference reporter already registered" )
@@ -205,9 +196,10 @@ func (s *state) compareAny(vx, vy reflect.Value) {
205196s .curPath .push (& pathStep {typ : t })
206197defer s .curPath .pop ()
207198}
199+ vx , vy = s .tryExporting (vx , vy )
208200
209201// Rule 1: Check whether an option applies on this node in the value tree.
210- if s .tryOptions (& vx , & vy , t ) {
202+ if s .tryOptions (vx , vy , t ) {
211203return
212204}
213205
@@ -284,90 +276,37 @@ func (s *state) compareAny(vx, vy reflect.Value) {
284276}
285277}
286278
287- // tryOptions iterates through all of the options and evaluates whether any
288- // of them can be applied. This may modify the underlying values vx and vy
289- // if an unexported field is being forcibly exported.
290- func (s * state ) tryOptions (vx , vy * reflect.Value , t reflect.Type ) bool {
291- // Try all ignore options that do not depend on the value first.
292- // This avoids possible panics when processing unexported fields.
293- for _ , opt := range s .optsIgn {
294- var v reflect.Value // Dummy value; should never be used
295- if s .applyFilters (v , v , t , opt ) {
296- return true // Ignore option applied
297- }
298- }
299-
300- // Since the values must be used after this point, verify that the values
301- // are either exported or can be forcibly exported.
279+ func (s * state ) tryExporting (vx , vy reflect.Value ) (reflect.Value , reflect.Value ) {
302280if sf , ok := s .curPath [len (s .curPath )- 1 ].(* structField ); ok && sf .unexported {
303- if ! sf .force {
304- const help = "consider using AllowUnexported or cmpopts.IgnoreUnexported"
305- panic (fmt .Sprintf ("cannot handle unexported field: %#v\n %s" , s .curPath , help ))
306- }
307-
308- // Use unsafe pointer arithmetic to get read-write access to an
309- // unexported field in the struct.
310- * vx = unsafeRetrieveField (sf .pvx , sf .field )
311- * vy = unsafeRetrieveField (sf .pvy , sf .field )
312- }
313-
314- // Try all other options now.
315- optIdx := - 1 // Index of Option to apply
316- for i , opt := range s .opts {
317- if ! s .applyFilters (* vx , * vy , t , opt ) {
318- continue
319- }
320- if opt .op == nil {
321- return true // Ignored comparison
322- }
323- if optIdx >= 0 {
324- panic (fmt .Sprintf ("ambiguous set of options at %#v\n \n %v\n \n %v\n " , s .curPath , s .opts [optIdx ], opt ))
281+ if sf .force {
282+ // Use unsafe pointer arithmetic to get read-write access to an
283+ // unexported field in the struct.
284+ vx = unsafeRetrieveField (sf .pvx , sf .field )
285+ vy = unsafeRetrieveField (sf .pvy , sf .field )
286+ } else {
287+ // We are not allowed to export the value, so invalidate them
288+ // so that tryOptions can panic later if not explicitly ignored.
289+ vx = nothing
290+ vy = nothing
325291}
326- optIdx = i
327- }
328- if optIdx >= 0 {
329- s .applyOption (* vx , * vy , t , s .opts [optIdx ])
330- return true
331292}
332- return false
293+ return vx , vy
333294}
334295
335- func (s * state ) applyFilters (vx , vy reflect.Value , t reflect.Type , opt option ) bool {
336- if opt .typeFilter != nil {
337- if ! t .AssignableTo (opt .typeFilter ) {
338- return false
339- }
340- }
341- for _ , f := range opt .pathFilters {
342- if ! f (s .curPath ) {
343- return false
344- }
345- }
346- for _ , f := range opt .valueFilters {
347- if ! t .AssignableTo (f .in ) || ! s .callTTBFunc (f .fnc , vx , vy ) {
348- return false
349- }
296+ func (s * state ) tryOptions (vx , vy reflect.Value , t reflect.Type ) bool {
297+ // If there were no FilterValues, we will not detect invalid inputs,
298+ // so manually check for them and append invalid if necessary.
299+ // We still evaluate the options since an ignore can override invalid.
300+ opts := s .opts
301+ if ! vx .IsValid () || ! vy .IsValid () {
302+ opts = Options {opts , invalid {}}
350303}
351- return true
352- }
353-
354- func (s * state ) applyOption (vx , vy reflect.Value , t reflect.Type , opt option ) {
355- switch op := opt .op .(type ) {
356- case * transformer :
357- // Update path before calling the Transformer so that dynamic checks
358- // will use the updated path.
359- s .curPath .push (& transform {pathStep {op .fnc .Type ().Out (0 )}, op })
360- defer s .curPath .pop ()
361304
362- vx = s .callTRFunc (op .fnc , vx )
363- vy = s .callTRFunc (op .fnc , vy )
364- s .compareAny (vx , vy )
365- return
366- case * comparer :
367- eq := s .callTTBFunc (op .fnc , vx , vy )
368- s .report (eq , vx , vy )
369- return
305+ // Evaluate all filters and apply the remaining options.
306+ if opt := opts .filter (s , vx , vy , t ); opt != nil {
307+ return opt .apply (s , vx , vy )
370308}
309+ return false
371310}
372311
373312func (s * state ) tryMethod (vx , vy reflect.Value , t reflect.Type ) bool {
0 commit comments