aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/zclconf/go-cty/cty/path_set.go
blob: f1c892b9d9d4a05804796f7f6dcff6a17fac0402 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package cty

import (
	"fmt"
	"hash/crc64"

	"github.com/zclconf/go-cty/cty/set"
)

// PathSet represents a set of Path objects. This can be used, for example,
// 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
}

// 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{}),
	}

	for _, path := range paths {
		ret.Add(path)
	}

	return ret
}

// Add inserts a single given path into the set.
//
// Paths are immutable after construction by convention. It is particularly
// important not to mutate a path after it has been placed into a PathSet.
// If a Path is mutated while in a set, behavior is undefined.
func (s PathSet) Add(path Path) {
	s.set.Add(path)
}

// AddAllSteps is like Add but it also adds all of the steps leading to
// the given path.
//
// For example, if given a path representing "foo.bar", it will add both
// "foo" and "bar".
func (s PathSet) AddAllSteps(path Path) {
	for i := 1; i <= len(path); i++ {
		s.Add(path[:i])
	}
}

// Has returns true if the given path is in the receiving set.
func (s PathSet) Has(path Path) bool {
	return s.set.Has(path)
}

// List makes and returns a slice of all of the paths in the receiving set,
// in an undefined but consistent order.
func (s PathSet) List() []Path {
	if s.Empty() {
		return nil
	}
	ret := make([]Path, 0, s.set.Length())
	for it := s.set.Iterator(); it.Next(); {
		ret = append(ret, it.Value().(Path))
	}
	return ret
}

// Remove modifies the receving set to no longer include the given path.
// If the given path was already absent, this is a no-op.
func (s PathSet) Remove(path Path) {
	s.set.Remove(path)
}

// Empty returns true if the length of the receiving set is zero.
func (s PathSet) Empty() bool {
	return s.set.Length() == 0
}

// Union returns a new set whose contents are the union of the receiver and
// the given other set.
func (s PathSet) Union(other PathSet) PathSet {
	return PathSet{
		set: s.set.Union(other.set),
	}
}

// Intersection returns a new set whose contents are the intersection of the
// receiver and the given other set.
func (s PathSet) Intersection(other PathSet) PathSet {
	return PathSet{
		set: s.set.Intersection(other.set),
	}
}

// Subtract returns a new set whose contents are those from the receiver with
// any elements of the other given set subtracted.
func (s PathSet) Subtract(other PathSet) PathSet {
	return PathSet{
		set: s.set.Subtract(other.set),
	}
}

// SymmetricDifference returns a new set whose contents are the symmetric
// difference of the receiver and the given other set.
func (s PathSet) SymmetricDifference(other PathSet) PathSet {
	return PathSet{
		set: s.set.SymmetricDifference(other.set),
	}
}

// Equal returns true if and only if both the receiver and the given other
// set contain exactly the same paths.
func (s PathSet) Equal(other PathSet) bool {
	if s.set.Length() != other.set.Length() {
		return false
	}
	// Now we know the lengths are the same we only need to test in one
	// direction whether everything in one is in the other.
	for it := s.set.Iterator(); it.Next(); {
		if !other.set.Has(it.Value()) {
			return false
		}
	}
	return true
}

var crc64Table = crc64.MakeTable(crc64.ISO)

var indexStepPlaceholder = []byte("#")

// pathSetRules is an implementation of set.Rules from the set package,
// used internally within PathSet.
type pathSetRules struct {
}

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

	for _, rawStep := range path {
		switch step := rawStep.(type) {
		case GetAttrStep:
			// (this creates some garbage converting the string name to a
			// []byte, but that's okay since cty is not designed to be
			// used in tight loops under memory pressure.)
			hash.Write([]byte(step.Name))
		default:
			// For any other step type we just append a predefined value,
			// which means that e.g. all indexes into a given collection will
			// hash to the same value but we assume that collections are
			// small and thus this won't hurt too much.
			hash.Write(indexStepPlaceholder)
		}
	}

	// We discard half of the hash on 32-bit platforms; collisions just make
	// our lookups take marginally longer, so not a big deal.
	return int(hash.Sum64())
}

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

	if len(aPath) != len(bPath) {
		return false
	}

	for i := range aPath {
		switch aStep := aPath[i].(type) {
		case GetAttrStep:
			bStep, ok := bPath[i].(GetAttrStep)
			if !ok {
				return false
			}

			if aStep.Name != bStep.Name {
				return false
			}
		case IndexStep:
			bStep, ok := bPath[i].(IndexStep)
			if !ok {
				return false
			}

			eq := aStep.Key.Equals(bStep.Key)
			if !eq.IsKnown() || eq.False() {
				return false
			}
		default:
			// Should never happen, since we document PathStep as a closed type.
			panic(fmt.Errorf("unsupported step type %T", aStep))
		}
	}

	return true
}