diff --git a/CHANGELOG.md b/CHANGELOG.md index 23a91d1b..935e9977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/cty/element_iterator.go b/cty/element_iterator.go index 9e4fff66..62c9ea57 100644 --- a/cty/element_iterator.go +++ b/cty/element_iterator.go @@ -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(), @@ -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) { diff --git a/cty/gocty/helpers.go b/cty/gocty/helpers.go index 94ffd2fb..98e5ba1a 100644 --- a/cty/gocty/helpers.go +++ b/cty/gocty/helpers.go @@ -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{}) diff --git a/cty/gocty/in.go b/cty/gocty/in.go index ca9de21d..6cb308b5 100644 --- a/cty/gocty/in.go +++ b/cty/gocty/in.go @@ -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 { diff --git a/cty/gocty/in_test.go b/cty/gocty/in_test.go index 42c8d629..96d97020 100644 --- a/cty/gocty/in_test.go +++ b/cty/gocty/in_test.go @@ -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), @@ -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 } diff --git a/cty/path_set.go b/cty/path_set.go index 1960c01e..3ebfdc38 100644 --- a/cty/path_set.go +++ b/cty/path_set.go @@ -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 { @@ -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 } @@ -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 { @@ -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 } @@ -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 } diff --git a/cty/set/iterator.go b/cty/set/iterator.go index 4a60494f..60825b0c 100644 --- a/cty/set/iterator.go +++ b/cty/set/iterator.go @@ -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) } diff --git a/cty/set/ops.go b/cty/set/ops.go index fd1555f2..ffd950ac 100644 --- a/cty/set/ops.go +++ b/cty/set/ops.go @@ -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] @@ -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 { @@ -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 { @@ -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 { @@ -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 @@ -92,10 +92,10 @@ 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, } @@ -103,7 +103,7 @@ func (s Set) Iterator() *Iterator { // 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()) @@ -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)) @@ -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]) }) @@ -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) @@ -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 @@ -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) } @@ -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) } @@ -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) } diff --git a/cty/set/ops_test.go b/cty/set/ops_test.go index 284c31e0..b0127323 100644 --- a/cty/set/ops_test.go +++ b/cty/set/ops_test.go @@ -11,13 +11,13 @@ import ( // directly on the underlying data structure. The remaining operations are implemented // in terms of these. func TestBasicSetOps(t *testing.T) { - s := NewSet(testRules{}) - want := map[int][]interface{}{} + s := NewSet(newTestRules()) + want := map[int][]int{} if !reflect.DeepEqual(s.vals, want) { t.Fatalf("new set has unexpected contents %#v; want %#v", s.vals, want) } s.Add(1) - want[1] = []interface{}{1} + want[1] = []int{1} if !reflect.DeepEqual(s.vals, want) { t.Fatalf("after s.Add(1) set has unexpected contents %#v; want %#v", s.vals, want) } @@ -25,7 +25,7 @@ func TestBasicSetOps(t *testing.T) { t.Fatalf("s.Has(1) returned false; want true") } s.Add(2) - want[2] = []interface{}{2} + want[2] = []int{2} if !reflect.DeepEqual(s.vals, want) { t.Fatalf("after s.Add(2) set has unexpected contents %#v; want %#v", s.vals, want) } @@ -52,8 +52,8 @@ func TestBasicSetOps(t *testing.T) { } vals := make([]int, 0) - s.EachValue(func(v interface{}) { - vals = append(vals, v.(int)) + s.EachValue(func(v int) { + vals = append(vals, v) }) sort.Ints(vals) if want := []int{1, 2, 17, 33}; !reflect.DeepEqual(vals, want) { @@ -67,13 +67,13 @@ func TestBasicSetOps(t *testing.T) { } s.Remove(17) - want[1] = []interface{}{1, 33} + want[1] = []int{1, 33} if !reflect.DeepEqual(s.vals, want) { t.Fatalf("after s.Remove(17) set has unexpected contents %#v; want %#v", s.vals, want) } s.Remove(1) - want[1] = []interface{}{33} + want[1] = []int{33} if !reflect.DeepEqual(s.vals, want) { t.Fatalf("after s.Remove(1) set has unexpected contents %#v; want %#v", s.vals, want) } @@ -85,8 +85,8 @@ func TestBasicSetOps(t *testing.T) { } vals = make([]int, 0) - s.EachValue(func(v interface{}) { - vals = append(vals, v.(int)) + s.EachValue(func(v int) { + vals = append(vals, v) }) if len(vals) > 0 { t.Fatalf("s.EachValue produced values %#v; want no calls", vals) @@ -95,38 +95,38 @@ func TestBasicSetOps(t *testing.T) { func TestUnion(t *testing.T) { tests := []struct { - s1 Set - s2 Set + s1 Set[int] + s2 Set[int] wantValues []int }{ { - NewSet(testRules{}), - NewSet(testRules{}), + NewSet(newTestRules()), + NewSet(newTestRules()), nil, }, { - NewSetFromSlice(testRules{}, []interface{}{1}), - NewSet(testRules{}), + NewSetFromSlice(newTestRules(), []int{1}), + NewSet(newTestRules()), []int{1}, }, { - NewSetFromSlice(testRules{}, []interface{}{1}), - NewSetFromSlice(testRules{}, []interface{}{2}), + NewSetFromSlice(newTestRules(), []int{1}), + NewSetFromSlice(newTestRules(), []int{2}), []int{1, 2}, }, { - NewSetFromSlice(testRules{}, []interface{}{1}), - NewSetFromSlice(testRules{}, []interface{}{1}), + NewSetFromSlice(newTestRules(), []int{1}), + NewSetFromSlice(newTestRules(), []int{1}), []int{1}, }, { - NewSetFromSlice(testRules{}, []interface{}{17, 33}), - NewSetFromSlice(testRules{}, []interface{}{1}), + NewSetFromSlice(newTestRules(), []int{17, 33}), + NewSetFromSlice(newTestRules(), []int{1}), []int{1, 17, 33}, }, { - NewSetFromSlice(testRules{}, []interface{}{17, 33}), - NewSetFromSlice(testRules{}, []interface{}{2, 1}), + NewSetFromSlice(newTestRules(), []int{17, 33}), + NewSetFromSlice(newTestRules(), []int{2, 1}), []int{1, 2, 17, 33}, }, } @@ -135,8 +135,8 @@ func TestUnion(t *testing.T) { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { got := test.s1.Union(test.s2) var gotValues []int - got.EachValue(func(v interface{}) { - gotValues = append(gotValues, v.(int)) + got.EachValue(func(v int) { + gotValues = append(gotValues, v) }) sort.Ints(gotValues) sort.Ints(test.wantValues) @@ -157,48 +157,48 @@ func TestUnion(t *testing.T) { func TestIntersection(t *testing.T) { tests := []struct { - s1 Set - s2 Set + s1 Set[int] + s2 Set[int] wantValues []int }{ { - NewSet(testRules{}), - NewSet(testRules{}), + NewSet(newTestRules()), + NewSet(newTestRules()), nil, }, { - NewSetFromSlice(testRules{}, []interface{}{1}), - NewSet(testRules{}), + NewSetFromSlice(newTestRules(), []int{1}), + NewSet(newTestRules()), nil, }, { - NewSetFromSlice(testRules{}, []interface{}{1}), - NewSetFromSlice(testRules{}, []interface{}{2}), + NewSetFromSlice(newTestRules(), []int{1}), + NewSetFromSlice(newTestRules(), []int{2}), nil, }, { - NewSetFromSlice(testRules{}, []interface{}{1}), - NewSetFromSlice(testRules{}, []interface{}{1}), + NewSetFromSlice(newTestRules(), []int{1}), + NewSetFromSlice(newTestRules(), []int{1}), []int{1}, }, { - NewSetFromSlice(testRules{}, []interface{}{1, 17}), - NewSetFromSlice(testRules{}, []interface{}{1, 2, 3}), + NewSetFromSlice(newTestRules(), []int{1, 17}), + NewSetFromSlice(newTestRules(), []int{1, 2, 3}), []int{1}, }, { - NewSetFromSlice(testRules{}, []interface{}{3, 2, 1}), - NewSetFromSlice(testRules{}, []interface{}{1, 2, 3}), + NewSetFromSlice(newTestRules(), []int{3, 2, 1}), + NewSetFromSlice(newTestRules(), []int{1, 2, 3}), []int{1, 2, 3}, }, { - NewSetFromSlice(testRules{}, []interface{}{17, 33}), - NewSetFromSlice(testRules{}, []interface{}{1}), + NewSetFromSlice(newTestRules(), []int{17, 33}), + NewSetFromSlice(newTestRules(), []int{1}), nil, }, { - NewSetFromSlice(testRules{}, []interface{}{17, 33}), - NewSetFromSlice(testRules{}, []interface{}{2, 1}), + NewSetFromSlice(newTestRules(), []int{17, 33}), + NewSetFromSlice(newTestRules(), []int{2, 1}), nil, }, } @@ -207,8 +207,8 @@ func TestIntersection(t *testing.T) { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { got := test.s1.Intersection(test.s2) var gotValues []int - got.EachValue(func(v interface{}) { - gotValues = append(gotValues, v.(int)) + got.EachValue(func(v int) { + gotValues = append(gotValues, v) }) sort.Ints(gotValues) sort.Ints(test.wantValues) @@ -229,48 +229,48 @@ func TestIntersection(t *testing.T) { func TestSubtract(t *testing.T) { tests := []struct { - s1 Set - s2 Set + s1 Set[int] + s2 Set[int] wantValues []int }{ { - NewSet(testRules{}), - NewSet(testRules{}), + NewSet(newTestRules()), + NewSet(newTestRules()), nil, }, { - NewSetFromSlice(testRules{}, []interface{}{1}), - NewSet(testRules{}), + NewSetFromSlice(newTestRules(), []int{1}), + NewSet(newTestRules()), []int{1}, }, { - NewSetFromSlice(testRules{}, []interface{}{1}), - NewSetFromSlice(testRules{}, []interface{}{2}), + NewSetFromSlice(newTestRules(), []int{1}), + NewSetFromSlice(newTestRules(), []int{2}), []int{1}, }, { - NewSetFromSlice(testRules{}, []interface{}{1}), - NewSetFromSlice(testRules{}, []interface{}{1}), + NewSetFromSlice(newTestRules(), []int{1}), + NewSetFromSlice(newTestRules(), []int{1}), nil, }, { - NewSetFromSlice(testRules{}, []interface{}{1, 17}), - NewSetFromSlice(testRules{}, []interface{}{1, 2, 3}), + NewSetFromSlice(newTestRules(), []int{1, 17}), + NewSetFromSlice(newTestRules(), []int{1, 2, 3}), []int{17}, }, { - NewSetFromSlice(testRules{}, []interface{}{3, 2, 1}), - NewSetFromSlice(testRules{}, []interface{}{1, 2, 3}), + NewSetFromSlice(newTestRules(), []int{3, 2, 1}), + NewSetFromSlice(newTestRules(), []int{1, 2, 3}), nil, }, { - NewSetFromSlice(testRules{}, []interface{}{17, 33}), - NewSetFromSlice(testRules{}, []interface{}{1}), + NewSetFromSlice(newTestRules(), []int{17, 33}), + NewSetFromSlice(newTestRules(), []int{1}), []int{17, 33}, }, { - NewSetFromSlice(testRules{}, []interface{}{17, 33}), - NewSetFromSlice(testRules{}, []interface{}{2, 1}), + NewSetFromSlice(newTestRules(), []int{17, 33}), + NewSetFromSlice(newTestRules(), []int{2, 1}), []int{17, 33}, }, } @@ -279,8 +279,8 @@ func TestSubtract(t *testing.T) { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { got := test.s1.Subtract(test.s2) var gotValues []int - got.EachValue(func(v interface{}) { - gotValues = append(gotValues, v.(int)) + got.EachValue(func(v int) { + gotValues = append(gotValues, v) }) sort.Ints(gotValues) sort.Ints(test.wantValues) @@ -301,48 +301,48 @@ func TestSubtract(t *testing.T) { func TestSymmetricDifference(t *testing.T) { tests := []struct { - s1 Set - s2 Set + s1 Set[int] + s2 Set[int] wantValues []int }{ { - NewSet(testRules{}), - NewSet(testRules{}), + NewSet(newTestRules()), + NewSet(newTestRules()), nil, }, { - NewSetFromSlice(testRules{}, []interface{}{1}), - NewSet(testRules{}), + NewSetFromSlice(newTestRules(), []int{1}), + NewSet(newTestRules()), []int{1}, }, { - NewSetFromSlice(testRules{}, []interface{}{1}), - NewSetFromSlice(testRules{}, []interface{}{2}), + NewSetFromSlice(newTestRules(), []int{1}), + NewSetFromSlice(newTestRules(), []int{2}), []int{1, 2}, }, { - NewSetFromSlice(testRules{}, []interface{}{1}), - NewSetFromSlice(testRules{}, []interface{}{1}), + NewSetFromSlice(newTestRules(), []int{1}), + NewSetFromSlice(newTestRules(), []int{1}), nil, }, { - NewSetFromSlice(testRules{}, []interface{}{1, 17}), - NewSetFromSlice(testRules{}, []interface{}{1, 2, 3}), + NewSetFromSlice(newTestRules(), []int{1, 17}), + NewSetFromSlice(newTestRules(), []int{1, 2, 3}), []int{2, 3, 17}, }, { - NewSetFromSlice(testRules{}, []interface{}{3, 2, 1}), - NewSetFromSlice(testRules{}, []interface{}{1, 2, 3}), + NewSetFromSlice(newTestRules(), []int{3, 2, 1}), + NewSetFromSlice(newTestRules(), []int{1, 2, 3}), nil, }, { - NewSetFromSlice(testRules{}, []interface{}{17, 33}), - NewSetFromSlice(testRules{}, []interface{}{1}), + NewSetFromSlice(newTestRules(), []int{17, 33}), + NewSetFromSlice(newTestRules(), []int{1}), []int{1, 17, 33}, }, { - NewSetFromSlice(testRules{}, []interface{}{17, 33}), - NewSetFromSlice(testRules{}, []interface{}{2, 1}), + NewSetFromSlice(newTestRules(), []int{17, 33}), + NewSetFromSlice(newTestRules(), []int{2, 1}), []int{1, 2, 17, 33}, }, } @@ -351,8 +351,8 @@ func TestSymmetricDifference(t *testing.T) { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { got := test.s1.SymmetricDifference(test.s2) var gotValues []int - got.EachValue(func(v interface{}) { - gotValues = append(gotValues, v.(int)) + got.EachValue(func(v int) { + gotValues = append(gotValues, v) }) sort.Ints(gotValues) sort.Ints(test.wantValues) diff --git a/cty/set/rules.go b/cty/set/rules.go index 03ecd25b..da4c7684 100644 --- a/cty/set/rules.go +++ b/cty/set/rules.go @@ -4,13 +4,13 @@ package set // // Each Set has a Rules instance, whose methods must satisfy the interface // contracts given below for any value that will be added to the set. -type Rules interface { +type Rules[T any] interface { // Hash returns an int that somewhat-uniquely identifies the given value. // // A good hash function will minimize collisions for values that will be // added to the set, though collisions *are* permitted. Collisions will // simply reduce the efficiency of operations on the set. - Hash(interface{}) int + Hash(T) int // Equivalent returns true if and only if the two values are considered // equivalent for the sake of set membership. Two values that are @@ -21,11 +21,11 @@ type Rules interface { // Two values that are equivalent *must* result in the same hash value, // though it is *not* required that two values with the same hash value // be equivalent. - Equivalent(interface{}, interface{}) bool + Equivalent(T, T) bool // SameRules returns true if the instance is equivalent to another Rules - // instance. - SameRules(Rules) bool + // instance over the same element type. + SameRules(Rules[T]) bool } // OrderedRules is an extension of Rules that can apply a partial order to @@ -37,8 +37,8 @@ type Rules interface { // is undefined but consistent for a particular version of cty. The exact // order in that case is not part of the contract and is subject to change // between versions. -type OrderedRules interface { - Rules +type OrderedRules[T any] interface { + Rules[T] // Less returns true if and only if the first argument should sort before // the second argument. If the second argument should sort before the first diff --git a/cty/set/rules_test.go b/cty/set/rules_test.go index b439cac0..2641d83e 100644 --- a/cty/set/rules_test.go +++ b/cty/set/rules_test.go @@ -6,14 +6,20 @@ package set // situation where two non-equivalent values have the same hash value. type testRules struct{} -func (r testRules) Hash(val interface{}) int { - return val.(int) % 16 +func newTestRules() Rules[int] { + return testRules{} } -func (r testRules) Equivalent(val1 interface{}, val2 interface{}) bool { +func (r testRules) Hash(val int) int { + return val % 16 +} + +func (r testRules) Equivalent(val1 int, val2 int) bool { return val1 == val2 } -func (r testRules) SameRules(other Rules) bool { - return r == other // true if "other" is also a testRules +func (r testRules) SameRules(other Rules[int]) bool { + // All testRules values are equal, so type-checking is enough. + _, ok := other.(testRules) + return ok } diff --git a/cty/set/set.go b/cty/set/set.go index 15a76638..761b0ffe 100644 --- a/cty/set/set.go +++ b/cty/set/set.go @@ -19,20 +19,20 @@ import ( // Set operations are not optimized to minimize memory pressure. Mutating // a set will generally create garbage and so should perhaps be avoided in // tight loops where memory pressure is a concern. -type Set struct { - vals map[int][]interface{} - rules Rules +type Set[T any] struct { + vals map[int][]T + rules Rules[T] } // NewSet returns an empty set with the membership rules given. -func NewSet(rules Rules) Set { - return Set{ - vals: map[int][]interface{}{}, +func NewSet[T any](rules Rules[T]) Set[T] { + return Set[T]{ + vals: map[int][]T{}, rules: rules, } } -func NewSetFromSlice(rules Rules, vals []interface{}) Set { +func NewSetFromSlice[T any](rules Rules[T], vals []T) Set[T] { s := NewSet(rules) for _, v := range vals { s.Add(v) @@ -40,11 +40,11 @@ func NewSetFromSlice(rules Rules, vals []interface{}) Set { return s } -func sameRules(s1 Set, s2 Set) bool { +func sameRules[T any](s1 Set[T], s2 Set[T]) bool { return s1.rules.SameRules(s2.rules) } -func mustHaveSameRules(s1 Set, s2 Set) { +func mustHaveSameRules[T any](s1 Set[T], s2 Set[T]) { if !sameRules(s1, s2) { panic(fmt.Errorf("incompatible set rules: %#v, %#v", s1.rules, s2.rules)) } @@ -52,11 +52,11 @@ func mustHaveSameRules(s1 Set, s2 Set) { // HasRules returns true if and only if the receiving set has the given rules // instance as its rules. -func (s Set) HasRules(rules Rules) bool { +func (s Set[T]) HasRules(rules Rules[T]) bool { return s.rules.SameRules(rules) } // Rules returns the receiving set's rules instance. -func (s Set) Rules() Rules { +func (s Set[T]) Rules() Rules[T] { return s.rules } diff --git a/cty/set_helper.go b/cty/set_helper.go index 962bb529..5d39805b 100644 --- a/cty/set_helper.go +++ b/cty/set_helper.go @@ -21,15 +21,15 @@ type ValueSet struct { // ValueSet is just a thin wrapper around a set.Set with our value-oriented // "rules" applied. We do this so that the caller can work in terms of // cty.Value objects even though the set internals use the raw values. - s set.Set + s set.Set[interface{}] } // NewValueSet creates and returns a new ValueSet with the given element type. func NewValueSet(ety Type) ValueSet { - return newValueSet(set.NewSet(setRules{Type: ety})) + return newValueSet(set.NewSet(newSetRules(ety))) } -func newValueSet(s set.Set) ValueSet { +func newValueSet(s set.Set[interface{}]) ValueSet { return ValueSet{ s: s, } diff --git a/cty/set_internals.go b/cty/set_internals.go index 2b8af1e2..6faa288a 100644 --- a/cty/set_internals.go +++ b/cty/set_internals.go @@ -21,7 +21,11 @@ type setRules struct { Type Type } -var _ set.OrderedRules = setRules{} +var _ set.OrderedRules[interface{}] = setRules{} + +func newSetRules(ety Type) set.Rules[interface{}] { + return setRules{ety} +} // Hash returns a hash value for the receiver that can be used for equality // checks where some inaccuracy is tolerable. @@ -67,7 +71,7 @@ func (r setRules) Equivalent(v1 interface{}, v2 interface{}) bool { // SameRules is only true if the other Rules instance is also a setRules struct, // and the types are considered equal. -func (r setRules) SameRules(other set.Rules) bool { +func (r setRules) SameRules(other set.Rules[interface{}]) bool { rules, ok := other.(setRules) if !ok { return false diff --git a/cty/set_internals_test.go b/cty/set_internals_test.go index 2f65f26c..95d848c6 100644 --- a/cty/set_internals_test.go +++ b/cty/set_internals_test.go @@ -303,8 +303,8 @@ func TestSetOrder(t *testing.T) { func TestSetRulesSameRules(t *testing.T) { tests := []struct { - a set.Rules - b set.Rules + a set.Rules[interface{}] + b set.Rules[interface{}] want bool }{ { @@ -332,16 +332,6 @@ func TestSetRulesSameRules(t *testing.T) { setRules{Object(map[string]Type{"a": Bool})}, false, }, - { - pathSetRules{}, - pathSetRules{}, - true, - }, - { - setRules{DynamicPseudoType}, - pathSetRules{}, - false, - }, } for _, test := range tests { diff --git a/cty/value_init.go b/cty/value_init.go index 25ee0b67..6dcae273 100644 --- a/cty/value_init.go +++ b/cty/value_init.go @@ -287,7 +287,7 @@ func SetVal(vals []Value) Value { rawList[i] = val.v } - rawVal := set.NewSetFromSlice(setRules{elementType}, rawList) + rawVal := set.NewSetFromSlice(set.Rules[interface{}](setRules{elementType}), rawList) return Value{ ty: Set(elementType), @@ -334,7 +334,7 @@ func SetValFromValueSet(s ValueSet) Value { func SetValEmpty(element Type) Value { return Value{ ty: Set(element), - v: set.NewSet(setRules{element}), + v: set.NewSet(set.Rules[interface{}](setRules{element})), } } diff --git a/cty/value_ops.go b/cty/value_ops.go index cdcc1506..88b3637c 100644 --- a/cty/value_ops.go +++ b/cty/value_ops.go @@ -47,6 +47,9 @@ func (val Value) GoString() string { } return "cty.False" case Number: + if f, ok := val.v.(big.Float); ok { + panic(fmt.Sprintf("number value contains big.Float value %s, rather than pointer to big.Float", f.Text('g', -1))) + } fv := val.v.(*big.Float) // We'll try to use NumberIntVal or NumberFloatVal if we can, since // the fully-general initializer call is pretty ugly-looking. @@ -265,8 +268,8 @@ func (val Value) Equals(other Value) Value { } } case ty.IsSetType(): - s1 := val.v.(set.Set) - s2 := other.v.(set.Set) + s1 := val.v.(set.Set[interface{}]) + s2 := other.v.(set.Set[interface{}]) equal := true // Two sets are equal if all of their values are known and all values @@ -983,7 +986,7 @@ func (val Value) HasElement(elem Value) Value { return False } - s := val.v.(set.Set) + s := val.v.(set.Set[interface{}]) return BoolVal(s.Has(elem.v)) } @@ -1017,7 +1020,7 @@ func (val Value) Length() Value { // may or may not be equal to other elements in the set, and thus they // may or may not coalesce with other elements and produce fewer // items in the resulting set. - storeLength := int64(val.v.(set.Set).Length()) + storeLength := int64(val.v.(set.Set[interface{}]).Length()) if storeLength == 1 || val.IsWhollyKnown() { // If our set is wholly known then we know its length. // @@ -1078,7 +1081,7 @@ func (val Value) LengthInt() int { // compatibility with callers that were relying on LengthInt rather // than calling Length. Instead of panicking when a set contains an // unknown value, LengthInt returns the largest possible length. - return val.v.(set.Set).Length() + return val.v.(set.Set[interface{}]).Length() case val.ty.IsMapType(): return len(val.v.(map[string]interface{})) diff --git a/go.mod b/go.mod index 2217e140..84633faa 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,11 @@ require ( golang.org/x/text v0.3.5 ) -go 1.12 +require ( + github.com/golang/protobuf v1.3.4 // indirect + github.com/vmihailenco/tagparser v0.1.1 // indirect + golang.org/x/net v0.0.0-20200301022130-244492dfa37a // indirect + google.golang.org/appengine v1.6.5 // indirect +) + +go 1.18