diff --git a/typecheck/builtin/int_test.go b/typecheck/builtin/int_test.go index 63dc04e..0461847 100644 --- a/typecheck/builtin/int_test.go +++ b/typecheck/builtin/int_test.go @@ -92,6 +92,15 @@ func TestInt_Values(t *testing.T) { // we cannot just add 1 because of how precision works {float64(math.MaxInt64 + 1024 + 2), false}, + // json number + {fmt.Sprintf("%f", float64(math.MinInt64-1024-1)), false}, + {fmt.Sprintf("%d", math.MinInt64), true}, + {"-1", true}, + {"0", true}, + {"1", true}, + {fmt.Sprintf("%d", math.MaxInt64), true}, + {fmt.Sprintf("%f", float64(math.MaxInt64+1024+2)), false}, + {"string", false}, {[]byte("bytes"), false}, {-0.1, false}, diff --git a/typecheck/builtin/uint.go b/typecheck/builtin/uint.go index 2eb7914..fbbaf38 100644 --- a/typecheck/builtin/uint.go +++ b/typecheck/builtin/uint.go @@ -1,6 +1,9 @@ package builtin import ( + "encoding/json" + "math" + "git.xdrm.io/go/aicra/typecheck" ) @@ -19,12 +22,40 @@ func (Uint) Checker(typeName string) typecheck.CheckerFunc { return nil } return func(value interface{}) bool { - cast, isFloat := readFloat(value) + _, isInt := readUint(value) + + return isInt + } +} + +// readUint tries to read a serialized uint and returns whether it succeeded. +func readUint(value interface{}) (uint, bool) { + switch cast := value.(type) { + + case int: + return uint(cast), cast >= 0 + + case uint: + return cast, true + + case float64: + uintVal := uint(cast) + overflows := cast < 0 || cast > math.MaxUint64 + return uintVal, cast == float64(uintVal) && !overflows + + // serialized string -> try to convert to float + case string: + num := json.Number(cast) + floatVal, err := num.Float64() + if err != nil { + return 0, false + } + overflows := floatVal < 0 || floatVal > math.MaxUint64 + return uint(floatVal), !overflows + + // unknown type + default: + return 0, false - if !isFloat { - return false - } - - return cast >= 0 && cast == float64(int(cast)) } } diff --git a/typecheck/builtin/uint_test.go b/typecheck/builtin/uint_test.go new file mode 100644 index 0000000..c1d26aa --- /dev/null +++ b/typecheck/builtin/uint_test.go @@ -0,0 +1,122 @@ +package builtin_test + +import ( + "fmt" + "math" + "testing" + + "git.xdrm.io/go/aicra/typecheck/builtin" +) + +func TestUint_New(t *testing.T) { + t.Parallel() + + inst := interface{}(builtin.NewUint()) + + switch cast := inst.(type) { + case *builtin.Uint: + return + default: + t.Errorf("expect %T ; got %T", &builtin.Uint{}, cast) + } +} + +func TestUint_AvailableTypes(t *testing.T) { + t.Parallel() + + inst := builtin.NewUint() + + tests := []struct { + Type string + Handled bool + }{ + {"uint", true}, + {"Uint", false}, + {"UINT", false}, + {" uint", false}, + {"uint ", false}, + {" uint ", false}, + } + + for _, test := range tests { + t.Run(test.Type, func(t *testing.T) { + checker := inst.Checker(test.Type) + if checker == nil { + if test.Handled { + t.Errorf("expect %q to be handled", test.Type) + t.Fail() + } + return + } + + if !test.Handled { + t.Errorf("expect %q NOT to be handled", test.Type) + t.Fail() + } + }) + } + +} + +func TestUint_Values(t *testing.T) { + t.Parallel() + + const typeName = "uint" + + checker := builtin.NewUint().Checker(typeName) + if checker == nil { + t.Errorf("expect %q to be handled", typeName) + t.Fail() + } + + tests := []struct { + Value interface{} + Valid bool + }{ + {uint(0), true}, + {uint(math.MaxInt64), true}, + {uint(math.MaxUint64), true}, + {-1, false}, + {-math.MaxInt64, false}, + + {float64(math.MinInt64), false}, + {float64(0), true}, + {float64(math.MaxInt64), true}, + // we cannot just compare because of how precision works + {float64(math.MaxUint64 - 1024), true}, + {float64(math.MaxUint64 + 1), false}, + + // json number + {fmt.Sprintf("%d", math.MinInt64), false}, + {"-1", false}, + {"0", true}, + {"1", true}, + {fmt.Sprintf("%d", math.MaxInt64), true}, + {fmt.Sprintf("%d", uint(math.MaxUint64)), true}, + // strane offset because of how precision works + {fmt.Sprintf("%f", float64(math.MaxUint64+1024*3)), false}, + + {"string", false}, + {[]byte("bytes"), false}, + {-0.1, false}, + {0.1, false}, + {nil, false}, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + if checker(test.Value) { + if !test.Valid { + t.Errorf("expect value to be invalid") + t.Fail() + } + return + } + if test.Valid { + t.Errorf("expect value to be valid") + t.Fail() + } + }) + } + +}