147 lines
3.5 KiB
Go
147 lines
3.5 KiB
Go
|
package cty
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"hash/crc32"
|
||
|
"math/big"
|
||
|
"sort"
|
||
|
)
|
||
|
|
||
|
// setRules provides a Rules implementation for the ./set package that
|
||
|
// respects the equality rules for cty values of the given type.
|
||
|
//
|
||
|
// This implementation expects that values added to the set will be
|
||
|
// valid internal values for the given Type, which is to say that wrapping
|
||
|
// the given value in a Value struct along with the ruleset's type should
|
||
|
// produce a valid, working Value.
|
||
|
type setRules struct {
|
||
|
Type Type
|
||
|
}
|
||
|
|
||
|
func (r setRules) Hash(v interface{}) int {
|
||
|
hashBytes := makeSetHashBytes(Value{
|
||
|
ty: r.Type,
|
||
|
v: v,
|
||
|
})
|
||
|
return int(crc32.ChecksumIEEE(hashBytes))
|
||
|
}
|
||
|
|
||
|
func (r setRules) Equivalent(v1 interface{}, v2 interface{}) bool {
|
||
|
v1v := Value{
|
||
|
ty: r.Type,
|
||
|
v: v1,
|
||
|
}
|
||
|
v2v := Value{
|
||
|
ty: r.Type,
|
||
|
v: v2,
|
||
|
}
|
||
|
|
||
|
eqv := v1v.Equals(v2v)
|
||
|
|
||
|
// By comparing the result to true we ensure that an Unknown result,
|
||
|
// which will result if either value is unknown, will be considered
|
||
|
// as non-equivalent. Two unknown values are not equivalent for the
|
||
|
// sake of set membership.
|
||
|
return eqv.v == true
|
||
|
}
|
||
|
|
||
|
func makeSetHashBytes(val Value) []byte {
|
||
|
var buf bytes.Buffer
|
||
|
appendSetHashBytes(val, &buf)
|
||
|
return buf.Bytes()
|
||
|
}
|
||
|
|
||
|
func appendSetHashBytes(val Value, buf *bytes.Buffer) {
|
||
|
// Exactly what bytes we generate here don't matter as long as the following
|
||
|
// constraints hold:
|
||
|
// - Unknown and null values all generate distinct strings from
|
||
|
// each other and from any normal value of the given type.
|
||
|
// - The delimiter used to separate items in a compound structure can
|
||
|
// never appear literally in any of its elements.
|
||
|
// Since we don't support hetrogenous lists we don't need to worry about
|
||
|
// collisions between values of different types, apart from
|
||
|
// PseudoTypeDynamic.
|
||
|
// If in practice we *do* get a collision then it's not a big deal because
|
||
|
// the Equivalent function will still distinguish values, but set
|
||
|
// performance will be best if we are able to produce a distinct string
|
||
|
// for each distinct value, unknown values notwithstanding.
|
||
|
if !val.IsKnown() {
|
||
|
buf.WriteRune('?')
|
||
|
return
|
||
|
}
|
||
|
if val.IsNull() {
|
||
|
buf.WriteRune('~')
|
||
|
return
|
||
|
}
|
||
|
|
||
|
switch val.ty {
|
||
|
case Number:
|
||
|
buf.WriteString(val.v.(*big.Float).String())
|
||
|
return
|
||
|
case Bool:
|
||
|
if val.v.(bool) {
|
||
|
buf.WriteRune('T')
|
||
|
} else {
|
||
|
buf.WriteRune('F')
|
||
|
}
|
||
|
return
|
||
|
case String:
|
||
|
buf.WriteString(fmt.Sprintf("%q", val.v.(string)))
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if val.ty.IsMapType() {
|
||
|
buf.WriteRune('{')
|
||
|
val.ForEachElement(func(keyVal, elementVal Value) bool {
|
||
|
appendSetHashBytes(keyVal, buf)
|
||
|
buf.WriteRune(':')
|
||
|
appendSetHashBytes(elementVal, buf)
|
||
|
buf.WriteRune(';')
|
||
|
return false
|
||
|
})
|
||
|
buf.WriteRune('}')
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if val.ty.IsListType() || val.ty.IsSetType() {
|
||
|
buf.WriteRune('[')
|
||
|
val.ForEachElement(func(keyVal, elementVal Value) bool {
|
||
|
appendSetHashBytes(elementVal, buf)
|
||
|
buf.WriteRune(';')
|
||
|
return false
|
||
|
})
|
||
|
buf.WriteRune(']')
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if val.ty.IsObjectType() {
|
||
|
buf.WriteRune('<')
|
||
|
attrNames := make([]string, 0, len(val.ty.AttributeTypes()))
|
||
|
for attrName := range val.ty.AttributeTypes() {
|
||
|
attrNames = append(attrNames, attrName)
|
||
|
}
|
||
|
sort.Strings(attrNames)
|
||
|
for _, attrName := range attrNames {
|
||
|
appendSetHashBytes(val.GetAttr(attrName), buf)
|
||
|
buf.WriteRune(';')
|
||
|
}
|
||
|
buf.WriteRune('>')
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if val.ty.IsTupleType() {
|
||
|
buf.WriteRune('<')
|
||
|
val.ForEachElement(func(keyVal, elementVal Value) bool {
|
||
|
appendSetHashBytes(elementVal, buf)
|
||
|
buf.WriteRune(';')
|
||
|
return false
|
||
|
})
|
||
|
buf.WriteRune('>')
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// should never get down here
|
||
|
panic("unsupported type in set hash")
|
||
|
}
|