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:
@@ -205,6 +205,7 @@ pub const Lowering = struct {
|
||||
break_target: ?BlockId = null,
|
||||
continue_target: ?BlockId = null,
|
||||
loop_defer_base: usize = 0, // defer-stack height at the innermost loop's body start (break/continue drain to here)
|
||||
suppress_int_fit_check: bool = false, // inside an explicit `xx` cast operand: truncation is requested, skip the literal fits-check
|
||||
block_counter: u32 = 0,
|
||||
comptime_counter: u32 = 0,
|
||||
main_file: ?[]const u8 = null, // path of the main file; imported functions are declared extern
|
||||
@@ -1129,6 +1130,74 @@ pub const Lowering = struct {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Value range of an integer type, for literal fits-checks. Null for
|
||||
/// 64-bit types — every i64 literal bit pattern is legal there (a 64-bit
|
||||
/// hex literal wraps negative through the lexer's i64 value, so a
|
||||
/// min/max check would false-positive) — and for non-integers.
|
||||
pub fn intLiteralRange(self: *Lowering, ty: TypeId) ?struct { min: i64, max: i64 } {
|
||||
var width: u8 = 0;
|
||||
var is_signed = false;
|
||||
switch (ty) {
|
||||
.s8 => {
|
||||
width = 8;
|
||||
is_signed = true;
|
||||
},
|
||||
.s16 => {
|
||||
width = 16;
|
||||
is_signed = true;
|
||||
},
|
||||
.s32 => {
|
||||
width = 32;
|
||||
is_signed = true;
|
||||
},
|
||||
.u8 => width = 8,
|
||||
.u16 => width = 16,
|
||||
.u32 => width = 32,
|
||||
else => {
|
||||
if (ty.isBuiltin()) return null; // s64/u64/isize/usize/non-int
|
||||
switch (self.module.types.get(ty)) {
|
||||
.signed => |w| {
|
||||
width = w;
|
||||
is_signed = true;
|
||||
},
|
||||
.unsigned => |w| width = w,
|
||||
else => return null,
|
||||
}
|
||||
if (width >= 64) return null;
|
||||
},
|
||||
}
|
||||
if (is_signed) {
|
||||
const max = (@as(i64, 1) << @intCast(width - 1)) - 1;
|
||||
return .{ .min = -max - 1, .max = max };
|
||||
}
|
||||
const max = (@as(i64, 1) << @intCast(width)) - 1;
|
||||
return .{ .min = 0, .max = max };
|
||||
}
|
||||
|
||||
/// Diagnose an integer literal that cannot be represented in `ty`
|
||||
/// (REJECTED PATTERNS: no silent wrap). The constant is still emitted by
|
||||
/// the caller so lowering continues and surfaces further errors.
|
||||
pub fn checkIntLiteralFits(self: *Lowering, value: i64, ty: TypeId, span: ast.Span) void {
|
||||
if (self.suppress_int_fit_check) return;
|
||||
const r = self.intLiteralRange(ty) orelse return;
|
||||
if (value < r.min or value > r.max) {
|
||||
if (self.diagnostics) |d| {
|
||||
// Custom-width ints are structural (unnamed in the type
|
||||
// table) — render them as s{N}/u{N}.
|
||||
var name_buf: [8]u8 = undefined;
|
||||
const tn = blk: {
|
||||
if (ty.isBuiltin()) break :blk self.module.types.typeName(ty);
|
||||
break :blk switch (self.module.types.get(ty)) {
|
||||
.signed => |w| std.fmt.bufPrint(&name_buf, "s{d}", .{w}) catch "integer",
|
||||
.unsigned => |w| std.fmt.bufPrint(&name_buf, "u{d}", .{w}) catch "integer",
|
||||
else => self.module.types.typeName(ty),
|
||||
};
|
||||
};
|
||||
d.addFmt(.err, span, "integer literal {} does not fit in {s} (range {}..{}) — use an explicit `xx` / `cast` to truncate", .{ value, tn, r.min, r.max });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Operands valid for a scalar numeric op (`+ - * / %`): ints (incl.
|
||||
/// custom widths), floats, SIMD vectors, and pointers (pointer
|
||||
/// arithmetic). `.unresolved` returns true so a type we couldn't infer
|
||||
|
||||
Reference in New Issue
Block a user