Skip to content

Commit

Permalink
Use of hotlinking of Go identifiers (#337)
Browse files Browse the repository at this point in the history
GoDoc now supports hot linking to specific Go identifiers.
Use that feature to enhance the usability of cmp docs.

Since cmp lacks a direct dependency on cmpopts,
you are forced to use the fully-qualified import path
to references identifiers in the cmpopts package.

It can future improvements to the go/doc to handle this case,
either by rendering the doc with short form,
or by detecting the use of cmpopts from the unit tests as well
(since those can break circular dependencies).
  • Loading branch information
dsnet authored Aug 31, 2023
1 parent 8a3e8dd commit e250a55
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 106 deletions.
20 changes: 10 additions & 10 deletions cmp/cmpopts/equate.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import (

func equateAlways(_, _ interface{}) bool { return true }

// EquateEmpty returns a Comparer option that determines all maps and slices
// EquateEmpty returns a [cmp.Comparer] option that determines all maps and slices
// with a length of zero to be equal, regardless of whether they are nil.
//
// EquateEmpty can be used in conjunction with SortSlices and SortMaps.
// EquateEmpty can be used in conjunction with [SortSlices] and [SortMaps].
func EquateEmpty() cmp.Option {
return cmp.FilterValues(isEmpty, cmp.Comparer(equateAlways))
}
Expand All @@ -31,7 +31,7 @@ func isEmpty(x, y interface{}) bool {
(vx.Len() == 0 && vy.Len() == 0)
}

// EquateApprox returns a Comparer option that determines float32 or float64
// EquateApprox returns a [cmp.Comparer] option that determines float32 or float64
// values to be equal if they are within a relative fraction or absolute margin.
// This option is not used when either x or y is NaN or infinite.
//
Expand All @@ -45,7 +45,7 @@ func isEmpty(x, y interface{}) bool {
//
// |x-y| ≤ max(fraction*min(|x|, |y|), margin)
//
// EquateApprox can be used in conjunction with EquateNaNs.
// EquateApprox can be used in conjunction with [EquateNaNs].
func EquateApprox(fraction, margin float64) cmp.Option {
if margin < 0 || fraction < 0 || math.IsNaN(margin) || math.IsNaN(fraction) {
panic("margin or fraction must be a non-negative number")
Expand Down Expand Up @@ -73,10 +73,10 @@ func (a approximator) compareF32(x, y float32) bool {
return a.compareF64(float64(x), float64(y))
}

// EquateNaNs returns a Comparer option that determines float32 and float64
// EquateNaNs returns a [cmp.Comparer] option that determines float32 and float64
// NaN values to be equal.
//
// EquateNaNs can be used in conjunction with EquateApprox.
// EquateNaNs can be used in conjunction with [EquateApprox].
func EquateNaNs() cmp.Option {
return cmp.Options{
cmp.FilterValues(areNaNsF64s, cmp.Comparer(equateAlways)),
Expand All @@ -91,8 +91,8 @@ func areNaNsF32s(x, y float32) bool {
return areNaNsF64s(float64(x), float64(y))
}

// EquateApproxTime returns a Comparer option that determines two non-zero
// time.Time values to be equal if they are within some margin of one another.
// EquateApproxTime returns a [cmp.Comparer] option that determines two non-zero
// [time.Time] values to be equal if they are within some margin of one another.
// If both times have a monotonic clock reading, then the monotonic time
// difference will be used. The margin must be non-negative.
func EquateApproxTime(margin time.Duration) cmp.Option {
Expand Down Expand Up @@ -131,8 +131,8 @@ type anyError struct{}
func (anyError) Error() string { return "any error" }
func (anyError) Is(err error) bool { return err != nil }

// EquateErrors returns a Comparer option that determines errors to be equal
// if errors.Is reports them to match. The AnyError error can be used to
// EquateErrors returns a [cmp.Comparer] option that determines errors to be equal
// if [errors.Is] reports them to match. The [AnyError] error can be used to
// match any non-nil error.
func EquateErrors() cmp.Option {
return cmp.FilterValues(areConcreteErrors, cmp.Comparer(compareErrors))
Expand Down
16 changes: 8 additions & 8 deletions cmp/cmpopts/ignore.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"github.com/google/go-cmp/cmp/internal/function"
)

// IgnoreFields returns an Option that ignores fields of the
// IgnoreFields returns an [cmp.Option] that ignores fields of the
// given names on a single struct type. It respects the names of exported fields
// that are forwarded due to struct embedding.
// The struct type is specified by passing in a value of that type.
Expand All @@ -26,7 +26,7 @@ func IgnoreFields(typ interface{}, names ...string) cmp.Option {
return cmp.FilterPath(sf.filter, cmp.Ignore())
}

// IgnoreTypes returns an Option that ignores all values assignable to
// IgnoreTypes returns an [cmp.Option] that ignores all values assignable to
// certain types, which are specified by passing in a value of each type.
func IgnoreTypes(typs ...interface{}) cmp.Option {
tf := newTypeFilter(typs...)
Expand Down Expand Up @@ -59,10 +59,10 @@ func (tf typeFilter) filter(p cmp.Path) bool {
return false
}

// IgnoreInterfaces returns an Option that ignores all values or references of
// IgnoreInterfaces returns an [cmp.Option] that ignores all values or references of
// values assignable to certain interface types. These interfaces are specified
// by passing in an anonymous struct with the interface types embedded in it.
// For example, to ignore sync.Locker, pass in struct{sync.Locker}{}.
// For example, to ignore [sync.Locker], pass in struct{sync.Locker}{}.
func IgnoreInterfaces(ifaces interface{}) cmp.Option {
tf := newIfaceFilter(ifaces)
return cmp.FilterPath(tf.filter, cmp.Ignore())
Expand Down Expand Up @@ -107,15 +107,15 @@ func (tf ifaceFilter) filter(p cmp.Path) bool {
return false
}

// IgnoreUnexported returns an Option that only ignores the immediate unexported
// IgnoreUnexported returns an [cmp.Option] that only ignores the immediate unexported
// fields of a struct, including anonymous fields of unexported types.
// In particular, unexported fields within the struct's exported fields
// of struct types, including anonymous fields, will not be ignored unless the
// type of the field itself is also passed to IgnoreUnexported.
//
// Avoid ignoring unexported fields of a type which you do not control (i.e. a
// type from another repository), as changes to the implementation of such types
// may change how the comparison behaves. Prefer a custom Comparer instead.
// may change how the comparison behaves. Prefer a custom [cmp.Comparer] instead.
func IgnoreUnexported(typs ...interface{}) cmp.Option {
ux := newUnexportedFilter(typs...)
return cmp.FilterPath(ux.filter, cmp.Ignore())
Expand Down Expand Up @@ -148,7 +148,7 @@ func isExported(id string) bool {
return unicode.IsUpper(r)
}

// IgnoreSliceElements returns an Option that ignores elements of []V.
// IgnoreSliceElements returns an [cmp.Option] that ignores elements of []V.
// The discard function must be of the form "func(T) bool" which is used to
// ignore slice elements of type V, where V is assignable to T.
// Elements are ignored if the function reports true.
Expand Down Expand Up @@ -176,7 +176,7 @@ func IgnoreSliceElements(discardFunc interface{}) cmp.Option {
}, cmp.Ignore())
}

// IgnoreMapEntries returns an Option that ignores entries of map[K]V.
// IgnoreMapEntries returns an [cmp.Option] that ignores entries of map[K]V.
// The discard function must be of the form "func(T, R) bool" which is used to
// ignore map entries of type K and V, where K and V are assignable to T and R.
// Entries are ignored if the function reports true.
Expand Down
12 changes: 6 additions & 6 deletions cmp/cmpopts/sort.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/google/go-cmp/cmp/internal/function"
)

// SortSlices returns a Transformer option that sorts all []V.
// SortSlices returns a [cmp.Transformer] option that sorts all []V.
// The less function must be of the form "func(T, T) bool" which is used to
// sort any slice with element type V that is assignable to T.
//
Expand All @@ -25,7 +25,7 @@ import (
// The less function does not have to be "total". That is, if !less(x, y) and
// !less(y, x) for two elements x and y, their relative order is maintained.
//
// SortSlices can be used in conjunction with EquateEmpty.
// SortSlices can be used in conjunction with [EquateEmpty].
func SortSlices(lessFunc interface{}) cmp.Option {
vf := reflect.ValueOf(lessFunc)
if !function.IsType(vf.Type(), function.Less) || vf.IsNil() {
Expand Down Expand Up @@ -82,21 +82,21 @@ func (ss sliceSorter) less(v reflect.Value, i, j int) bool {
return ss.fnc.Call([]reflect.Value{vx, vy})[0].Bool()
}

// SortMaps returns a Transformer option that flattens map[K]V types to be a
// SortMaps returns a [cmp.Transformer] option that flattens map[K]V types to be a
// sorted []struct{K, V}. The less function must be of the form
// "func(T, T) bool" which is used to sort any map with key K that is
// assignable to T.
//
// Flattening the map into a slice has the property that cmp.Equal is able to
// use Comparers on K or the K.Equal method if it exists.
// Flattening the map into a slice has the property that [cmp.Equal] is able to
// use [cmp.Comparer] options on K or the K.Equal method if it exists.
//
// The less function must be:
// - Deterministic: less(x, y) == less(x, y)
// - Irreflexive: !less(x, x)
// - Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
// - Total: if x != y, then either less(x, y) or less(y, x)
//
// SortMaps can be used in conjunction with EquateEmpty.
// SortMaps can be used in conjunction with [EquateEmpty].
func SortMaps(lessFunc interface{}) cmp.Option {
vf := reflect.ValueOf(lessFunc)
if !function.IsType(vf.Type(), function.Less) || vf.IsNil() {
Expand Down
4 changes: 2 additions & 2 deletions cmp/cmpopts/xform.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func (xf xformFilter) filter(p cmp.Path) bool {
return true
}

// AcyclicTransformer returns a Transformer with a filter applied that ensures
// AcyclicTransformer returns a [cmp.Transformer] with a filter applied that ensures
// that the transformer cannot be recursively applied upon its own output.
//
// An example use case is a transformer that splits a string by lines:
Expand All @@ -28,7 +28,7 @@ func (xf xformFilter) filter(p cmp.Path) bool {
// return strings.Split(s, "\n")
// })
//
// Had this been an unfiltered Transformer instead, this would result in an
// Had this been an unfiltered [cmp.Transformer] instead, this would result in an
// infinite cycle converting a string to []string to [][]string and so on.
func AcyclicTransformer(name string, xformFunc interface{}) cmp.Option {
xf := xformFilter{cmp.Transformer(name, xformFunc)}
Expand Down
38 changes: 20 additions & 18 deletions cmp/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Package cmp determines equality of values.
//
// This package is intended to be a more powerful and safer alternative to
// reflect.DeepEqual for comparing whether two values are semantically equal.
// [reflect.DeepEqual] for comparing whether two values are semantically equal.
// It is intended to only be used in tests, as performance is not a goal and
// it may panic if it cannot compare the values. Its propensity towards
// panicking means that its unsuitable for production environments where a
Expand All @@ -18,16 +18,17 @@
// For example, an equality function may report floats as equal so long as
// they are within some tolerance of each other.
//
// - Types with an Equal method may use that method to determine equality.
// This allows package authors to determine the equality operation
// for the types that they define.
// - Types with an Equal method (e.g., [time.Time.Equal]) may use that method
// to determine equality. This allows package authors to determine
// the equality operation for the types that they define.
//
// - If no custom equality functions are used and no Equal method is defined,
// equality is determined by recursively comparing the primitive kinds on
// both values, much like reflect.DeepEqual. Unlike reflect.DeepEqual,
// both values, much like [reflect.DeepEqual]. Unlike [reflect.DeepEqual],
// unexported fields are not compared by default; they result in panics
// unless suppressed by using an Ignore option (see cmpopts.IgnoreUnexported)
// or explicitly compared using the Exporter option.
// unless suppressed by using an [Ignore] option
// (see [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported])
// or explicitly compared using the [Exporter] option.
package cmp

import (
Expand All @@ -45,14 +46,14 @@ import (
// Equal reports whether x and y are equal by recursively applying the
// following rules in the given order to x and y and all of their sub-values:
//
// - Let S be the set of all Ignore, Transformer, and Comparer options that
// - Let S be the set of all [Ignore], [Transformer], and [Comparer] options that
// remain after applying all path filters, value filters, and type filters.
// If at least one Ignore exists in S, then the comparison is ignored.
// If the number of Transformer and Comparer options in S is non-zero,
// If at least one [Ignore] exists in S, then the comparison is ignored.
// If the number of [Transformer] and [Comparer] options in S is non-zero,
// then Equal panics because it is ambiguous which option to use.
// If S contains a single Transformer, then use that to transform
// If S contains a single [Transformer], then use that to transform
// the current values and recursively call Equal on the output values.
// If S contains a single Comparer, then use that to compare the current values.
// If S contains a single [Comparer], then use that to compare the current values.
// Otherwise, evaluation proceeds to the next rule.
//
// - If the values have an Equal method of the form "(T) Equal(T) bool" or
Expand All @@ -66,21 +67,22 @@ import (
// Functions are only equal if they are both nil, otherwise they are unequal.
//
// Structs are equal if recursively calling Equal on all fields report equal.
// If a struct contains unexported fields, Equal panics unless an Ignore option
// (e.g., cmpopts.IgnoreUnexported) ignores that field or the Exporter option
// explicitly permits comparing the unexported field.
// If a struct contains unexported fields, Equal panics unless an [Ignore] option
// (e.g., [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported]) ignores that field
// or the [Exporter] option explicitly permits comparing the unexported field.
//
// Slices are equal if they are both nil or both non-nil, where recursively
// calling Equal on all non-ignored slice or array elements report equal.
// Empty non-nil slices and nil slices are not equal; to equate empty slices,
// consider using cmpopts.EquateEmpty.
// consider using [github.com/google/go-cmp/cmp/cmpopts.EquateEmpty].
//
// Maps are equal if they are both nil or both non-nil, where recursively
// calling Equal on all non-ignored map entries report equal.
// Map keys are equal according to the == operator.
// To use custom comparisons for map keys, consider using cmpopts.SortMaps.
// To use custom comparisons for map keys, consider using
// [github.com/google/go-cmp/cmp/cmpopts.SortMaps].
// Empty non-nil maps and nil maps are not equal; to equate empty maps,
// consider using cmpopts.EquateEmpty.
// consider using [github.com/google/go-cmp/cmp/cmpopts.EquateEmpty].
//
// Pointers and interfaces are equal if they are both nil or both non-nil,
// where they have the same underlying concrete type and recursively
Expand Down
14 changes: 9 additions & 5 deletions cmp/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ func ExampleDiff_testing() {
// comparer on floats that determines two values to be equal if they are within
// some range of each other.
//
// This example is for demonstrative purposes; use cmpopts.EquateApprox instead.
// This example is for demonstrative purposes;
// use [github.com/google/go-cmp/cmp/cmpopts.EquateApprox] instead.
func ExampleOption_approximateFloats() {
// This Comparer only operates on float64.
// To handle float32s, either define a similar function for that type
Expand Down Expand Up @@ -89,7 +90,8 @@ func ExampleOption_approximateFloats() {
// Normal floating-point arithmetic defines == to be false when comparing
// NaN with itself. In certain cases, this is not the desired property.
//
// This example is for demonstrative purposes; use cmpopts.EquateNaNs instead.
// This example is for demonstrative purposes;
// use [github.com/google/go-cmp/cmp/cmpopts.EquateNaNs] instead.
func ExampleOption_equalNaNs() {
// This Comparer only operates on float64.
// To handle float32s, either define a similar function for that type
Expand Down Expand Up @@ -117,7 +119,7 @@ func ExampleOption_equalNaNs() {
// to restrict the scope of the comparison so that they are composable.
//
// This example is for demonstrative purposes;
// use cmpopts.EquateNaNs and cmpopts.EquateApprox instead.
// use [github.com/google/go-cmp/cmp/cmpopts.EquateApprox] instead.
func ExampleOption_equalNaNsAndApproximateFloats() {
alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })

Expand Down Expand Up @@ -156,7 +158,8 @@ func ExampleOption_equalNaNsAndApproximateFloats() {
// Sometimes, an empty map or slice is considered equal to an allocated one
// of zero length.
//
// This example is for demonstrative purposes; use cmpopts.EquateEmpty instead.
// This example is for demonstrative purposes;
// use [github.com/google/go-cmp/cmp/cmpopts.EquateEmpty] instead.
func ExampleOption_equalEmpty() {
alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })

Expand Down Expand Up @@ -190,7 +193,8 @@ func ExampleOption_equalEmpty() {
// regardless of the order that they appear in. Transformations can be used
// to sort the slice.
//
// This example is for demonstrative purposes; use cmpopts.SortSlices instead.
// This example is for demonstrative purposes;
// use [github.com/google/go-cmp/cmp/cmpopts.SortSlices] instead.
func ExampleOption_sortedSlice() {
// This Transformer sorts a []int.
trans := cmp.Transformer("Sort", func(in []int) []int {
Expand Down
Loading

0 comments on commit e250a55

Please sign in to comment.