diff --git a/.gitignore b/.gitignore index 9d3410bdf..6316303ee 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ custom_tests.json pro/ test/avs/ +.codebuddy/ +.DS_Store diff --git a/ast/node.go b/ast/node.go index fbb9ae825..e22bd8322 100644 --- a/ast/node.go +++ b/ast/node.go @@ -86,6 +86,12 @@ type IntegerNode struct { Value int // Value of the integer. } +// UintegerNode represents an unsigned integer literal (values > MaxInt64). +type UintegerNode struct { + base + Value uint64 // Value of the unsigned integer. +} + // FloatNode represents a float. type FloatNode struct { base diff --git a/ast/print.go b/ast/print.go index 1c197445e..b2b4bdfe7 100644 --- a/ast/print.go +++ b/ast/print.go @@ -21,6 +21,10 @@ func (n *IntegerNode) String() string { return fmt.Sprintf("%d", n.Value) } +func (n *UintegerNode) String() string { + return fmt.Sprintf("%d", n.Value) +} + func (n *FloatNode) String() string { return fmt.Sprintf("%v", n.Value) } diff --git a/ast/visitor.go b/ast/visitor.go index ef23758e1..6fcd09393 100644 --- a/ast/visitor.go +++ b/ast/visitor.go @@ -14,6 +14,7 @@ func Walk(node *Node, v Visitor) { case *NilNode: case *IdentifierNode: case *IntegerNode: + case *UintegerNode: case *FloatNode: case *BoolNode: case *StringNode: diff --git a/checker/checker.go b/checker/checker.go index 3620f2075..421181497 100644 --- a/checker/checker.go +++ b/checker/checker.go @@ -18,6 +18,7 @@ var ( anyType = reflect.TypeOf(new(any)).Elem() boolType = reflect.TypeOf(true) intType = reflect.TypeOf(0) + uint64Type = reflect.TypeOf(uint64(0)) floatType = reflect.TypeOf(float64(0)) stringType = reflect.TypeOf("") arrayType = reflect.TypeOf([]any{}) @@ -189,6 +190,8 @@ func (v *Checker) visit(node ast.Node) Nature { nt = v.identifierNode(n) case *ast.IntegerNode: nt = v.config.NtCache.FromType(intType) + case *ast.UintegerNode: + nt = v.config.NtCache.FromType(uint64Type) case *ast.FloatNode: nt = v.config.NtCache.FromType(floatType) case *ast.BoolNode: @@ -1177,6 +1180,9 @@ func traverseAndReplaceIntegerNodesWithFloatNodes(node *ast.Node, newNature Natu case *ast.IntegerNode: *node = &ast.FloatNode{Value: float64((*node).(*ast.IntegerNode).Value)} (*node).SetType(newNature.Type) + case *ast.UintegerNode: + *node = &ast.FloatNode{Value: float64((*node).(*ast.UintegerNode).Value)} + (*node).SetType(newNature.Type) case *ast.UnaryNode: unaryNode := (*node).(*ast.UnaryNode) traverseAndReplaceIntegerNodesWithFloatNodes(&unaryNode.Node, newNature) @@ -1194,6 +1200,8 @@ func traverseAndReplaceIntegerNodesWithIntegerNodes(node *ast.Node, newNature Na switch (*node).(type) { case *ast.IntegerNode: (*node).SetType(newNature.Type) + case *ast.UintegerNode: + (*node).SetType(newNature.Type) case *ast.UnaryNode: (*node).SetType(newNature.Type) unaryNode := (*node).(*ast.UnaryNode) diff --git a/compiler/compiler.go b/compiler/compiler.go index 685175350..bb1dafb2c 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -248,6 +248,8 @@ func (c *compiler) compile(node ast.Node) { c.IdentifierNode(n) case *ast.IntegerNode: c.IntegerNode(n) + case *ast.UintegerNode: + c.UintegerNode(n) case *ast.FloatNode: c.FloatNode(n) case *ast.BoolNode: @@ -389,6 +391,66 @@ func (c *compiler) IntegerNode(node *ast.IntegerNode) { } } +func (c *compiler) UintegerNode(node *ast.UintegerNode) { + t := node.Type() + if t == nil { + c.emitPush(node.Value) + return + } + switch t.Kind() { + case reflect.Float32: + c.emitPush(float32(node.Value)) + case reflect.Float64: + c.emitPush(float64(node.Value)) + case reflect.Uint: + c.emitPush(uint(node.Value)) + case reflect.Uint8: + if node.Value > math.MaxUint8 { + panic(fmt.Sprintf("constant %d overflows uint8", node.Value)) + } + c.emitPush(uint8(node.Value)) + case reflect.Uint16: + if node.Value > math.MaxUint16 { + panic(fmt.Sprintf("constant %d overflows uint16", node.Value)) + } + c.emitPush(uint16(node.Value)) + case reflect.Uint32: + if node.Value > math.MaxUint32 { + panic(fmt.Sprintf("constant %d overflows uint32", node.Value)) + } + c.emitPush(uint32(node.Value)) + case reflect.Uint64: + c.emitPush(node.Value) + case reflect.Int: + if node.Value > uint64(math.MaxInt) { + panic(fmt.Sprintf("constant %d overflows int", node.Value)) + } + c.emitPush(int(node.Value)) + case reflect.Int8: + if node.Value > uint64(math.MaxInt8) { + panic(fmt.Sprintf("constant %d overflows int8", node.Value)) + } + c.emitPush(int8(node.Value)) + case reflect.Int16: + if node.Value > uint64(math.MaxInt16) { + panic(fmt.Sprintf("constant %d overflows int16", node.Value)) + } + c.emitPush(int16(node.Value)) + case reflect.Int32: + if node.Value > uint64(math.MaxInt32) { + panic(fmt.Sprintf("constant %d overflows int32", node.Value)) + } + c.emitPush(int32(node.Value)) + case reflect.Int64: + if node.Value > uint64(math.MaxInt64) { + panic(fmt.Sprintf("constant %d overflows int64", node.Value)) + } + c.emitPush(int64(node.Value)) + default: + c.emitPush(node.Value) + } +} + func (c *compiler) FloatNode(node *ast.FloatNode) { switch node.Type().Kind() { case reflect.Float32: diff --git a/expr_test.go b/expr_test.go index 4a34f0c45..ea03225b6 100644 --- a/expr_test.go +++ b/expr_test.go @@ -803,14 +803,14 @@ func TestExpr(t *testing.T) { `Int + 0`, 0, }, - { - `Uint64 + 0`, - 0, - }, - { - `Uint64 + Int64`, - 0, - }, + { + `Uint64 + 0`, + uint64(0), + }, + { + `Uint64 + Int64`, + uint64(0), + }, { `Int32 + Int64`, 0, diff --git a/expr_uint64_test.go b/expr_uint64_test.go new file mode 100644 index 000000000..14f8dd886 --- /dev/null +++ b/expr_uint64_test.go @@ -0,0 +1,306 @@ +package expr_test + +import ( + "fmt" + "math" + "testing" + + "github.com/expr-lang/expr" + "github.com/expr-lang/expr/internal/testify/assert" + "github.com/expr-lang/expr/internal/testify/require" +) + +// TestUint64Literal_Parse tests that large integer literals (exceeding int64 range) +// can be parsed correctly as uint64 values. +func TestUint64Literal_Parse(t *testing.T) { + tests := []struct { + code string + want uint64 + }{ + // The original reported issue + {`16141183638984196173`, 16141183638984196173}, + // MaxInt64 + 1 (first value that overflows int64) + {`9223372036854775808`, uint64(math.MaxInt64) + 1}, + // MaxUint64 + {`18446744073709551615`, math.MaxUint64}, + // Another large value + {`9228157111460438039`, 9228157111460438039}, + // Hex literal exceeding int64 + {`0xFFFFFFFFFFFFFFFF`, math.MaxUint64}, + {`0x8000000000000000`, uint64(math.MaxInt64) + 1}, + // Binary literal exceeding int64 + {`0b1000000000000000000000000000000000000000000000000000000000000000`, uint64(math.MaxInt64) + 1}, + // Octal literal exceeding int64 + {`0o1000000000000000000000`, 9223372036854775808}, + } + + for _, tt := range tests { + t.Run(tt.code, func(t *testing.T) { + program, err := expr.Compile(tt.code) + require.NoError(t, err, "failed to compile: %s", tt.code) + + got, err := expr.Run(program, nil) + require.NoError(t, err, "failed to run: %s", tt.code) + assert.Equal(t, tt.want, got, "unexpected result for: %s", tt.code) + }) + } +} + +// TestUint64Literal_Boundary tests boundary values around int64 max. +func TestUint64Literal_Boundary(t *testing.T) { + tests := []struct { + code string + want any + }{ + // MaxInt64 should still parse as int (existing behavior) + {`9223372036854775807`, int(math.MaxInt64)}, + // MaxInt64 + 1 should parse as uint64 + {`9223372036854775808`, uint64(math.MaxInt64) + 1}, + } + + for _, tt := range tests { + t.Run(tt.code, func(t *testing.T) { + got, err := expr.Eval(tt.code, nil) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +// TestUint64Literal_Comparison tests comparison operators with large uint64 values. +func TestUint64Literal_Comparison(t *testing.T) { + tests := []struct { + code string + want bool + }{ + // Equal + {`16141183638984196173 == 16141183638984196173`, true}, + {`9223372036854775808 == 9223372036854775808`, true}, + {`18446744073709551615 == 18446744073709551615`, true}, + {`9223372036854775808 == 9223372036854775809`, false}, + + // Not equal + {`16141183638984196173 != 16141183638984196174`, true}, + {`16141183638984196173 != 16141183638984196173`, false}, + + // Less than + {`9223372036854775808 < 9223372036854775809`, true}, + {`9223372036854775809 < 9223372036854775808`, false}, + {`9223372036854775808 < 9223372036854775808`, false}, + + // Greater than + {`9223372036854775809 > 9223372036854775808`, true}, + {`9223372036854775808 > 9223372036854775809`, false}, + {`9223372036854775808 > 9223372036854775808`, false}, + + // Less than or equal + {`9223372036854775808 <= 9223372036854775808`, true}, + {`9223372036854775808 <= 9223372036854775809`, true}, + {`9223372036854775809 <= 9223372036854775808`, false}, + + // Greater than or equal + {`9223372036854775808 >= 9223372036854775808`, true}, + {`9223372036854775809 >= 9223372036854775808`, true}, + {`9223372036854775808 >= 9223372036854775809`, false}, + + // Mixed uint64 literal and int literal comparisons + {`9223372036854775808 > 0`, true}, + {`9223372036854775808 > 100`, true}, + {`0 < 9223372036854775808`, true}, + } + + for _, tt := range tests { + t.Run(tt.code, func(t *testing.T) { + got, err := expr.Eval(tt.code, nil) + require.NoError(t, err, "eval error: %s", tt.code) + assert.Equal(t, tt.want, got, "unexpected result for: %s", tt.code) + }) + } +} + +// TestUint64Literal_ComparisonWithEnv tests comparison with uint64 values from environment. +func TestUint64Literal_ComparisonWithEnv(t *testing.T) { + env := map[string]any{ + "big": uint64(16141183638984196173), + "big2": uint64(16141183638984196173), + "big3": uint64(16141183638984196174), + "max": uint64(math.MaxUint64), + } + + tests := []struct { + code string + want any + }{ + // Comparing env uint64 with env uint64 + {`big == big2`, true}, + {`big == big3`, false}, + {`big < big3`, true}, + {`big3 > big`, true}, + {`big != big3`, true}, + + // Comparing env uint64 with literal + {`big == 16141183638984196173`, true}, + {`big != 16141183638984196174`, true}, + {`big < 16141183638984196174`, true}, + + // MaxUint64 comparisons + {`max == 18446744073709551615`, true}, + {`max > 9223372036854775808`, true}, + } + + for _, tt := range tests { + t.Run(tt.code, func(t *testing.T) { + got, err := expr.Eval(tt.code, env) + require.NoError(t, err, "eval error: %s", tt.code) + assert.Equal(t, tt.want, got, "unexpected result for: %s", tt.code) + }) + } +} + +// TestUint64Literal_Arithmetic tests arithmetic operations with large uint64 values. +func TestUint64Literal_Arithmetic(t *testing.T) { + env := map[string]any{ + "a": uint64(9223372036854775808), // MaxInt64 + 1 + "b": uint64(1), + } + + tests := []struct { + code string + want any + }{ + // Addition + {`9223372036854775808 + 1`, uint64(9223372036854775809)}, + {`a + b`, uint64(9223372036854775809)}, + + // Subtraction + {`9223372036854775809 - 1`, uint64(9223372036854775808)}, + {`9223372036854775808 - 9223372036854775808`, uint64(0)}, + + // Multiplication + {`9223372036854775808 * 2`, uint64(0)}, // wraps around on overflow + {`a * 2`, uint64(0)}, // 0x8000000000000000 * 2 overflows to 0 + + // Modulo + {`9223372036854775809 % 2`, uint64(1)}, + {`18446744073709551615 % 2`, uint64(1)}, + } + + for _, tt := range tests { + t.Run(tt.code, func(t *testing.T) { + got, err := expr.Eval(tt.code, env) + require.NoError(t, err, "eval error: %s", tt.code) + assert.Equal(t, tt.want, got, "unexpected result for: %s", tt.code) + }) + } +} + +// TestUint64Literal_Overflow tests that values exceeding uint64 max still produce errors. +func TestUint64Literal_Overflow(t *testing.T) { + overflowCases := []string{ + // MaxUint64 + 1 + `18446744073709551616`, + // Much larger than uint64 + `99999999999999999999`, + } + + for _, code := range overflowCases { + t.Run(code, func(t *testing.T) { + _, err := expr.Compile(code) + require.Error(t, err, "expected error for overflow: %s", code) + }) + } +} + +// TestUint64Literal_InExpression tests uint64 literals used in more complex expressions. +func TestUint64Literal_InExpression(t *testing.T) { + env := map[string]any{ + "values": []uint64{16141183638984196173, 9223372036854775808, 18446744073709551615}, + "target": uint64(16141183638984196173), + } + + tests := []struct { + code string + want any + }{ + // uint64 in ternary + {`true ? 9223372036854775808 : 0`, uint64(9223372036854775808)}, + + // uint64 in let expression + {`let x = 9223372036854775808; x == 9223372036854775808`, true}, + + // uint64 in array contains (from env) + {`target in values`, true}, + + // Negation should fail or be handled correctly + // (can't negate uint64 > MaxInt64) + } + + for _, tt := range tests { + t.Run(tt.code, func(t *testing.T) { + got, err := expr.Eval(tt.code, env) + require.NoError(t, err, "eval error: %s", tt.code) + assert.Equal(t, tt.want, got, "unexpected result for: %s", tt.code) + }) + } +} + +// TestUint64_RuntimeEqual tests the runtime Equal function with large uint64 values +// that would overflow if incorrectly cast to int. +func TestUint64_RuntimeEqual(t *testing.T) { + // These values are > MaxInt64, they would produce wrong results + // if cast to int for comparison. + env := map[string]any{ + "a": uint64(16141183638984196173), + "b": uint64(16141183638984196173), + "c": uint64(16141183638984196174), + } + + tests := []struct { + code string + want bool + }{ + {`a == b`, true}, + {`a == c`, false}, + {`a != c`, true}, + {`a != b`, false}, + } + + for _, tt := range tests { + t.Run(tt.code, func(t *testing.T) { + got, err := expr.Eval(tt.code, env) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +// TestUint64_WithUnderscore tests uint64 literals with underscore separators. +func TestUint64_WithUnderscore(t *testing.T) { + tests := []struct { + code string + want uint64 + }{ + {`16_141_183_638_984_196_173`, 16141183638984196173}, + {`9_223_372_036_854_775_808`, uint64(math.MaxInt64) + 1}, + } + + for _, tt := range tests { + t.Run(tt.code, func(t *testing.T) { + got, err := expr.Eval(tt.code, nil) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +// TestUint64_String tests that uint64 values can be used with string formatting. +func TestUint64Literal_String(t *testing.T) { + env := map[string]any{ + "val": uint64(16141183638984196173), + "sprintf": fmt.Sprintf, + } + + got, err := expr.Eval(`sprintf("%d", val)`, env) + require.NoError(t, err) + assert.Equal(t, "16141183638984196173", got) +} diff --git a/optimizer/const_expr.go b/optimizer/const_expr.go index 1b45385f6..6cb3f2641 100644 --- a/optimizer/const_expr.go +++ b/optimizer/const_expr.go @@ -44,6 +44,8 @@ func (c *constExpr) Visit(node *Node) { param = nil case *IntegerNode: param = a.Value + case *UintegerNode: + param = a.Value case *FloatNode: param = a.Value case *BoolNode: diff --git a/optimizer/fold.go b/optimizer/fold.go index 2e5498fa5..ff434d2c8 100644 --- a/optimizer/fold.go +++ b/optimizer/fold.go @@ -55,6 +55,13 @@ func (fold *fold) Visit(node *Node) { patch(&IntegerNode{Value: a.Value + b.Value}) } } + { + a := toUinteger(n.Left) + b := toUinteger(n.Right) + if a != nil && b != nil { + patch(&UintegerNode{Value: a.Value + b.Value}) + } + } { a := toInteger(n.Left) b := toFloat(n.Right) @@ -91,6 +98,13 @@ func (fold *fold) Visit(node *Node) { patch(&IntegerNode{Value: a.Value - b.Value}) } } + { + a := toUinteger(n.Left) + b := toUinteger(n.Right) + if a != nil && b != nil { + patch(&UintegerNode{Value: a.Value - b.Value}) + } + } { a := toInteger(n.Left) b := toFloat(n.Right) @@ -120,6 +134,13 @@ func (fold *fold) Visit(node *Node) { patch(&IntegerNode{Value: a.Value * b.Value}) } } + { + a := toUinteger(n.Left) + b := toUinteger(n.Right) + if a != nil && b != nil { + patch(&UintegerNode{Value: a.Value * b.Value}) + } + } { a := toInteger(n.Left) b := toFloat(n.Right) @@ -149,6 +170,13 @@ func (fold *fold) Visit(node *Node) { patch(&FloatNode{Value: float64(a.Value) / float64(b.Value)}) } } + { + a := toUinteger(n.Left) + b := toUinteger(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: float64(a.Value) / float64(b.Value)}) + } + } { a := toInteger(n.Left) b := toFloat(n.Right) @@ -183,6 +211,18 @@ func (fold *fold) Visit(node *Node) { patch(&IntegerNode{Value: a.Value % b.Value}) } } + if a, ok := n.Left.(*UintegerNode); ok { + if b, ok := n.Right.(*UintegerNode); ok { + if b.Value == 0 { + fold.err = &file.Error{ + Location: (*node).Location(), + Message: "integer divide by zero", + } + return + } + patch(&UintegerNode{Value: a.Value % b.Value}) + } + } case "**", "^": { a := toInteger(n.Left) @@ -242,6 +282,13 @@ func (fold *fold) Visit(node *Node) { patch(&BoolNode{Value: a.Value == b.Value}) } } + { + a := toUinteger(n.Left) + b := toUinteger(n.Right) + if a != nil && b != nil { + patch(&BoolNode{Value: a.Value == b.Value}) + } + } { a := toString(n.Left) b := toString(n.Right) @@ -262,7 +309,7 @@ func (fold *fold) Visit(node *Node) { if len(n.Nodes) > 0 { for _, a := range n.Nodes { switch a.(type) { - case *IntegerNode, *FloatNode, *StringNode, *BoolNode: + case *IntegerNode, *UintegerNode, *FloatNode, *StringNode, *BoolNode: continue default: return @@ -273,6 +320,8 @@ func (fold *fold) Visit(node *Node) { switch b := a.(type) { case *IntegerNode: value[i] = b.Value + case *UintegerNode: + value[i] = b.Value case *FloatNode: value[i] = b.Value case *StringNode: @@ -326,6 +375,14 @@ func toInteger(n Node) *IntegerNode { return nil } +func toUinteger(n Node) *UintegerNode { + switch a := n.(type) { + case *UintegerNode: + return a + } + return nil +} + func toFloat(n Node) *FloatNode { switch a := n.(type) { case *FloatNode: diff --git a/optimizer/in_array.go b/optimizer/in_array.go index e91320c0f..95552485a 100644 --- a/optimizer/in_array.go +++ b/optimizer/in_array.go @@ -19,12 +19,12 @@ func (*inArray) Visit(node *Node) { // This optimization can be only performed if left side is int type, // as runtime.in func uses reflect.Map.MapIndex and keys of map must, // be same as checked value type. - goto string + goto uint64check } for _, a := range array.Nodes { if _, ok := a.(*IntegerNode); !ok { - goto string + goto uint64check } } { @@ -39,6 +39,33 @@ func (*inArray) Visit(node *Node) { Left: n.Left, Right: m, }) + return + } + + uint64check: + t = n.Left.Type() + if t == nil || t.Kind() != reflect.Uint64 { + goto string + } + + for _, a := range array.Nodes { + if _, ok := a.(*UintegerNode); !ok { + goto string + } + } + { + value := make(map[uint64]struct{}) + for _, a := range array.Nodes { + value[a.(*UintegerNode).Value] = struct{}{} + } + m := &ConstantNode{Value: value} + m.SetType(reflect.TypeOf(value)) + patchCopyType(node, &BinaryNode{ + Operator: n.Operator, + Left: n.Left, + Right: m, + }) + return } string: diff --git a/optimizer/in_range.go b/optimizer/in_range.go index ed2f557ea..e5160534c 100644 --- a/optimizer/in_range.go +++ b/optimizer/in_range.go @@ -16,7 +16,7 @@ func (*inRange) Visit(node *Node) { if t == nil { return } - if t.Kind() != reflect.Int { + if t.Kind() != reflect.Int && t.Kind() != reflect.Uint64 { return } if rangeOp, ok := n.Right.(*BinaryNode); ok && rangeOp.Operator == ".." { @@ -35,6 +35,25 @@ func (*inRange) Visit(node *Node) { Right: to, }, }) + return + } + } + if from, ok := rangeOp.Left.(*UintegerNode); ok { + if to, ok := rangeOp.Right.(*UintegerNode); ok { + patchCopyType(node, &BinaryNode{ + Operator: "and", + Left: &BinaryNode{ + Operator: ">=", + Left: n.Left, + Right: from, + }, + Right: &BinaryNode{ + Operator: "<=", + Left: n.Left, + Right: to, + }, + }) + return } } } diff --git a/optimizer/optimizer.go b/optimizer/optimizer.go index 9e4c75d3b..b28717686 100644 --- a/optimizer/optimizer.go +++ b/optimizer/optimizer.go @@ -51,6 +51,7 @@ func Optimize(node *Node, config *conf.Config) error { var ( boolType = reflect.TypeOf(true) integerType = reflect.TypeOf(0) + uint64Type = reflect.TypeOf(uint64(0)) floatType = reflect.TypeOf(float64(0)) stringType = reflect.TypeOf("") ) @@ -61,6 +62,8 @@ func patchWithType(node *Node, newNode Node) { newNode.SetType(boolType) case *IntegerNode: newNode.SetType(integerType) + case *UintegerNode: + newNode.SetType(uint64Type) case *FloatNode: newNode.SetType(floatType) case *StringNode: diff --git a/parser/parser.go b/parser/parser.go index 9e24a71e4..234be9686 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -491,9 +491,14 @@ func (p *Parser) parseSecondary() Node { case strings.HasPrefix(valueLower, "0x"): number, err := strconv.ParseInt(value, 0, 64) if err != nil { - p.error("invalid hex literal: %v", err) + unum, uerr := strconv.ParseUint(value, 0, 64) + if uerr != nil { + p.error("invalid hex literal: %v", err) + } + node = p.toUintegerNode(unum) + } else { + node = p.toIntegerNode(number) } - node = p.toIntegerNode(number) case strings.ContainsAny(valueLower, ".e"): number, err := strconv.ParseFloat(value, 64) if err != nil { @@ -503,21 +508,36 @@ func (p *Parser) parseSecondary() Node { case strings.HasPrefix(valueLower, "0b"): number, err := strconv.ParseInt(value, 0, 64) if err != nil { - p.error("invalid binary literal: %v", err) + unum, uerr := strconv.ParseUint(value, 0, 64) + if uerr != nil { + p.error("invalid binary literal: %v", err) + } + node = p.toUintegerNode(unum) + } else { + node = p.toIntegerNode(number) } - node = p.toIntegerNode(number) case strings.HasPrefix(valueLower, "0o"): number, err := strconv.ParseInt(value, 0, 64) if err != nil { - p.error("invalid octal literal: %v", err) + unum, uerr := strconv.ParseUint(value, 0, 64) + if uerr != nil { + p.error("invalid octal literal: %v", err) + } + node = p.toUintegerNode(unum) + } else { + node = p.toIntegerNode(number) } - node = p.toIntegerNode(number) default: number, err := strconv.ParseInt(value, 10, 64) if err != nil { - p.error("invalid integer literal: %v", err) + unum, uerr := strconv.ParseUint(value, 10, 64) + if uerr != nil { + p.error("invalid integer literal: %v", err) + } + node = p.toUintegerNode(unum) + } else { + node = p.toIntegerNode(number) } - node = p.toIntegerNode(number) } if node != nil { node.SetLocation(token.Location) @@ -558,6 +578,10 @@ func (p *Parser) toIntegerNode(number int64) Node { return p.createNode(&IntegerNode{Value: int(number)}, p.current.Location) } +func (p *Parser) toUintegerNode(number uint64) Node { + return p.createNode(&UintegerNode{Value: number}, p.current.Location) +} + func (p *Parser) toFloatNode(number float64) Node { if number > math.MaxFloat64 { p.error("float literal is too large") diff --git a/vm/runtime/helpers/main.go b/vm/runtime/helpers/main.go index 54a4fc235..bc66ee382 100644 --- a/vm/runtime/helpers/main.go +++ b/vm/runtime/helpers/main.go @@ -71,18 +71,18 @@ func cases(op string, xs ...[]string) string { echo(`case %v:`, a) echo(`switch y := b.(type) {`) for _, b := range types { - t := "int" - if isDuration(a) || isDuration(b) { - t = "time.Duration" - } - if isFloat(a) || isFloat(b) { - t = "float64" - } echo(`case %v:`, b) if op == "/" { echo(`return float64(x) / float64(y)`) } else { - echo(`return %v(x) %v %v(y)`, t, op, t) + t := castType(a, b, op) + if t == "safe_uint64" { + // Special cross-sign comparison for uint64 vs signed + // Use direct append to avoid fmt.Sprintf interpreting % as format verb + out += safeUint64Op(a, b, op) + "\n" + } else { + echo(`return %v(x) %v %v(y)`, t, op, t) + } } } echo(`}`) @@ -90,6 +90,80 @@ func cases(op string, xs ...[]string) string { return strings.TrimRight(out, "\n") } +func castType(a, b, op string) string { + // Float takes priority over duration (matching original behavior): + // duration * float → float64(x) * float64(y) → float64 result + if isFloat(a) || isFloat(b) { + return "float64" + } + if isDuration(a) || isDuration(b) { + return "time.Duration" + } + // For uint64 mixed with signed integers, we need safe comparison + if isUint64(a) && isSigned(b) { + return "safe_uint64" + } + if isSigned(a) && isUint64(b) { + return "safe_uint64" + } + // For uint64 vs uint64 or uint64 vs other unsigned types, use uint64 + if isUint64(a) || isUint64(b) { + return "uint64" + } + return "int" +} + +func isUint64(t string) bool { + return t == "uint64" +} + +func isSigned(t string) bool { + return strings.HasPrefix(t, "int") +} + +func safeUint64Op(a, b, op string) string { + // a is the x type (outer switch), b is the y type (inner switch) + if isUint64(a) && isSigned(b) { + // x is uint64, y is signed + switch op { + case "==": + return "if y < 0 { return false }\nreturn x == uint64(y)" + case "<": + return "if y < 0 { return false }\nreturn x < uint64(y)" + case ">": + return "if y < 0 { return true }\nreturn x > uint64(y)" + case "<=": + return "if y < 0 { return false }\nreturn x <= uint64(y)" + case ">=": + return "if y < 0 { return true }\nreturn x >= uint64(y)" + case "+", "-", "*": + return fmt.Sprintf("return uint64(x) %s uint64(y)", op) + case "%": + return fmt.Sprintf("return uint64(x) %s uint64(y)", op) + } + } + if isSigned(a) && isUint64(b) { + // x is signed, y is uint64 + switch op { + case "==": + return "if x < 0 { return false }\nreturn uint64(x) == y" + case "<": + return "if x < 0 { return true }\nreturn uint64(x) < y" + case ">": + return "if x < 0 { return false }\nreturn uint64(x) > y" + case "<=": + return "if x < 0 { return true }\nreturn uint64(x) <= y" + case ">=": + return "if x < 0 { return false }\nreturn uint64(x) >= y" + case "+", "-", "*": + return fmt.Sprintf("return uint64(x) %s uint64(y)", op) + case "%": + return fmt.Sprintf("return uint64(x) %s uint64(y)", op) + } + } + return fmt.Sprintf("return int(x) %s int(y)", op) +} + func arrayEqualCases(xs ...[]string) string { var types []string for _, x := range xs { @@ -323,7 +397,7 @@ func Divide(a, b interface{}) float64 { panic(fmt.Sprintf("invalid operation: %T / %T", a, b)) } -func Modulo(a, b interface{}) int { +func Modulo(a, b interface{}) interface{} { switch x := a.(type) { {{ cases_int_only "%" }} } diff --git a/vm/runtime/helpers[generated].go b/vm/runtime/helpers[generated].go index d950f1111..662f12772 100644 --- a/vm/runtime/helpers[generated].go +++ b/vm/runtime/helpers[generated].go @@ -21,7 +21,7 @@ func Equal(a, b interface{}) bool { case uint32: return int(x) == int(y) case uint64: - return int(x) == int(y) + return uint64(x) == uint64(y) case int: return int(x) == int(y) case int8: @@ -48,7 +48,7 @@ func Equal(a, b interface{}) bool { case uint32: return int(x) == int(y) case uint64: - return int(x) == int(y) + return uint64(x) == uint64(y) case int: return int(x) == int(y) case int8: @@ -75,7 +75,7 @@ func Equal(a, b interface{}) bool { case uint32: return int(x) == int(y) case uint64: - return int(x) == int(y) + return uint64(x) == uint64(y) case int: return int(x) == int(y) case int8: @@ -102,7 +102,7 @@ func Equal(a, b interface{}) bool { case uint32: return int(x) == int(y) case uint64: - return int(x) == int(y) + return uint64(x) == uint64(y) case int: return int(x) == int(y) case int8: @@ -121,25 +121,40 @@ func Equal(a, b interface{}) bool { case uint64: switch y := b.(type) { case uint: - return int(x) == int(y) + return uint64(x) == uint64(y) case uint8: - return int(x) == int(y) + return uint64(x) == uint64(y) case uint16: - return int(x) == int(y) + return uint64(x) == uint64(y) case uint32: - return int(x) == int(y) + return uint64(x) == uint64(y) case uint64: - return int(x) == int(y) + return uint64(x) == uint64(y) case int: - return int(x) == int(y) + if y < 0 { + return false + } + return x == uint64(y) case int8: - return int(x) == int(y) + if y < 0 { + return false + } + return x == uint64(y) case int16: - return int(x) == int(y) + if y < 0 { + return false + } + return x == uint64(y) case int32: - return int(x) == int(y) + if y < 0 { + return false + } + return x == uint64(y) case int64: - return int(x) == int(y) + if y < 0 { + return false + } + return x == uint64(y) case float32: return float64(x) == float64(y) case float64: @@ -156,7 +171,10 @@ func Equal(a, b interface{}) bool { case uint32: return int(x) == int(y) case uint64: - return int(x) == int(y) + if x < 0 { + return false + } + return uint64(x) == y case int: return int(x) == int(y) case int8: @@ -183,7 +201,10 @@ func Equal(a, b interface{}) bool { case uint32: return int(x) == int(y) case uint64: - return int(x) == int(y) + if x < 0 { + return false + } + return uint64(x) == y case int: return int(x) == int(y) case int8: @@ -210,7 +231,10 @@ func Equal(a, b interface{}) bool { case uint32: return int(x) == int(y) case uint64: - return int(x) == int(y) + if x < 0 { + return false + } + return uint64(x) == y case int: return int(x) == int(y) case int8: @@ -237,7 +261,10 @@ func Equal(a, b interface{}) bool { case uint32: return int(x) == int(y) case uint64: - return int(x) == int(y) + if x < 0 { + return false + } + return uint64(x) == y case int: return int(x) == int(y) case int8: @@ -264,7 +291,10 @@ func Equal(a, b interface{}) bool { case uint32: return int(x) == int(y) case uint64: - return int(x) == int(y) + if x < 0 { + return false + } + return uint64(x) == y case int: return int(x) == int(y) case int8: @@ -712,7 +742,7 @@ func Less(a, b interface{}) bool { case uint32: return int(x) < int(y) case uint64: - return int(x) < int(y) + return uint64(x) < uint64(y) case int: return int(x) < int(y) case int8: @@ -739,7 +769,7 @@ func Less(a, b interface{}) bool { case uint32: return int(x) < int(y) case uint64: - return int(x) < int(y) + return uint64(x) < uint64(y) case int: return int(x) < int(y) case int8: @@ -766,7 +796,7 @@ func Less(a, b interface{}) bool { case uint32: return int(x) < int(y) case uint64: - return int(x) < int(y) + return uint64(x) < uint64(y) case int: return int(x) < int(y) case int8: @@ -793,7 +823,7 @@ func Less(a, b interface{}) bool { case uint32: return int(x) < int(y) case uint64: - return int(x) < int(y) + return uint64(x) < uint64(y) case int: return int(x) < int(y) case int8: @@ -812,25 +842,40 @@ func Less(a, b interface{}) bool { case uint64: switch y := b.(type) { case uint: - return int(x) < int(y) + return uint64(x) < uint64(y) case uint8: - return int(x) < int(y) + return uint64(x) < uint64(y) case uint16: - return int(x) < int(y) + return uint64(x) < uint64(y) case uint32: - return int(x) < int(y) + return uint64(x) < uint64(y) case uint64: - return int(x) < int(y) + return uint64(x) < uint64(y) case int: - return int(x) < int(y) + if y < 0 { + return false + } + return x < uint64(y) case int8: - return int(x) < int(y) + if y < 0 { + return false + } + return x < uint64(y) case int16: - return int(x) < int(y) + if y < 0 { + return false + } + return x < uint64(y) case int32: - return int(x) < int(y) + if y < 0 { + return false + } + return x < uint64(y) case int64: - return int(x) < int(y) + if y < 0 { + return false + } + return x < uint64(y) case float32: return float64(x) < float64(y) case float64: @@ -847,7 +892,10 @@ func Less(a, b interface{}) bool { case uint32: return int(x) < int(y) case uint64: - return int(x) < int(y) + if x < 0 { + return true + } + return uint64(x) < y case int: return int(x) < int(y) case int8: @@ -874,7 +922,10 @@ func Less(a, b interface{}) bool { case uint32: return int(x) < int(y) case uint64: - return int(x) < int(y) + if x < 0 { + return true + } + return uint64(x) < y case int: return int(x) < int(y) case int8: @@ -901,7 +952,10 @@ func Less(a, b interface{}) bool { case uint32: return int(x) < int(y) case uint64: - return int(x) < int(y) + if x < 0 { + return true + } + return uint64(x) < y case int: return int(x) < int(y) case int8: @@ -928,7 +982,10 @@ func Less(a, b interface{}) bool { case uint32: return int(x) < int(y) case uint64: - return int(x) < int(y) + if x < 0 { + return true + } + return uint64(x) < y case int: return int(x) < int(y) case int8: @@ -955,7 +1012,10 @@ func Less(a, b interface{}) bool { case uint32: return int(x) < int(y) case uint64: - return int(x) < int(y) + if x < 0 { + return true + } + return uint64(x) < y case int: return int(x) < int(y) case int8: @@ -1057,7 +1117,7 @@ func More(a, b interface{}) bool { case uint32: return int(x) > int(y) case uint64: - return int(x) > int(y) + return uint64(x) > uint64(y) case int: return int(x) > int(y) case int8: @@ -1084,7 +1144,7 @@ func More(a, b interface{}) bool { case uint32: return int(x) > int(y) case uint64: - return int(x) > int(y) + return uint64(x) > uint64(y) case int: return int(x) > int(y) case int8: @@ -1111,7 +1171,7 @@ func More(a, b interface{}) bool { case uint32: return int(x) > int(y) case uint64: - return int(x) > int(y) + return uint64(x) > uint64(y) case int: return int(x) > int(y) case int8: @@ -1138,7 +1198,7 @@ func More(a, b interface{}) bool { case uint32: return int(x) > int(y) case uint64: - return int(x) > int(y) + return uint64(x) > uint64(y) case int: return int(x) > int(y) case int8: @@ -1157,25 +1217,40 @@ func More(a, b interface{}) bool { case uint64: switch y := b.(type) { case uint: - return int(x) > int(y) + return uint64(x) > uint64(y) case uint8: - return int(x) > int(y) + return uint64(x) > uint64(y) case uint16: - return int(x) > int(y) + return uint64(x) > uint64(y) case uint32: - return int(x) > int(y) + return uint64(x) > uint64(y) case uint64: - return int(x) > int(y) + return uint64(x) > uint64(y) case int: - return int(x) > int(y) + if y < 0 { + return true + } + return x > uint64(y) case int8: - return int(x) > int(y) + if y < 0 { + return true + } + return x > uint64(y) case int16: - return int(x) > int(y) + if y < 0 { + return true + } + return x > uint64(y) case int32: - return int(x) > int(y) + if y < 0 { + return true + } + return x > uint64(y) case int64: - return int(x) > int(y) + if y < 0 { + return true + } + return x > uint64(y) case float32: return float64(x) > float64(y) case float64: @@ -1192,7 +1267,10 @@ func More(a, b interface{}) bool { case uint32: return int(x) > int(y) case uint64: - return int(x) > int(y) + if x < 0 { + return false + } + return uint64(x) > y case int: return int(x) > int(y) case int8: @@ -1219,7 +1297,10 @@ func More(a, b interface{}) bool { case uint32: return int(x) > int(y) case uint64: - return int(x) > int(y) + if x < 0 { + return false + } + return uint64(x) > y case int: return int(x) > int(y) case int8: @@ -1246,7 +1327,10 @@ func More(a, b interface{}) bool { case uint32: return int(x) > int(y) case uint64: - return int(x) > int(y) + if x < 0 { + return false + } + return uint64(x) > y case int: return int(x) > int(y) case int8: @@ -1273,7 +1357,10 @@ func More(a, b interface{}) bool { case uint32: return int(x) > int(y) case uint64: - return int(x) > int(y) + if x < 0 { + return false + } + return uint64(x) > y case int: return int(x) > int(y) case int8: @@ -1300,7 +1387,10 @@ func More(a, b interface{}) bool { case uint32: return int(x) > int(y) case uint64: - return int(x) > int(y) + if x < 0 { + return false + } + return uint64(x) > y case int: return int(x) > int(y) case int8: @@ -1402,7 +1492,7 @@ func LessOrEqual(a, b interface{}) bool { case uint32: return int(x) <= int(y) case uint64: - return int(x) <= int(y) + return uint64(x) <= uint64(y) case int: return int(x) <= int(y) case int8: @@ -1429,7 +1519,7 @@ func LessOrEqual(a, b interface{}) bool { case uint32: return int(x) <= int(y) case uint64: - return int(x) <= int(y) + return uint64(x) <= uint64(y) case int: return int(x) <= int(y) case int8: @@ -1456,7 +1546,7 @@ func LessOrEqual(a, b interface{}) bool { case uint32: return int(x) <= int(y) case uint64: - return int(x) <= int(y) + return uint64(x) <= uint64(y) case int: return int(x) <= int(y) case int8: @@ -1483,7 +1573,7 @@ func LessOrEqual(a, b interface{}) bool { case uint32: return int(x) <= int(y) case uint64: - return int(x) <= int(y) + return uint64(x) <= uint64(y) case int: return int(x) <= int(y) case int8: @@ -1502,25 +1592,40 @@ func LessOrEqual(a, b interface{}) bool { case uint64: switch y := b.(type) { case uint: - return int(x) <= int(y) + return uint64(x) <= uint64(y) case uint8: - return int(x) <= int(y) + return uint64(x) <= uint64(y) case uint16: - return int(x) <= int(y) + return uint64(x) <= uint64(y) case uint32: - return int(x) <= int(y) + return uint64(x) <= uint64(y) case uint64: - return int(x) <= int(y) + return uint64(x) <= uint64(y) case int: - return int(x) <= int(y) + if y < 0 { + return false + } + return x <= uint64(y) case int8: - return int(x) <= int(y) + if y < 0 { + return false + } + return x <= uint64(y) case int16: - return int(x) <= int(y) + if y < 0 { + return false + } + return x <= uint64(y) case int32: - return int(x) <= int(y) + if y < 0 { + return false + } + return x <= uint64(y) case int64: - return int(x) <= int(y) + if y < 0 { + return false + } + return x <= uint64(y) case float32: return float64(x) <= float64(y) case float64: @@ -1537,7 +1642,10 @@ func LessOrEqual(a, b interface{}) bool { case uint32: return int(x) <= int(y) case uint64: - return int(x) <= int(y) + if x < 0 { + return true + } + return uint64(x) <= y case int: return int(x) <= int(y) case int8: @@ -1564,7 +1672,10 @@ func LessOrEqual(a, b interface{}) bool { case uint32: return int(x) <= int(y) case uint64: - return int(x) <= int(y) + if x < 0 { + return true + } + return uint64(x) <= y case int: return int(x) <= int(y) case int8: @@ -1591,7 +1702,10 @@ func LessOrEqual(a, b interface{}) bool { case uint32: return int(x) <= int(y) case uint64: - return int(x) <= int(y) + if x < 0 { + return true + } + return uint64(x) <= y case int: return int(x) <= int(y) case int8: @@ -1618,7 +1732,10 @@ func LessOrEqual(a, b interface{}) bool { case uint32: return int(x) <= int(y) case uint64: - return int(x) <= int(y) + if x < 0 { + return true + } + return uint64(x) <= y case int: return int(x) <= int(y) case int8: @@ -1645,7 +1762,10 @@ func LessOrEqual(a, b interface{}) bool { case uint32: return int(x) <= int(y) case uint64: - return int(x) <= int(y) + if x < 0 { + return true + } + return uint64(x) <= y case int: return int(x) <= int(y) case int8: @@ -1747,7 +1867,7 @@ func MoreOrEqual(a, b interface{}) bool { case uint32: return int(x) >= int(y) case uint64: - return int(x) >= int(y) + return uint64(x) >= uint64(y) case int: return int(x) >= int(y) case int8: @@ -1774,7 +1894,7 @@ func MoreOrEqual(a, b interface{}) bool { case uint32: return int(x) >= int(y) case uint64: - return int(x) >= int(y) + return uint64(x) >= uint64(y) case int: return int(x) >= int(y) case int8: @@ -1801,7 +1921,7 @@ func MoreOrEqual(a, b interface{}) bool { case uint32: return int(x) >= int(y) case uint64: - return int(x) >= int(y) + return uint64(x) >= uint64(y) case int: return int(x) >= int(y) case int8: @@ -1828,7 +1948,7 @@ func MoreOrEqual(a, b interface{}) bool { case uint32: return int(x) >= int(y) case uint64: - return int(x) >= int(y) + return uint64(x) >= uint64(y) case int: return int(x) >= int(y) case int8: @@ -1847,25 +1967,40 @@ func MoreOrEqual(a, b interface{}) bool { case uint64: switch y := b.(type) { case uint: - return int(x) >= int(y) + return uint64(x) >= uint64(y) case uint8: - return int(x) >= int(y) + return uint64(x) >= uint64(y) case uint16: - return int(x) >= int(y) + return uint64(x) >= uint64(y) case uint32: - return int(x) >= int(y) + return uint64(x) >= uint64(y) case uint64: - return int(x) >= int(y) + return uint64(x) >= uint64(y) case int: - return int(x) >= int(y) + if y < 0 { + return true + } + return x >= uint64(y) case int8: - return int(x) >= int(y) + if y < 0 { + return true + } + return x >= uint64(y) case int16: - return int(x) >= int(y) + if y < 0 { + return true + } + return x >= uint64(y) case int32: - return int(x) >= int(y) + if y < 0 { + return true + } + return x >= uint64(y) case int64: - return int(x) >= int(y) + if y < 0 { + return true + } + return x >= uint64(y) case float32: return float64(x) >= float64(y) case float64: @@ -1882,7 +2017,10 @@ func MoreOrEqual(a, b interface{}) bool { case uint32: return int(x) >= int(y) case uint64: - return int(x) >= int(y) + if x < 0 { + return false + } + return uint64(x) >= y case int: return int(x) >= int(y) case int8: @@ -1909,7 +2047,10 @@ func MoreOrEqual(a, b interface{}) bool { case uint32: return int(x) >= int(y) case uint64: - return int(x) >= int(y) + if x < 0 { + return false + } + return uint64(x) >= y case int: return int(x) >= int(y) case int8: @@ -1936,7 +2077,10 @@ func MoreOrEqual(a, b interface{}) bool { case uint32: return int(x) >= int(y) case uint64: - return int(x) >= int(y) + if x < 0 { + return false + } + return uint64(x) >= y case int: return int(x) >= int(y) case int8: @@ -1963,7 +2107,10 @@ func MoreOrEqual(a, b interface{}) bool { case uint32: return int(x) >= int(y) case uint64: - return int(x) >= int(y) + if x < 0 { + return false + } + return uint64(x) >= y case int: return int(x) >= int(y) case int8: @@ -1990,7 +2137,10 @@ func MoreOrEqual(a, b interface{}) bool { case uint32: return int(x) >= int(y) case uint64: - return int(x) >= int(y) + if x < 0 { + return false + } + return uint64(x) >= y case int: return int(x) >= int(y) case int8: @@ -2092,7 +2242,7 @@ func Add(a, b interface{}) interface{} { case uint32: return int(x) + int(y) case uint64: - return int(x) + int(y) + return uint64(x) + uint64(y) case int: return int(x) + int(y) case int8: @@ -2119,7 +2269,7 @@ func Add(a, b interface{}) interface{} { case uint32: return int(x) + int(y) case uint64: - return int(x) + int(y) + return uint64(x) + uint64(y) case int: return int(x) + int(y) case int8: @@ -2146,7 +2296,7 @@ func Add(a, b interface{}) interface{} { case uint32: return int(x) + int(y) case uint64: - return int(x) + int(y) + return uint64(x) + uint64(y) case int: return int(x) + int(y) case int8: @@ -2173,7 +2323,7 @@ func Add(a, b interface{}) interface{} { case uint32: return int(x) + int(y) case uint64: - return int(x) + int(y) + return uint64(x) + uint64(y) case int: return int(x) + int(y) case int8: @@ -2192,25 +2342,25 @@ func Add(a, b interface{}) interface{} { case uint64: switch y := b.(type) { case uint: - return int(x) + int(y) + return uint64(x) + uint64(y) case uint8: - return int(x) + int(y) + return uint64(x) + uint64(y) case uint16: - return int(x) + int(y) + return uint64(x) + uint64(y) case uint32: - return int(x) + int(y) + return uint64(x) + uint64(y) case uint64: - return int(x) + int(y) + return uint64(x) + uint64(y) case int: - return int(x) + int(y) + return uint64(x) + uint64(y) case int8: - return int(x) + int(y) + return uint64(x) + uint64(y) case int16: - return int(x) + int(y) + return uint64(x) + uint64(y) case int32: - return int(x) + int(y) + return uint64(x) + uint64(y) case int64: - return int(x) + int(y) + return uint64(x) + uint64(y) case float32: return float64(x) + float64(y) case float64: @@ -2227,7 +2377,7 @@ func Add(a, b interface{}) interface{} { case uint32: return int(x) + int(y) case uint64: - return int(x) + int(y) + return uint64(x) + uint64(y) case int: return int(x) + int(y) case int8: @@ -2254,7 +2404,7 @@ func Add(a, b interface{}) interface{} { case uint32: return int(x) + int(y) case uint64: - return int(x) + int(y) + return uint64(x) + uint64(y) case int: return int(x) + int(y) case int8: @@ -2281,7 +2431,7 @@ func Add(a, b interface{}) interface{} { case uint32: return int(x) + int(y) case uint64: - return int(x) + int(y) + return uint64(x) + uint64(y) case int: return int(x) + int(y) case int8: @@ -2308,7 +2458,7 @@ func Add(a, b interface{}) interface{} { case uint32: return int(x) + int(y) case uint64: - return int(x) + int(y) + return uint64(x) + uint64(y) case int: return int(x) + int(y) case int8: @@ -2335,7 +2485,7 @@ func Add(a, b interface{}) interface{} { case uint32: return int(x) + int(y) case uint64: - return int(x) + int(y) + return uint64(x) + uint64(y) case int: return int(x) + int(y) case int8: @@ -2439,7 +2589,7 @@ func Subtract(a, b interface{}) interface{} { case uint32: return int(x) - int(y) case uint64: - return int(x) - int(y) + return uint64(x) - uint64(y) case int: return int(x) - int(y) case int8: @@ -2466,7 +2616,7 @@ func Subtract(a, b interface{}) interface{} { case uint32: return int(x) - int(y) case uint64: - return int(x) - int(y) + return uint64(x) - uint64(y) case int: return int(x) - int(y) case int8: @@ -2493,7 +2643,7 @@ func Subtract(a, b interface{}) interface{} { case uint32: return int(x) - int(y) case uint64: - return int(x) - int(y) + return uint64(x) - uint64(y) case int: return int(x) - int(y) case int8: @@ -2520,7 +2670,7 @@ func Subtract(a, b interface{}) interface{} { case uint32: return int(x) - int(y) case uint64: - return int(x) - int(y) + return uint64(x) - uint64(y) case int: return int(x) - int(y) case int8: @@ -2539,25 +2689,25 @@ func Subtract(a, b interface{}) interface{} { case uint64: switch y := b.(type) { case uint: - return int(x) - int(y) + return uint64(x) - uint64(y) case uint8: - return int(x) - int(y) + return uint64(x) - uint64(y) case uint16: - return int(x) - int(y) + return uint64(x) - uint64(y) case uint32: - return int(x) - int(y) + return uint64(x) - uint64(y) case uint64: - return int(x) - int(y) + return uint64(x) - uint64(y) case int: - return int(x) - int(y) + return uint64(x) - uint64(y) case int8: - return int(x) - int(y) + return uint64(x) - uint64(y) case int16: - return int(x) - int(y) + return uint64(x) - uint64(y) case int32: - return int(x) - int(y) + return uint64(x) - uint64(y) case int64: - return int(x) - int(y) + return uint64(x) - uint64(y) case float32: return float64(x) - float64(y) case float64: @@ -2574,7 +2724,7 @@ func Subtract(a, b interface{}) interface{} { case uint32: return int(x) - int(y) case uint64: - return int(x) - int(y) + return uint64(x) - uint64(y) case int: return int(x) - int(y) case int8: @@ -2601,7 +2751,7 @@ func Subtract(a, b interface{}) interface{} { case uint32: return int(x) - int(y) case uint64: - return int(x) - int(y) + return uint64(x) - uint64(y) case int: return int(x) - int(y) case int8: @@ -2628,7 +2778,7 @@ func Subtract(a, b interface{}) interface{} { case uint32: return int(x) - int(y) case uint64: - return int(x) - int(y) + return uint64(x) - uint64(y) case int: return int(x) - int(y) case int8: @@ -2655,7 +2805,7 @@ func Subtract(a, b interface{}) interface{} { case uint32: return int(x) - int(y) case uint64: - return int(x) - int(y) + return uint64(x) - uint64(y) case int: return int(x) - int(y) case int8: @@ -2682,7 +2832,7 @@ func Subtract(a, b interface{}) interface{} { case uint32: return int(x) - int(y) case uint64: - return int(x) - int(y) + return uint64(x) - uint64(y) case int: return int(x) - int(y) case int8: @@ -2781,7 +2931,7 @@ func Multiply(a, b interface{}) interface{} { case uint32: return int(x) * int(y) case uint64: - return int(x) * int(y) + return uint64(x) * uint64(y) case int: return int(x) * int(y) case int8: @@ -2810,7 +2960,7 @@ func Multiply(a, b interface{}) interface{} { case uint32: return int(x) * int(y) case uint64: - return int(x) * int(y) + return uint64(x) * uint64(y) case int: return int(x) * int(y) case int8: @@ -2839,7 +2989,7 @@ func Multiply(a, b interface{}) interface{} { case uint32: return int(x) * int(y) case uint64: - return int(x) * int(y) + return uint64(x) * uint64(y) case int: return int(x) * int(y) case int8: @@ -2868,7 +3018,7 @@ func Multiply(a, b interface{}) interface{} { case uint32: return int(x) * int(y) case uint64: - return int(x) * int(y) + return uint64(x) * uint64(y) case int: return int(x) * int(y) case int8: @@ -2889,25 +3039,25 @@ func Multiply(a, b interface{}) interface{} { case uint64: switch y := b.(type) { case uint: - return int(x) * int(y) + return uint64(x) * uint64(y) case uint8: - return int(x) * int(y) + return uint64(x) * uint64(y) case uint16: - return int(x) * int(y) + return uint64(x) * uint64(y) case uint32: - return int(x) * int(y) + return uint64(x) * uint64(y) case uint64: - return int(x) * int(y) + return uint64(x) * uint64(y) case int: - return int(x) * int(y) + return uint64(x) * uint64(y) case int8: - return int(x) * int(y) + return uint64(x) * uint64(y) case int16: - return int(x) * int(y) + return uint64(x) * uint64(y) case int32: - return int(x) * int(y) + return uint64(x) * uint64(y) case int64: - return int(x) * int(y) + return uint64(x) * uint64(y) case float32: return float64(x) * float64(y) case float64: @@ -2926,7 +3076,7 @@ func Multiply(a, b interface{}) interface{} { case uint32: return int(x) * int(y) case uint64: - return int(x) * int(y) + return uint64(x) * uint64(y) case int: return int(x) * int(y) case int8: @@ -2955,7 +3105,7 @@ func Multiply(a, b interface{}) interface{} { case uint32: return int(x) * int(y) case uint64: - return int(x) * int(y) + return uint64(x) * uint64(y) case int: return int(x) * int(y) case int8: @@ -2984,7 +3134,7 @@ func Multiply(a, b interface{}) interface{} { case uint32: return int(x) * int(y) case uint64: - return int(x) * int(y) + return uint64(x) * uint64(y) case int: return int(x) * int(y) case int8: @@ -3013,7 +3163,7 @@ func Multiply(a, b interface{}) interface{} { case uint32: return int(x) * int(y) case uint64: - return int(x) * int(y) + return uint64(x) * uint64(y) case int: return int(x) * int(y) case int8: @@ -3042,7 +3192,7 @@ func Multiply(a, b interface{}) interface{} { case uint32: return int(x) * int(y) case uint64: - return int(x) * int(y) + return uint64(x) * uint64(y) case int: return int(x) * int(y) case int8: @@ -3481,7 +3631,7 @@ func Divide(a, b interface{}) float64 { panic(fmt.Sprintf("invalid operation: %T / %T", a, b)) } -func Modulo(a, b interface{}) int { +func Modulo(a, b interface{}) interface{} { switch x := a.(type) { case uint: switch y := b.(type) { @@ -3494,7 +3644,7 @@ func Modulo(a, b interface{}) int { case uint32: return int(x) % int(y) case uint64: - return int(x) % int(y) + return uint64(x) % uint64(y) case int: return int(x) % int(y) case int8: @@ -3517,7 +3667,7 @@ func Modulo(a, b interface{}) int { case uint32: return int(x) % int(y) case uint64: - return int(x) % int(y) + return uint64(x) % uint64(y) case int: return int(x) % int(y) case int8: @@ -3540,7 +3690,7 @@ func Modulo(a, b interface{}) int { case uint32: return int(x) % int(y) case uint64: - return int(x) % int(y) + return uint64(x) % uint64(y) case int: return int(x) % int(y) case int8: @@ -3563,7 +3713,7 @@ func Modulo(a, b interface{}) int { case uint32: return int(x) % int(y) case uint64: - return int(x) % int(y) + return uint64(x) % uint64(y) case int: return int(x) % int(y) case int8: @@ -3578,25 +3728,25 @@ func Modulo(a, b interface{}) int { case uint64: switch y := b.(type) { case uint: - return int(x) % int(y) + return uint64(x) % uint64(y) case uint8: - return int(x) % int(y) + return uint64(x) % uint64(y) case uint16: - return int(x) % int(y) + return uint64(x) % uint64(y) case uint32: - return int(x) % int(y) + return uint64(x) % uint64(y) case uint64: - return int(x) % int(y) + return uint64(x) % uint64(y) case int: - return int(x) % int(y) + return uint64(x) % uint64(y) case int8: - return int(x) % int(y) + return uint64(x) % uint64(y) case int16: - return int(x) % int(y) + return uint64(x) % uint64(y) case int32: - return int(x) % int(y) + return uint64(x) % uint64(y) case int64: - return int(x) % int(y) + return uint64(x) % uint64(y) } case int: switch y := b.(type) { @@ -3609,7 +3759,7 @@ func Modulo(a, b interface{}) int { case uint32: return int(x) % int(y) case uint64: - return int(x) % int(y) + return uint64(x) % uint64(y) case int: return int(x) % int(y) case int8: @@ -3632,7 +3782,7 @@ func Modulo(a, b interface{}) int { case uint32: return int(x) % int(y) case uint64: - return int(x) % int(y) + return uint64(x) % uint64(y) case int: return int(x) % int(y) case int8: @@ -3655,7 +3805,7 @@ func Modulo(a, b interface{}) int { case uint32: return int(x) % int(y) case uint64: - return int(x) % int(y) + return uint64(x) % uint64(y) case int: return int(x) % int(y) case int8: @@ -3678,7 +3828,7 @@ func Modulo(a, b interface{}) int { case uint32: return int(x) % int(y) case uint64: - return int(x) % int(y) + return uint64(x) % uint64(y) case int: return int(x) % int(y) case int8: @@ -3701,7 +3851,7 @@ func Modulo(a, b interface{}) int { case uint32: return int(x) % int(y) case uint64: - return int(x) % int(y) + return uint64(x) % uint64(y) case int: return int(x) % int(y) case int8: diff --git a/vm/runtime/helpers_uint64_test.go b/vm/runtime/helpers_uint64_test.go new file mode 100644 index 000000000..e9b244851 --- /dev/null +++ b/vm/runtime/helpers_uint64_test.go @@ -0,0 +1,148 @@ +package runtime_test + +import ( + "math" + "testing" + + "github.com/expr-lang/expr/internal/testify/assert" + "github.com/expr-lang/expr/vm/runtime" +) + +// TestEqual_Uint64Large tests runtime.Equal with large uint64 values +// that exceed int64 range. These values would produce incorrect results +// if erroneously cast to int for comparison. +func TestEqual_Uint64Large(t *testing.T) { + tests := []struct { + name string + a, b any + want bool + }{ + // Same large uint64 values should be equal + {"uint64 large == same", uint64(16141183638984196173), uint64(16141183638984196173), true}, + {"uint64 maxuint64 == same", uint64(math.MaxUint64), uint64(math.MaxUint64), true}, + {"uint64 maxint64+1 == same", uint64(math.MaxInt64) + 1, uint64(math.MaxInt64) + 1, true}, + + // Different large uint64 values should not be equal + {"uint64 large != different", uint64(16141183638984196173), uint64(16141183638984196174), false}, + {"uint64 maxuint64 != maxuint64-1", uint64(math.MaxUint64), uint64(math.MaxUint64) - 1, false}, + + // uint64 vs int comparisons (cross-type) + {"uint64 large != int 0", uint64(16141183638984196173), 0, false}, + {"uint64 large != int 1", uint64(16141183638984196173), 1, false}, + {"uint64(5) == int(5)", uint64(5), 5, true}, + {"uint64(5) == int64(5)", uint64(5), int64(5), true}, + + // uint64 boundary with int64 + {"uint64(maxint64) == int64(maxint64)", uint64(math.MaxInt64), int64(math.MaxInt64), true}, + {"uint64(maxint64+1) != int64(maxint64)", uint64(math.MaxInt64) + 1, int64(math.MaxInt64), false}, + + // uint64 vs float64 + {"uint64(5) == float64(5)", uint64(5), float64(5), true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := runtime.Equal(tt.a, tt.b) + assert.Equal(t, tt.want, got, "Equal(%v, %v) = %v; want %v", tt.a, tt.b, got, tt.want) + // Also test reverse order (commutativity) + got = runtime.Equal(tt.b, tt.a) + assert.Equal(t, tt.want, got, "Equal(%v, %v) = %v; want %v", tt.b, tt.a, got, tt.want) + }) + } +} + +// TestLess_Uint64Large tests runtime.Less with large uint64 values. +func TestLess_Uint64Large(t *testing.T) { + tests := []struct { + name string + a, b any + want bool + }{ + // uint64 vs uint64 + {"uint64(maxint64+1) < uint64(maxint64+2)", uint64(math.MaxInt64) + 1, uint64(math.MaxInt64) + 2, true}, + {"uint64(maxint64+2) < uint64(maxint64+1)", uint64(math.MaxInt64) + 2, uint64(math.MaxInt64) + 1, false}, + {"uint64(maxuint64-1) < uint64(maxuint64)", uint64(math.MaxUint64) - 1, uint64(math.MaxUint64), true}, + {"uint64(same) not less", uint64(16141183638984196173), uint64(16141183638984196173), false}, + + // uint64 vs int (cross-type) + {"uint64(maxint64+1) > int(0)", uint64(math.MaxInt64) + 1, 0, false}, // a < b is false + {"int(0) < uint64(maxint64+1)", 0, uint64(math.MaxInt64) + 1, true}, + + // Small values should work normally + {"uint64(1) < uint64(2)", uint64(1), uint64(2), true}, + {"uint64(2) < uint64(1)", uint64(2), uint64(1), false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := runtime.Less(tt.a, tt.b) + assert.Equal(t, tt.want, got, "Less(%v, %v) = %v; want %v", tt.a, tt.b, got, tt.want) + }) + } +} + +// TestMore_Uint64Large tests runtime.More with large uint64 values. +func TestMore_Uint64Large(t *testing.T) { + tests := []struct { + name string + a, b any + want bool + }{ + // uint64 vs uint64 + {"uint64(maxint64+2) > uint64(maxint64+1)", uint64(math.MaxInt64) + 2, uint64(math.MaxInt64) + 1, true}, + {"uint64(maxint64+1) > uint64(maxint64+2)", uint64(math.MaxInt64) + 1, uint64(math.MaxInt64) + 2, false}, + {"uint64(maxuint64) > uint64(maxuint64-1)", uint64(math.MaxUint64), uint64(math.MaxUint64) - 1, true}, + {"uint64(same) not more", uint64(16141183638984196173), uint64(16141183638984196173), false}, + + // uint64 vs int (cross-type) + {"uint64(maxint64+1) > int(0)", uint64(math.MaxInt64) + 1, 0, true}, + {"int(0) > uint64(maxint64+1)", 0, uint64(math.MaxInt64) + 1, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := runtime.More(tt.a, tt.b) + assert.Equal(t, tt.want, got, "More(%v, %v) = %v; want %v", tt.a, tt.b, got, tt.want) + }) + } +} + +// TestLessOrEqual_Uint64Large tests runtime.LessOrEqual with large uint64 values. +func TestLessOrEqual_Uint64Large(t *testing.T) { + tests := []struct { + name string + a, b any + want bool + }{ + {"uint64 same <= same", uint64(16141183638984196173), uint64(16141183638984196173), true}, + {"uint64 less <= more", uint64(math.MaxInt64) + 1, uint64(math.MaxInt64) + 2, true}, + {"uint64 more <= less", uint64(math.MaxInt64) + 2, uint64(math.MaxInt64) + 1, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := runtime.LessOrEqual(tt.a, tt.b) + assert.Equal(t, tt.want, got, "LessOrEqual(%v, %v) = %v; want %v", tt.a, tt.b, got, tt.want) + }) + } +} + +// TestMoreOrEqual_Uint64Large tests runtime.MoreOrEqual with large uint64 values. +func TestMoreOrEqual_Uint64Large(t *testing.T) { + tests := []struct { + name string + a, b any + want bool + }{ + {"uint64 same >= same", uint64(16141183638984196173), uint64(16141183638984196173), true}, + {"uint64 more >= less", uint64(math.MaxInt64) + 2, uint64(math.MaxInt64) + 1, true}, + {"uint64 less >= more", uint64(math.MaxInt64) + 1, uint64(math.MaxInt64) + 2, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := runtime.MoreOrEqual(tt.a, tt.b) + assert.Equal(t, tt.want, got, "MoreOrEqual(%v, %v) = %v; want %v", tt.a, tt.b, got, tt.want) + }) + } +}