Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.
Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).
Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.
zig build test: 426/426; examples suite: 595/595.
4.6 KiB
RESOLVED — 0112: out-of-range int literal silently wraps into a narrower annotated target
Root cause: the .int_literal arm adopted an integer target_type with no
fits-check, truncating at emission width; globalInitValue serialized literal
global initializers raw the same way.
Fix: Lowering.checkIntLiteralFits (src/ir/lower.zig) range-checks a
literal against its integer target (intLiteralRange: builtins + custom
widths; width-64 types skip — every representable literal is legal there) and
diagnoses integer literal N does not fit in T (range lo..hi) — use an explicit xx/cast to truncate. Wired into the .int_literal arm,
lowerStructConstant, and globalInitValue. A negated literal now folds to
one constant (-128 checks as -128, not as an out-of-range +128
intermediate), and an explicit xx operand skips the check
(suppress_int_fit_check) — truncation stays available on request;
cast(T) was already exempt (its value arg lowers without the target).
Coverage via the shared arm: decls, assignments, call args, struct-literal
fields, struct constants, globals.
Behavior change: examples/0300-closures-lambda.sx passed 133 to an
i3 param and pinned the wrapped -3; updated to a fitting value.
Regression tests: examples/1156-diagnostics-int-literal-out-of-range.sx
(both faces diagnosed in one run) and
examples/0174-types-int-literal-boundaries.sx (extreme in-range values,
width-64 types, xx/cast escapes, call args).
Found during the fix: negated-literal GLOBAL initializers (g : i64 = -1;)
are rejected as non-constant — pre-existing gap, filed as issue 0113.
0112 — out-of-range int literal silently wraps into a narrower annotated target
Symptom. An integer literal that does not fit its explicitly-annotated
integer target truncates with no diagnostic: x : i8 = 300; binds 44,
y : u8 = 256; binds 0. Expected: a compile-time error (the value is known
exactly at compile time; this is the integer analogue of the float→int
narrowing rule, which errors on non-exact y : i64 = 1.5).
Split from issue 0111 (whose fix removed the implicit narrowing — an
unannotated x := 0 no longer adopts the fn return type — but the explicit
annotation path keeps wrapping).
Reproduction
#import "modules/std.sx";
main :: () {
x : i8 = 300;
print("x: {}\n", x);
y : u8 = 256;
print("y: {}\n", y);
}
- Observed (current master): prints
x: 44/y: 0, exit 0, no diagnostic. - Expected: compile error per literal, e.g.
integer literal 300 does not fit in i8 (range -128..127), and the analog for256/u8 (range 0..255).
Repro co-located: issues/0112-int-literal-out-of-range-silent-wrap.sx
(unpinned until fixed).
Root cause (suspected area)
src/ir/lower/expr.zig .int_literal arm (~1499): when target_type is an
integer type, it emits constInt(lit.value, tt) with no fits-check — the
value truncates at LLVM emission width. The annotated-decl path
(lowerVarDecl with type_annotation, src/ir/lower/stmt.zig ~255) sets
target_type to the annotation before lowering the initializer, so every
annotated narrow decl funnels through this arm. Assignments to narrow
lvalues (b = 300 where b: i8) reach the same arm via lowerAssignment's
LHS-derived target and likely need the same check.
Investigation prompt (paste into a fresh session)
Fix issue 0112: an int literal that does not fit its integer target type silently wraps. In the
.int_literalarm oflowerExpr(src/ir/lower/expr.zig~1499), before adopting an integertarget_type, range-checklit.valueagainst the target's signedness/width (the type table knows both; mirror the bounds logic used byTypeResolver.integerLimitFor). On overflow emit a diagnostic viaself.diagnostics.addFmt(.err, node.span, ...)naming the literal, the type, and its range — do NOT silently fall back to i64 (REJECTED PATTERNS: no silent fallback defaults); still return aconstIntof the target type so lowering continues to surface further errors. Audit sibling literal sinks that bypass this arm (comptime folds,lowerStructConstant, global initializers) for the same check.Verify:
issues/0112-int-literal-out-of-range-silent-wrap.sxerrors with two diagnostics (i8/300, u8/256); boundary values still compile (x : i8 = -128/127,y : u8 = 0/255,m : u64large literals).zig build && zig build test && bash tests/run_examples.sh— any example that relied on silent wrapping must be reviewed individually. Promote the repro per the resolution flow (likelyexamples/11xx-diagnostics-...).