| // 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) |
| } |