@@ -338,17 +338,15 @@ 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.
343343//
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.
344+ // Comparing unexported fields from packages that are not owned by the user
345+ // is unsafe since changes in the internal implementation may cause the result
346+ // of Equal to unexpectedly change. This option should only be used on types
347+ // where the semantic meaning of unexported fields is in full control of the user.
350348//
351- // For some cases, a custom Comparer should be used instead that defines
349+ // For most cases, a custom Comparer should be used instead that defines
352350// equality as a function of the public API of a type rather than the underlying
353351// unexported implementation.
354352//
@@ -363,24 +361,90 @@ func (cm comparer) String() string {
363361//
364362// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore
365363// all unexported fields on specified struct types.
366- func AllowUnexported (types ... interface {}) Option {
364+ func AllowUnexported (typs ... interface {}) Option {
367365 if ! supportAllowUnexported {
368366 panic ("AllowUnexported is not supported on purego builds, Google App Engine Standard, or GopherJS" )
369367 }
370- m := make ( map [reflect. Type ] bool )
371- for _ , typ := range types {
372- t := reflect .TypeOf (typ )
368+ var x fieldExporter
369+ for _ , v := range typs {
370+ t := reflect .TypeOf (v )
373371 if t .Kind () != reflect .Struct {
374- panic (fmt .Sprintf ("invalid struct type: %T" , typ ))
372+ panic (fmt .Sprintf ("invalid struct type: %T" , v ))
375373 }
376- m [ t ] = true
374+ x . insertType ( t )
377375 }
378- return visibleStructs ( m )
376+ return x
379377}
380378
381- type visibleStructs map [reflect.Type ]bool
379+ // AllowUnexportedWithinModule returns an Option that forcibly allows
380+ // operations on unexported fields in certain structs.
381+ // See AllowUnexported for proper guidance on comparing unexported fields.
382+ //
383+ // Unexported visibility is permitted for any struct type declared within a
384+ // module where the pkgPrefix is a path prefix match of the full package path.
385+ // A path prefix match is defined as a string prefix match where the next
386+ // character is either the first character, a forward slash,
387+ // or the end of the string.
388+ //
389+ // For example, the package path "example.com/foo/bar" is matched by:
390+ // • "example.com/foo/bar"
391+ // • "example.com/foo"
392+ // • "example.com"
393+ // and is not matched by:
394+ // • "example.com/foo/ba"
395+ // • "example.com/fizz"
396+ // • "example.org"
397+ func AllowUnexportedWithinModule (pkgPrefix string ) Option {
398+ if ! supportAllowUnexported {
399+ panic ("AllowUnexported is not supported on purego builds, Google App Engine Standard, or GopherJS" )
400+ }
401+ var x fieldExporter
402+ x .insertPrefix (pkgPrefix )
403+ return x
404+ }
405+
406+ type fieldExporter struct {
407+ typs map [reflect.Type ]struct {}
408+ pkgs map [string ]struct {}
409+ }
410+
411+ func (x * fieldExporter ) insertType (t reflect.Type ) {
412+ if x .typs == nil {
413+ x .typs = make (map [reflect.Type ]struct {})
414+ }
415+ x .typs [t ] = struct {}{}
416+ }
417+
418+ func (x * fieldExporter ) insertPrefix (p string ) {
419+ if x .pkgs == nil {
420+ x .pkgs = make (map [string ]struct {})
421+ }
422+ x .pkgs [p ] = struct {}{}
423+ }
424+
425+ func (x fieldExporter ) mayExport (t reflect.Type , sf reflect.StructField ) bool {
426+ // TODO(dsnet): Workaround for reflect bug (https://golang.org/issue/21122).
427+ // The upstream fix landed in Go1.10, so we can remove this when dropping
428+ // support for Go1.9 and below.
429+ if len (x .pkgs ) > 0 && sf .PkgPath == "" {
430+ return true // Liberally allow exporting since we lack path information
431+ }
432+
433+ if _ , ok := x .typs [t ]; ok {
434+ return true
435+ }
436+ for pkgPrefix := range x .pkgs {
437+ if ! strings .HasPrefix (sf .PkgPath , string (pkgPrefix )) {
438+ continue
439+ }
440+ if len (sf .PkgPath ) == len (pkgPrefix ) || sf .PkgPath [len (pkgPrefix )] == '/' || len (pkgPrefix ) == 0 {
441+ return true
442+ }
443+ }
444+ return false
445+ }
382446
383- func (visibleStructs ) filter (_ * state , _ , _ reflect.Value , _ reflect.Type ) applicableOption {
447+ func (fieldExporter ) filter (_ * state , _ , _ reflect.Value , _ reflect.Type ) applicableOption {
384448 panic ("not implemented" )
385449}
386450
0 commit comments