blob: e34cee366148172a3b6d461114bd48b3c637c18d [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vdltest
import (
"fmt"
"math"
"v.io/v23/vdl"
)
const (
// TODO(toddw): Move these constants to a common place, they already exist in
// the vdl and vom packages. Also think about how to factor out the number
// conversion logic.
float64MaxInt = (1 << 53)
float64MinInt = -(1 << 53)
float32MaxInt = (1 << 24)
float32MinInt = -(1 << 24)
)
// MimicValue returns a value of type tt that, when converted to the type of the
// base value, results in exactly the base value. If tt is Any, the type of the
// returned value is the type of the base value.
//
// Returns nil if no such value is possible. E.g. if tt is uint32 and base is
// int32(-1), no value of type tt can be converted to the base value.
func MimicValue(tt *vdl.Type, base *vdl.Value) *vdl.Value {
// Handle nil first, to get rid of pesky corner cases. After this is done,
// we've flattened the base value to its non-nil element.
baseWasAny := false
if base.Kind() == vdl.Any && !base.IsNil() {
baseWasAny = true
base = base.Elem()
}
if base.IsNil() {
return mimicNilValue(tt, base)
}
// Now we know we're dealing with a non-nil non-any base.
if tt == vdl.AnyType {
// We've been asked to build a value of any type, so we build exactly the
// same type as the base. There are two cases:
// 1) base: int64 tt: any
// 2) base: any(int64) tt: any
//
// For case 1 we could actually fill in tt with any type compatible with the
// base type, but for case 2 we must fill in tt with the exact base type;
// see case 4 below. For simplicity we just always use the exact base type.
tt = base.Type()
}
if baseWasAny && tt != base.Type() {
// If base was an any value, tt must be exactly the same type as the base.
// There are two cases:
// 3) base: any(int64) tt: int64
// 4) base: any(int64) tt: any(int64)
//
// Note that it's not good enough to mimic a value of a compatible type tt;
// if we changed tt to int16 above, we wouldn't end up with a result of the
// right type. Case 4 was ensured by case 2 above.
return nil
}
value := mimicNonNilValue(tt.NonOptional(), base.NonOptional())
if value == nil {
return nil
}
if tt.Kind() == vdl.Optional {
value = vdl.OptionalValue(value)
}
return value
}
// mimicNilValue implements MimicValue for nil base values.
//
// REQUIRES: base.IsNil() is true
func mimicNilValue(tt *vdl.Type, base *vdl.Value) *vdl.Value {
switch {
case tt == vdl.AnyType:
// We can convert from any(nil) to all nil base values.
return vdl.ZeroValue(vdl.AnyType)
case tt.Kind() != vdl.Optional:
// Can't convert from a non-any non-optional type to a nil value.
return nil
}
// Now we know that tt is optional, and base is either any(nil) or
// optional(nil).
switch {
case base.Type() == vdl.AnyType:
// Can't convert from optional(nil) to any(nil).
return nil
case !vdl.Compatible2(tt, base.Type()):
// Can't convert incompatible optional types.
return nil
}
// Now we know that tt and base are both optional and compatible.
return vdl.ZeroValue(tt)
}
// mimicNonNilValue implements MimicValue for non-nil base values.
//
// REQUIRES: tt and base cannot be Optional or Any.
func mimicNonNilValue(tt *vdl.Type, base *vdl.Value) *vdl.Value {
if !vdl.Compatible2(tt, base.Type()) {
return nil
}
switch tt.Kind() {
case vdl.Bool:
return vdl.BoolValue(tt, base.Bool())
case vdl.String:
return vdl.StringValue(tt, stringOrEnumLabel(base))
case vdl.Enum:
index := tt.EnumIndex(stringOrEnumLabel(base))
if index == -1 {
return nil
}
return vdl.EnumValue(tt, index)
case vdl.TypeObject:
return base // TypeObject is only convertible from itself.
case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64:
return mimicUint(tt, base)
case vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64:
return mimicInt(tt, base)
case vdl.Float32, vdl.Float64:
return mimicFloat(tt, base)
case vdl.Array, vdl.List:
return mimicArrayList(tt, base)
case vdl.Set:
return mimicSet(tt, base)
case vdl.Map:
return mimicMap(tt, base)
case vdl.Struct:
return mimicStruct(tt, base)
case vdl.Union:
return mimicUnion(tt, base)
}
panic(fmt.Errorf("vdltest: mimicNonNilValue unhandled type %v", tt))
}
func stringOrEnumLabel(vv *vdl.Value) string {
if vv.Kind() == vdl.String {
return vv.RawString()
}
return vv.EnumLabel()
}
func mimicUint(tt *vdl.Type, base *vdl.Value) *vdl.Value {
bitlen := uint(tt.Kind().BitLen())
var x uint64
switch base.Kind() {
case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64:
x = base.Uint()
if shift := 64 - bitlen; x != (x<<shift)>>shift {
return nil
}
case vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64:
ix := base.Int()
x = uint64(ix)
if shift := 64 - bitlen; ix < 0 || x != (x<<shift)>>shift {
return nil
}
case vdl.Float32, vdl.Float64:
fx := base.Float()
x = uint64(fx)
if shift := 64 - bitlen; fx != float64(x) || x != (x<<shift)>>shift {
return nil
}
}
return vdl.UintValue(tt, x)
}
func mimicInt(tt *vdl.Type, base *vdl.Value) *vdl.Value {
bitlen := uint(tt.Kind().BitLen())
var x int64
switch base.Kind() {
case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64:
ux := base.Uint()
x = int64(ux)
// The shift uses 65 since the topmost bit is the sign bit. E.g. 32 bit
// numbers should be shifted by 33 rather than 32.
if shift := 65 - bitlen; x < 0 || ux != (ux<<shift)>>shift {
return nil
}
case vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64:
x = base.Int()
if shift := 64 - bitlen; x != (x<<shift)>>shift {
return nil
}
case vdl.Float32, vdl.Float64:
fx := base.Float()
x = int64(fx)
if shift := 64 - bitlen; fx != float64(x) || x != (x<<shift)>>shift {
return nil
}
}
return vdl.IntValue(tt, x)
}
func mimicFloat(tt *vdl.Type, base *vdl.Value) *vdl.Value {
bitlen := uint(tt.Kind().BitLen())
var x float64
switch base.Kind() {
case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64:
ux := base.Uint()
x = float64(ux)
var max uint64
if bitlen > 32 {
max = float64MaxInt
} else {
max = float32MaxInt
}
if ux > max {
return nil
}
case vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64:
ix := base.Int()
x = float64(ix)
var min, max int64
if bitlen > 32 {
min, max = float64MinInt, float64MaxInt
} else {
min, max = float32MinInt, float32MaxInt
}
if ix < min || ix > max {
return nil
}
case vdl.Float32:
x = base.Float()
case vdl.Float64:
x = base.Float()
if tt.Kind() == vdl.Float32 {
// We're trying to mimic a base float64 value with a float32 value. Make
// sure we won't lose precision.
//
// Float64 has 1 sign bit, 11 exponent bits, and 52 fraction bits.
// Float32 has 1 sign bit, 8 exponent bits, and 23 fraction bits.
//
// The offsetExp is offset by 1023. Some special values:
// offsetExp == 0 && frac == 0 : Negative 0
// offsetExp == 0 && frac > 0 : Subnormal number
// offsetExp == 7ff && frac == 0 : Inf
// offsetExp == 7ff && frac > 0 : NaN
//
// https://en.wikipedia.org/wiki/Double-precision_floating-point_format
// https://en.wikipedia.org/wiki/Single-precision_floating-point_format
bits := math.Float64bits(x)
offsetExp := bits >> 52 & ((1 << 11) - 1)
frac := bits & ((1 << 52) - 1)
switch exp := int(offsetExp) - 1023; {
case offsetExp == 0 && frac > 0:
// Float64 subnormals can't be represented by float32.
return nil
case exp < -0xff || exp > 0xff:
// Float64 with >8 exponent bits can't be represented by float32.
// TODO(toddw): Handle Inf and Nan in vdl float consts.
return nil
case frac&((1<<23)-1) != 0:
// Float64 with >23 fraction bits can't be represented by float32.
return nil
}
}
}
return vdl.FloatValue(tt, x)
}
func mimicArrayList(tt *vdl.Type, base *vdl.Value) *vdl.Value {
value := vdl.ZeroValue(tt)
switch tt.Kind() {
case vdl.Array:
if tt.Len() != base.Len() {
return nil
}
case vdl.List:
value.AssignLen(base.Len())
}
for ix := 0; ix < base.Len(); ix++ {
elem := MimicValue(tt.Elem(), base.Index(ix))
if elem == nil {
return nil
}
value.AssignIndex(ix, elem)
}
return value
}
func mimicSet(tt *vdl.Type, base *vdl.Value) *vdl.Value {
value := vdl.ZeroValue(tt)
for _, baseKey := range base.Keys() {
key := MimicValue(tt.Key(), baseKey)
if key == nil {
return nil
}
value.AssignSetKey(key)
}
return value
}
func mimicMap(tt *vdl.Type, base *vdl.Value) *vdl.Value {
value := vdl.ZeroValue(tt)
for _, baseKey := range base.Keys() {
key := MimicValue(tt.Key(), baseKey)
if key == nil {
return nil
}
elem := MimicValue(tt.Elem(), base.MapIndex(baseKey))
if elem == nil {
return nil
}
value.AssignMapIndex(key, elem)
}
return value
}
func mimicStruct(tt *vdl.Type, base *vdl.Value) *vdl.Value {
value := vdl.ZeroValue(tt)
for ix := 0; ix < base.Type().NumField(); ix++ {
baseField := base.StructField(ix)
if baseField.IsZero() {
// Skip zero base fields. It's fine for the base field to be missing from
// tt, as long as the base field is zero, since Convert(dst, src) sets all
// dst (which has type base.Type) fields to zero before setting each
// matching field in src (which has type tt).
continue
}
ttField, ttIndex := tt.FieldByName(base.Type().Field(ix).Name)
if ttIndex == -1 {
// This is a non-zero base field that doesn't exist in tt. There's no way
// to create a value of type tt that converts to exactly the base value.
return nil
}
field := MimicValue(ttField.Type, baseField)
if field == nil {
return nil
}
value.AssignField(ttIndex, field)
}
return value
}
func mimicUnion(tt *vdl.Type, base *vdl.Value) *vdl.Value {
baseIndex, baseField := base.UnionField()
ttField, ttIndex := tt.FieldByName(base.Type().Field(baseIndex).Name)
if ttIndex == -1 {
// The base field doesn't exist in tt. There's no way to create a value of
// type tt that converts to exactly the base value.
return nil
}
field := MimicValue(ttField.Type, baseField)
if field == nil {
return nil
}
return vdl.UnionValue(tt, ttIndex, field)
}