Skip to content

Commit

Permalink
set: Use type parameters for the Set, Rules, and Iterator types
Browse files Browse the repository at this point in the history
Although we're not yet making great use of this due to the fact that the
top-level "set rules" are still for interface{} anyway, this is a step
towards doing something a bit more specific later, and for now it allows
some type-safety around _path sets_ in particular.
  • Loading branch information
apparentlymart committed Jun 3, 2022
1 parent 97bafac commit 74095df
Show file tree
Hide file tree
Showing 18 changed files with 187 additions and 178 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ This release contains some changes to some aspects of the API that are either le
* **`encoding/gob` support utilities removed**: we added these as a concession to HashiCorp who wanted to try to send `cty` values over some legacy protocols/formats used by legacy versions of HashiCorp Terraform. In the end those efforts were not successful for other reasons and so no Terraform release ever relied on this functionality.

`encoding/gob` support has been burdensome due to how its unmarshaler interface is defined and so `cty` values and types are no longer automatically compatible with `encoding/gob`. Callers should instead use explicitly-implemented encodings, such as the built-in JSON and msgpack encodings or external libraries which use the public `cty` API to encode and decode.
* **cty now requires Go 1.18**: although the main API is not yet making any use of type parameters, we've begun to adopt it in the hope of improving the maintainability of some internal details, starting with the backing implementation of set types.

Since type parameters are not supported by earlier versions of the Go compiler, callers must upgrade to Go 1.18 before using cty v1.11.0 or later.

# 1.10.0 (November 2, 2021)

Expand Down
4 changes: 2 additions & 2 deletions cty/element_iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func elementIterator(val Value) ElementIterator {
idx: -1,
}
case val.ty.IsSetType():
rawSet := val.v.(set.Set)
rawSet := val.v.(set.Set[interface{}])
return &setElementIterator{
ety: val.ty.ElementType(),
setIt: rawSet.Iterator(),
Expand Down Expand Up @@ -139,7 +139,7 @@ func (it *mapElementIterator) Next() bool {

type setElementIterator struct {
ety Type
setIt *set.Iterator
setIt *set.Iterator[interface{}]
}

func (it *setElementIterator) Element() (Value, Value) {
Expand Down
2 changes: 1 addition & 1 deletion cty/gocty/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
var valueType = reflect.TypeOf(cty.Value{})
var typeType = reflect.TypeOf(cty.Type{})

var setType = reflect.TypeOf(set.Set{})
var setType = reflect.TypeOf(set.Set[interface{}]{})

var bigFloatType = reflect.TypeOf(big.Float{})
var bigIntType = reflect.TypeOf(big.Int{})
Expand Down
2 changes: 1 addition & 1 deletion cty/gocty/in.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ func toCtySet(val reflect.Value, ety cty.Type, path cty.Path) (cty.Value, error)
return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Type(), cty.Set(ety))
}

rawSet := val.Interface().(set.Set)
rawSet := val.Interface().(set.Set[interface{}])
inVals := rawSet.Values()

if len(inVals) == 0 {
Expand Down
6 changes: 3 additions & 3 deletions cty/gocty/in_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,12 +243,12 @@ func TestIn(t *testing.T) {
Want: cty.SetValEmpty(cty.Number),
},
{
GoValue: set.NewSet(&testSetRules{}),
GoValue: set.NewSet(set.Rules[interface{}](&testSetRules{})),
Type: cty.Set(cty.Number),
Want: cty.SetValEmpty(cty.Number),
},
{
GoValue: set.NewSetFromSlice(&testSetRules{}, []interface{}{1, 2}),
GoValue: set.NewSetFromSlice(set.Rules[interface{}](&testSetRules{}), []interface{}{1, 2}),
Type: cty.Set(cty.Number),
Want: cty.SetVal([]cty.Value{
cty.NumberIntVal(1),
Expand Down Expand Up @@ -480,7 +480,7 @@ func (r testSetRules) Equivalent(v1 interface{}, v2 interface{}) bool {
return v1 == v2
}

func (r testSetRules) SameRules(other set.Rules) bool {
func (r testSetRules) SameRules(other set.Rules[interface{}]) bool {
return r == other
}

Expand Down
16 changes: 6 additions & 10 deletions cty/path_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import (
// to talk about a subset of paths within a value that meet some criteria,
// without directly modifying the values at those paths.
type PathSet struct {
set set.Set
set set.Set[Path]
}

// NewPathSet creates and returns a PathSet, with initial contents optionally
// set by the given arguments.
func NewPathSet(paths ...Path) PathSet {
ret := PathSet{
set: set.NewSet(pathSetRules{}),
set: set.NewSet(set.Rules[Path](pathSetRules{})),
}

for _, path := range paths {
Expand Down Expand Up @@ -61,7 +61,7 @@ func (s PathSet) List() []Path {
}
ret := make([]Path, 0, s.set.Length())
for it := s.set.Iterator(); it.Next(); {
ret = append(ret, it.Value().(Path))
ret = append(ret, it.Value())
}
return ret
}
Expand Down Expand Up @@ -134,8 +134,7 @@ var indexStepPlaceholder = []byte("#")
type pathSetRules struct {
}

func (r pathSetRules) Hash(v interface{}) int {
path := v.(Path)
func (r pathSetRules) Hash(path Path) int {
hash := crc64.New(crc64Table)

for _, rawStep := range path {
Expand All @@ -159,10 +158,7 @@ func (r pathSetRules) Hash(v interface{}) int {
return int(hash.Sum64())
}

func (r pathSetRules) Equivalent(a, b interface{}) bool {
aPath := a.(Path)
bPath := b.(Path)

func (r pathSetRules) Equivalent(aPath, bPath Path) bool {
if len(aPath) != len(bPath) {
return false
}
Expand Down Expand Up @@ -198,7 +194,7 @@ func (r pathSetRules) Equivalent(a, b interface{}) bool {
}

// SameRules is true if both Rules instances are pathSetRules structs.
func (r pathSetRules) SameRules(other set.Rules) bool {
func (r pathSetRules) SameRules(other set.Rules[Path]) bool {
_, ok := other.(pathSetRules)
return ok
}
8 changes: 4 additions & 4 deletions cty/set/iterator.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package set

type Iterator struct {
vals []interface{}
type Iterator[T any] struct {
vals []T
idx int
}

func (it *Iterator) Value() interface{} {
func (it *Iterator[T]) Value() T {
return it.vals[it.idx]
}

func (it *Iterator) Next() bool {
func (it *Iterator[T]) Next() bool {
it.idx++
return it.idx < len(it.vals)
}
46 changes: 23 additions & 23 deletions cty/set/ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import (
// Add inserts the given value into the receiving Set.
//
// This mutates the set in-place. This operation is not thread-safe.
func (s Set) Add(val interface{}) {
func (s Set[T]) Add(val T) {
hv := s.rules.Hash(val)
if _, ok := s.vals[hv]; !ok {
s.vals[hv] = make([]interface{}, 0, 1)
s.vals[hv] = make([]T, 0, 1)
}
bucket := s.vals[hv]

Expand All @@ -26,7 +26,7 @@ func (s Set) Add(val interface{}) {

// Remove deletes the given value from the receiving set, if indeed it was
// there in the first place. If the value is not present, this is a no-op.
func (s Set) Remove(val interface{}) {
func (s Set[T]) Remove(val T) {
hv := s.rules.Hash(val)
bucket, ok := s.vals[hv]
if !ok {
Expand All @@ -35,7 +35,7 @@ func (s Set) Remove(val interface{}) {

for i, ev := range bucket {
if s.rules.Equivalent(val, ev) {
newBucket := make([]interface{}, 0, len(bucket)-1)
newBucket := make([]T, 0, len(bucket)-1)
newBucket = append(newBucket, bucket[:i]...)
newBucket = append(newBucket, bucket[i+1:]...)
if len(newBucket) > 0 {
Expand All @@ -50,7 +50,7 @@ func (s Set) Remove(val interface{}) {

// Has returns true if the given value is in the receiving set, or false if
// it is not.
func (s Set) Has(val interface{}) bool {
func (s Set[T]) Has(val T) bool {
hv := s.rules.Hash(val)
bucket, ok := s.vals[hv]
if !ok {
Expand All @@ -67,7 +67,7 @@ func (s Set) Has(val interface{}) bool {

// Copy performs a shallow copy of the receiving set, returning a new set
// with the same rules and elements.
func (s Set) Copy() Set {
func (s Set[T]) Copy() Set[T] {
ret := NewSet(s.rules)
for k, v := range s.vals {
ret.vals[k] = v
Expand All @@ -92,18 +92,18 @@ func (s Set) Copy() Set {
//
// Once an iterator has been created for a set, the set *must not* be mutated
// until the iterator is no longer in use.
func (s Set) Iterator() *Iterator {
func (s Set[T]) Iterator() *Iterator[T] {
vals := s.Values()

return &Iterator{
return &Iterator[T]{
vals: vals,
idx: -1,
}
}

// EachValue calls the given callback once for each value in the set, in an
// undefined order that callers should not depend on.
func (s Set) EachValue(cb func(interface{})) {
func (s Set[T]) EachValue(cb func(T)) {
it := s.Iterator()
for it.Next() {
cb(it.Value())
Expand All @@ -114,8 +114,8 @@ func (s Set) EachValue(cb func(interface{})) {
// an order then the result is in that order. If no order is provided or if
// it is not a total order then the result order is undefined, but consistent
// for a particular set value within a specific release of cty.
func (s Set) Values() []interface{} {
var ret []interface{}
func (s Set[T]) Values() []T {
var ret []T
// Sort the bucketIds to ensure that we always traverse in a
// consistent order.
bucketIDs := make([]int, 0, len(s.vals))
Expand All @@ -128,7 +128,7 @@ func (s Set) Values() []interface{} {
ret = append(ret, s.vals[bucketID]...)
}

if orderRules, ok := s.rules.(OrderedRules); ok {
if orderRules, ok := s.rules.(OrderedRules[T]); ok {
sort.SliceStable(ret, func(i, j int) bool {
return orderRules.Less(ret[i], ret[j])
})
Expand All @@ -138,7 +138,7 @@ func (s Set) Values() []interface{} {
}

// Length returns the number of values in the set.
func (s Set) Length() int {
func (s Set[T]) Length() int {
var count int
for _, bucket := range s.vals {
count = count + len(bucket)
Expand All @@ -149,13 +149,13 @@ func (s Set) Length() int {
// Union returns a new set that contains all of the members of both the
// receiving set and the given set. Both sets must have the same rules, or
// else this function will panic.
func (s1 Set) Union(s2 Set) Set {
func (s1 Set[T]) Union(s2 Set[T]) Set[T] {
mustHaveSameRules(s1, s2)
rs := NewSet(s1.rules)
s1.EachValue(func(v interface{}) {
s1.EachValue(func(v T) {
rs.Add(v)
})
s2.EachValue(func(v interface{}) {
s2.EachValue(func(v T) {
rs.Add(v)
})
return rs
Expand All @@ -164,10 +164,10 @@ func (s1 Set) Union(s2 Set) Set {
// Intersection returns a new set that contains the values that both the
// receiver and given sets have in common. Both sets must have the same rules,
// or else this function will panic.
func (s1 Set) Intersection(s2 Set) Set {
func (s1 Set[T]) Intersection(s2 Set[T]) Set[T] {
mustHaveSameRules(s1, s2)
rs := NewSet(s1.rules)
s1.EachValue(func(v interface{}) {
s1.EachValue(func(v T) {
if s2.Has(v) {
rs.Add(v)
}
Expand All @@ -178,10 +178,10 @@ func (s1 Set) Intersection(s2 Set) Set {
// Subtract returns a new set that contains all of the values from the receiver
// that are not also in the given set. Both sets must have the same rules,
// or else this function will panic.
func (s1 Set) Subtract(s2 Set) Set {
func (s1 Set[T]) Subtract(s2 Set[T]) Set[T] {
mustHaveSameRules(s1, s2)
rs := NewSet(s1.rules)
s1.EachValue(func(v interface{}) {
s1.EachValue(func(v T) {
if !s2.Has(v) {
rs.Add(v)
}
Expand All @@ -193,15 +193,15 @@ func (s1 Set) Subtract(s2 Set) Set {
// both the receiver and given sets, except those that both sets have in
// common. Both sets must have the same rules, or else this function will
// panic.
func (s1 Set) SymmetricDifference(s2 Set) Set {
func (s1 Set[T]) SymmetricDifference(s2 Set[T]) Set[T] {
mustHaveSameRules(s1, s2)
rs := NewSet(s1.rules)
s1.EachValue(func(v interface{}) {
s1.EachValue(func(v T) {
if !s2.Has(v) {
rs.Add(v)
}
})
s2.EachValue(func(v interface{}) {
s2.EachValue(func(v T) {
if !s1.Has(v) {
rs.Add(v)
}
Expand Down
Loading

0 comments on commit 74095df

Please sign in to comment.