Bump github.com/hashicorp/terraform-plugin-sdk/v2 from 2.20.0 to 2.24.1

Bumps [github.com/hashicorp/terraform-plugin-sdk/v2](https://github.com/hashicorp/terraform-plugin-sdk) from 2.20.0 to 2.24.1.
- [Release notes](https://github.com/hashicorp/terraform-plugin-sdk/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-sdk/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-sdk/compare/v2.20.0...v2.24.1)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-sdk/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2022-12-24 16:57:19 +00:00
committed by Tobias Trabelsi
parent 683a051502
commit 282cd097f9
195 changed files with 3914 additions and 3093 deletions

View File

@ -49,6 +49,18 @@ type CapsuleOps struct {
// pointer identity of the encapsulated value.
RawEquals func(a, b interface{}) bool
// HashKey provides a hashing function for values of the corresponding
// capsule type. If defined, cty will use the resulting hashes as part
// of the implementation of sets whose element type is or contains the
// corresponding capsule type.
//
// If a capsule type defines HashValue then the function _must_ return
// an equal hash value for any two values that would cause Equals or
// RawEquals to return true when given those values. If a given type
// does not uphold that assumption then sets including this type will
// not behave correctly.
HashKey func(v interface{}) string
// ConversionFrom can provide conversions from the corresponding type to
// some other type when values of the corresponding type are used with
// the "convert" package. (The main cty package does not use this operation.)

View File

@ -43,14 +43,14 @@ func getConversion(in cty.Type, out cty.Type, unsafe bool) conversion {
out = out.WithoutOptionalAttributesDeep()
if !isKnown {
return cty.UnknownVal(out), nil
return cty.UnknownVal(dynamicReplace(in.Type(), out)), nil
}
if isNull {
// We'll pass through nulls, albeit type converted, and let
// the caller deal with whatever handling they want to do in
// case null values are considered valid in some applications.
return cty.NullVal(out), nil
return cty.NullVal(dynamicReplace(in.Type(), out)), nil
}
}

View File

@ -39,6 +39,11 @@ func conversionCollectionToList(ety cty.Type, conv conversion) conversion {
return cty.NilVal, err
}
}
if val.IsNull() {
val = cty.NullVal(val.Type().WithoutOptionalAttributesDeep())
}
elems = append(elems, val)
i++
@ -50,7 +55,7 @@ func conversionCollectionToList(ety cty.Type, conv conversion) conversion {
if ety == cty.DynamicPseudoType {
return cty.ListValEmpty(val.Type().ElementType()), nil
}
return cty.ListValEmpty(ety), nil
return cty.ListValEmpty(ety.WithoutOptionalAttributesDeep()), nil
}
if !cty.CanListVal(elems) {
@ -88,6 +93,11 @@ func conversionCollectionToSet(ety cty.Type, conv conversion) conversion {
return cty.NilVal, err
}
}
if val.IsNull() {
val = cty.NullVal(val.Type().WithoutOptionalAttributesDeep())
}
elems = append(elems, val)
i++
@ -99,7 +109,7 @@ func conversionCollectionToSet(ety cty.Type, conv conversion) conversion {
if ety == cty.DynamicPseudoType {
return cty.SetValEmpty(val.Type().ElementType()), nil
}
return cty.SetValEmpty(ety), nil
return cty.SetValEmpty(ety.WithoutOptionalAttributesDeep()), nil
}
if !cty.CanSetVal(elems) {
@ -180,7 +190,7 @@ func conversionTupleToSet(tupleType cty.Type, setEty cty.Type, unsafe bool) conv
if len(tupleEtys) == 0 {
// Empty tuple short-circuit
return func(val cty.Value, path cty.Path) (cty.Value, error) {
return cty.SetValEmpty(setEty), nil
return cty.SetValEmpty(setEty.WithoutOptionalAttributesDeep()), nil
}
}
@ -242,6 +252,11 @@ func conversionTupleToSet(tupleType cty.Type, setEty cty.Type, unsafe bool) conv
return cty.NilVal, err
}
}
if val.IsNull() {
val = cty.NullVal(val.Type().WithoutOptionalAttributesDeep())
}
elems = append(elems, val)
i++
@ -265,7 +280,7 @@ func conversionTupleToList(tupleType cty.Type, listEty cty.Type, unsafe bool) co
if len(tupleEtys) == 0 {
// Empty tuple short-circuit
return func(val cty.Value, path cty.Path) (cty.Value, error) {
return cty.ListValEmpty(listEty), nil
return cty.ListValEmpty(listEty.WithoutOptionalAttributesDeep()), nil
}
}
@ -357,7 +372,7 @@ func conversionObjectToMap(objectType cty.Type, mapEty cty.Type, unsafe bool) co
if len(objectAtys) == 0 {
// Empty object short-circuit
return func(val cty.Value, path cty.Path) (cty.Value, error) {
return cty.MapValEmpty(mapEty), nil
return cty.MapValEmpty(mapEty.WithoutOptionalAttributesDeep()), nil
}
}
@ -448,13 +463,28 @@ func conversionMapToObject(mapType cty.Type, objType cty.Type, unsafe bool) conv
elemConvs[name] = getConversion(mapEty, objectAty, unsafe)
if elemConvs[name] == nil {
// If any of our element conversions are impossible, then the our
// whole conversion is impossible.
// This means that this conversion is impossible. Typically, we
// would give up at this point and declare the whole conversion
// impossible. But, if this attribute is optional then maybe we will
// be able to do this conversion anyway provided the actual concrete
// map doesn't have this value set.
//
// We only do this in "unsafe" mode, because we cannot guarantee
// that the returned conversion will actually succeed once applied.
if objType.AttributeOptional(name) && unsafe {
// This attribute is optional, so let's leave this conversion in
// as a nil, and we can error later if we actually have to
// convert this.
continue
}
// Otherwise, give up. This conversion is impossible as we have a
// required attribute that doesn't match the map's inner type.
return nil
}
}
// If we fall out here then a conversion is possible, using the
// If we fall out here then a conversion may be possible, using the
// element conversions in elemConvs
return func(val cty.Value, path cty.Path) (cty.Value, error) {
elems := make(map[string]cty.Value, len(elemConvs))
@ -474,12 +504,43 @@ func conversionMapToObject(mapType cty.Type, objType cty.Type, unsafe bool) conv
Key: name,
}
conv := elemConvs[name.AsString()]
if conv != nil {
// There are 3 cases here:
// 1. This attribute is not in elemConvs
// 2. This attribute is in elemConvs and is not nil
// 3. This attribute is in elemConvs and is nil.
// In case 1, we do not enter any of the branches below. This case
// means the attribute type is the same between the map and the
// object, and we don't need to do any conversion.
if conv, ok := elemConvs[name.AsString()]; conv != nil {
// This is case 2. The attribute type is different between the
// map and the object, and we know how to convert between them.
// So, we reset val to be the converted value and carry on.
val, err = conv(val, elemPath)
if err != nil {
return cty.NilVal, err
}
} else if ok {
// This is case 3 and it is an error. The attribute types are
// different between the map and the object, but we cannot
// convert between them.
//
// Now typically, this would be picked earlier on when we were
// building elemConvs. However, in the case of optional
// attributes there was a chance we could still convert the
// overall object even if this particular attribute was not
// convertable. This is because it could have not been set in
// the map, and we could skip over it here and set a null value.
//
// Since we reached this branch, we know that map did actually
// contain a non-convertable optional attribute. This means we
// error.
return cty.NilVal, path.NewErrorf("map element type is incompatible with attribute %q: %s", name.AsString(), MismatchMessage(val.Type(), objType.AttributeType(name.AsString())))
}
if val.IsNull() {
val = cty.NullVal(val.Type().WithoutOptionalAttributesDeep())
}
elems[name.AsString()] = val

View File

@ -31,3 +31,107 @@ func dynamicFixup(wantType cty.Type) conversion {
func dynamicPassthrough(in cty.Value, path cty.Path) (cty.Value, error) {
return in, nil
}
// dynamicReplace aims to return the out type unchanged, but if it finds a
// dynamic type either directly or in any descendent elements it replaces them
// with the equivalent type from in.
//
// This function assumes that in and out are compatible from a Convert
// perspective, and will panic if it finds that they are not. For example if
// in is an object and out is a map, this function will still attempt to iterate
// through both as if they were the same.
func dynamicReplace(in, out cty.Type) cty.Type {
if in == cty.DynamicPseudoType || in == cty.NilType {
// Short circuit this case, there's no point worrying about this if in
// is a dynamic type or a nil type. Out is the best we can do.
return out
}
switch {
case out == cty.DynamicPseudoType:
// So replace out with in.
return in
case out.IsPrimitiveType(), out.IsCapsuleType():
// out is not dynamic and it doesn't contain descendent elements so just
// return it unchanged.
return out
case out.IsMapType():
var elemType cty.Type
// Maps are compatible with other maps or objects.
if in.IsMapType() {
elemType = dynamicReplace(in.ElementType(), out.ElementType())
}
if in.IsObjectType() {
var types []cty.Type
for _, t := range in.AttributeTypes() {
types = append(types, t)
}
unifiedType, _ := unify(types, true)
elemType = dynamicReplace(unifiedType, out.ElementType())
}
return cty.Map(elemType)
case out.IsObjectType():
// Objects are compatible with other objects and maps.
outTypes := map[string]cty.Type{}
if in.IsMapType() {
for attr, attrType := range out.AttributeTypes() {
outTypes[attr] = dynamicReplace(in.ElementType(), attrType)
}
}
if in.IsObjectType() {
for attr, attrType := range out.AttributeTypes() {
if !in.HasAttribute(attr) {
// If in does not have this attribute, then it is an
// optional attribute and there is nothing we can do except
// to return the type from out even if it is dynamic.
outTypes[attr] = attrType
continue
}
outTypes[attr] = dynamicReplace(in.AttributeType(attr), attrType)
}
}
return cty.Object(outTypes)
case out.IsSetType():
var elemType cty.Type
// Sets are compatible with other sets, lists, tuples.
if in.IsSetType() || in.IsListType() {
elemType = dynamicReplace(in.ElementType(), out.ElementType())
}
if in.IsTupleType() {
unifiedType, _ := unify(in.TupleElementTypes(), true)
elemType = dynamicReplace(unifiedType, out.ElementType())
}
return cty.Set(elemType)
case out.IsListType():
var elemType cty.Type
// Lists are compatible with other lists, sets, and tuples.
if in.IsSetType() || in.IsListType() {
elemType = dynamicReplace(in.ElementType(), out.ElementType())
}
if in.IsTupleType() {
unifiedType, _ := unify(in.TupleElementTypes(), true)
elemType = dynamicReplace(unifiedType, out.ElementType())
}
return cty.List(elemType)
case out.IsTupleType():
// Tuples are only compatible with other tuples
var types []cty.Type
for ix := 0; ix < len(out.TupleElementTypes()); ix++ {
types = append(types, dynamicReplace(in.TupleElementType(ix), out.TupleElementType(ix)))
}
return cty.Tuple(types)
default:
panic("unrecognized type " + out.FriendlyName())
}
}

View File

@ -80,13 +80,19 @@ func conversionObjectToObject(in, out cty.Type, unsafe bool) conversion {
}
}
if val.IsNull() {
// Strip optional attributes out of the embedded type for null
// values.
val = cty.NullVal(val.Type().WithoutOptionalAttributesDeep())
}
attrVals[name] = val
}
for name := range outOptionals {
if _, exists := attrVals[name]; !exists {
wantTy := outAtys[name]
attrVals[name] = cty.NullVal(wantTy)
attrVals[name] = cty.NullVal(wantTy.WithoutOptionalAttributesDeep())
}
}

View File

@ -40,7 +40,7 @@ func GetConversionUnsafe(in cty.Type, out cty.Type) Conversion {
// This is a convenience wrapper around calling GetConversionUnsafe and then
// immediately passing the given value to the resulting function.
func Convert(in cty.Value, want cty.Type) (cty.Value, error) {
if in.Type().Equals(want) {
if in.Type().Equals(want.WithoutOptionalAttributesDeep()) {
return in, nil
}

View File

@ -447,7 +447,6 @@ func unifyTupleTypes(types []cty.Type, unsafe bool, hasDynamic bool) (cty.Type,
conversions[i] = GetConversion(ty, retTy)
}
if conversions[i] == nil {
// Shouldn't be reachable, since we were able to unify
return unifyTupleTypesToList(types, unsafe)
}
}
@ -483,8 +482,8 @@ func unifyTupleTypesToList(types []cty.Type, unsafe bool) (cty.Type, []Conversio
conversions[i] = GetConversion(ty, retTy)
}
if conversions[i] == nil {
// Shouldn't be reachable, since we were able to unify
return unifyObjectTypesToMap(types, unsafe)
// no conversion was found
return cty.NilType, nil
}
}
return retTy, conversions

View File

@ -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) {

View File

@ -10,6 +10,9 @@ type Parameter struct {
// value, but callers may use it for documentation, etc.
Name string
// Description is an optional description for the argument.
Description string
// A type that any argument for this parameter must conform to.
// cty.DynamicPseudoType can be used, either at top-level or nested
// in a parameterized type, to indicate that any type should be

View File

@ -14,6 +14,9 @@ type Function struct {
// Spec is the specification of a function, used to instantiate
// a new Function.
type Spec struct {
// Description is an optional description for the function specification.
Description string
// Params is a description of the positional parameters for the function.
// The standard checking logic rejects any calls that do not provide
// arguments conforming to this definition, freeing the function
@ -344,3 +347,62 @@ func (f Function) VarParam() *Parameter {
ret := *f.spec.VarParam
return &ret
}
// Description returns a human-readable description of the function.
func (f Function) Description() string {
return f.spec.Description
}
// WithNewDescriptions returns a new function that has the same signature
// and implementation as the receiver but has the function description and
// the parameter descriptions replaced with those given in the arguments.
//
// All descriptions may be given as an empty string to specify that there
// should be no description at all.
//
// The paramDescs argument must match the number of parameters
// the reciever expects, or this function will panic. If the function has a
// VarParam then that counts as one parameter for the sake of this rule. The
// given descriptions will be assigned in order starting with the positional
// arguments in their declared order, followed by the variadic parameter if
// any.
//
// As a special case, WithNewDescriptions will accept a paramDescs which
// does not cover the reciever's variadic parameter (if any), so that it's
// possible to add a variadic parameter to a function which didn't previously
// have one without that being a breaking change for an existing caller using
// WithNewDescriptions against that function. In this case the base description
// of the variadic parameter will be preserved.
func (f Function) WithNewDescriptions(funcDesc string, paramDescs []string) Function {
retSpec := *f.spec // shallow copy of the reciever
retSpec.Description = funcDesc
retSpec.Params = make([]Parameter, len(f.spec.Params))
copy(retSpec.Params, f.spec.Params) // shallow copy of positional parameters
if f.spec.VarParam != nil {
retVarParam := *f.spec.VarParam // shallow copy of variadic parameter
retSpec.VarParam = &retVarParam
}
if retSpec.VarParam != nil {
if with, without := len(retSpec.Params)+1, len(retSpec.Params); len(paramDescs) != with && len(paramDescs) != without {
panic(fmt.Sprintf("paramDescs must have length of either %d or %d", with, without))
}
} else {
if want := len(retSpec.Params); len(paramDescs) != want {
panic(fmt.Sprintf("paramDescs must have length %d", want))
}
}
posParamDescs := paramDescs[:len(retSpec.Params)]
varParamDescs := paramDescs[len(retSpec.Params):] // guaranteed to be zero or one elements because of the rules above
for i, desc := range posParamDescs {
retSpec.Params[i].Description = desc
}
for _, desc := range varParamDescs {
retSpec.VarParam.Description = desc
}
return New(&retSpec)
}

View File

@ -6,6 +6,7 @@ import (
)
var NotFunc = function.New(&function.Spec{
Description: `Applies the logical NOT operation to the given boolean value.`,
Params: []function.Parameter{
{
Name: "val",
@ -21,6 +22,7 @@ var NotFunc = function.New(&function.Spec{
})
var AndFunc = function.New(&function.Spec{
Description: `Applies the logical AND operation to the given boolean values.`,
Params: []function.Parameter{
{
Name: "a",
@ -42,6 +44,7 @@ var AndFunc = function.New(&function.Spec{
})
var OrFunc = function.New(&function.Spec{
Description: `Applies the logical OR operation to the given boolean values.`,
Params: []function.Parameter{
{
Name: "a",

View File

@ -30,6 +30,7 @@ func BytesVal(buf []byte) cty.Value {
// BytesLen is a Function that returns the length of the buffer encapsulated
// in a Bytes value.
var BytesLenFunc = function.New(&function.Spec{
Description: `Returns the total number of bytes in the given buffer.`,
Params: []function.Parameter{
{
Name: "buf",
@ -46,6 +47,7 @@ var BytesLenFunc = function.New(&function.Spec{
// BytesSlice is a Function that returns a slice of the given Bytes value.
var BytesSliceFunc = function.New(&function.Spec{
Description: `Extracts a subslice from the given buffer.`,
Params: []function.Parameter{
{
Name: "buf",

View File

@ -12,6 +12,7 @@ import (
)
var HasIndexFunc = function.New(&function.Spec{
Description: `Returns true if if the given collection can be indexed with the given key without producing an error, or false otherwise.`,
Params: []function.Parameter{
{
Name: "collection",
@ -37,6 +38,7 @@ var HasIndexFunc = function.New(&function.Spec{
})
var IndexFunc = function.New(&function.Spec{
Description: `Returns the element with the given key from the given collection, or raises an error if there is no such element.`,
Params: []function.Parameter{
{
Name: "collection",
@ -106,6 +108,7 @@ var IndexFunc = function.New(&function.Spec{
})
var LengthFunc = function.New(&function.Spec{
Description: `Returns the number of elements in the given collection.`,
Params: []function.Parameter{
{
Name: "collection",
@ -127,6 +130,7 @@ var LengthFunc = function.New(&function.Spec{
})
var ElementFunc = function.New(&function.Spec{
Description: `Returns the element with the given index from the given list or tuple, applying the modulo operation to the given index if it's greater than the number of elements.`,
Params: []function.Parameter{
{
Name: "list",
@ -206,9 +210,11 @@ var ElementFunc = function.New(&function.Spec{
// CoalesceListFunc is a function that takes any number of list arguments
// and returns the first one that isn't empty.
var CoalesceListFunc = function.New(&function.Spec{
Params: []function.Parameter{},
Description: `Returns the first of the given sequences that has a length greater than zero.`,
Params: []function.Parameter{},
VarParam: &function.Parameter{
Name: "vals",
Description: `List or tuple values to test in the given order.`,
Type: cty.DynamicPseudoType,
AllowUnknown: true,
AllowDynamicType: true,
@ -270,6 +276,7 @@ var CoalesceListFunc = function.New(&function.Spec{
// CompactFunc is a function that takes a list of strings and returns a new list
// with any empty string elements removed.
var CompactFunc = function.New(&function.Spec{
Description: `Removes all empty string elements from the given list of strings.`,
Params: []function.Parameter{
{
Name: "list",
@ -306,6 +313,7 @@ var CompactFunc = function.New(&function.Spec{
// ContainsFunc is a function that determines whether a given list or
// set contains a given single value as one of its elements.
var ContainsFunc = function.New(&function.Spec{
Description: `Returns true if the given value is a value in the given list, tuple, or set, or false otherwise.`,
Params: []function.Parameter{
{
Name: "list",
@ -364,6 +372,7 @@ var ContainsFunc = function.New(&function.Spec{
// DistinctFunc is a function that takes a list and returns a new list
// with any duplicate elements removed.
var DistinctFunc = function.New(&function.Spec{
Description: `Removes any duplicate values from the given list, preserving the order of remaining elements.`,
Params: []function.Parameter{
{
Name: "list",
@ -399,14 +408,17 @@ var DistinctFunc = function.New(&function.Spec{
// ChunklistFunc is a function that splits a single list into fixed-size chunks,
// returning a list of lists.
var ChunklistFunc = function.New(&function.Spec{
Description: `Splits a single list into multiple lists where each has at most the given number of elements.`,
Params: []function.Parameter{
{
Name: "list",
Description: `The list to split into chunks.`,
Type: cty.List(cty.DynamicPseudoType),
AllowMarked: true,
},
{
Name: "size",
Description: `The maximum length of each chunk. All but the last element of the result is guaranteed to be of exactly this size.`,
Type: cty.Number,
AllowMarked: true,
},
@ -471,6 +483,7 @@ var ChunklistFunc = function.New(&function.Spec{
// FlattenFunc is a function that takes a list and replaces any elements
// that are lists with a flattened sequence of the list contents.
var FlattenFunc = function.New(&function.Spec{
Description: `Transforms a list, set, or tuple value into a tuple by replacing any given elements that are themselves sequences with a flattened tuple of all of the nested elements concatenated together.`,
Params: []function.Parameter{
{
Name: "list",
@ -525,6 +538,7 @@ func flattener(flattenList cty.Value) ([]cty.Value, []cty.ValueMarks, bool) {
if len(flattenListMarks) > 0 {
markses = append(markses, flattenListMarks)
}
if !flattenList.Length().IsKnown() {
// If we don't know the length of what we're flattening then we can't
// predict the length of our result yet either.
@ -542,7 +556,7 @@ func flattener(flattenList cty.Value) ([]cty.Value, []cty.ValueMarks, bool) {
isKnown = false
}
if val.Type().IsListType() || val.Type().IsSetType() || val.Type().IsTupleType() {
if !val.IsNull() && (val.Type().IsListType() || val.Type().IsSetType() || val.Type().IsTupleType()) {
if !val.IsKnown() {
isKnown = false
_, unknownMarks := val.Unmark()
@ -566,9 +580,11 @@ func flattener(flattenList cty.Value) ([]cty.Value, []cty.ValueMarks, bool) {
// KeysFunc is a function that takes a map and returns a sorted list of the map keys.
var KeysFunc = function.New(&function.Spec{
Description: `Returns a list of the keys of the given map in lexicographical order.`,
Params: []function.Parameter{
{
Name: "inputMap",
Description: `The map to extract keys from. May instead be an object-typed value, in which case the result is a tuple of the object attributes.`,
Type: cty.DynamicPseudoType,
AllowUnknown: true,
AllowMarked: true,
@ -641,6 +657,7 @@ var KeysFunc = function.New(&function.Spec{
// LookupFunc is a function that performs dynamic lookups of map types.
var LookupFunc = function.New(&function.Spec{
Description: `Returns the value of the element with the given key from the given map, or returns the default value if there is no such element.`,
Params: []function.Parameter{
{
Name: "inputMap",
@ -733,7 +750,8 @@ var LookupFunc = function.New(&function.Spec{
// If more than one given map or object defines the same key then the one that
// is later in the argument sequence takes precedence.
var MergeFunc = function.New(&function.Spec{
Params: []function.Parameter{},
Description: `Merges all of the elements from the given maps into a single map, or the attributes from given objects into a single object.`,
Params: []function.Parameter{},
VarParam: &function.Parameter{
Name: "maps",
Type: cty.DynamicPseudoType,
@ -849,6 +867,7 @@ var MergeFunc = function.New(&function.Spec{
// ReverseListFunc takes a sequence and produces a new sequence of the same length
// with all of the same elements as the given sequence but in reverse order.
var ReverseListFunc = function.New(&function.Spec{
Description: `Returns the given list with its elements in reverse order.`,
Params: []function.Parameter{
{
Name: "list",
@ -897,9 +916,11 @@ var ReverseListFunc = function.New(&function.Spec{
// preserving the ordering of all of the input lists. Otherwise the result is a
// set of tuples.
var SetProductFunc = function.New(&function.Spec{
Params: []function.Parameter{},
Description: `Calculates the cartesian product of two or more sets.`,
Params: []function.Parameter{},
VarParam: &function.Parameter{
Name: "sets",
Description: "The sets to consider. Also accepts lists and tuples, and if all arguments are of list or tuple type then the result will preserve the input ordering",
Type: cty.DynamicPseudoType,
AllowMarked: true,
},
@ -1037,6 +1058,7 @@ var SetProductFunc = function.New(&function.Spec{
// SliceFunc is a function that extracts some consecutive elements
// from within a list.
var SliceFunc = function.New(&function.Spec{
Description: `Extracts a subslice of the given list or tuple value.`,
Params: []function.Parameter{
{
Name: "list",
@ -1158,9 +1180,10 @@ func sliceIndexes(args []cty.Value) (int, int, bool, error) {
// ValuesFunc is a function that returns a list of the map values,
// in the order of the sorted keys.
var ValuesFunc = function.New(&function.Spec{
Description: `Returns the values of elements of a given map, or the values of attributes of a given object, in lexicographic order by key or attribute name.`,
Params: []function.Parameter{
{
Name: "values",
Name: "mapping",
Type: cty.DynamicPseudoType,
AllowMarked: true,
},
@ -1225,6 +1248,7 @@ var ValuesFunc = function.New(&function.Spec{
// ZipmapFunc is a function that constructs a map from a list of keys
// and a corresponding list of values.
var ZipmapFunc = function.New(&function.Spec{
Description: `Constructs a map from a list of keys and a corresponding list of values, which must both be of the same length.`,
Params: []function.Parameter{
{
Name: "keys",

View File

@ -1,6 +1,7 @@
package stdlib
import (
"fmt"
"strconv"
"github.com/zclconf/go-cty/cty"
@ -18,6 +19,7 @@ import (
// a tuple.
func MakeToFunc(wantTy cty.Type) function.Function {
return function.New(&function.Spec{
Description: fmt.Sprintf("Converts the given value to %s, or raises an error if that conversion is impossible.", wantTy.FriendlyName()),
Params: []function.Parameter{
{
Name: "v",

View File

@ -11,6 +11,7 @@ import (
)
var CSVDecodeFunc = function.New(&function.Spec{
Description: `Parses the given string as Comma Separated Values (as defined by RFC 4180) and returns a map of objects representing the table of data, using the first row as a header row to define the object attributes.`,
Params: []function.Parameter{
{
Name: "str",

View File

@ -12,6 +12,7 @@ import (
)
var FormatDateFunc = function.New(&function.Spec{
Description: `Formats a timestamp given in RFC 3339 syntax into another timestamp in some other machine-oriented time syntax, as described in the format string.`,
Params: []function.Parameter{
{
Name: "format",
@ -205,6 +206,7 @@ var FormatDateFunc = function.New(&function.Spec{
// TimeAddFunc is a function that adds a duration to a timestamp, returning a new timestamp.
var TimeAddFunc = function.New(&function.Spec{
Description: `Adds the duration represented by the given duration string to the given RFC 3339 timestamp string, returning another RFC 3339 timestamp.`,
Params: []function.Parameter{
{
Name: "timestamp",

View File

@ -18,6 +18,7 @@ import (
//go:generate gofmt -w format_fsm.go
var FormatFunc = function.New(&function.Spec{
Description: `Constructs a string by applying formatting verbs to a series of arguments, using a similar syntax to the C function \"printf\".`,
Params: []function.Parameter{
{
Name: "format",
@ -45,6 +46,7 @@ var FormatFunc = function.New(&function.Spec{
})
var FormatListFunc = function.New(&function.Spec{
Description: `Constructs a list of strings by applying formatting verbs to a series of arguments, using a similar syntax to the C function \"printf\".`,
Params: []function.Parameter{
{
Name: "format",

View File

@ -9,6 +9,7 @@ import (
)
var EqualFunc = function.New(&function.Spec{
Description: `Returns true if the two given values are equal, or false otherwise.`,
Params: []function.Parameter{
{
Name: "a",
@ -32,6 +33,7 @@ var EqualFunc = function.New(&function.Spec{
})
var NotEqualFunc = function.New(&function.Spec{
Description: `Returns false if the two given values are equal, or true otherwise.`,
Params: []function.Parameter{
{
Name: "a",
@ -55,7 +57,8 @@ var NotEqualFunc = function.New(&function.Spec{
})
var CoalesceFunc = function.New(&function.Spec{
Params: []function.Parameter{},
Description: `Returns the first of the given arguments that isn't null, or raises an error if there are no non-null arguments.`,
Params: []function.Parameter{},
VarParam: &function.Parameter{
Name: "vals",
Type: cty.DynamicPseudoType,

View File

@ -7,6 +7,7 @@ import (
)
var JSONEncodeFunc = function.New(&function.Spec{
Description: `Returns a string containing a JSON representation of the given value.`,
Params: []function.Parameter{
{
Name: "val",
@ -39,6 +40,7 @@ var JSONEncodeFunc = function.New(&function.Spec{
})
var JSONDecodeFunc = function.New(&function.Spec{
Description: `Parses the given string as JSON and returns a value corresponding to what the JSON document describes.`,
Params: []function.Parameter{
{
Name: "str",

View File

@ -11,6 +11,7 @@ import (
)
var AbsoluteFunc = function.New(&function.Spec{
Description: `If the given number is negative then returns its positive equivalent, or otherwise returns the given number unchanged.`,
Params: []function.Parameter{
{
Name: "num",
@ -26,6 +27,7 @@ var AbsoluteFunc = function.New(&function.Spec{
})
var AddFunc = function.New(&function.Spec{
Description: `Returns the sum of the two given numbers.`,
Params: []function.Parameter{
{
Name: "a",
@ -59,6 +61,7 @@ var AddFunc = function.New(&function.Spec{
})
var SubtractFunc = function.New(&function.Spec{
Description: `Returns the difference between the two given numbers.`,
Params: []function.Parameter{
{
Name: "a",
@ -92,6 +95,7 @@ var SubtractFunc = function.New(&function.Spec{
})
var MultiplyFunc = function.New(&function.Spec{
Description: `Returns the product of the two given numbers.`,
Params: []function.Parameter{
{
Name: "a",
@ -126,6 +130,7 @@ var MultiplyFunc = function.New(&function.Spec{
})
var DivideFunc = function.New(&function.Spec{
Description: `Divides the first given number by the second.`,
Params: []function.Parameter{
{
Name: "a",
@ -160,6 +165,7 @@ var DivideFunc = function.New(&function.Spec{
})
var ModuloFunc = function.New(&function.Spec{
Description: `Divides the first given number by the second and then returns the remainder.`,
Params: []function.Parameter{
{
Name: "a",
@ -194,6 +200,7 @@ var ModuloFunc = function.New(&function.Spec{
})
var GreaterThanFunc = function.New(&function.Spec{
Description: `Returns true if and only if the second number is greater than the first.`,
Params: []function.Parameter{
{
Name: "a",
@ -215,6 +222,7 @@ var GreaterThanFunc = function.New(&function.Spec{
})
var GreaterThanOrEqualToFunc = function.New(&function.Spec{
Description: `Returns true if and only if the second number is greater than or equal to the first.`,
Params: []function.Parameter{
{
Name: "a",
@ -236,6 +244,7 @@ var GreaterThanOrEqualToFunc = function.New(&function.Spec{
})
var LessThanFunc = function.New(&function.Spec{
Description: `Returns true if and only if the second number is less than the first.`,
Params: []function.Parameter{
{
Name: "a",
@ -257,6 +266,7 @@ var LessThanFunc = function.New(&function.Spec{
})
var LessThanOrEqualToFunc = function.New(&function.Spec{
Description: `Returns true if and only if the second number is less than or equal to the first.`,
Params: []function.Parameter{
{
Name: "a",
@ -278,6 +288,7 @@ var LessThanOrEqualToFunc = function.New(&function.Spec{
})
var NegateFunc = function.New(&function.Spec{
Description: `Multiplies the given number by -1.`,
Params: []function.Parameter{
{
Name: "num",
@ -293,7 +304,8 @@ var NegateFunc = function.New(&function.Spec{
})
var MinFunc = function.New(&function.Spec{
Params: []function.Parameter{},
Description: `Returns the numerically smallest of all of the given numbers.`,
Params: []function.Parameter{},
VarParam: &function.Parameter{
Name: "numbers",
Type: cty.Number,
@ -317,7 +329,8 @@ var MinFunc = function.New(&function.Spec{
})
var MaxFunc = function.New(&function.Spec{
Params: []function.Parameter{},
Description: `Returns the numerically greatest of all of the given numbers.`,
Params: []function.Parameter{},
VarParam: &function.Parameter{
Name: "numbers",
Type: cty.Number,
@ -341,6 +354,7 @@ var MaxFunc = function.New(&function.Spec{
})
var IntFunc = function.New(&function.Spec{
Description: `Discards any fractional portion of the given number.`,
Params: []function.Parameter{
{
Name: "num",
@ -363,6 +377,7 @@ var IntFunc = function.New(&function.Spec{
// CeilFunc is a function that returns the closest whole number greater
// than or equal to the given value.
var CeilFunc = function.New(&function.Spec{
Description: `Returns the smallest whole number that is greater than or equal to the given value.`,
Params: []function.Parameter{
{
Name: "num",
@ -392,6 +407,7 @@ var CeilFunc = function.New(&function.Spec{
// FloorFunc is a function that returns the closest whole number lesser
// than or equal to the given value.
var FloorFunc = function.New(&function.Spec{
Description: `Returns the greatest whole number that is less than or equal to the given value.`,
Params: []function.Parameter{
{
Name: "num",
@ -420,6 +436,7 @@ var FloorFunc = function.New(&function.Spec{
// LogFunc is a function that returns the logarithm of a given number in a given base.
var LogFunc = function.New(&function.Spec{
Description: `Returns the logarithm of the given number in the given base.`,
Params: []function.Parameter{
{
Name: "num",
@ -448,6 +465,7 @@ var LogFunc = function.New(&function.Spec{
// PowFunc is a function that returns the logarithm of a given number in a given base.
var PowFunc = function.New(&function.Spec{
Description: `Returns the given number raised to the given power (exponentiation).`,
Params: []function.Parameter{
{
Name: "num",
@ -477,6 +495,7 @@ var PowFunc = function.New(&function.Spec{
// SignumFunc is a function that determines the sign of a number, returning a
// number between -1 and 1 to represent the sign..
var SignumFunc = function.New(&function.Spec{
Description: `Returns 0 if the given number is zero, 1 if the given number is positive, or -1 if the given number is negative.`,
Params: []function.Parameter{
{
Name: "num",
@ -502,6 +521,7 @@ var SignumFunc = function.New(&function.Spec{
// ParseIntFunc is a function that parses a string argument and returns an integer of the specified base.
var ParseIntFunc = function.New(&function.Spec{
Description: `Parses the given string as a number of the given base, or raises an error if the string contains invalid characters.`,
Params: []function.Parameter{
{
Name: "number",

View File

@ -10,6 +10,7 @@ import (
)
var RegexFunc = function.New(&function.Spec{
Description: `Applies the given regular expression pattern to the given string and returns information about a single match, or raises an error if there is no match.`,
Params: []function.Parameter{
{
Name: "pattern",
@ -54,6 +55,7 @@ var RegexFunc = function.New(&function.Spec{
})
var RegexAllFunc = function.New(&function.Spec{
Description: `Applies the given regular expression pattern to the given string and returns a list of information about all non-overlapping matches, or an empty list if there are no matches.`,
Params: []function.Parameter{
{
Name: "pattern",

View File

@ -9,7 +9,8 @@ import (
)
var ConcatFunc = function.New(&function.Spec{
Params: []function.Parameter{},
Description: `Concatenates together all of the given lists or tuples into a single sequence, preserving the input order.`,
Params: []function.Parameter{},
VarParam: &function.Parameter{
Name: "seqs",
Type: cty.DynamicPseudoType,
@ -137,6 +138,7 @@ var ConcatFunc = function.New(&function.Spec{
})
var RangeFunc = function.New(&function.Spec{
Description: `Returns a list of numbers spread evenly over a particular range.`,
VarParam: &function.Parameter{
Name: "params",
Type: cty.Number,

View File

@ -10,6 +10,7 @@ import (
)
var SetHasElementFunc = function.New(&function.Spec{
Description: `Returns true if the given set contains the given element, or false otherwise.`,
Params: []function.Parameter{
{
Name: "set",
@ -29,6 +30,7 @@ var SetHasElementFunc = function.New(&function.Spec{
})
var SetUnionFunc = function.New(&function.Spec{
Description: `Returns the union of all given sets.`,
Params: []function.Parameter{
{
Name: "first_set",
@ -48,6 +50,7 @@ var SetUnionFunc = function.New(&function.Spec{
})
var SetIntersectionFunc = function.New(&function.Spec{
Description: `Returns the intersection of all given sets.`,
Params: []function.Parameter{
{
Name: "first_set",
@ -67,6 +70,7 @@ var SetIntersectionFunc = function.New(&function.Spec{
})
var SetSubtractFunc = function.New(&function.Spec{
Description: `Returns the relative complement of the two given sets.`,
Params: []function.Parameter{
{
Name: "a",
@ -86,6 +90,7 @@ var SetSubtractFunc = function.New(&function.Spec{
})
var SetSymmetricDifferenceFunc = function.New(&function.Spec{
Description: `Returns the symmetric difference of the two given sets.`,
Params: []function.Parameter{
{
Name: "first_set",

View File

@ -14,6 +14,7 @@ import (
)
var UpperFunc = function.New(&function.Spec{
Description: "Returns the given string with all Unicode letters translated to their uppercase equivalents.",
Params: []function.Parameter{
{
Name: "str",
@ -30,6 +31,7 @@ var UpperFunc = function.New(&function.Spec{
})
var LowerFunc = function.New(&function.Spec{
Description: "Returns the given string with all Unicode letters translated to their lowercase equivalents.",
Params: []function.Parameter{
{
Name: "str",
@ -46,6 +48,7 @@ var LowerFunc = function.New(&function.Spec{
})
var ReverseFunc = function.New(&function.Spec{
Description: "Returns the given string with all of its Unicode characters in reverse order.",
Params: []function.Parameter{
{
Name: "str",
@ -73,6 +76,7 @@ var ReverseFunc = function.New(&function.Spec{
})
var StrlenFunc = function.New(&function.Spec{
Description: "Returns the number of Unicode characters (technically: grapheme clusters) in the given string.",
Params: []function.Parameter{
{
Name: "str",
@ -97,19 +101,23 @@ var StrlenFunc = function.New(&function.Spec{
})
var SubstrFunc = function.New(&function.Spec{
Description: "Extracts a substring from the given string.",
Params: []function.Parameter{
{
Name: "str",
Description: "The input string.",
Type: cty.String,
AllowDynamicType: true,
},
{
Name: "offset",
Description: "The starting offset in Unicode characters.",
Type: cty.Number,
AllowDynamicType: true,
},
{
Name: "length",
Description: "The maximum length of the result in Unicode characters.",
Type: cty.Number,
AllowDynamicType: true,
},
@ -197,15 +205,18 @@ var SubstrFunc = function.New(&function.Spec{
})
var JoinFunc = function.New(&function.Spec{
Description: "Concatenates together the elements of all given lists with a delimiter, producing a single string.",
Params: []function.Parameter{
{
Name: "separator",
Type: cty.String,
Name: "separator",
Description: "Delimiter to insert between the given strings.",
Type: cty.String,
},
},
VarParam: &function.Parameter{
Name: "lists",
Type: cty.List(cty.String),
Name: "lists",
Description: "One or more lists of strings to join.",
Type: cty.List(cty.String),
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
@ -244,6 +255,7 @@ var JoinFunc = function.New(&function.Spec{
})
var SortFunc = function.New(&function.Spec{
Description: "Applies a lexicographic sort to the elements of the given list.",
Params: []function.Parameter{
{
Name: "list",
@ -282,14 +294,17 @@ var SortFunc = function.New(&function.Spec{
})
var SplitFunc = function.New(&function.Spec{
Description: "Produces a list of one or more strings by splitting the given string at all instances of a given separator substring.",
Params: []function.Parameter{
{
Name: "separator",
Type: cty.String,
Name: "separator",
Description: "The substring that delimits the result strings.",
Type: cty.String,
},
{
Name: "str",
Type: cty.String,
Name: "str",
Description: "The string to split.",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.List(cty.String)),
@ -311,6 +326,7 @@ var SplitFunc = function.New(&function.Spec{
// ChompFunc is a function that removes newline characters at the end of a
// string.
var ChompFunc = function.New(&function.Spec{
Description: "Removes one or more newline characters from the end of the given string.",
Params: []function.Parameter{
{
Name: "str",
@ -327,14 +343,17 @@ var ChompFunc = function.New(&function.Spec{
// IndentFunc is a function that adds a given number of spaces to the
// beginnings of all but the first line in a given multi-line string.
var IndentFunc = function.New(&function.Spec{
Description: "Adds a given number of spaces after each newline character in the given string.",
Params: []function.Parameter{
{
Name: "spaces",
Type: cty.Number,
Name: "spaces",
Description: "Number of spaces to add after each newline character.",
Type: cty.Number,
},
{
Name: "str",
Type: cty.String,
Name: "str",
Description: "The string to transform.",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
@ -352,6 +371,7 @@ var IndentFunc = function.New(&function.Spec{
// TitleFunc is a function that converts the first letter of each word in the
// given string to uppercase.
var TitleFunc = function.New(&function.Spec{
Description: "Replaces one letter after each non-letter and non-digit character with its uppercase equivalent.",
Params: []function.Parameter{
{
Name: "str",
@ -367,6 +387,7 @@ var TitleFunc = function.New(&function.Spec{
// TrimSpaceFunc is a function that removes any space characters from the start
// and end of the given string.
var TrimSpaceFunc = function.New(&function.Spec{
Description: "Removes any consecutive space characters (as defined by Unicode) from the start and end of the given string.",
Params: []function.Parameter{
{
Name: "str",
@ -382,20 +403,26 @@ var TrimSpaceFunc = function.New(&function.Spec{
// TrimFunc is a function that removes the specified characters from the start
// and end of the given string.
var TrimFunc = function.New(&function.Spec{
Description: "Removes consecutive sequences of characters in \"cutset\" from the start and end of the given string.",
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
Name: "str",
Description: "The string to trim.",
Type: cty.String,
},
{
Name: "cutset",
Type: cty.String,
Name: "cutset",
Description: "A string containing all of the characters to trim. Each character is taken separately, so the order of characters is insignificant.",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
str := args[0].AsString()
cutset := args[1].AsString()
// NOTE: This doesn't properly handle any character that is encoded
// with multiple sequential code units, such as letters with
// combining diacritics and emoji modifier sequences.
return cty.StringVal(strings.Trim(str, cutset)), nil
},
})
@ -403,14 +430,17 @@ var TrimFunc = function.New(&function.Spec{
// TrimPrefixFunc is a function that removes the specified characters from the
// start the given string.
var TrimPrefixFunc = function.New(&function.Spec{
Description: "Removes the given prefix from the start of the given string, if present.",
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
Name: "str",
Description: "The string to trim.",
Type: cty.String,
},
{
Name: "prefix",
Type: cty.String,
Name: "prefix",
Description: "The prefix to remove, if present.",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
@ -424,14 +454,17 @@ var TrimPrefixFunc = function.New(&function.Spec{
// TrimSuffixFunc is a function that removes the specified characters from the
// end of the given string.
var TrimSuffixFunc = function.New(&function.Spec{
Description: "Removes the given suffix from the start of the given string, if present.",
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
Name: "str",
Description: "The string to trim.",
Type: cty.String,
},
{
Name: "suffix",
Type: cty.String,
Name: "suffix",
Description: "The suffix to remove, if present.",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),

View File

@ -12,18 +12,22 @@ import (
// substring, and replaces each occurence with a given replacement string.
// The substr argument is a simple string.
var ReplaceFunc = function.New(&function.Spec{
Description: `Replaces all instances of the given substring in the given string with the given replacement string.`,
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
Name: "str",
Description: `The string to search within.`,
Type: cty.String,
},
{
Name: "substr",
Type: cty.String,
Name: "substr",
Description: `The substring to search for.`,
Type: cty.String,
},
{
Name: "replace",
Type: cty.String,
Name: "replace",
Description: `The new substring to replace substr with.`,
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
@ -40,13 +44,14 @@ var ReplaceFunc = function.New(&function.Spec{
// given substring, and replaces each occurence with a given replacement
// string. The substr argument must be a valid regular expression.
var RegexReplaceFunc = function.New(&function.Spec{
Description: `Applies the given regular expression pattern to the given string and replaces all matches with the given replacement string.`,
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
{
Name: "substr",
Name: "pattern",
Type: cty.String,
},
{

View File

@ -1,204 +0,0 @@
package cty
import (
"bytes"
"encoding/gob"
"errors"
"fmt"
"math/big"
"github.com/zclconf/go-cty/cty/set"
)
// GobEncode is an implementation of the gob.GobEncoder interface, which
// allows Values to be included in structures encoded with encoding/gob.
//
// Currently it is not possible to represent values of capsule types in gob,
// because the types themselves cannot be represented.
func (val Value) GobEncode() ([]byte, error) {
if val.IsMarked() {
return nil, errors.New("value is marked")
}
buf := &bytes.Buffer{}
enc := gob.NewEncoder(buf)
gv := gobValue{
Version: 0,
Ty: val.ty,
V: val.v,
}
err := enc.Encode(gv)
if err != nil {
return nil, fmt.Errorf("error encoding cty.Value: %s", err)
}
return buf.Bytes(), nil
}
// GobDecode is an implementation of the gob.GobDecoder interface, which
// inverts the operation performed by GobEncode. See the documentation of
// GobEncode for considerations when using cty.Value instances with gob.
func (val *Value) GobDecode(buf []byte) error {
r := bytes.NewReader(buf)
dec := gob.NewDecoder(r)
var gv gobValue
err := dec.Decode(&gv)
if err != nil {
return fmt.Errorf("error decoding cty.Value: %s", err)
}
if gv.Version != 0 {
return fmt.Errorf("unsupported cty.Value encoding version %d; only 0 is supported", gv.Version)
}
// Because big.Float.GobEncode is implemented with a pointer reciever,
// gob encoding of an interface{} containing a *big.Float value does not
// round-trip correctly, emerging instead as a non-pointer big.Float.
// The rest of cty expects all number values to be represented by
// *big.Float, so we'll fix that up here.
gv.V = gobDecodeFixNumberPtr(gv.V, gv.Ty)
val.ty = gv.Ty
val.v = gv.V
return nil
}
// GobEncode is an implementation of the gob.GobEncoder interface, which
// allows Types to be included in structures encoded with encoding/gob.
//
// Currently it is not possible to represent capsule types in gob.
func (t Type) GobEncode() ([]byte, error) {
buf := &bytes.Buffer{}
enc := gob.NewEncoder(buf)
gt := gobType{
Version: 0,
Impl: t.typeImpl,
}
err := enc.Encode(gt)
if err != nil {
return nil, fmt.Errorf("error encoding cty.Type: %s", err)
}
return buf.Bytes(), nil
}
// GobDecode is an implementatino of the gob.GobDecoder interface, which
// reverses the encoding performed by GobEncode to allow types to be recovered
// from gob buffers.
func (t *Type) GobDecode(buf []byte) error {
r := bytes.NewReader(buf)
dec := gob.NewDecoder(r)
var gt gobType
err := dec.Decode(&gt)
if err != nil {
return fmt.Errorf("error decoding cty.Type: %s", err)
}
if gt.Version != 0 {
return fmt.Errorf("unsupported cty.Type encoding version %d; only 0 is supported", gt.Version)
}
t.typeImpl = gt.Impl
return nil
}
// Capsule types cannot currently be gob-encoded, because they rely on pointer
// equality and we have no way to recover the original pointer on decode.
func (t *capsuleType) GobEncode() ([]byte, error) {
return nil, fmt.Errorf("cannot gob-encode capsule type %q", t.FriendlyName(friendlyTypeName))
}
func (t *capsuleType) GobDecode() ([]byte, error) {
return nil, fmt.Errorf("cannot gob-decode capsule type %q", t.FriendlyName(friendlyTypeName))
}
type gobValue struct {
Version int
Ty Type
V interface{}
}
type gobType struct {
Version int
Impl typeImpl
}
type gobCapsuleTypeImpl struct {
}
// goDecodeFixNumberPtr fixes an unfortunate quirk of round-tripping cty.Number
// values through gob: the big.Float.GobEncode method is implemented on a
// pointer receiver, and so it loses the "pointer-ness" of the value on
// encode, causing the values to emerge the other end as big.Float rather than
// *big.Float as we expect elsewhere in cty.
//
// The implementation of gobDecodeFixNumberPtr mutates the given raw value
// during its work, and may either return the same value mutated or a new
// value. Callers must no longer use whatever value they pass as "raw" after
// this function is called.
func gobDecodeFixNumberPtr(raw interface{}, ty Type) interface{} {
// Unfortunately we need to work recursively here because number values
// might be embedded in structural or collection type values.
switch {
case ty.Equals(Number):
if bf, ok := raw.(big.Float); ok {
return &bf // wrap in pointer
}
case ty.IsMapType() && ty.ElementType().Equals(Number):
if m, ok := raw.(map[string]interface{}); ok {
for k, v := range m {
m[k] = gobDecodeFixNumberPtr(v, ty.ElementType())
}
}
case ty.IsListType() && ty.ElementType().Equals(Number):
if s, ok := raw.([]interface{}); ok {
for i, v := range s {
s[i] = gobDecodeFixNumberPtr(v, ty.ElementType())
}
}
case ty.IsSetType() && ty.ElementType().Equals(Number):
if s, ok := raw.(set.Set); ok {
newS := set.NewSet(s.Rules())
for it := s.Iterator(); it.Next(); {
newV := gobDecodeFixNumberPtr(it.Value(), ty.ElementType())
newS.Add(newV)
}
return newS
}
case ty.IsObjectType():
if m, ok := raw.(map[string]interface{}); ok {
for k, v := range m {
aty := ty.AttributeType(k)
m[k] = gobDecodeFixNumberPtr(v, aty)
}
}
case ty.IsTupleType():
if s, ok := raw.([]interface{}); ok {
for i, v := range s {
ety := ty.TupleElementType(i)
s[i] = gobDecodeFixNumberPtr(v, ety)
}
}
}
return raw
}
// gobDecodeFixNumberPtrVal is a helper wrapper around gobDecodeFixNumberPtr
// that works with already-constructed values. This is primarily for testing,
// to fix up intentionally-invalid number values for the parts of the test
// code that need them to be valid, such as calling GoString on them.
func gobDecodeFixNumberPtrVal(v Value) Value {
raw := gobDecodeFixNumberPtr(v.v, v.ty)
return Value{
v: raw,
ty: v.ty,
}
}

View File

@ -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{})

View File

@ -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 {

View File

@ -51,7 +51,7 @@ func (t typeMap) GoString() string {
return fmt.Sprintf("cty.Map(%#v)", t.ElementTypeT)
}
// IsMapType returns true if the given type is a list type, regardless of its
// IsMapType returns true if the given type is a map type, regardless of its
// element type.
func (t Type) IsMapType() bool {
_, ok := t.typeImpl.(typeMap)

View File

@ -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
}

View File

@ -74,6 +74,8 @@ func rawNumberEqual(a, b *big.Float) bool {
return false
case a == nil: // b == nil too then, due to previous case
return true
case a.Sign() != b.Sign():
return false
default:
// This format and precision matches that used by cty/json.Marshal,
// and thus achieves our definition of "two numbers are equal if

View File

@ -1,76 +0,0 @@
package set
import (
"bytes"
"encoding/gob"
"fmt"
)
// GobEncode is an implementation of the interface gob.GobEncoder, allowing
// sets to be included in structures encoded via gob.
//
// The set rules are included in the serialized value, so the caller must
// register its concrete rules type with gob.Register before using a
// set in a gob, and possibly also implement GobEncode/GobDecode to customize
// how any parameters are persisted.
//
// The set elements are also included, so if they are of non-primitive types
// they too must be registered with gob.
//
// If the produced gob values will persist for a long time, the caller must
// ensure compatibility of the rules implementation. In particular, if the
// definition of element equivalence changes between encoding and decoding
// then two distinct stored elements may be considered equivalent on decoding,
// causing the recovered set to have fewer elements than when it was stored.
func (s Set) GobEncode() ([]byte, error) {
gs := gobSet{
Version: 0,
Rules: s.rules,
Values: s.Values(),
}
buf := &bytes.Buffer{}
enc := gob.NewEncoder(buf)
err := enc.Encode(gs)
if err != nil {
return nil, fmt.Errorf("error encoding set.Set: %s", err)
}
return buf.Bytes(), nil
}
// GobDecode is the opposite of GobEncode. See GobEncode for information
// on the requirements for and caveats of including set values in gobs.
func (s *Set) GobDecode(buf []byte) error {
r := bytes.NewReader(buf)
dec := gob.NewDecoder(r)
var gs gobSet
err := dec.Decode(&gs)
if err != nil {
return fmt.Errorf("error decoding set.Set: %s", err)
}
if gs.Version != 0 {
return fmt.Errorf("unsupported set.Set encoding version %d; need 0", gs.Version)
}
victim := NewSetFromSlice(gs.Rules, gs.Values)
s.vals = victim.vals
s.rules = victim.rules
return nil
}
type gobSet struct {
Version int
Rules Rules
// The bucket-based representation is for efficient in-memory access, but
// for serialization it's enough to just retain the values themselves,
// which we can re-bucket using the rules (which may have changed!) when
// we re-inflate.
Values []interface{}
}
func init() {
gob.Register([]interface{}(nil))
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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,
}

View File

@ -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
@ -250,6 +254,25 @@ func appendSetHashBytes(val Value, buf *bytes.Buffer, marks ValueMarks) {
return
}
if val.ty.IsCapsuleType() {
buf.WriteRune('«')
ops := val.ty.CapsuleOps()
if ops != nil && ops.HashKey != nil {
key := ops.HashKey(val.EncapsulatedValue())
buf.WriteString(fmt.Sprintf("%q", key))
} else {
// If there isn't an explicit hash implementation then we'll
// just generate the same hash value for every value of this
// type, which is logically fine but less efficient for
// larger sets because we'll have to bucket all values
// together and scan over them with Equals to determine
// set membership.
buf.WriteRune('?')
}
buf.WriteRune('»')
return
}
// should never get down here
panic("unsupported type in set hash")
panic(fmt.Sprintf("unsupported type %#v in set hash", val.ty))
}

View File

@ -1,57 +0,0 @@
package cty
import (
"encoding/gob"
"fmt"
"math/big"
"strings"
"github.com/zclconf/go-cty/cty/set"
)
// InternalTypesToRegister is a slice of values that covers all of the
// internal types used in the representation of cty.Type and cty.Value
// across all cty Types.
//
// This is intended to be used to register these types with encoding
// packages that require registration of types used in interfaces, such as
// encoding/gob, thus allowing cty types and values to be included in streams
// created from those packages. However, registering with gob is not necessary
// since that is done automatically as a side-effect of importing this package.
//
// Callers should not do anything with the values here except pass them on
// verbatim to a registration function.
//
// If the calling application uses Capsule types that wrap local structs either
// directly or indirectly, these structs may also need to be registered in
// order to support encoding and decoding of values of these types. That is the
// responsibility of the calling application.
var InternalTypesToRegister []interface{}
func init() {
InternalTypesToRegister = []interface{}{
primitiveType{},
typeList{},
typeMap{},
typeObject{},
typeSet{},
setRules{},
set.Set{},
typeTuple{},
big.Float{},
capsuleType{},
[]interface{}(nil),
map[string]interface{}(nil),
}
// Register these with gob here, rather than in gob.go, to ensure
// that this will always happen after we build the above.
for _, tv := range InternalTypesToRegister {
typeName := fmt.Sprintf("%T", tv)
if strings.HasPrefix(typeName, "cty.") {
gob.RegisterName(fmt.Sprintf("github.com/zclconf/go-cty/%s", typeName), tv)
} else {
gob.Register(tv)
}
}
}

View File

@ -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})),
}
}

View File

@ -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{}))