Convert should only handle specific types

This commit is contained in:
Thomas Pelletier
2021-03-08 09:42:19 -05:00
parent 2cee819ce4
commit f698c102c7
2 changed files with 87 additions and 37 deletions
@@ -350,20 +350,6 @@ type errStruct struct {
String *string `toml:"string"`
}
var errTomls = []string{
"bool = truly\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"",
"bool = true\ndate = 1979-05-27T07:3200Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"",
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123a4\nint = 5000\nstring = \"Bite me\"",
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = j000\nstring = \"Bite me\"",
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me",
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me",
"bool = 1\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"",
"bool = true\ndate = 1\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"",
"bool = true\ndate = 1979-05-27T07:32:00Z\n\"sorry\"\nint = 5000\nstring = \"Bite me\"",
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = \"sorry\"\nstring = \"Bite me\"",
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = 1",
}
type mapErr struct {
Vals map[string]float64
}
@@ -399,12 +385,28 @@ var intErrTomls = []string{
}
func TestErrUnmarshal(t *testing.T) {
var errTomls = []string{
"bool = truly\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"",
"bool = true\ndate = 1979-05-27T07:3200Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"",
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123a4\nint = 5000\nstring = \"Bite me\"",
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = j000\nstring = \"Bite me\"",
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me",
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me",
"bool = 1\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"",
"bool = true\ndate = 1\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"",
"bool = true\ndate = 1979-05-27T07:32:00Z\n\"sorry\"\nint = 5000\nstring = \"Bite me\"",
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = \"sorry\"\nstring = \"Bite me\"",
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = 1",
}
for ind, x := range errTomls {
result := errStruct{}
err := toml.Unmarshal([]byte(x), &result)
if err == nil {
t.Errorf("Expected err from case %d\n", ind)
}
t.Run(fmt.Sprintf("Base Case %d", ind), func(t *testing.T) {
result := errStruct{}
err := toml.Unmarshal([]byte(x), &result)
if err == nil {
t.Errorf("Expected err from case %d\n", ind)
}
})
}
result2 := mapErr{}
err := toml.Unmarshal([]byte("[Vals]\nfred=\"1.2\""), &result2)
+66 -18
View File
@@ -22,13 +22,6 @@ type target interface {
fmt.Stringer
}
func isAssignable(t reflect.Type, v reflect.Value) error {
if v.Type().AssignableTo(t) {
return nil
}
return fmt.Errorf("cannot assign '%s' ('%s') to a '%s'", v, v.Type(), t)
}
type valueTarget reflect.Value
func (v valueTarget) get() reflect.Value {
@@ -39,6 +32,9 @@ func (v valueTarget) set(value reflect.Value) error {
rv := reflect.Value(v)
// value is guaranteed to be a pointer
if value.Kind() != reflect.Ptr {
panic(fmt.Sprintf("set() should receive a pointer, not a '%s'", value.Kind()))
}
if rv.Kind() != reflect.Ptr {
// TODO: check value is nil?
@@ -46,12 +42,12 @@ func (v valueTarget) set(value reflect.Value) error {
}
targetType := rv.Type()
value, err := tryConvert(targetType, value)
value, err := convert(targetType, value)
if err != nil {
return err
}
reflect.Value(v).Set(value)
rv.Set(value)
return nil
}
@@ -77,7 +73,7 @@ func (v mapTarget) set(value reflect.Value) error {
}
targetType := v.m.Type().Elem()
value, err := tryConvert(targetType, value)
value, err := convert(targetType, value)
if err != nil {
return err
}
@@ -277,7 +273,7 @@ func (b *Builder) DigField(s string) error {
// TODO: handle error when map is not indexed by strings
key := reflect.ValueOf(s)
key, err := tryConvert(v.Type().Key(), key)
key, err := convert(v.Type().Key(), key)
if err != nil {
return err
}
@@ -433,7 +429,7 @@ func (b *Builder) SliceAppend(value reflect.Value) error {
}
if v.Type().Elem() != value.Type() {
//nv, err := tryConvert(v.Type().Elem(), value)
//nv, err := convert(v.Type().Elem(), value)
//if err != nil {
return fmt.Errorf("cannot assign '%s' to '%s'", value.Type(), v.Type().Elem())
//}
@@ -446,7 +442,19 @@ func (b *Builder) SliceAppend(value reflect.Value) error {
return nil
}
func tryConvert(t reflect.Type, value reflect.Value) (reflect.Value, error) {
// convert value so that it can be assigned to t.
//
// Conversion rules:
//
// * Pointers are de-referenced as needed.
// * Integer types are converted between each other as long as they don't
// overflow.
// * Float types are converted between each other as long as they don't
// overflow.
//
// TODO: this function acts as a switchboard. Runtime has enough information to
// generate per-type functions avoiding the double type switches.
func convert(t reflect.Type, value reflect.Value) (reflect.Value, error) {
result := value
if value.Type().AssignableTo(t) {
@@ -462,13 +470,20 @@ func tryConvert(t reflect.Type, value reflect.Value) (reflect.Value, error) {
t = t.Elem()
}
if !value.Type().ConvertibleTo(t) {
return result, fmt.Errorf("cannot convert '%s' to '%s'", value.Type(), t)
}
var err error
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
value.Convert(reflect.TypeOf(int64(0)))
value, err = convertInt(t, value)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
value, err = convertUint(t, value)
case reflect.Float32, reflect.Float64:
value, err = convertFloat(t, value)
default:
err = fmt.Errorf("not converting a %s into a %s", value.Kind(), t.Kind())
}
if err != nil {
return value, err
}
result = reflect.New(t)
@@ -476,6 +491,39 @@ func tryConvert(t reflect.Type, value reflect.Value) (reflect.Value, error) {
return result.Elem(), nil
}
func convertInt(t reflect.Type, value reflect.Value) (reflect.Value, error) {
switch value.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return value.Convert(t), nil // reflect.TypeOf(int64(0))
default:
return value, fmt.Errorf("cannot convert %s to integer (%s)", value.Kind(), t.Kind())
}
}
func convertUint(t reflect.Type, value reflect.Value) (reflect.Value, error) {
switch value.Kind() {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return value.Convert(t), nil // reflect.TypeOf(int64(0))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
x := value.Int()
if x < 0 {
return value, fmt.Errorf("cannot store negative integer '%d' into %s", x, t.Kind())
}
return value.Convert(t), nil
default:
return value, fmt.Errorf("cannot convert %s to unsigned integer (%s)", value.Kind(), t.Kind())
}
}
func convertFloat(t reflect.Type, value reflect.Value) (reflect.Value, error) {
switch value.Kind() {
case reflect.Float32, reflect.Float64:
return value.Convert(t), nil
default:
return value, fmt.Errorf("cannot convert %s to integer (%s)", value.Kind(), t.Kind())
}
}
// Set the value at the cursor to the given string.
// Errors if a string cannot be assigned to the current value.
func (b *Builder) SetString(s string) error {