bump github.com/zclconf/go-cty from 1.7.1 to 1.10.0

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2022-01-13 14:32:10 +01:00
parent 785c861233
commit b67bdedb23
30 changed files with 13397 additions and 193 deletions

View File

@ -142,7 +142,7 @@ func (f Function) ReturnTypeForValues(args []cty.Value) (ty cty.Type, err error)
for i, spec := range f.spec.Params {
val := posArgs[i]
if val.IsMarked() && !spec.AllowMarked {
if val.ContainsMarked() && !spec.AllowMarked {
// During type checking we just unmark values and discard their
// marks, under the assumption that during actual execution of
// the function we'll do similarly and then re-apply the marks
@ -150,7 +150,7 @@ func (f Function) ReturnTypeForValues(args []cty.Value) (ty cty.Type, err error)
// inspects values (rather than just types) in its Type
// implementation can potentially fail to take into account marks,
// unless it specifically opts in to seeing them.
unmarked, _ := val.Unmark()
unmarked, _ := val.UnmarkDeep()
newArgs := make([]cty.Value, len(args))
copy(newArgs, args)
newArgs[i] = unmarked
@ -183,9 +183,9 @@ func (f Function) ReturnTypeForValues(args []cty.Value) (ty cty.Type, err error)
for i, val := range varArgs {
realI := i + len(posArgs)
if val.IsMarked() && !spec.AllowMarked {
if val.ContainsMarked() && !spec.AllowMarked {
// See the similar block in the loop above for what's going on here.
unmarked, _ := val.Unmark()
unmarked, _ := val.UnmarkDeep()
newArgs := make([]cty.Value, len(args))
copy(newArgs, args)
newArgs[realI] = unmarked

View File

@ -111,6 +111,7 @@ var LengthFunc = function.New(&function.Spec{
Name: "collection",
Type: cty.DynamicPseudoType,
AllowDynamicType: true,
AllowMarked: true,
},
},
Type: func(args []cty.Value) (ret cty.Type, err error) {
@ -128,8 +129,9 @@ var LengthFunc = function.New(&function.Spec{
var ElementFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "list",
Type: cty.DynamicPseudoType,
Name: "list",
Type: cty.DynamicPseudoType,
AllowMarked: true,
},
{
Name: "index",
@ -184,11 +186,12 @@ var ElementFunc = function.New(&function.Spec{
return cty.DynamicVal, fmt.Errorf("cannot use element function with a negative index")
}
if !args[0].IsKnown() {
input, marks := args[0].Unmark()
if !input.IsKnown() {
return cty.UnknownVal(retType), nil
}
l := args[0].LengthInt()
l := input.LengthInt()
if l == 0 {
return cty.DynamicVal, errors.New("cannot use element function with an empty list")
}
@ -196,7 +199,7 @@ var ElementFunc = function.New(&function.Spec{
// We did all the necessary type checks in the type function above,
// so this is guaranteed not to fail.
return args[0].Index(cty.NumberIntVal(int64(index))), nil
return input.Index(cty.NumberIntVal(int64(index))).WithMarks(marks), nil
},
})
@ -398,12 +401,14 @@ var DistinctFunc = function.New(&function.Spec{
var ChunklistFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "list",
Type: cty.List(cty.DynamicPseudoType),
Name: "list",
Type: cty.List(cty.DynamicPseudoType),
AllowMarked: true,
},
{
Name: "size",
Type: cty.Number,
Name: "size",
Type: cty.Number,
AllowMarked: true,
},
},
Type: func(args []cty.Value) (cty.Type, error) {
@ -411,35 +416,40 @@ var ChunklistFunc = function.New(&function.Spec{
},
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
listVal := args[0]
if !listVal.IsKnown() {
return cty.UnknownVal(retType), nil
}
if listVal.LengthInt() == 0 {
return cty.ListValEmpty(listVal.Type()), nil
}
sizeVal := args[1]
listVal, listMarks := listVal.Unmark()
sizeVal, sizeMarks := sizeVal.Unmark()
// All return paths below must include .WithMarks(retMarks) to propagate
// the top-level marks into the return value. Deep marks inside the
// list will just propagate naturally because we treat those values
// as opaque here.
retMarks := cty.NewValueMarks(listMarks, sizeMarks)
var size int
err = gocty.FromCtyValue(args[1], &size)
err = gocty.FromCtyValue(sizeVal, &size)
if err != nil {
return cty.NilVal, fmt.Errorf("invalid index: %s", err)
return cty.NilVal, fmt.Errorf("invalid size: %s", err)
}
if size < 0 {
return cty.NilVal, errors.New("the size argument must be positive")
}
if listVal.LengthInt() == 0 {
return cty.ListValEmpty(listVal.Type()).WithMarks(retMarks), nil
}
output := make([]cty.Value, 0)
// if size is 0, returns a list made of the initial list
if size == 0 {
output = append(output, listVal)
return cty.ListVal(output), nil
return cty.ListVal(output).WithMarks(retMarks), nil
}
chunk := make([]cty.Value, 0)
l := args[0].LengthInt()
l := listVal.LengthInt()
i := 0
for it := listVal.ElementIterator(); it.Next(); {
@ -454,7 +464,7 @@ var ChunklistFunc = function.New(&function.Spec{
i++
}
return cty.ListVal(output), nil
return cty.ListVal(output).WithMarks(retMarks), nil
},
})
@ -463,8 +473,9 @@ var ChunklistFunc = function.New(&function.Spec{
var FlattenFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "list",
Type: cty.DynamicPseudoType,
Name: "list",
Type: cty.DynamicPseudoType,
AllowMarked: true,
},
},
Type: func(args []cty.Value) (cty.Type, error) {
@ -477,7 +488,8 @@ var FlattenFunc = function.New(&function.Spec{
return cty.NilType, errors.New("can only flatten lists, sets and tuples")
}
retVal, known := flattener(args[0])
// marks are attached to values, so ignore while determining type
retVal, _, known := flattener(args[0])
if !known {
return cty.DynamicPseudoType, nil
}
@ -490,46 +502,66 @@ var FlattenFunc = function.New(&function.Spec{
},
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
inputList := args[0]
if inputList.LengthInt() == 0 {
return cty.EmptyTupleVal, nil
if unmarked, marks := inputList.Unmark(); unmarked.LengthInt() == 0 {
return cty.EmptyTupleVal.WithMarks(marks), nil
}
out, known := flattener(inputList)
out, markses, known := flattener(inputList)
if !known {
return cty.UnknownVal(retType), nil
return cty.UnknownVal(retType).WithMarks(markses...), nil
}
return cty.TupleVal(out), nil
return cty.TupleVal(out).WithMarks(markses...), nil
},
})
// Flatten until it's not a cty.List, and return whether the value is known.
// We can flatten lists with unknown values, as long as they are not
// lists themselves.
func flattener(flattenList cty.Value) ([]cty.Value, bool) {
func flattener(flattenList cty.Value) ([]cty.Value, []cty.ValueMarks, bool) {
var markses []cty.ValueMarks
flattenList, flattenListMarks := flattenList.Unmark()
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.
return nil, false
return nil, markses, false
}
out := make([]cty.Value, 0)
isKnown := true
for it := flattenList.ElementIterator(); it.Next(); {
_, val := it.Element()
// Any dynamic types could result in more collections that need to be
// flattened, so the type cannot be known.
if val == cty.DynamicVal {
isKnown = false
}
if val.Type().IsListType() || val.Type().IsSetType() || val.Type().IsTupleType() {
if !val.IsKnown() {
return out, false
isKnown = false
_, unknownMarks := val.Unmark()
markses = append(markses, unknownMarks)
continue
}
res, known := flattener(val)
if !known {
return res, known
res, resMarks, known := flattener(val)
markses = append(markses, resMarks...)
if known {
out = append(out, res...)
} else {
isKnown = false
}
out = append(out, res...)
} else {
out = append(out, val)
}
}
return out, true
return out, markses, isKnown
}
// KeysFunc is a function that takes a map and returns a sorted list of the map keys.
@ -539,6 +571,7 @@ var KeysFunc = function.New(&function.Spec{
Name: "inputMap",
Type: cty.DynamicPseudoType,
AllowUnknown: true,
AllowMarked: true,
},
},
Type: func(args []cty.Value) (cty.Type, error) {
@ -563,7 +596,11 @@ var KeysFunc = function.New(&function.Spec{
}
},
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
m := args[0]
// We must unmark the value before we can use ElementIterator on it, and
// then re-apply the same marks (possibly none) when we return. Since we
// don't mark map keys, we can throw away any nested marks, which would
// only apply to values.
m, marks := args[0].Unmark()
var keys []cty.Value
switch {
@ -576,28 +613,28 @@ var KeysFunc = function.New(&function.Spec{
}
sort.Strings(names) // same ordering guaranteed by cty's ElementIterator
if len(names) == 0 {
return cty.EmptyTupleVal, nil
return cty.EmptyTupleVal.WithMarks(marks), nil
}
keys = make([]cty.Value, len(names))
for i, name := range names {
keys[i] = cty.StringVal(name)
}
return cty.TupleVal(keys), nil
return cty.TupleVal(keys).WithMarks(marks), nil
default:
if !m.IsKnown() {
return cty.UnknownVal(retType), nil
return cty.UnknownVal(retType).WithMarks(marks), nil
}
// cty guarantees that ElementIterator will iterate in lexicographical
// order by key.
for it := args[0].ElementIterator(); it.Next(); {
for it := m.ElementIterator(); it.Next(); {
k, _ := it.Element()
keys = append(keys, k)
}
if len(keys) == 0 {
return cty.ListValEmpty(cty.String), nil
return cty.ListValEmpty(cty.String).WithMarks(marks), nil
}
return cty.ListVal(keys), nil
return cty.ListVal(keys).WithMarks(marks), nil
}
},
})
@ -606,16 +643,19 @@ var KeysFunc = function.New(&function.Spec{
var LookupFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "inputMap",
Type: cty.DynamicPseudoType,
Name: "inputMap",
Type: cty.DynamicPseudoType,
AllowMarked: true,
},
{
Name: "key",
Type: cty.String,
Name: "key",
Type: cty.String,
AllowMarked: true,
},
{
Name: "default",
Type: cty.DynamicPseudoType,
Name: "default",
Type: cty.DynamicPseudoType,
AllowMarked: true,
},
},
Type: func(args []cty.Value) (ret cty.Type, err error) {
@ -627,7 +667,8 @@ var LookupFunc = function.New(&function.Spec{
return cty.DynamicPseudoType, nil
}
key := args[1].AsString()
keyVal, _ := args[1].Unmark()
key := keyVal.AsString()
if ty.HasAttribute(key) {
return args[0].GetAttr(key).Type(), nil
} else if len(args) == 3 {
@ -649,28 +690,39 @@ var LookupFunc = function.New(&function.Spec{
}
},
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
// leave default value marked
defaultVal := args[2]
mapVar := args[0]
lookupKey := args[1].AsString()
var markses []cty.ValueMarks
// unmark collection, retain marks to reapply later
mapVar, mapMarks := args[0].Unmark()
markses = append(markses, mapMarks)
// include marks on the key in the result
keyVal, keyMarks := args[1].Unmark()
if len(keyMarks) > 0 {
markses = append(markses, keyMarks)
}
lookupKey := keyVal.AsString()
if !mapVar.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
return cty.UnknownVal(retType).WithMarks(markses...), nil
}
if mapVar.Type().IsObjectType() {
if mapVar.Type().HasAttribute(lookupKey) {
return mapVar.GetAttr(lookupKey), nil
return mapVar.GetAttr(lookupKey).WithMarks(markses...), nil
}
} else if mapVar.HasIndex(cty.StringVal(lookupKey)) == cty.True {
return mapVar.Index(cty.StringVal(lookupKey)), nil
return mapVar.Index(cty.StringVal(lookupKey)).WithMarks(markses...), nil
}
defaultVal, err = convert.Convert(defaultVal, retType)
if err != nil {
return cty.NilVal, err
}
return defaultVal, nil
return defaultVal.WithMarks(markses...), nil
},
})
@ -687,6 +739,7 @@ var MergeFunc = function.New(&function.Spec{
Type: cty.DynamicPseudoType,
AllowDynamicType: true,
AllowNull: true,
AllowMarked: true,
},
Type: func(args []cty.Value) (cty.Type, error) {
// empty args is accepted, so assume an empty object since we have no
@ -712,6 +765,8 @@ var MergeFunc = function.New(&function.Spec{
if !ty.IsMapType() && !ty.IsObjectType() {
return cty.NilType, fmt.Errorf("arguments must be maps or objects, got %#v", ty.FriendlyName())
}
// marks are attached to values, so ignore while determining type
arg, _ = arg.Unmark()
switch {
case ty.IsObjectType() && !arg.IsNull():
@ -761,11 +816,16 @@ var MergeFunc = function.New(&function.Spec{
},
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
outputMap := make(map[string]cty.Value)
var markses []cty.ValueMarks // remember any marked maps/objects we find
for _, arg := range args {
if arg.IsNull() {
continue
}
arg, argMarks := arg.Unmark()
if len(argMarks) > 0 {
markses = append(markses, argMarks)
}
for it := arg.ElementIterator(); it.Next(); {
k, v := it.Element()
outputMap[k.AsString()] = v
@ -775,11 +835,11 @@ var MergeFunc = function.New(&function.Spec{
switch {
case retType.IsMapType():
if len(outputMap) == 0 {
return cty.MapValEmpty(retType.ElementType()), nil
return cty.MapValEmpty(retType.ElementType()).WithMarks(markses...), nil
}
return cty.MapVal(outputMap), nil
return cty.MapVal(outputMap).WithMarks(markses...), nil
case retType.IsObjectType(), retType.Equals(cty.DynamicPseudoType):
return cty.ObjectVal(outputMap), nil
return cty.ObjectVal(outputMap).WithMarks(markses...), nil
default:
panic(fmt.Sprintf("unexpected return type: %#v", retType))
}
@ -791,8 +851,9 @@ var MergeFunc = function.New(&function.Spec{
var ReverseListFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "list",
Type: cty.DynamicPseudoType,
Name: "list",
Type: cty.DynamicPseudoType,
AllowMarked: true,
},
},
Type: func(args []cty.Value) (cty.Type, error) {
@ -812,19 +873,21 @@ var ReverseListFunc = function.New(&function.Spec{
}
},
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
in := args[0].AsValueSlice()
outVals := make([]cty.Value, len(in))
for i, v := range in {
in, marks := args[0].Unmark()
inVals := in.AsValueSlice()
outVals := make([]cty.Value, len(inVals))
for i, v := range inVals {
outVals[len(outVals)-i-1] = v
}
switch {
case retType.IsTupleType():
return cty.TupleVal(outVals), nil
return cty.TupleVal(outVals).WithMarks(marks), nil
default:
if len(outVals) == 0 {
return cty.ListValEmpty(retType.ElementType()), nil
return cty.ListValEmpty(retType.ElementType()).WithMarks(marks), nil
}
return cty.ListVal(outVals), nil
return cty.ListVal(outVals).WithMarks(marks), nil
}
},
})
@ -836,8 +899,9 @@ var ReverseListFunc = function.New(&function.Spec{
var SetProductFunc = function.New(&function.Spec{
Params: []function.Parameter{},
VarParam: &function.Parameter{
Name: "sets",
Type: cty.DynamicPseudoType,
Name: "sets",
Type: cty.DynamicPseudoType,
AllowMarked: true,
},
Type: func(args []cty.Value) (retType cty.Type, err error) {
if len(args) < 2 {
@ -881,11 +945,19 @@ var SetProductFunc = function.New(&function.Spec{
},
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
ety := retType.ElementType()
var retMarks cty.ValueMarks
total := 1
var hasUnknownLength bool
for _, arg := range args {
arg, marks := arg.Unmark()
retMarks = cty.NewValueMarks(retMarks, marks)
// Continue processing after we find an argument with unknown
// length to ensure that we cover all the marks
if !arg.Length().IsKnown() {
return cty.UnknownVal(retType), nil
hasUnknownLength = true
continue
}
// Because of our type checking function, we are guaranteed that
@ -894,13 +966,17 @@ var SetProductFunc = function.New(&function.Spec{
total *= arg.LengthInt()
}
if hasUnknownLength {
return cty.UnknownVal(retType).WithMarks(retMarks), nil
}
if total == 0 {
// If any of the arguments was an empty collection then our result
// is also an empty collection, which we'll short-circuit here.
if retType.IsListType() {
return cty.ListValEmpty(ety), nil
return cty.ListValEmpty(ety).WithMarks(retMarks), nil
}
return cty.SetValEmpty(ety), nil
return cty.SetValEmpty(ety).WithMarks(retMarks), nil
}
subEtys := ety.TupleElementTypes()
@ -911,6 +987,8 @@ var SetProductFunc = function.New(&function.Spec{
s := 0
argVals := make([][]cty.Value, len(args))
for i, arg := range args {
// We've already stored the marks in retMarks
arg, _ := arg.Unmark()
argVals[i] = arg.AsValueSlice()
}
@ -950,9 +1028,9 @@ var SetProductFunc = function.New(&function.Spec{
}
if retType.IsListType() {
return cty.ListVal(productVals), nil
return cty.ListVal(productVals).WithMarks(retMarks), nil
}
return cty.SetVal(productVals), nil
return cty.SetVal(productVals).WithMarks(retMarks), nil
},
})
@ -961,8 +1039,9 @@ var SetProductFunc = function.New(&function.Spec{
var SliceFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "list",
Type: cty.DynamicPseudoType,
Name: "list",
Type: cty.DynamicPseudoType,
AllowMarked: true,
},
{
Name: "start_index",
@ -1001,10 +1080,10 @@ var SliceFunc = function.New(&function.Spec{
return cty.Tuple(argTy.TupleElementTypes()[startIndex:endIndex]), nil
},
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
inputList := args[0]
inputList, marks := args[0].Unmark()
if retType == cty.DynamicPseudoType {
return cty.DynamicVal, nil
return cty.DynamicVal.WithMarks(marks), nil
}
// we ignore idxsKnown return value here because the indices are always
@ -1016,18 +1095,18 @@ var SliceFunc = function.New(&function.Spec{
if endIndex-startIndex == 0 {
if retType.IsTupleType() {
return cty.EmptyTupleVal, nil
return cty.EmptyTupleVal.WithMarks(marks), nil
}
return cty.ListValEmpty(retType.ElementType()), nil
return cty.ListValEmpty(retType.ElementType()).WithMarks(marks), nil
}
outputList := inputList.AsValueSlice()[startIndex:endIndex]
if retType.IsTupleType() {
return cty.TupleVal(outputList), nil
return cty.TupleVal(outputList).WithMarks(marks), nil
}
return cty.ListVal(outputList), nil
return cty.ListVal(outputList).WithMarks(marks), nil
},
})
@ -1035,9 +1114,12 @@ func sliceIndexes(args []cty.Value) (int, int, bool, error) {
var startIndex, endIndex, length int
var startKnown, endKnown, lengthKnown bool
// remove marks from args[0]
list, _ := args[0].Unmark()
// If it's a tuple then we always know the length by the type, but collections might be unknown or have unknown length
if args[0].Type().IsTupleType() || args[0].Length().IsKnown() {
length = args[0].LengthInt()
if list.Type().IsTupleType() || list.Length().IsKnown() {
length = list.LengthInt()
lengthKnown = true
}
@ -1078,8 +1160,9 @@ func sliceIndexes(args []cty.Value) (int, int, bool, error) {
var ValuesFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "values",
Type: cty.DynamicPseudoType,
Name: "values",
Type: cty.DynamicPseudoType,
AllowMarked: true,
},
},
Type: func(args []cty.Value) (ret cty.Type, err error) {
@ -1112,6 +1195,13 @@ var ValuesFunc = function.New(&function.Spec{
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
mapVar := args[0]
// We must unmark the value before we can use ElementIterator on it,
// and then re-apply the same marks (possibly none) when we return.
// (We leave the inner values just as they are, because we won't be
// doing anything with them aside from copying them verbatim into the
// result, marks and all.)
mapVar, marks := mapVar.Unmark()
// We can just iterate the map/object value here because cty guarantees
// that these types always iterate in key lexicographical order.
var values []cty.Value
@ -1120,13 +1210,15 @@ var ValuesFunc = function.New(&function.Spec{
values = append(values, val)
}
// All of the return paths must include .WithMarks(marks) so that we
// will preserve the markings of the overall map/object we were given.
if retType.IsTupleType() {
return cty.TupleVal(values), nil
return cty.TupleVal(values).WithMarks(marks), nil
}
if len(values) == 0 {
return cty.ListValEmpty(retType.ElementType()), nil
return cty.ListValEmpty(retType.ElementType()).WithMarks(marks), nil
}
return cty.ListVal(values), nil
return cty.ListVal(values).WithMarks(marks), nil
},
})
@ -1135,12 +1227,14 @@ var ValuesFunc = function.New(&function.Spec{
var ZipmapFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "keys",
Type: cty.List(cty.String),
Name: "keys",
Type: cty.List(cty.String),
AllowMarked: true,
},
{
Name: "values",
Type: cty.DynamicPseudoType,
Name: "values",
Type: cty.DynamicPseudoType,
AllowMarked: true,
},
},
Type: func(args []cty.Value) (ret cty.Type, err error) {
@ -1158,6 +1252,13 @@ var ZipmapFunc = function.New(&function.Spec{
return cty.DynamicPseudoType, nil
}
// NOTE: Marking of the keys list can't be represented in the
// result type, so the tuple type here will disclose the keys.
// This is unfortunate but is a common compromise with dynamic
// return types; the result from Impl will still reflect the marks
// from the keys list, so a mark-using caller should look out for
// that if it's important for their use-case.
keys, _ := keys.Unmark()
keysRaw := keys.AsValueSlice()
valueTypesRaw := valuesTy.TupleElementTypes()
if len(keysRaw) != len(valueTypesRaw) {
@ -1165,6 +1266,7 @@ var ZipmapFunc = function.New(&function.Spec{
}
atys := make(map[string]cty.Type, len(valueTypesRaw))
for i, keyVal := range keysRaw {
keyVal, _ = keyVal.Unmark()
if keyVal.IsNull() {
return cty.NilType, fmt.Errorf("keys list has null value at index %d", i)
}
@ -1180,11 +1282,17 @@ var ZipmapFunc = function.New(&function.Spec{
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
keys := args[0]
values := args[1]
keys, keysMarks := keys.Unmark()
values, valuesMarks := values.Unmark()
// All of our return paths must pass through the merged marks from
// both the keys and the values, if any, using .WithMarks(retMarks)
retMarks := cty.NewValueMarks(keysMarks, valuesMarks)
if !keys.IsWhollyKnown() {
// Unknown map keys and object attributes are not supported, so
// our entire result must be unknown in this case.
return cty.UnknownVal(retType), nil
return cty.UnknownVal(retType).WithMarks(retMarks), nil
}
// both keys and values are guaranteed to be shallowly-known here,
@ -1198,19 +1306,25 @@ var ZipmapFunc = function.New(&function.Spec{
i := 0
for it := keys.ElementIterator(); it.Next(); {
_, v := it.Element()
v, vMarks := v.Unmark()
val := values.Index(cty.NumberIntVal(int64(i)))
output[v.AsString()] = val
// We also need to accumulate the individual key marks on the
// returned map, because keys can't carry marks on their own.
retMarks = cty.NewValueMarks(retMarks, vMarks)
i++
}
switch {
case retType.IsMapType():
if len(output) == 0 {
return cty.MapValEmpty(retType.ElementType()), nil
return cty.MapValEmpty(retType.ElementType()).WithMarks(retMarks), nil
}
return cty.MapVal(output), nil
return cty.MapVal(output).WithMarks(retMarks), nil
case retType.IsObjectType():
return cty.ObjectVal(output), nil
return cty.ObjectVal(output).WithMarks(retMarks), nil
default:
// Should never happen because the type-check function should've
// caught any other case.

View File

@ -30,7 +30,7 @@ var CSVDecodeFunc = function.New(&function.Spec{
return cty.DynamicPseudoType, fmt.Errorf("missing header line")
}
if err != nil {
return cty.DynamicPseudoType, err
return cty.DynamicPseudoType, csvError(err)
}
atys := make(map[string]cty.Type, len(headers))
@ -64,7 +64,7 @@ var CSVDecodeFunc = function.New(&function.Spec{
break
}
if err != nil {
return cty.DynamicVal, err
return cty.DynamicVal, csvError(err)
}
vals := make(map[string]cty.Value, len(cols))
@ -91,3 +91,12 @@ var CSVDecodeFunc = function.New(&function.Spec{
func CSVDecode(str cty.Value) (cty.Value, error) {
return CSVDecodeFunc.Call([]cty.Value{str})
}
func csvError(err error) error {
switch err := err.(type) {
case *csv.ParseError:
return fmt.Errorf("CSV parse error on line %d: %w", err.Line, err.Err)
default:
return err
}
}

View File

@ -6,7 +6,7 @@ import (
"math/big"
"strings"
"github.com/apparentlymart/go-textseg/v12/textseg"
"github.com/apparentlymart/go-textseg/v13/textseg"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
@ -114,6 +114,8 @@ var FormatListFunc = function.New(&function.Spec{
continue
}
iterators[i] = arg.ElementIterator()
case arg == cty.DynamicVal:
unknowns[i] = true
default:
singleVals[i] = arg
}

View File

@ -371,14 +371,21 @@ var CeilFunc = function.New(&function.Spec{
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
var val float64
if err := gocty.FromCtyValue(args[0], &val); err != nil {
return cty.UnknownVal(cty.String), err
f := args[0].AsBigFloat()
if f.IsInf() {
return cty.NumberVal(f), nil
}
if math.IsInf(val, 0) {
return cty.NumberFloatVal(val), nil
i, acc := f.Int(nil)
switch acc {
case big.Exact, big.Above:
// Done.
case big.Below:
i.Add(i, big.NewInt(1))
}
return cty.NumberIntVal(int64(math.Ceil(val))), nil
return cty.NumberVal(f.SetInt(i)), nil
},
})
@ -393,14 +400,21 @@ var FloorFunc = function.New(&function.Spec{
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
var val float64
if err := gocty.FromCtyValue(args[0], &val); err != nil {
return cty.UnknownVal(cty.String), err
f := args[0].AsBigFloat()
if f.IsInf() {
return cty.NumberVal(f), nil
}
if math.IsInf(val, 0) {
return cty.NumberFloatVal(val), nil
i, acc := f.Int(nil)
switch acc {
case big.Exact, big.Below:
// Done.
case big.Above:
i.Sub(i, big.NewInt(1))
}
return cty.NumberIntVal(int64(math.Floor(val))), nil
return cty.NumberVal(f.SetInt(i)), nil
},
})

View File

@ -11,8 +11,9 @@ import (
var ConcatFunc = function.New(&function.Spec{
Params: []function.Parameter{},
VarParam: &function.Parameter{
Name: "seqs",
Type: cty.DynamicPseudoType,
Name: "seqs",
Type: cty.DynamicPseudoType,
AllowMarked: true,
},
Type: func(args []cty.Value) (ret cty.Type, err error) {
if len(args) == 0 {
@ -42,6 +43,10 @@ var ConcatFunc = function.New(&function.Spec{
etys := make([]cty.Type, 0, len(args))
for i, val := range args {
// Discard marks for nested values, as we only need to handle types
// and lengths.
val, _ := val.UnmarkDeep()
ety := val.Type()
switch {
case ety.IsTupleType():
@ -75,6 +80,7 @@ var ConcatFunc = function.New(&function.Spec{
// given values will be lists and that they will either be of
// retType or of something we can convert to retType.
vals := make([]cty.Value, 0, len(args))
var markses []cty.ValueMarks // remember any marked lists we find
for i, list := range args {
list, err = convert.Convert(list, retType)
if err != nil {
@ -83,6 +89,11 @@ var ConcatFunc = function.New(&function.Spec{
return cty.NilVal, function.NewArgError(i, err)
}
list, listMarks := list.Unmark()
if len(listMarks) > 0 {
markses = append(markses, listMarks)
}
it := list.ElementIterator()
for it.Next() {
_, v := it.Element()
@ -90,10 +101,10 @@ var ConcatFunc = function.New(&function.Spec{
}
}
if len(vals) == 0 {
return cty.ListValEmpty(retType.ElementType()), nil
return cty.ListValEmpty(retType.ElementType()).WithMarks(markses...), nil
}
return cty.ListVal(vals), nil
return cty.ListVal(vals).WithMarks(markses...), nil
case retType.IsTupleType():
// If retType is a tuple type then we could have a mixture of
// lists and tuples but we know they all have known values
@ -101,8 +112,14 @@ var ConcatFunc = function.New(&function.Spec{
// concatenating them all together will produce a tuple of
// retType because of the work we did in the Type function above.
vals := make([]cty.Value, 0, len(args))
var markses []cty.ValueMarks // remember any marked seqs we find
for _, seq := range args {
seq, seqMarks := seq.Unmark()
if len(seqMarks) > 0 {
markses = append(markses, seqMarks)
}
// Both lists and tuples support ElementIterator, so this is easy.
it := seq.ElementIterator()
for it.Next() {
@ -111,7 +128,7 @@ var ConcatFunc = function.New(&function.Spec{
}
}
return cty.TupleVal(vals), nil
return cty.TupleVal(vals).WithMarks(markses...), nil
default:
// should never happen if Type is working correctly above
panic("unsupported return type")

View File

@ -6,7 +6,7 @@ import (
"sort"
"strings"
"github.com/apparentlymart/go-textseg/v12/textseg"
"github.com/apparentlymart/go-textseg/v13/textseg"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
@ -151,7 +151,6 @@ var SubstrFunc = function.New(&function.Spec{
return cty.StringVal(""), nil
}
sub := in
pos := 0
var i int