Extend hcl2 support with more functions

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2020-12-23 12:33:55 +01:00
parent 74f76cf4e9
commit 96e7f3224a
90 changed files with 4425 additions and 198 deletions

View File

@ -3,7 +3,6 @@ package cty
import (
"fmt"
"math/big"
"reflect"
"github.com/zclconf/go-cty/cty/set"
)
@ -133,9 +132,9 @@ func (val Value) Equals(other Value) Value {
case val.IsKnown() && !other.IsKnown():
switch {
case val.IsNull(), other.ty.HasDynamicTypes():
// If known is Null, we need to wait for the unkown value since
// If known is Null, we need to wait for the unknown value since
// nulls of any type are equal.
// An unkown with a dynamic type compares as unknown, which we need
// An unknown with a dynamic type compares as unknown, which we need
// to check before the type comparison below.
return UnknownVal(Bool)
case !val.ty.Equals(other.ty):
@ -148,9 +147,9 @@ func (val Value) Equals(other Value) Value {
case other.IsKnown() && !val.IsKnown():
switch {
case other.IsNull(), val.ty.HasDynamicTypes():
// If known is Null, we need to wait for the unkown value since
// If known is Null, we need to wait for the unknown value since
// nulls of any type are equal.
// An unkown with a dynamic type compares as unknown, which we need
// An unknown with a dynamic type compares as unknown, which we need
// to check before the type comparison below.
return UnknownVal(Bool)
case !other.ty.Equals(val.ty):
@ -171,7 +170,15 @@ func (val Value) Equals(other Value) Value {
return BoolVal(false)
}
if val.ty.HasDynamicTypes() || other.ty.HasDynamicTypes() {
// Check if there are any nested dynamic values making this comparison
// unknown.
if !val.HasWhollyKnownType() || !other.HasWhollyKnownType() {
// Even if we have dynamic values, we can still determine inequality if
// there is no way the types could later conform.
if val.ty.TestConformance(other.ty) != nil && other.ty.TestConformance(val.ty) != nil {
return BoolVal(false)
}
return UnknownVal(Bool)
}
@ -262,24 +269,26 @@ func (val Value) Equals(other Value) Value {
s2 := other.v.(set.Set)
equal := true
// Note that by our definition of sets it's never possible for two
// sets that contain unknown values (directly or indicrectly) to
// ever be equal, even if they are otherwise identical.
// FIXME: iterating both lists and checking each item is not the
// ideal implementation here, but it works with the primitives we
// have in the set implementation. Perhaps the set implementation
// can provide its own equality test later.
s1.EachValue(func(v interface{}) {
if !s2.Has(v) {
// Two sets are equal if all of their values are known and all values
// in one are also in the other.
for it := s1.Iterator(); it.Next(); {
rv := it.Value()
if rv == unknown { // "unknown" is the internal representation of unknown-ness
return UnknownVal(Bool)
}
if !s2.Has(rv) {
equal = false
}
})
s2.EachValue(func(v interface{}) {
if !s1.Has(v) {
}
for it := s2.Iterator(); it.Next(); {
rv := it.Value()
if rv == unknown { // "unknown" is the internal representation of unknown-ness
return UnknownVal(Bool)
}
if !s1.Has(rv) {
equal = false
}
})
}
result = equal
case ty.IsMapType():
@ -454,17 +463,32 @@ func (val Value) RawEquals(other Value) bool {
return true
}
return false
case ty.IsSetType():
s1 := val.v.(set.Set)
s2 := other.v.(set.Set)
// Since we're intentionally ignoring our rule that two unknowns
// are never equal, we can cheat here.
// (This isn't 100% right since e.g. it will fail if the set contains
// numbers that are infinite, which DeepEqual can't compare properly.
// We're accepting that limitation for simplicity here, since this
// function is here primarily for testing.)
return reflect.DeepEqual(s1, s2)
case ty.IsSetType():
// Convert the set values into a slice so that we can compare each
// value. This is safe because the underlying sets are ordered (see
// setRules in set_internals.go), and so the results are guaranteed to
// be in a consistent order for two equal sets
setList1 := val.AsValueSlice()
setList2 := other.AsValueSlice()
// If both physical sets have the same length and they have all of their
// _known_ values in common, we know that both sets also have the same
// number of unknown values.
if len(setList1) != len(setList2) {
return false
}
for i := range setList1 {
eq := setList1[i].RawEquals(setList2[i])
if !eq {
return false
}
}
// If we got here without returning false already then our sets are
// equal enough for RawEquals purposes.
return true
case ty.IsMapType():
ety := ty.typeImpl.(typeMap).ElementTypeT
@ -572,8 +596,25 @@ func (val Value) Multiply(other Value) Value {
return *shortCircuit
}
ret := new(big.Float)
// find the larger precision of the arguments
resPrec := val.v.(*big.Float).Prec()
otherPrec := other.v.(*big.Float).Prec()
if otherPrec > resPrec {
resPrec = otherPrec
}
// make sure we have enough precision for the product
ret := new(big.Float).SetPrec(512)
ret.Mul(val.v.(*big.Float), other.v.(*big.Float))
// now reduce the precision back to the greater argument, or the minimum
// required by the product.
minPrec := ret.MinPrec()
if minPrec > resPrec {
resPrec = minPrec
}
ret.SetPrec(resPrec)
return NumberVal(ret)
}
@ -645,11 +686,14 @@ func (val Value) Modulo(other Value) Value {
// FIXME: This is a bit clumsy. Should come back later and see if there's a
// more straightforward way to do this.
rat := val.Divide(other)
ratFloorInt := &big.Int{}
rat.v.(*big.Float).Int(ratFloorInt)
work := (&big.Float{}).SetInt(ratFloorInt)
ratFloorInt, _ := rat.v.(*big.Float).Int(nil)
// start with a copy of the original larger value so that we do not lose
// precision.
v := val.v.(*big.Float)
work := new(big.Float).Copy(v).SetInt(ratFloorInt)
work.Mul(other.v.(*big.Float), work)
work.Sub(val.v.(*big.Float), work)
work.Sub(v, work)
return NumberVal(work)
}
@ -947,8 +991,7 @@ func (val Value) HasElement(elem Value) Value {
// If the receiver is null then this function will panic.
//
// Note that Length is not supported for strings. To determine the length
// of a string, call AsString and take the length of the native Go string
// that is returned.
// of a string, use the Length function in funcs/stdlib.
func (val Value) Length() Value {
if val.IsMarked() {
val, valMarks := val.Unmark()
@ -963,6 +1006,25 @@ func (val Value) Length() Value {
if !val.IsKnown() {
return UnknownVal(Number)
}
if val.Type().IsSetType() {
// The Length rules are a little different for sets because if any
// unknown values are present then the values they are standing in for
// 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())
if storeLength == 1 || val.IsWhollyKnown() {
// If our set is wholly known then we know its length.
//
// We also know the length if the physical store has only one
// element, even if that element is unknown, because there's
// nothing else in the set for it to coalesce with and a single
// unknown value cannot represent more than one known value.
return NumberIntVal(storeLength)
}
// Otherwise, we cannot predict the length.
return UnknownVal(Number)
}
return NumberIntVal(int64(val.LengthInt()))
}
@ -972,6 +1034,13 @@ func (val Value) Length() Value {
//
// This is an integration method provided for the convenience of code bridging
// into Go's type system.
//
// For backward compatibility with an earlier implementation error, LengthInt's
// result can disagree with Length's result for any set containing unknown
// values. Length can potentially indicate the set's length is unknown in that
// case, whereas LengthInt will return the maximum possible length as if the
// unknown values were each a placeholder for a value not equal to any other
// value in the set.
func (val Value) LengthInt() int {
val.assertUnmarked()
if val.Type().IsTupleType() {
@ -995,6 +1064,15 @@ func (val Value) LengthInt() int {
return len(val.v.([]interface{}))
case val.ty.IsSetType():
// NOTE: This is technically not correct in cases where the set
// contains unknown values, because in that case we can't know how
// many known values those unknown values are standing in for -- they
// might coalesce with other values once known.
//
// However, this incorrect behavior is preserved for backward
// 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()
case val.ty.IsMapType():