diff --git a/pkg/interpreter/evaluator.go b/pkg/interpreter/evaluator.go index b11e6f4c..3294301b 100644 --- a/pkg/interpreter/evaluator.go +++ b/pkg/interpreter/evaluator.go @@ -62,15 +62,19 @@ func init() { } // getTypeBounds returns the min and max values for a given integer type +// Returns nil, nil for arbitrary precision types (int, uint) that have no bounds func getTypeBounds(typeName string) (min, max *big.Int) { switch typeName { + case "int", "uint": + // Arbitrary precision - no bounds + return nil, nil case "i8": return minInt8, maxInt8 case "i16": return minInt16, maxInt16 case "i32": return minInt32, maxInt32 - case "i64", "int", "": + case "i64", "": return minInt64, maxInt64 case "i128": return minInt128, maxInt128 @@ -82,7 +86,7 @@ func getTypeBounds(typeName string) (min, max *big.Int) { return zero, maxUint16 case "u32": return zero, maxUint32 - case "u64", "uint": + case "u64": return zero, maxUint64 case "u128": return zero, maxUint128 @@ -95,8 +99,13 @@ func getTypeBounds(typeName string) (min, max *big.Int) { } // checkOverflow checks if a value is within bounds for a given type +// Returns false for arbitrary precision types (int, uint) that have no bounds func checkOverflow(result *big.Int, typeName string) bool { min, max := getTypeBounds(typeName) + if min == nil || max == nil { + // Arbitrary precision type - no overflow possible + return false + } return result.Cmp(min) < 0 || result.Cmp(max) > 0 } diff --git a/pkg/interpreter/evaluator_test.go b/pkg/interpreter/evaluator_test.go index 5b9ce3d9..c7443e09 100644 --- a/pkg/interpreter/evaluator_test.go +++ b/pkg/interpreter/evaluator_test.go @@ -4132,7 +4132,7 @@ func TestPostfixOverflowDetection(t *testing.T) { { name: "increment overflow", input: ` -temp i int = 9223372036854775807 +temp i i64 = 9223372036854775807 i++ `, expectError: true, @@ -4141,7 +4141,7 @@ i++ { name: "decrement underflow", input: ` -temp i int = -9223372036854775808 +temp i i64 = -9223372036854775808 i-- `, expectError: true, @@ -4276,8 +4276,8 @@ func TestLargeIntegerOverflow(t *testing.T) { "E5006", }, { - "int overflow still works", - "temp max int = 9223372036854775807\ntemp r int = max + 1", + "i64 overflow still works", + "temp max i64 = 9223372036854775807\ntemp r i64 = max + 1", "E5005", }, } diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go index 892c3f9e..88c862bc 100644 --- a/pkg/parser/parser.go +++ b/pkg/parser/parser.go @@ -2788,12 +2788,39 @@ func (p *Parser) parseInterpolatedExpression(exprStr string, origToken Token) Ex // Parse the expression expr := tempParser.parseExpression(LOWEST) - // Check for errors + // Check for lexer errors (e.g., illegal characters like | or ^) + lexerErrors := lexer.Errors() + if len(lexerErrors) > 0 { + for _, err := range lexerErrors { + p.addEZError(errors.E1001, err.Message, origToken) + } + return nil + } + + // Check for parser errors if len(tempParser.errors) > 0 { p.errors = append(p.errors, tempParser.errors...) return nil } + // Check for unconsumed tokens - if there are tokens left after parsing, + // it likely means an invalid operator was encountered (e.g., &, |, ^) + if tempParser.peekToken.Type != EOF { + // Determine appropriate error message based on the unconsumed token + unexpectedToken := tempParser.peekToken + var msg string + switch unexpectedToken.Type { + case AMPERSAND: + msg = "& is not a valid binary operator in expressions" + case ILLEGAL: + msg = fmt.Sprintf("illegal character '%s' in expression", unexpectedToken.Literal) + default: + msg = fmt.Sprintf("unexpected token '%s' in interpolated expression", unexpectedToken.Literal) + } + p.addEZError(errors.E2001, msg, origToken) + return nil + } + return expr } diff --git a/pkg/typechecker/typechecker.go b/pkg/typechecker/typechecker.go index 7a90e2bc..3103b207 100644 --- a/pkg/typechecker/typechecker.go +++ b/pkg/typechecker/typechecker.go @@ -396,6 +396,13 @@ func (tc *TypeChecker) TypeExists(typeName string) bool { return true } + // Check if the type looks like a built-in sized type pattern (i, u, f) + // If it matches the pattern but wasn't found in tc.types, it's definitely invalid + // and should NOT be deferred to module resolution + if looksLikeBuiltinSizedType(typeName) { + return false + } + // Check if the type might be available via file-level 'using' directive // For unqualified type names, if a module is imported via 'using', // the type will be validated at runtime when the module is loaded @@ -421,6 +428,31 @@ func (tc *TypeChecker) TypeExists(typeName string) bool { return false } +// looksLikeBuiltinSizedType checks if a type name follows the pattern of built-in +// sized types (i8, i16, u32, f64, etc.) but is NOT a valid one. +// This prevents invalid types like i512 or u1024 from being deferred to module resolution. +func looksLikeBuiltinSizedType(typeName string) bool { + if len(typeName) < 2 { + return false + } + + prefix := typeName[0] + if prefix != 'i' && prefix != 'u' && prefix != 'f' { + return false + } + + // Check if the rest is all digits + for _, c := range typeName[1:] { + if c < '0' || c > '9' { + return false + } + } + + // It matches the pattern (e.g., i512, u1024, f128) + // If we got here, it wasn't found in tc.types, so it's an invalid sized type + return true +} + // RegisterType adds a user-defined type to the registry func (tc *TypeChecker) RegisterType(name string, t *Type) { tc.types[name] = t