Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions pkg/interpreter/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
}

Expand Down
8 changes: 4 additions & 4 deletions pkg/interpreter/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4132,7 +4132,7 @@ func TestPostfixOverflowDetection(t *testing.T) {
{
name: "increment overflow",
input: `
temp i int = 9223372036854775807
temp i i64 = 9223372036854775807
i++
`,
expectError: true,
Expand All @@ -4141,7 +4141,7 @@ i++
{
name: "decrement underflow",
input: `
temp i int = -9223372036854775808
temp i i64 = -9223372036854775808
i--
`,
expectError: true,
Expand Down Expand Up @@ -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",
},
}
Expand Down
29 changes: 28 additions & 1 deletion pkg/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
32 changes: 32 additions & 0 deletions pkg/typechecker/typechecker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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<N>, u<N>, f<N>)
// 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
Expand All @@ -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
Expand Down
Loading