fix(0112): out-of-range int literals error instead of silently wrapping
checkIntLiteralFits range-checks a literal against its integer target (builtins + custom widths via intLiteralRange; width-64 types skip — every representable literal is a legal bit pattern there) and diagnoses with the type's range and an xx/cast hint. Wired into the .int_literal arm (covers decls, assignments, call args, struct-literal fields), lowerStructConstant, and globalInitValue. A negated literal now folds to a single constant so -128 range-checks as -128 rather than as an out-of-range +128 intermediate. An explicit xx operand skips the check — truncation stays available on request (cast(T) was already exempt: its value arg lowers without the target). examples/0300-closures-lambda.sx pinned 133 wrapping to -3 through an s3 param — the exact class this outlaws; updated to a fitting value. Found during the fix and filed separately: issue 0113 (negated-literal global initializers rejected as non-constant; pre-existing). Regressions: examples/1156-diagnostics-int-literal-out-of-range.sx, examples/0174-types-int-literal-boundaries.sx.
This commit is contained in:
@@ -627,7 +627,10 @@ pub fn lowerNumericLimit(self: *Lowering, fa: *const ast.FieldAccess, span: ast.
|
||||
pub fn lowerStructConstant(self: *Lowering, info: StructConstInfo) Ref {
|
||||
const val_node = info.value;
|
||||
return switch (val_node.data) {
|
||||
.int_literal => |lit| self.builder.constInt(lit.value, info.ty orelse .s64),
|
||||
.int_literal => |lit| blk: {
|
||||
if (info.ty) |t| self.checkIntLiteralFits(lit.value, t, val_node.span);
|
||||
break :blk self.builder.constInt(lit.value, info.ty orelse .s64);
|
||||
},
|
||||
.float_literal => |lit| self.builder.constFloat(lit.value, info.ty orelse .f64),
|
||||
.bool_literal => |lit| self.builder.constBool(lit.value),
|
||||
.string_literal => |lit| self.builder.constString(self.module.types.internString(lit.raw)),
|
||||
@@ -1501,6 +1504,7 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
||||
const ty = if (self.target_type) |tt| blk: {
|
||||
break :blk if (self.isIntEx(tt)) tt else .s64;
|
||||
} else .s64;
|
||||
self.checkIntLiteralFits(lit.value, ty, node.span);
|
||||
return self.builder.constInt(lit.value, ty);
|
||||
},
|
||||
.float_literal => |lit| {
|
||||
@@ -1777,7 +1781,26 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
||||
break :blk self.builder.emit(.{ .global_addr = gi.id }, ptr_ty);
|
||||
}
|
||||
}
|
||||
// Fold a negated integer literal into one constant: `-128` must
|
||||
// range-check as -128, not as an out-of-range +128 intermediate.
|
||||
if (uop.op == .negate and uop.operand.data == .int_literal) {
|
||||
const lit = uop.operand.data.int_literal;
|
||||
const v = -%lit.value;
|
||||
if (self.target_type) |tt| {
|
||||
if (tt == .f32 or tt == .f64) {
|
||||
break :blk self.builder.constFloat(@floatFromInt(v), tt);
|
||||
}
|
||||
}
|
||||
const nty = if (self.target_type) |tt| (if (self.isIntEx(tt)) tt else TypeId.s64) else TypeId.s64;
|
||||
self.checkIntLiteralFits(v, nty, node.span);
|
||||
break :blk self.builder.constInt(v, nty);
|
||||
}
|
||||
// An explicit `xx` cast requests the conversion, truncation
|
||||
// included — literal operands skip the fits-check.
|
||||
const saved_fit = self.suppress_int_fit_check;
|
||||
if (uop.op == .xx) self.suppress_int_fit_check = true;
|
||||
const operand = self.lowerExpr(uop.operand);
|
||||
self.suppress_int_fit_check = saved_fit;
|
||||
break :blk switch (uop.op) {
|
||||
.negate => self.builder.emit(.{ .neg = .{ .operand = operand } }, self.inferExprType(uop.operand)),
|
||||
.not => self.builder.emit(.{ .bool_not = .{ .operand = operand } }, .bool),
|
||||
|
||||
Reference in New Issue
Block a user