diff --git a/crates/jam-sema/src/astgen.rs b/crates/jam-sema/src/astgen.rs index c0b7d34..581703e 100644 --- a/crates/jam-sema/src/astgen.rs +++ b/crates/jam-sema/src/astgen.rs @@ -7981,6 +7981,13 @@ fn astgen_for(gctx: &mut AstGenCtx, n: &AstNode) -> Result<(), String> { start_hint = t; } } + // A literal end bound makes the loop var u64. It used to type from + // the START literal (`0` -> u8), so `for i in 0:256` truncated the + // bound through u8 and never ran; smallest-fit on the end is no + // better (`i * i` wraps at u16 for `0:1024`). Indices are u64. + if en.tag == AstTag::NumberLit && en.flags & 3 == 0 { + start_hint = builtin::U64; + } } let start_ref = astgen_expr(gctx, start_idx, start_hint)?; let idx_ty = gctx.jfn.get_inst(start_ref).ty; diff --git a/tests/unit/test_for_range_bounds.jam b/tests/unit/test_for_range_bounds.jam new file mode 100644 index 0000000..08d6de9 --- /dev/null +++ b/tests/unit/test_for_range_bounds.jam @@ -0,0 +1,28 @@ +// The loop index takes its width from a literal end bound. It used to +// type from the start literal (`0` -> u8), so `for i in 0:256` truncated +// the bound through u8 and never ran. +const { assert } = import("test"); + +tfn forBoundPastU8() { + var count: u64 = 0; + for i in 0:256 { + count = count + 1; + } + assert(count as i32, 256); +} + +tfn forBoundPastU16() { + var count: u64 = 0; + for i in 0:70000 { + count = count + 1; + } + assert((count == 70000) as u8, 1); +} + +tfn forSmallBoundStillWorks() { + var sum: u64 = 0; + for i in 0:10 { + sum = sum + i; + } + assert(sum as i32, 45); +}