collections: go-cmp helpers for the collection types

A downside of using custom collection types is that go-cmp doesn't support
them automatically, and so we need to help it out a little by providing
additional options.

An additional awkwardness is that go-cmp does all of its work at runtime
using reflection, at which point the generic types have all been erased
and lowered to many separate concrete types. Therefore we need some helper
functions to produce a dynamically representation using interface types,
cautiously exposing some internal details of these types only for go-cmp's
use.

The dynamic adapter here unfortunately means that the compiler likely
won't be able to optimize away CmpOptions in non-test builds, but we'll
accept that for now since the cost of that inert data and code is
relatively small. If it turns out to be a problem then we'll need to find
a different strategy, but hopefully not.
pull/34505/head
Martin Atkins 2 years ago
parent 306b9008c4
commit 8cb51f415b

@ -0,0 +1,57 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package collections
import (
"github.com/google/go-cmp/cmp"
)
// CmpOptions is a set of options for use with the "go-cmp" module when
// comparing data structures that contain collection types from this package.
//
// Specifically, these options arrange for [Set] and [Map] values to be
// transformed into map[any]any to allow for element-based comparisons.
//
// [Set] of T values transform into a map whose keys have dynamic type
// UniqueKey[T] and whose values have dynamic type T.
//
// [Map] of K, V values transform into a map whose keys have dynamic type
// UniqueKey[K] and whose values have dynamic type MapElem[K, V].
var CmpOptions cmp.Option
func init() {
CmpOptions = cmp.Options([]cmp.Option{
cmp.Transformer("collectionElementsRaw", func(v transformerForCmp) any {
return v.transformForCmp()
}),
})
}
// transformerForCmp is a helper interface implemented by both `Set` and `Map`
// types, to work around the fact that go-cmp does all its work with reflection
// and thus cannot rely on the static type information from the type
// parameters.
type transformerForCmp interface {
transformForCmp() any
}
func (s Set[T]) transformForCmp() any {
ret := make(map[any]any, s.Len())
// It's okay to access the keys here because this package is allowed to
// depend on its own implementation details.
for k, v := range s.Elems() {
ret[k] = v
}
return ret
}
func (m Map[K, V]) transformForCmp() any {
ret := make(map[any]any, m.Len())
// It's okay to access the keys here because this package is allowed to
// depend on its own implementation details.
for k, v := range m.Elems() {
ret[k] = v
}
return ret
}
Loading…
Cancel
Save