1package stdlib
2
3import (
4	"fmt"
5
6	"github.com/zclconf/go-cty/cty/convert"
7
8	"github.com/zclconf/go-cty/cty"
9	"github.com/zclconf/go-cty/cty/function"
10)
11
12var SetHasElementFunc = function.New(&function.Spec{
13	Params: []function.Parameter{
14		{
15			Name:             "set",
16			Type:             cty.Set(cty.DynamicPseudoType),
17			AllowDynamicType: true,
18		},
19		{
20			Name:             "elem",
21			Type:             cty.DynamicPseudoType,
22			AllowDynamicType: true,
23		},
24	},
25	Type: function.StaticReturnType(cty.Bool),
26	Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
27		return args[0].HasElement(args[1]), nil
28	},
29})
30
31var SetUnionFunc = function.New(&function.Spec{
32	Params: []function.Parameter{
33		{
34			Name:             "first_set",
35			Type:             cty.Set(cty.DynamicPseudoType),
36			AllowDynamicType: true,
37		},
38	},
39	VarParam: &function.Parameter{
40		Name:             "other_sets",
41		Type:             cty.Set(cty.DynamicPseudoType),
42		AllowDynamicType: true,
43	},
44	Type: setOperationReturnType,
45	Impl: setOperationImpl(func(s1, s2 cty.ValueSet) cty.ValueSet {
46		return s1.Union(s2)
47	}, true),
48})
49
50var SetIntersectionFunc = function.New(&function.Spec{
51	Params: []function.Parameter{
52		{
53			Name:             "first_set",
54			Type:             cty.Set(cty.DynamicPseudoType),
55			AllowDynamicType: true,
56		},
57	},
58	VarParam: &function.Parameter{
59		Name:             "other_sets",
60		Type:             cty.Set(cty.DynamicPseudoType),
61		AllowDynamicType: true,
62	},
63	Type: setOperationReturnType,
64	Impl: setOperationImpl(func(s1, s2 cty.ValueSet) cty.ValueSet {
65		return s1.Intersection(s2)
66	}, false),
67})
68
69var SetSubtractFunc = function.New(&function.Spec{
70	Params: []function.Parameter{
71		{
72			Name:             "a",
73			Type:             cty.Set(cty.DynamicPseudoType),
74			AllowDynamicType: true,
75		},
76		{
77			Name:             "b",
78			Type:             cty.Set(cty.DynamicPseudoType),
79			AllowDynamicType: true,
80		},
81	},
82	Type: setOperationReturnType,
83	Impl: setOperationImpl(func(s1, s2 cty.ValueSet) cty.ValueSet {
84		return s1.Subtract(s2)
85	}, false),
86})
87
88var SetSymmetricDifferenceFunc = function.New(&function.Spec{
89	Params: []function.Parameter{
90		{
91			Name:             "first_set",
92			Type:             cty.Set(cty.DynamicPseudoType),
93			AllowDynamicType: true,
94		},
95	},
96	VarParam: &function.Parameter{
97		Name:             "other_sets",
98		Type:             cty.Set(cty.DynamicPseudoType),
99		AllowDynamicType: true,
100	},
101	Type: setOperationReturnType,
102	Impl: setOperationImpl(func(s1, s2 cty.ValueSet) cty.ValueSet {
103		return s1.SymmetricDifference(s2)
104	}, false),
105})
106
107// SetHasElement determines whether the given set contains the given value as an
108// element.
109func SetHasElement(set cty.Value, elem cty.Value) (cty.Value, error) {
110	return SetHasElementFunc.Call([]cty.Value{set, elem})
111}
112
113// SetUnion returns a new set containing all of the elements from the given
114// sets, which must have element types that can all be converted to some
115// common type using the standard type unification rules. If conversion
116// is not possible, an error is returned.
117//
118// The union operation is performed after type conversion, which may result
119// in some previously-distinct values being conflated.
120//
121// At least one set must be provided.
122func SetUnion(sets ...cty.Value) (cty.Value, error) {
123	return SetUnionFunc.Call(sets)
124}
125
126// Intersection returns a new set containing the elements that exist
127// in all of the given sets, which must have element types that can all be
128// converted to some common type using the standard type unification rules.
129// If conversion is not possible, an error is returned.
130//
131// The intersection operation is performed after type conversion, which may
132// result in some previously-distinct values being conflated.
133//
134// At least one set must be provided.
135func SetIntersection(sets ...cty.Value) (cty.Value, error) {
136	return SetIntersectionFunc.Call(sets)
137}
138
139// SetSubtract returns a new set containing the elements from the
140// first set that are not present in the second set. The sets must have
141// element types that can both be converted to some common type using the
142// standard type unification rules. If conversion is not possible, an error
143// is returned.
144//
145// The subtract operation is performed after type conversion, which may
146// result in some previously-distinct values being conflated.
147func SetSubtract(a, b cty.Value) (cty.Value, error) {
148	return SetSubtractFunc.Call([]cty.Value{a, b})
149}
150
151// SetSymmetricDifference returns a new set containing elements that appear
152// in any of the given sets but not multiple. The sets must have
153// element types that can all be converted to some common type using the
154// standard type unification rules. If conversion is not possible, an error
155// is returned.
156//
157// The difference operation is performed after type conversion, which may
158// result in some previously-distinct values being conflated.
159func SetSymmetricDifference(sets ...cty.Value) (cty.Value, error) {
160	return SetSymmetricDifferenceFunc.Call(sets)
161}
162
163func setOperationReturnType(args []cty.Value) (ret cty.Type, err error) {
164	var etys []cty.Type
165	for _, arg := range args {
166		ty := arg.Type().ElementType()
167
168		// Do not unify types for empty dynamic pseudo typed collections. These
169		// will always convert to any other concrete type.
170		if arg.IsKnown() && arg.LengthInt() == 0 && ty.Equals(cty.DynamicPseudoType) {
171			continue
172		}
173
174		etys = append(etys, ty)
175	}
176
177	// If all element types were skipped (due to being empty dynamic collections),
178	// the return type should also be a set of dynamic pseudo type.
179	if len(etys) == 0 {
180		return cty.Set(cty.DynamicPseudoType), nil
181	}
182
183	newEty, _ := convert.UnifyUnsafe(etys)
184	if newEty == cty.NilType {
185		return cty.NilType, fmt.Errorf("given sets must all have compatible element types")
186	}
187	return cty.Set(newEty), nil
188}
189
190func setOperationImpl(f func(s1, s2 cty.ValueSet) cty.ValueSet, allowUnknowns bool) function.ImplFunc {
191	return func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
192		first := args[0]
193		first, err = convert.Convert(first, retType)
194		if err != nil {
195			return cty.NilVal, function.NewArgError(0, err)
196		}
197		if !allowUnknowns && !first.IsWhollyKnown() {
198			// This set function can produce a correct result only when all
199			// elements are known, because eventually knowing the unknown
200			// values may cause the result to have fewer known elements, or
201			// might cause a result with no unknown elements at all to become
202			// one with a different length.
203			return cty.UnknownVal(retType), nil
204		}
205
206		set := first.AsValueSet()
207		for i, arg := range args[1:] {
208			arg, err := convert.Convert(arg, retType)
209			if err != nil {
210				return cty.NilVal, function.NewArgError(i+1, err)
211			}
212			if !allowUnknowns && !arg.IsWhollyKnown() {
213				// (For the same reason as we did this check for "first" above.)
214				return cty.UnknownVal(retType), nil
215			}
216
217			argSet := arg.AsValueSet()
218			set = f(set, argSet)
219		}
220		return cty.SetValFromValueSet(set), nil
221	}
222}
223