2022-04-03 04:07:16 +00:00
|
|
|
package cty
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"math/big"
|
|
|
|
|
|
|
|
"github.com/zclconf/go-cty/cty/set"
|
|
|
|
)
|
|
|
|
|
|
|
|
// GoString is an implementation of fmt.GoStringer that produces concise
|
|
|
|
// source-like representations of values suitable for use in debug messages.
|
|
|
|
func (val Value) GoString() string {
|
|
|
|
if val.IsMarked() {
|
|
|
|
unVal, marks := val.Unmark()
|
|
|
|
if len(marks) == 1 {
|
|
|
|
var mark interface{}
|
|
|
|
for m := range marks {
|
|
|
|
mark = m
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%#v.Mark(%#v)", unVal, mark)
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%#v.WithMarks(%#v)", unVal, marks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if val == NilVal {
|
|
|
|
return "cty.NilVal"
|
|
|
|
}
|
|
|
|
|
|
|
|
if val.IsNull() {
|
|
|
|
return fmt.Sprintf("cty.NullVal(%#v)", val.ty)
|
|
|
|
}
|
|
|
|
if val == DynamicVal { // is unknown, so must be before the IsKnown check below
|
|
|
|
return "cty.DynamicVal"
|
|
|
|
}
|
|
|
|
if !val.IsKnown() {
|
|
|
|
return fmt.Sprintf("cty.UnknownVal(%#v)", val.ty)
|
|
|
|
}
|
|
|
|
|
|
|
|
// By the time we reach here we've dealt with all of the exceptions around
|
|
|
|
// unknowns and nulls, so we're guaranteed that the values are the
|
|
|
|
// canonical internal representation of the given type.
|
|
|
|
|
|
|
|
switch val.ty {
|
|
|
|
case Bool:
|
|
|
|
if val.v.(bool) {
|
|
|
|
return "cty.True"
|
|
|
|
}
|
|
|
|
return "cty.False"
|
|
|
|
case Number:
|
2022-12-24 16:57:19 +00:00
|
|
|
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)))
|
|
|
|
}
|
2022-04-03 04:07:16 +00:00
|
|
|
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.
|
|
|
|
if fv.IsInt() {
|
|
|
|
return fmt.Sprintf("cty.NumberIntVal(%#v)", fv)
|
|
|
|
}
|
|
|
|
if rfv, accuracy := fv.Float64(); accuracy == big.Exact {
|
|
|
|
return fmt.Sprintf("cty.NumberFloatVal(%#v)", rfv)
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("cty.MustParseNumberVal(%q)", fv.Text('f', -1))
|
|
|
|
case String:
|
|
|
|
return fmt.Sprintf("cty.StringVal(%#v)", val.v)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case val.ty.IsSetType():
|
|
|
|
vals := val.AsValueSlice()
|
|
|
|
if len(vals) == 0 {
|
|
|
|
return fmt.Sprintf("cty.SetValEmpty(%#v)", val.ty.ElementType())
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("cty.SetVal(%#v)", vals)
|
|
|
|
case val.ty.IsListType():
|
|
|
|
vals := val.AsValueSlice()
|
|
|
|
if len(vals) == 0 {
|
|
|
|
return fmt.Sprintf("cty.ListValEmpty(%#v)", val.ty.ElementType())
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("cty.ListVal(%#v)", vals)
|
|
|
|
case val.ty.IsMapType():
|
|
|
|
vals := val.AsValueMap()
|
|
|
|
if len(vals) == 0 {
|
|
|
|
return fmt.Sprintf("cty.MapValEmpty(%#v)", val.ty.ElementType())
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("cty.MapVal(%#v)", vals)
|
|
|
|
case val.ty.IsTupleType():
|
|
|
|
if val.ty.Equals(EmptyTuple) {
|
|
|
|
return "cty.EmptyTupleVal"
|
|
|
|
}
|
|
|
|
vals := val.AsValueSlice()
|
|
|
|
return fmt.Sprintf("cty.TupleVal(%#v)", vals)
|
|
|
|
case val.ty.IsObjectType():
|
|
|
|
if val.ty.Equals(EmptyObject) {
|
|
|
|
return "cty.EmptyObjectVal"
|
|
|
|
}
|
|
|
|
vals := val.AsValueMap()
|
|
|
|
return fmt.Sprintf("cty.ObjectVal(%#v)", vals)
|
|
|
|
case val.ty.IsCapsuleType():
|
|
|
|
impl := val.ty.CapsuleOps().GoString
|
|
|
|
if impl == nil {
|
|
|
|
return fmt.Sprintf("cty.CapsuleVal(%#v, %#v)", val.ty, val.v)
|
|
|
|
}
|
|
|
|
return impl(val.EncapsulatedValue())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default exposes implementation details, so should actually cover
|
|
|
|
// all of the cases above for good caller UX.
|
|
|
|
return fmt.Sprintf("cty.Value{ty: %#v, v: %#v}", val.ty, val.v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Equals returns True if the receiver and the given other value have the
|
|
|
|
// same type and are exactly equal in value.
|
|
|
|
//
|
|
|
|
// As a special case, two null values are always equal regardless of type.
|
|
|
|
//
|
|
|
|
// The usual short-circuit rules apply, so the result will be unknown if
|
|
|
|
// either of the given values are.
|
|
|
|
//
|
|
|
|
// Use RawEquals to compare if two values are equal *ignoring* the
|
|
|
|
// short-circuit rules and the exception for null values.
|
|
|
|
func (val Value) Equals(other Value) Value {
|
|
|
|
if val.ContainsMarked() || other.ContainsMarked() {
|
|
|
|
val, valMarks := val.UnmarkDeep()
|
|
|
|
other, otherMarks := other.UnmarkDeep()
|
|
|
|
return val.Equals(other).WithMarks(valMarks, otherMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start by handling Unknown values before considering types.
|
|
|
|
// This needs to be done since Null values are always equal regardless of
|
|
|
|
// type.
|
|
|
|
switch {
|
|
|
|
case !val.IsKnown() && !other.IsKnown():
|
|
|
|
// both unknown
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
case val.IsKnown() && !other.IsKnown():
|
|
|
|
switch {
|
|
|
|
case val.IsNull(), other.ty.HasDynamicTypes():
|
|
|
|
// If known is Null, we need to wait for the unknown value since
|
|
|
|
// nulls of any type are equal.
|
|
|
|
// 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):
|
|
|
|
// There is no null comparison or dynamic types, so unequal types
|
|
|
|
// will never be equal.
|
|
|
|
return False
|
|
|
|
default:
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
}
|
|
|
|
case other.IsKnown() && !val.IsKnown():
|
|
|
|
switch {
|
|
|
|
case other.IsNull(), val.ty.HasDynamicTypes():
|
|
|
|
// If known is Null, we need to wait for the unknown value since
|
|
|
|
// nulls of any type are equal.
|
|
|
|
// 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):
|
|
|
|
// There's no null comparison or dynamic types, so unequal types
|
|
|
|
// will never be equal.
|
|
|
|
return False
|
|
|
|
default:
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case val.IsNull() && other.IsNull():
|
|
|
|
// Nulls are always equal, regardless of type
|
|
|
|
return BoolVal(true)
|
|
|
|
case val.IsNull() || other.IsNull():
|
|
|
|
// If only one is null then the result must be false
|
|
|
|
return BoolVal(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !val.ty.Equals(other.ty) {
|
|
|
|
return BoolVal(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
ty := val.ty
|
|
|
|
result := false
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case ty == Number:
|
|
|
|
result = rawNumberEqual(val.v.(*big.Float), other.v.(*big.Float))
|
|
|
|
case ty == Bool:
|
|
|
|
result = val.v.(bool) == other.v.(bool)
|
|
|
|
case ty == String:
|
|
|
|
// Simple equality is safe because we NFC-normalize strings as they
|
|
|
|
// enter our world from StringVal, and so we can assume strings are
|
|
|
|
// always in normal form.
|
|
|
|
result = val.v.(string) == other.v.(string)
|
|
|
|
case ty.IsObjectType():
|
|
|
|
oty := ty.typeImpl.(typeObject)
|
|
|
|
result = true
|
|
|
|
for attr, aty := range oty.AttrTypes {
|
|
|
|
lhs := Value{
|
|
|
|
ty: aty,
|
|
|
|
v: val.v.(map[string]interface{})[attr],
|
|
|
|
}
|
|
|
|
rhs := Value{
|
|
|
|
ty: aty,
|
|
|
|
v: other.v.(map[string]interface{})[attr],
|
|
|
|
}
|
|
|
|
eq := lhs.Equals(rhs)
|
|
|
|
if !eq.IsKnown() {
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
}
|
|
|
|
if eq.False() {
|
|
|
|
result = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case ty.IsTupleType():
|
|
|
|
tty := ty.typeImpl.(typeTuple)
|
|
|
|
result = true
|
|
|
|
for i, ety := range tty.ElemTypes {
|
|
|
|
lhs := Value{
|
|
|
|
ty: ety,
|
|
|
|
v: val.v.([]interface{})[i],
|
|
|
|
}
|
|
|
|
rhs := Value{
|
|
|
|
ty: ety,
|
|
|
|
v: other.v.([]interface{})[i],
|
|
|
|
}
|
|
|
|
eq := lhs.Equals(rhs)
|
|
|
|
if !eq.IsKnown() {
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
}
|
|
|
|
if eq.False() {
|
|
|
|
result = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case ty.IsListType():
|
|
|
|
ety := ty.typeImpl.(typeList).ElementTypeT
|
|
|
|
if len(val.v.([]interface{})) == len(other.v.([]interface{})) {
|
|
|
|
result = true
|
|
|
|
for i := range val.v.([]interface{}) {
|
|
|
|
lhs := Value{
|
|
|
|
ty: ety,
|
|
|
|
v: val.v.([]interface{})[i],
|
|
|
|
}
|
|
|
|
rhs := Value{
|
|
|
|
ty: ety,
|
|
|
|
v: other.v.([]interface{})[i],
|
|
|
|
}
|
|
|
|
eq := lhs.Equals(rhs)
|
|
|
|
if !eq.IsKnown() {
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
}
|
|
|
|
if eq.False() {
|
|
|
|
result = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case ty.IsSetType():
|
2022-12-24 16:57:19 +00:00
|
|
|
s1 := val.v.(set.Set[interface{}])
|
|
|
|
s2 := other.v.(set.Set[interface{}])
|
2022-04-03 04:07:16 +00:00
|
|
|
equal := true
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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():
|
|
|
|
ety := ty.typeImpl.(typeMap).ElementTypeT
|
|
|
|
if len(val.v.(map[string]interface{})) == len(other.v.(map[string]interface{})) {
|
|
|
|
result = true
|
|
|
|
for k := range val.v.(map[string]interface{}) {
|
|
|
|
if _, ok := other.v.(map[string]interface{})[k]; !ok {
|
|
|
|
result = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
lhs := Value{
|
|
|
|
ty: ety,
|
|
|
|
v: val.v.(map[string]interface{})[k],
|
|
|
|
}
|
|
|
|
rhs := Value{
|
|
|
|
ty: ety,
|
|
|
|
v: other.v.(map[string]interface{})[k],
|
|
|
|
}
|
|
|
|
eq := lhs.Equals(rhs)
|
|
|
|
if !eq.IsKnown() {
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
}
|
|
|
|
if eq.False() {
|
|
|
|
result = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case ty.IsCapsuleType():
|
|
|
|
impl := val.ty.CapsuleOps().Equals
|
|
|
|
if impl == nil {
|
|
|
|
impl := val.ty.CapsuleOps().RawEquals
|
|
|
|
if impl == nil {
|
|
|
|
// A capsule type's encapsulated value is a pointer to a value of its
|
|
|
|
// native type, so we can just compare these to get the identity test
|
|
|
|
// we need.
|
|
|
|
return BoolVal(val.v == other.v)
|
|
|
|
}
|
|
|
|
return BoolVal(impl(val.v, other.v))
|
|
|
|
}
|
|
|
|
ret := impl(val.v, other.v)
|
|
|
|
if !ret.Type().Equals(Bool) {
|
|
|
|
panic(fmt.Sprintf("Equals for %#v returned %#v, not cty.Bool", ty, ret.Type()))
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
|
|
|
|
default:
|
|
|
|
// should never happen
|
|
|
|
panic(fmt.Errorf("unsupported value type %#v in Equals", ty))
|
|
|
|
}
|
|
|
|
|
|
|
|
return BoolVal(result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NotEqual is a shorthand for Equals followed by Not.
|
|
|
|
func (val Value) NotEqual(other Value) Value {
|
|
|
|
return val.Equals(other).Not()
|
|
|
|
}
|
|
|
|
|
|
|
|
// True returns true if the receiver is True, false if False, and panics if
|
|
|
|
// the receiver is not of type Bool.
|
|
|
|
//
|
|
|
|
// This is a helper function to help write application logic that works with
|
|
|
|
// values, rather than a first-class operation. It does not work with unknown
|
|
|
|
// or null values. For more robust handling with unknown value
|
|
|
|
// short-circuiting, use val.Equals(cty.True).
|
|
|
|
func (val Value) True() bool {
|
|
|
|
val.assertUnmarked()
|
|
|
|
if val.ty != Bool {
|
|
|
|
panic("not bool")
|
|
|
|
}
|
|
|
|
return val.Equals(True).v.(bool)
|
|
|
|
}
|
|
|
|
|
|
|
|
// False is the opposite of True.
|
|
|
|
func (val Value) False() bool {
|
|
|
|
return !val.True()
|
|
|
|
}
|
|
|
|
|
|
|
|
// RawEquals returns true if and only if the two given values have the same
|
|
|
|
// type and equal value, ignoring the usual short-circuit rules about
|
|
|
|
// unknowns and dynamic types.
|
|
|
|
//
|
|
|
|
// This method is more appropriate for testing than for real use, since it
|
|
|
|
// skips over usual semantics around unknowns but as a consequence allows
|
|
|
|
// testing the result of another operation that is expected to return unknown.
|
|
|
|
// It returns a primitive Go bool rather than a Value to remind us that it
|
|
|
|
// is not a first-class value operation.
|
|
|
|
func (val Value) RawEquals(other Value) bool {
|
|
|
|
if !val.ty.Equals(other.ty) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if !val.HasSameMarks(other) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// Since we've now checked the marks, we'll unmark for the rest of this...
|
|
|
|
val = val.unmarkForce()
|
|
|
|
other = other.unmarkForce()
|
|
|
|
|
|
|
|
if (!val.IsKnown()) && (!other.IsKnown()) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if (val.IsKnown() && !other.IsKnown()) || (other.IsKnown() && !val.IsKnown()) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if val.IsNull() && other.IsNull() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if (val.IsNull() && !other.IsNull()) || (other.IsNull() && !val.IsNull()) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if val.ty == DynamicPseudoType && other.ty == DynamicPseudoType {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
ty := val.ty
|
|
|
|
switch {
|
|
|
|
case ty == Number || ty == Bool || ty == String || ty == DynamicPseudoType:
|
|
|
|
return val.Equals(other).True()
|
|
|
|
case ty.IsObjectType():
|
|
|
|
oty := ty.typeImpl.(typeObject)
|
|
|
|
for attr, aty := range oty.AttrTypes {
|
|
|
|
lhs := Value{
|
|
|
|
ty: aty,
|
|
|
|
v: val.v.(map[string]interface{})[attr],
|
|
|
|
}
|
|
|
|
rhs := Value{
|
|
|
|
ty: aty,
|
|
|
|
v: other.v.(map[string]interface{})[attr],
|
|
|
|
}
|
|
|
|
eq := lhs.RawEquals(rhs)
|
|
|
|
if !eq {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
case ty.IsTupleType():
|
|
|
|
tty := ty.typeImpl.(typeTuple)
|
|
|
|
for i, ety := range tty.ElemTypes {
|
|
|
|
lhs := Value{
|
|
|
|
ty: ety,
|
|
|
|
v: val.v.([]interface{})[i],
|
|
|
|
}
|
|
|
|
rhs := Value{
|
|
|
|
ty: ety,
|
|
|
|
v: other.v.([]interface{})[i],
|
|
|
|
}
|
|
|
|
eq := lhs.RawEquals(rhs)
|
|
|
|
if !eq {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
case ty.IsListType():
|
|
|
|
ety := ty.typeImpl.(typeList).ElementTypeT
|
|
|
|
if len(val.v.([]interface{})) == len(other.v.([]interface{})) {
|
|
|
|
for i := range val.v.([]interface{}) {
|
|
|
|
lhs := Value{
|
|
|
|
ty: ety,
|
|
|
|
v: val.v.([]interface{})[i],
|
|
|
|
}
|
|
|
|
rhs := Value{
|
|
|
|
ty: ety,
|
|
|
|
v: other.v.([]interface{})[i],
|
|
|
|
}
|
|
|
|
eq := lhs.RawEquals(rhs)
|
|
|
|
if !eq {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
|
|
|
|
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
|
|
|
|
if !val.HasSameMarks(other) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
valUn, _ := val.Unmark()
|
|
|
|
otherUn, _ := other.Unmark()
|
|
|
|
if len(valUn.v.(map[string]interface{})) == len(otherUn.v.(map[string]interface{})) {
|
|
|
|
for k := range valUn.v.(map[string]interface{}) {
|
|
|
|
if _, ok := otherUn.v.(map[string]interface{})[k]; !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
lhs := Value{
|
|
|
|
ty: ety,
|
|
|
|
v: valUn.v.(map[string]interface{})[k],
|
|
|
|
}
|
|
|
|
rhs := Value{
|
|
|
|
ty: ety,
|
|
|
|
v: otherUn.v.(map[string]interface{})[k],
|
|
|
|
}
|
|
|
|
eq := lhs.RawEquals(rhs)
|
|
|
|
if !eq {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
case ty.IsCapsuleType():
|
|
|
|
impl := val.ty.CapsuleOps().RawEquals
|
|
|
|
if impl == nil {
|
|
|
|
// A capsule type's encapsulated value is a pointer to a value of its
|
|
|
|
// native type, so we can just compare these to get the identity test
|
|
|
|
// we need.
|
|
|
|
return val.v == other.v
|
|
|
|
}
|
|
|
|
return impl(val.v, other.v)
|
|
|
|
|
|
|
|
default:
|
|
|
|
// should never happen
|
|
|
|
panic(fmt.Errorf("unsupported value type %#v in RawEquals", ty))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add returns the sum of the receiver and the given other value. Both values
|
|
|
|
// must be numbers; this method will panic if not.
|
|
|
|
func (val Value) Add(other Value) Value {
|
|
|
|
if val.IsMarked() || other.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
other, otherMarks := other.Unmark()
|
|
|
|
return val.Add(other).WithMarks(valMarks, otherMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
|
|
|
|
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
|
|
|
return *shortCircuit
|
|
|
|
}
|
|
|
|
|
|
|
|
ret := new(big.Float)
|
|
|
|
ret.Add(val.v.(*big.Float), other.v.(*big.Float))
|
|
|
|
return NumberVal(ret)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Subtract returns receiver minus the given other value. Both values must be
|
|
|
|
// numbers; this method will panic if not.
|
|
|
|
func (val Value) Subtract(other Value) Value {
|
|
|
|
if val.IsMarked() || other.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
other, otherMarks := other.Unmark()
|
|
|
|
return val.Subtract(other).WithMarks(valMarks, otherMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
|
|
|
|
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
|
|
|
return *shortCircuit
|
|
|
|
}
|
|
|
|
|
|
|
|
return val.Add(other.Negate())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Negate returns the numeric negative of the receiver, which must be a number.
|
|
|
|
// This method will panic when given a value of any other type.
|
|
|
|
func (val Value) Negate() Value {
|
|
|
|
if val.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
return val.Negate().WithMarks(valMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if shortCircuit := mustTypeCheck(Number, Number, val); shortCircuit != nil {
|
|
|
|
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
|
|
|
return *shortCircuit
|
|
|
|
}
|
|
|
|
|
|
|
|
ret := new(big.Float).Neg(val.v.(*big.Float))
|
|
|
|
return NumberVal(ret)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Multiply returns the product of the receiver and the given other value.
|
|
|
|
// Both values must be numbers; this method will panic if not.
|
|
|
|
func (val Value) Multiply(other Value) Value {
|
|
|
|
if val.IsMarked() || other.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
other, otherMarks := other.Unmark()
|
|
|
|
return val.Multiply(other).WithMarks(valMarks, otherMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
|
|
|
|
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
|
|
|
return *shortCircuit
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Divide returns the quotient of the receiver and the given other value.
|
|
|
|
// Both values must be numbers; this method will panic if not.
|
|
|
|
//
|
|
|
|
// If the "other" value is exactly zero, this operation will return either
|
|
|
|
// PositiveInfinity or NegativeInfinity, depending on the sign of the
|
|
|
|
// receiver value. For some use-cases the presence of infinities may be
|
|
|
|
// undesirable, in which case the caller should check whether the
|
|
|
|
// other value equals zero before calling and raise an error instead.
|
|
|
|
//
|
|
|
|
// If both values are zero or infinity, this function will panic with
|
|
|
|
// an instance of big.ErrNaN.
|
|
|
|
func (val Value) Divide(other Value) Value {
|
|
|
|
if val.IsMarked() || other.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
other, otherMarks := other.Unmark()
|
|
|
|
return val.Divide(other).WithMarks(valMarks, otherMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
|
|
|
|
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
|
|
|
return *shortCircuit
|
|
|
|
}
|
|
|
|
|
|
|
|
ret := new(big.Float)
|
|
|
|
ret.Quo(val.v.(*big.Float), other.v.(*big.Float))
|
|
|
|
return NumberVal(ret)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Modulo returns the remainder of an integer division of the receiver and
|
|
|
|
// the given other value. Both values must be numbers; this method will panic
|
|
|
|
// if not.
|
|
|
|
//
|
|
|
|
// If the "other" value is exactly zero, this operation will return either
|
|
|
|
// PositiveInfinity or NegativeInfinity, depending on the sign of the
|
|
|
|
// receiver value. For some use-cases the presence of infinities may be
|
|
|
|
// undesirable, in which case the caller should check whether the
|
|
|
|
// other value equals zero before calling and raise an error instead.
|
|
|
|
//
|
|
|
|
// This operation is primarily here for use with nonzero natural numbers.
|
|
|
|
// Modulo with "other" as a non-natural number gets somewhat philosophical,
|
|
|
|
// and this function takes a position on what that should mean, but callers
|
|
|
|
// may wish to disallow such things outright or implement their own modulo
|
|
|
|
// if they disagree with the interpretation used here.
|
|
|
|
func (val Value) Modulo(other Value) Value {
|
|
|
|
if val.IsMarked() || other.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
other, otherMarks := other.Unmark()
|
|
|
|
return val.Modulo(other).WithMarks(valMarks, otherMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
|
|
|
|
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
|
|
|
return *shortCircuit
|
|
|
|
}
|
|
|
|
|
|
|
|
// We cheat a bit here with infinities, just abusing the Multiply operation
|
|
|
|
// to get an infinite result of the correct sign.
|
|
|
|
if val == PositiveInfinity || val == NegativeInfinity || other == PositiveInfinity || other == NegativeInfinity {
|
|
|
|
return val.Multiply(other)
|
|
|
|
}
|
|
|
|
|
|
|
|
if other.RawEquals(Zero) {
|
|
|
|
return val
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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, _ := 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(v, work)
|
|
|
|
|
|
|
|
return NumberVal(work)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Absolute returns the absolute (signless) value of the receiver, which must
|
|
|
|
// be a number or this method will panic.
|
|
|
|
func (val Value) Absolute() Value {
|
|
|
|
if val.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
return val.Absolute().WithMarks(valMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if shortCircuit := mustTypeCheck(Number, Number, val); shortCircuit != nil {
|
|
|
|
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
|
|
|
return *shortCircuit
|
|
|
|
}
|
|
|
|
|
|
|
|
ret := (&big.Float{}).Abs(val.v.(*big.Float))
|
|
|
|
return NumberVal(ret)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetAttr returns the value of the given attribute of the receiver, which
|
|
|
|
// must be of an object type that has an attribute of the given name.
|
|
|
|
// This method will panic if the receiver type is not compatible.
|
|
|
|
//
|
|
|
|
// The method will also panic if the given attribute name is not defined
|
|
|
|
// for the value's type. Use the attribute-related methods on Type to
|
|
|
|
// check for the validity of an attribute before trying to use it.
|
|
|
|
//
|
|
|
|
// This method may be called on a value whose type is DynamicPseudoType,
|
|
|
|
// in which case the result will also be DynamicVal.
|
|
|
|
func (val Value) GetAttr(name string) Value {
|
|
|
|
if val.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
return val.GetAttr(name).WithMarks(valMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if val.ty == DynamicPseudoType {
|
|
|
|
return DynamicVal
|
|
|
|
}
|
|
|
|
|
|
|
|
if !val.ty.IsObjectType() {
|
|
|
|
panic("value is not an object")
|
|
|
|
}
|
|
|
|
|
|
|
|
name = NormalizeString(name)
|
|
|
|
if !val.ty.HasAttribute(name) {
|
|
|
|
panic("value has no attribute of that name")
|
|
|
|
}
|
|
|
|
|
|
|
|
attrType := val.ty.AttributeType(name)
|
|
|
|
|
|
|
|
if !val.IsKnown() {
|
|
|
|
return UnknownVal(attrType)
|
|
|
|
}
|
|
|
|
|
|
|
|
return Value{
|
|
|
|
ty: attrType,
|
|
|
|
v: val.v.(map[string]interface{})[name],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Index returns the value of an element of the receiver, which must have
|
|
|
|
// either a list, map or tuple type. This method will panic if the receiver
|
|
|
|
// type is not compatible.
|
|
|
|
//
|
|
|
|
// The key value must be the correct type for the receving collection: a
|
|
|
|
// number if the collection is a list or tuple, or a string if it is a map.
|
|
|
|
// In the case of a list or tuple, the given number must be convertable to int
|
|
|
|
// or this method will panic. The key may alternatively be of
|
|
|
|
// DynamicPseudoType, in which case the result itself is an unknown of the
|
|
|
|
// collection's element type.
|
|
|
|
//
|
|
|
|
// The result is of the receiver collection's element type, or in the case
|
|
|
|
// of a tuple the type of the specific element index requested.
|
|
|
|
//
|
|
|
|
// This method may be called on a value whose type is DynamicPseudoType,
|
|
|
|
// in which case the result will also be the DynamicValue.
|
|
|
|
func (val Value) Index(key Value) Value {
|
|
|
|
if val.IsMarked() || key.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
key, keyMarks := key.Unmark()
|
|
|
|
return val.Index(key).WithMarks(valMarks, keyMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if val.ty == DynamicPseudoType {
|
|
|
|
return DynamicVal
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case val.Type().IsListType():
|
|
|
|
elty := val.Type().ElementType()
|
|
|
|
if key.Type() == DynamicPseudoType {
|
|
|
|
return UnknownVal(elty)
|
|
|
|
}
|
|
|
|
|
|
|
|
if key.Type() != Number {
|
|
|
|
panic("element key for list must be number")
|
|
|
|
}
|
|
|
|
if !key.IsKnown() {
|
|
|
|
return UnknownVal(elty)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !val.IsKnown() {
|
|
|
|
return UnknownVal(elty)
|
|
|
|
}
|
|
|
|
|
|
|
|
index, accuracy := key.v.(*big.Float).Int64()
|
|
|
|
if accuracy != big.Exact || index < 0 {
|
|
|
|
panic("element key for list must be non-negative integer")
|
|
|
|
}
|
|
|
|
|
|
|
|
return Value{
|
|
|
|
ty: elty,
|
|
|
|
v: val.v.([]interface{})[index],
|
|
|
|
}
|
|
|
|
case val.Type().IsMapType():
|
|
|
|
elty := val.Type().ElementType()
|
|
|
|
if key.Type() == DynamicPseudoType {
|
|
|
|
return UnknownVal(elty)
|
|
|
|
}
|
|
|
|
|
|
|
|
if key.Type() != String {
|
|
|
|
panic("element key for map must be string")
|
|
|
|
}
|
|
|
|
if !key.IsKnown() {
|
|
|
|
return UnknownVal(elty)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !val.IsKnown() {
|
|
|
|
return UnknownVal(elty)
|
|
|
|
}
|
|
|
|
|
|
|
|
keyStr := key.v.(string)
|
|
|
|
|
|
|
|
return Value{
|
|
|
|
ty: elty,
|
|
|
|
v: val.v.(map[string]interface{})[keyStr],
|
|
|
|
}
|
|
|
|
case val.Type().IsTupleType():
|
|
|
|
if key.Type() == DynamicPseudoType {
|
|
|
|
return DynamicVal
|
|
|
|
}
|
|
|
|
|
|
|
|
if key.Type() != Number {
|
|
|
|
panic("element key for tuple must be number")
|
|
|
|
}
|
|
|
|
if !key.IsKnown() {
|
|
|
|
return DynamicVal
|
|
|
|
}
|
|
|
|
|
|
|
|
index, accuracy := key.v.(*big.Float).Int64()
|
|
|
|
if accuracy != big.Exact || index < 0 {
|
|
|
|
panic("element key for list must be non-negative integer")
|
|
|
|
}
|
|
|
|
|
|
|
|
eltys := val.Type().TupleElementTypes()
|
|
|
|
|
|
|
|
if !val.IsKnown() {
|
|
|
|
return UnknownVal(eltys[index])
|
|
|
|
}
|
|
|
|
|
|
|
|
return Value{
|
|
|
|
ty: eltys[index],
|
|
|
|
v: val.v.([]interface{})[index],
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
panic("not a list, map, or tuple type")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasIndex returns True if the receiver (which must be supported for Index)
|
|
|
|
// has an element with the given index key, or False if it does not.
|
|
|
|
//
|
|
|
|
// The result will be UnknownVal(Bool) if either the collection or the
|
|
|
|
// key value are unknown.
|
|
|
|
//
|
|
|
|
// This method will panic if the receiver is not indexable, but does not
|
|
|
|
// impose any panic-causing type constraints on the key.
|
|
|
|
func (val Value) HasIndex(key Value) Value {
|
|
|
|
if val.IsMarked() || key.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
key, keyMarks := key.Unmark()
|
|
|
|
return val.HasIndex(key).WithMarks(valMarks, keyMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if val.ty == DynamicPseudoType {
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case val.Type().IsListType():
|
|
|
|
if key.Type() == DynamicPseudoType {
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
}
|
|
|
|
|
|
|
|
if key.Type() != Number {
|
|
|
|
return False
|
|
|
|
}
|
|
|
|
if !key.IsKnown() {
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
}
|
|
|
|
if !val.IsKnown() {
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
}
|
|
|
|
|
|
|
|
index, accuracy := key.v.(*big.Float).Int64()
|
|
|
|
if accuracy != big.Exact || index < 0 {
|
|
|
|
return False
|
|
|
|
}
|
|
|
|
|
|
|
|
return BoolVal(int(index) < len(val.v.([]interface{})) && index >= 0)
|
|
|
|
case val.Type().IsMapType():
|
|
|
|
if key.Type() == DynamicPseudoType {
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
}
|
|
|
|
|
|
|
|
if key.Type() != String {
|
|
|
|
return False
|
|
|
|
}
|
|
|
|
if !key.IsKnown() {
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
}
|
|
|
|
if !val.IsKnown() {
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
}
|
|
|
|
|
|
|
|
keyStr := key.v.(string)
|
|
|
|
_, exists := val.v.(map[string]interface{})[keyStr]
|
|
|
|
|
|
|
|
return BoolVal(exists)
|
|
|
|
case val.Type().IsTupleType():
|
|
|
|
if key.Type() == DynamicPseudoType {
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
}
|
|
|
|
|
|
|
|
if key.Type() != Number {
|
|
|
|
return False
|
|
|
|
}
|
|
|
|
if !key.IsKnown() {
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
}
|
|
|
|
|
|
|
|
index, accuracy := key.v.(*big.Float).Int64()
|
|
|
|
if accuracy != big.Exact || index < 0 {
|
|
|
|
return False
|
|
|
|
}
|
|
|
|
|
|
|
|
length := val.Type().Length()
|
|
|
|
return BoolVal(int(index) < length && index >= 0)
|
|
|
|
default:
|
|
|
|
panic("not a list, map, or tuple type")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasElement returns True if the receiver (which must be of a set type)
|
|
|
|
// has the given value as an element, or False if it does not.
|
|
|
|
//
|
|
|
|
// The result will be UnknownVal(Bool) if either the set or the
|
|
|
|
// given value are unknown.
|
|
|
|
//
|
|
|
|
// This method will panic if the receiver is not a set, or if it is a null set.
|
|
|
|
func (val Value) HasElement(elem Value) Value {
|
|
|
|
if val.IsMarked() || elem.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
elem, elemMarks := elem.Unmark()
|
|
|
|
return val.HasElement(elem).WithMarks(valMarks, elemMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
ty := val.Type()
|
|
|
|
|
|
|
|
if !ty.IsSetType() {
|
|
|
|
panic("not a set type")
|
|
|
|
}
|
|
|
|
if !val.IsKnown() || !elem.IsKnown() {
|
|
|
|
return UnknownVal(Bool)
|
|
|
|
}
|
|
|
|
if val.IsNull() {
|
|
|
|
panic("can't call HasElement on a nil value")
|
|
|
|
}
|
|
|
|
if !ty.ElementType().Equals(elem.Type()) {
|
|
|
|
return False
|
|
|
|
}
|
|
|
|
|
2022-12-24 16:57:19 +00:00
|
|
|
s := val.v.(set.Set[interface{}])
|
2022-04-03 04:07:16 +00:00
|
|
|
return BoolVal(s.Has(elem.v))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Length returns the length of the receiver, which must be a collection type
|
|
|
|
// or tuple type, as a number value. If the receiver is not a compatible type
|
|
|
|
// then this method will panic.
|
|
|
|
//
|
|
|
|
// If the receiver is unknown then the result is also unknown.
|
|
|
|
//
|
|
|
|
// 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, use the Length function in funcs/stdlib.
|
|
|
|
func (val Value) Length() Value {
|
|
|
|
if val.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
return val.Length().WithMarks(valMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if val.Type().IsTupleType() {
|
|
|
|
// For tuples, we can return the length even if the value is not known.
|
|
|
|
return NumberIntVal(int64(val.Type().Length()))
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
2022-12-24 16:57:19 +00:00
|
|
|
storeLength := int64(val.v.(set.Set[interface{}]).Length())
|
2022-04-03 04:07:16 +00:00
|
|
|
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()))
|
|
|
|
}
|
|
|
|
|
|
|
|
// LengthInt is like Length except it returns an int. It has the same behavior
|
|
|
|
// as Length except that it will panic if the receiver is unknown.
|
|
|
|
//
|
|
|
|
// 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() {
|
|
|
|
// For tuples, we can return the length even if the value is not known.
|
|
|
|
return val.Type().Length()
|
|
|
|
}
|
|
|
|
if val.Type().IsObjectType() {
|
|
|
|
// For objects, the length is the number of attributes associated with the type.
|
|
|
|
return len(val.Type().AttributeTypes())
|
|
|
|
}
|
|
|
|
if !val.IsKnown() {
|
|
|
|
panic("value is not known")
|
|
|
|
}
|
|
|
|
if val.IsNull() {
|
|
|
|
panic("value is null")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
|
|
|
|
case val.ty.IsListType():
|
|
|
|
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.
|
2022-12-24 16:57:19 +00:00
|
|
|
return val.v.(set.Set[interface{}]).Length()
|
2022-04-03 04:07:16 +00:00
|
|
|
|
|
|
|
case val.ty.IsMapType():
|
|
|
|
return len(val.v.(map[string]interface{}))
|
|
|
|
|
|
|
|
default:
|
|
|
|
panic("value is not a collection")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ElementIterator returns an ElementIterator for iterating the elements
|
|
|
|
// of the receiver, which must be a collection type, a tuple type, or an object
|
|
|
|
// type. If called on a method of any other type, this method will panic.
|
|
|
|
//
|
|
|
|
// The value must be Known and non-Null, or this method will panic.
|
|
|
|
//
|
|
|
|
// If the receiver is of a list type, the returned keys will be of type Number
|
|
|
|
// and the values will be of the list's element type.
|
|
|
|
//
|
|
|
|
// If the receiver is of a map type, the returned keys will be of type String
|
|
|
|
// and the value will be of the map's element type. Elements are passed in
|
|
|
|
// ascending lexicographical order by key.
|
|
|
|
//
|
|
|
|
// If the receiver is of a set type, each element is returned as both the
|
|
|
|
// key and the value, since set members are their own identity.
|
|
|
|
//
|
|
|
|
// If the receiver is of a tuple type, the returned keys will be of type Number
|
|
|
|
// and the value will be of the corresponding element's type.
|
|
|
|
//
|
|
|
|
// If the receiver is of an object type, the returned keys will be of type
|
|
|
|
// String and the value will be of the corresponding attributes's type.
|
|
|
|
//
|
|
|
|
// ElementIterator is an integration method, so it cannot handle Unknown
|
|
|
|
// values. This method will panic if the receiver is Unknown.
|
|
|
|
func (val Value) ElementIterator() ElementIterator {
|
|
|
|
val.assertUnmarked()
|
|
|
|
if !val.IsKnown() {
|
|
|
|
panic("can't use ElementIterator on unknown value")
|
|
|
|
}
|
|
|
|
if val.IsNull() {
|
|
|
|
panic("can't use ElementIterator on null value")
|
|
|
|
}
|
|
|
|
return elementIterator(val)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CanIterateElements returns true if the receiver can support the
|
|
|
|
// ElementIterator method (and by extension, ForEachElement) without panic.
|
|
|
|
func (val Value) CanIterateElements() bool {
|
|
|
|
return canElementIterator(val)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ForEachElement executes a given callback function for each element of
|
|
|
|
// the receiver, which must be a collection type or tuple type, or this method
|
|
|
|
// will panic.
|
|
|
|
//
|
|
|
|
// ForEachElement uses ElementIterator internally, and so the values passed
|
|
|
|
// to the callback are as described for ElementIterator.
|
|
|
|
//
|
|
|
|
// Returns true if the iteration exited early due to the callback function
|
|
|
|
// returning true, or false if the loop ran to completion.
|
|
|
|
//
|
|
|
|
// ForEachElement is an integration method, so it cannot handle Unknown
|
|
|
|
// values. This method will panic if the receiver is Unknown.
|
|
|
|
func (val Value) ForEachElement(cb ElementCallback) bool {
|
|
|
|
val.assertUnmarked()
|
|
|
|
it := val.ElementIterator()
|
|
|
|
for it.Next() {
|
|
|
|
key, val := it.Element()
|
|
|
|
stop := cb(key, val)
|
|
|
|
if stop {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Not returns the logical inverse of the receiver, which must be of type
|
|
|
|
// Bool or this method will panic.
|
|
|
|
func (val Value) Not() Value {
|
|
|
|
if val.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
return val.Not().WithMarks(valMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if shortCircuit := mustTypeCheck(Bool, Bool, val); shortCircuit != nil {
|
|
|
|
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
|
|
|
return *shortCircuit
|
|
|
|
}
|
|
|
|
|
|
|
|
return BoolVal(!val.v.(bool))
|
|
|
|
}
|
|
|
|
|
|
|
|
// And returns the result of logical AND with the receiver and the other given
|
|
|
|
// value, which must both be of type Bool or this method will panic.
|
|
|
|
func (val Value) And(other Value) Value {
|
|
|
|
if val.IsMarked() || other.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
other, otherMarks := other.Unmark()
|
|
|
|
return val.And(other).WithMarks(valMarks, otherMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if shortCircuit := mustTypeCheck(Bool, Bool, val, other); shortCircuit != nil {
|
|
|
|
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
|
|
|
return *shortCircuit
|
|
|
|
}
|
|
|
|
|
|
|
|
return BoolVal(val.v.(bool) && other.v.(bool))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Or returns the result of logical OR with the receiver and the other given
|
|
|
|
// value, which must both be of type Bool or this method will panic.
|
|
|
|
func (val Value) Or(other Value) Value {
|
|
|
|
if val.IsMarked() || other.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
other, otherMarks := other.Unmark()
|
|
|
|
return val.Or(other).WithMarks(valMarks, otherMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if shortCircuit := mustTypeCheck(Bool, Bool, val, other); shortCircuit != nil {
|
|
|
|
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
|
|
|
return *shortCircuit
|
|
|
|
}
|
|
|
|
|
|
|
|
return BoolVal(val.v.(bool) || other.v.(bool))
|
|
|
|
}
|
|
|
|
|
|
|
|
// LessThan returns True if the receiver is less than the other given value,
|
|
|
|
// which must both be numbers or this method will panic.
|
|
|
|
func (val Value) LessThan(other Value) Value {
|
|
|
|
if val.IsMarked() || other.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
other, otherMarks := other.Unmark()
|
|
|
|
return val.LessThan(other).WithMarks(valMarks, otherMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if shortCircuit := mustTypeCheck(Number, Bool, val, other); shortCircuit != nil {
|
|
|
|
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
|
|
|
return *shortCircuit
|
|
|
|
}
|
|
|
|
|
|
|
|
return BoolVal(val.v.(*big.Float).Cmp(other.v.(*big.Float)) < 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GreaterThan returns True if the receiver is greater than the other given
|
|
|
|
// value, which must both be numbers or this method will panic.
|
|
|
|
func (val Value) GreaterThan(other Value) Value {
|
|
|
|
if val.IsMarked() || other.IsMarked() {
|
|
|
|
val, valMarks := val.Unmark()
|
|
|
|
other, otherMarks := other.Unmark()
|
|
|
|
return val.GreaterThan(other).WithMarks(valMarks, otherMarks)
|
|
|
|
}
|
|
|
|
|
|
|
|
if shortCircuit := mustTypeCheck(Number, Bool, val, other); shortCircuit != nil {
|
|
|
|
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
|
|
|
return *shortCircuit
|
|
|
|
}
|
|
|
|
|
|
|
|
return BoolVal(val.v.(*big.Float).Cmp(other.v.(*big.Float)) > 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LessThanOrEqualTo is equivalent to LessThan and Equal combined with Or.
|
|
|
|
func (val Value) LessThanOrEqualTo(other Value) Value {
|
|
|
|
return val.LessThan(other).Or(val.Equals(other))
|
|
|
|
}
|
|
|
|
|
|
|
|
// GreaterThanOrEqualTo is equivalent to GreaterThan and Equal combined with Or.
|
|
|
|
func (val Value) GreaterThanOrEqualTo(other Value) Value {
|
|
|
|
return val.GreaterThan(other).Or(val.Equals(other))
|
|
|
|
}
|
|
|
|
|
|
|
|
// AsString returns the native string from a non-null, non-unknown cty.String
|
|
|
|
// value, or panics if called on any other value.
|
|
|
|
func (val Value) AsString() string {
|
|
|
|
val.assertUnmarked()
|
|
|
|
if val.ty != String {
|
|
|
|
panic("not a string")
|
|
|
|
}
|
|
|
|
if val.IsNull() {
|
|
|
|
panic("value is null")
|
|
|
|
}
|
|
|
|
if !val.IsKnown() {
|
|
|
|
panic("value is unknown")
|
|
|
|
}
|
|
|
|
|
|
|
|
return val.v.(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AsBigFloat returns a big.Float representation of a non-null, non-unknown
|
|
|
|
// cty.Number value, or panics if called on any other value.
|
|
|
|
//
|
|
|
|
// For more convenient conversions to other native numeric types, use the
|
|
|
|
// "gocty" package.
|
|
|
|
func (val Value) AsBigFloat() *big.Float {
|
|
|
|
val.assertUnmarked()
|
|
|
|
if val.ty != Number {
|
|
|
|
panic("not a number")
|
|
|
|
}
|
|
|
|
if val.IsNull() {
|
|
|
|
panic("value is null")
|
|
|
|
}
|
|
|
|
if !val.IsKnown() {
|
|
|
|
panic("value is unknown")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy the float so that callers can't mutate our internal state
|
|
|
|
return new(big.Float).Copy(val.v.(*big.Float))
|
|
|
|
}
|
|
|
|
|
|
|
|
// AsValueSlice returns a []cty.Value representation of a non-null, non-unknown
|
|
|
|
// value of any type that CanIterateElements, or panics if called on
|
|
|
|
// any other value.
|
|
|
|
//
|
|
|
|
// For more convenient conversions to slices of more specific types, use
|
|
|
|
// the "gocty" package.
|
|
|
|
func (val Value) AsValueSlice() []Value {
|
|
|
|
val.assertUnmarked()
|
|
|
|
l := val.LengthInt()
|
|
|
|
if l == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ret := make([]Value, 0, l)
|
|
|
|
for it := val.ElementIterator(); it.Next(); {
|
|
|
|
_, v := it.Element()
|
|
|
|
ret = append(ret, v)
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
// AsValueMap returns a map[string]cty.Value representation of a non-null,
|
|
|
|
// non-unknown value of any type that CanIterateElements, or panics if called
|
|
|
|
// on any other value.
|
|
|
|
//
|
|
|
|
// For more convenient conversions to maps of more specific types, use
|
|
|
|
// the "gocty" package.
|
|
|
|
func (val Value) AsValueMap() map[string]Value {
|
|
|
|
val.assertUnmarked()
|
|
|
|
l := val.LengthInt()
|
|
|
|
if l == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ret := make(map[string]Value, l)
|
|
|
|
for it := val.ElementIterator(); it.Next(); {
|
|
|
|
k, v := it.Element()
|
|
|
|
ret[k.AsString()] = v
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
// AsValueSet returns a ValueSet representation of a non-null,
|
|
|
|
// non-unknown value of any collection type, or panics if called
|
|
|
|
// on any other value.
|
|
|
|
//
|
|
|
|
// Unlike AsValueSlice and AsValueMap, this method requires specifically a
|
|
|
|
// collection type (list, set or map) and does not allow structural types
|
|
|
|
// (tuple or object), because the ValueSet type requires homogenous
|
|
|
|
// element types.
|
|
|
|
//
|
|
|
|
// The returned ValueSet can store only values of the receiver's element type.
|
|
|
|
func (val Value) AsValueSet() ValueSet {
|
|
|
|
val.assertUnmarked()
|
|
|
|
if !val.Type().IsCollectionType() {
|
|
|
|
panic("not a collection type")
|
|
|
|
}
|
|
|
|
|
|
|
|
// We don't give the caller our own set.Set (assuming we're a cty.Set value)
|
|
|
|
// because then the caller could mutate our internals, which is forbidden.
|
|
|
|
// Instead, we will construct a new set and append our elements into it.
|
|
|
|
ret := NewValueSet(val.Type().ElementType())
|
|
|
|
for it := val.ElementIterator(); it.Next(); {
|
|
|
|
_, v := it.Element()
|
|
|
|
ret.Add(v)
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
// EncapsulatedValue returns the native value encapsulated in a non-null,
|
|
|
|
// non-unknown capsule-typed value, or panics if called on any other value.
|
|
|
|
//
|
|
|
|
// The result is the same pointer that was passed to CapsuleVal to create
|
|
|
|
// the value. Since cty considers values to be immutable, it is strongly
|
|
|
|
// recommended to treat the encapsulated value itself as immutable too.
|
|
|
|
func (val Value) EncapsulatedValue() interface{} {
|
|
|
|
val.assertUnmarked()
|
|
|
|
if !val.Type().IsCapsuleType() {
|
|
|
|
panic("not a capsule-typed value")
|
|
|
|
}
|
|
|
|
|
|
|
|
return val.v
|
|
|
|
}
|