@@ -338,17 +338,19 @@ func (cm comparer) String() string {
338338}
339339
340340// AllowUnexported returns an Option that forcibly allows operations on
341- // unexported fields in certain structs, which are specified by passing in a
342- // value of each struct type.
341+ // unexported fields in certain structs. Struct types with permitted visibility
342+ // are specified by passing in a value of the struct type or by passing in a
343+ // PackagePrefix value, which represents all struct types defined in a package
344+ // for which the prefix is match of.
343345//
344- // Users of this option must understand that comparing on unexported fields
345- // from external packages is not safe since changes in the internal
346- // implementation of some external package may cause the result of Equal
347- // to unexpectedly change. However, it may be valid to use this option on types
348- // defined in an internal package where the semantic meaning of an unexported
349- // field is in the control of the user.
346+ // Users must understand that comparing unexported fields from external packages
347+ // that are not owned by the user is unsafe since changes in the internal
348+ // implementation of external packages may cause the result of Equal to
349+ // unexpectedly change. However, it may be valid to use this option on types
350+ // owned by the user where the semantic meaning of an unexported field is in
351+ // full control of the user.
350352//
351- // For some cases, a custom Comparer should be used instead that defines
353+ // For most cases, a custom Comparer should be used instead that defines
352354// equality as a function of the public API of a type rather than the underlying
353355// unexported implementation.
354356//
@@ -363,24 +365,75 @@ func (cm comparer) String() string {
363365//
364366// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore
365367// all unexported fields on specified struct types.
366- func AllowUnexported (types ... interface {}) Option {
368+ func AllowUnexported (templates ... interface {}) Option {
367369 if ! supportAllowUnexported {
368370 panic ("AllowUnexported is not supported on purego builds, Google App Engine Standard, or GopherJS" )
369371 }
370- m := make (map [reflect.Type ]bool )
371- for _ , typ := range types {
372- t := reflect .TypeOf (typ )
373- if t .Kind () != reflect .Struct {
374- panic (fmt .Sprintf ("invalid struct type: %T" , typ ))
372+ var x fieldExporter
373+ for _ , v := range templates {
374+ if p , ok := v .(PackagePrefix ); ok {
375+ x .insertPrefix (p )
376+ } else {
377+ t := reflect .TypeOf (v )
378+ if t .Kind () != reflect .Struct {
379+ panic (fmt .Sprintf ("invalid struct type: %T" , v ))
380+ }
381+ x .insertType (t )
375382 }
376- m [t ] = true
377383 }
378- return visibleStructs (m )
384+ return x
385+ }
386+
387+ // PackagePrefix is the prefix for a Go package path.
388+ //
389+ // A prefix match is either an identical string match to the package path or
390+ // a string prefix match where the next character is a forward slash.
391+ //
392+ // For example, the package path "example.com/foo/bar" is matched by:
393+ // • "example.com/foo/bar"
394+ // • "example.com/foo"
395+ // • "example.com"
396+ // and is not matched by:
397+ // • "example.com/foo/ba"
398+ // • "example.com/fizz"
399+ // • "example.org"
400+ type PackagePrefix string
401+
402+ type fieldExporter struct {
403+ pkgs map [PackagePrefix ]struct {}
404+ typs map [reflect.Type ]struct {}
379405}
380406
381- type visibleStructs map [reflect.Type ]bool
407+ func (x * fieldExporter ) insertPrefix (p PackagePrefix ) {
408+ if x .pkgs == nil {
409+ x .pkgs = make (map [PackagePrefix ]struct {})
410+ }
411+ x .pkgs [p ] = struct {}{}
412+ }
413+
414+ func (x * fieldExporter ) insertType (t reflect.Type ) {
415+ if x .typs == nil {
416+ x .typs = make (map [reflect.Type ]struct {})
417+ }
418+ x .typs [t ] = struct {}{}
419+ }
420+
421+ func (x fieldExporter ) mayExport (t reflect.Type , sf reflect.StructField ) bool {
422+ for pkgPrefix := range x .pkgs {
423+ if ! strings .HasPrefix (sf .PkgPath , string (pkgPrefix )) {
424+ continue
425+ }
426+ if len (sf .PkgPath ) == len (pkgPrefix ) || sf .PkgPath [len (pkgPrefix )] == '/' {
427+ return true
428+ }
429+ }
430+ if _ , ok := x .typs [t ]; ok {
431+ return true
432+ }
433+ return false
434+ }
382435
383- func (visibleStructs ) filter (_ * state , _ , _ reflect.Value , _ reflect.Type ) applicableOption {
436+ func (fieldExporter ) filter (_ * state , _ , _ reflect.Value , _ reflect.Type ) applicableOption {
384437 panic ("not implemented" )
385438}
386439
0 commit comments