checker: detect empty ranges with comptime constant bounds - #27890
checker: detect empty ranges with comptime constant bounds#27890rilaaax wants to merge 47 commits into
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8b7b1f37ad
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Please remember to add a regression test whenever you submit a valid codex review fix. Thanks! |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5368b548f0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e9b3b79363
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
e9b3b79 to
c1c2823
Compare
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c1c2823a0e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| typ_idx := typ.idx() | ||
| high_type := c.expr(mut node.high) | ||
| high_type_idx := high_type.idx() | ||
| errors_before_range_checks := c.errors.len |
There was a problem hiding this comment.
Snapshot errors before checking either range bound
This snapshot is taken after both c.expr calls, so errors emitted while checking the bounds are invisible to range_error and the invalid AST is still folded. Fresh evidence beyond the earlier comment is for i in 0 .. i64(1) << 99 {}: checking the high bound reports the oversized shift, but the evaluator subsequently folds the safe shift to zero and adds a spurious empty range: 0 .. 0 diagnostic. Capture the error count before evaluating the low and high bounds so any bound-checking error suppresses this secondary diagnostic.
AGENTS.md reference: AGENTS.md:L713-L713
Useful? React with 👍 / 👎.
| .right_shift { | ||
| result = i64(u64(left_raw) >> right_raw) |
There was a problem hiding this comment.
Preserve sign extension when folding signed right shifts
On C-backend targets where signed right shift sign-extends, casting the left operand to u64 makes the folded value disagree with the generated v__rshift_i64 operation. For example, for i in i64(-4) >> 1 .. i64(0) {} executes for -2 and -1 at runtime, but this evaluator produces 9223372036854775806 and rejects the loop as empty. Fold signed right shifts using signed semantics rather than the unsigned cast.
Useful? React with 👍 / 👎.
| if low_val := c.eval_comptime_const_expr(low, 0) { | ||
| if high_val := c.eval_comptime_const_expr(high, 0) { |
There was a problem hiding this comment.
Apply f32 conversions at comptime function boundaries
The new range check can fold @[comptime] calls, but the function evaluator does not convert arguments or returns to their declared types. With @[comptime] fn id_f32(x f32) f32 { return x }, for i in int(id_f32(16777217.0)) .. 16777217 {} starts at 16777216 at runtime because the argument is rounded to f32, while this call receives the original f64 literal and causes the range to be rejected as empty. Coerce evaluated arguments and return expressions at the function boundary before using their values for this diagnostic.
Useful? React with 👍 / 👎.
|
Implemented the latest Codex review feedback in 7aecd71:
Validation:
|
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7aecd7156b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| promoted_type := c.table.fully_unaliased_type(expr.promoted_type).clear_flags() | ||
| if promoted_type.is_pure_float() { |
There was a problem hiding this comment.
Skip numeric folding for overloaded operators
When a primitive alias defines an operator overload, fully unaliasing its promoted type sends the expression through the built-in arithmetic path instead of invoking or rejecting the overload. For example, if type A = u8 defines fn (a A) + (b A) A { return 0 }, then for i in int(A(2) + A(2)) .. 1 {} executes once at runtime, but this evaluator folds the low bound to 4 and emits an incorrect empty-range error. Detect operator-overloaded infix expressions and return none rather than applying primitive arithmetic.
Useful? React with 👍 / 👎.
|
Addressed the remaining exact-head findings in 57da40e: overloaded operators now opt out of numeric folding, signed right shifts use signed semantics, and comptime-call arguments/returns are converted at declared numeric type boundaries. Added regressions for all three cases. Focused fixture diff passes; checker suite passes 4/4; inout diagnostics pass 94/94; compiler_errors has the same 6 unrelated stale-branch module failures (1617 pass, 1 skip). |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 57da40ea8c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if _likely_(left_raw != none && right_raw != none) { | ||
| mut result := i64(0) | ||
|
|
||
| if promoted_type.is_signed() { |
There was a problem hiding this comment.
Treat untyped integer arithmetic as signed
When both operands are integer literals, expr.promoted_type is int_literal, which is considered a pure integer but is not included in is_signed(), so this branch evaluates it as unsigned. Consequently, compiling for i in 0 - 1 .. 0 {} folds the low bound to max_u64 rather than -1 and emits an empty-range error even though the valid loop executes once; handle int_literal with signed semantics before selecting this branch.
Useful? React with 👍 / 👎.
| .div { | ||
| if _unlikely_(right_raw == 0) { | ||
| return none | ||
| } else { | ||
| result = left_raw / right_raw |
There was a problem hiding this comment.
Avoid overflowing signed division during folding
For the valid constant expression i64(min_i64) / i64(-1), the divisor is nonzero but the signed division itself overflows; on targets such as x86_64 this operation can raise SIGFPE. Because every error-free range bound is now folded, merely compiling for i in i64(min_i64) / i64(-1) .. 1 {} can terminate the compiler instead of leaving the runtime expression alone, so this exceptional operand pair should return none before division.
Useful? React with 👍 / 👎.
| .power { | ||
| result = comptime_power_i64(i64(left_u), i64(right_u)) |
There was a problem hiding this comment.
Fold unsigned powers with unsigned-base semantics
On the C backend, __v_pow_u64 receives an unsigned base and returns 0 for a negative exponent unless that base is 1, but this path reinterprets the base as signed before calling the signed evaluator. Thus for i in u64(max_u64) ** u64(max_u64) .. 1 {} has a runtime low bound of 0 (the exponent becomes -1), while folding treats the base as -1, returns max_u64, and incorrectly rejects the one-iteration loop as empty; use the unsigned power helper's rules here.
Useful? React with 👍 / 👎.
|
Addressed the three findings from the 57da40e review in 7f94ea6: untyped integer expressions now fold as signed values, the min_i64/-1 signed division and modulo pair opts out of folding, and unsigned power follows unsigned-base runtime rules. Added regressions for each case. Focused fixture diff passes; checker suite passes 4/4; inout diagnostics pass 94/94; compiler_errors remains at the same 6 unrelated stale-branch module failures (1617 pass, 1 skip). |
|
Fixed right-operand overload dispatch in 3596294. Comptime arithmetic now follows the C backend and considers overloads only on the left operand. Added a mixed builtin-left and overloaded-alias-right regression; focused comptime and checker suites pass. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3596294ca9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| fn (mut c Checker) check_for_empty_range(low ast.Expr, high ast.Expr, low_type ast.Type, val_type ast.Type, high_type ast.Type) { | ||
| backend_has_distinct_range_conversions := c.pref.backend == .wasm || c.pref.backend.is_js() |
There was a problem hiding this comment.
Skip translated-C range folding
When compiling with -translated or checking an @[translated] file, this still applies normal V narrowing semantics. For example, for _ in u8(1) .. u8(255) + u8(1) { break } is folded to 1 .. 0 and rejected, but the inspected C generator explicitly disables its numeric result cast for translated code (vlib/v/gen/c/infix.v:2168-2202), so C integer promotion makes the high bound 256 and the loop enters. Include translated modes in this backend-sensitive bailout or model their arithmetic semantics separately.
Useful? React with 👍 / 👎.
|
Fixed translated C range semantics in ea1957b. Nonliteral bounds are no longer folded with normal V narrowing when -translated or an @[translated] file makes C promotion semantics distinct. The regression runs both translated modes and confirms the range executes; focused checker tests pass 5/5. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ea1957b356
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if is_untyped_int { | ||
| return c.wrap_comptime_int(result, literal_runtime_type) | ||
| } | ||
| return c.wrap_comptime_int(result, promoted_type) |
There was a problem hiding this comment.
Preserve integer promotion for narrow left shifts
When a range bound left-shifts a narrow integer, this unconditional wrapping loses the C integer promotion applied at runtime. For example, for _ in u8(1) .. u8(128) << u8(1) { break } is folded to 1 .. 0 and rejected as empty, while vlib/v/gen/c/infix.v:2168-2202 does not cast shift results back to u8, so the generated comparison uses the promoted result 256 and enters the loop. Model the promoted shift-result width here or decline folding narrow left shifts.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
The generated C path for this exact expression does preserve the narrow V shift type. infix_expr dispatches .left_shift to infix_expr_left_shift_op, whose scalar fallback calls gen_safe_shift_expr; the generated condition is i < v__lshift_u8((u8)128, (u64)(u8)1). V_SAFE_LSHIFT_UNSIGNED explicitly returns u8, so the high bound is 0, not 256. A direct runtime check also reports typeof(u8(128) << u8(1)) == u8 and value 0; the loop does not enter. The evaluator and empty-range diagnostic therefore match current C codegen, so no source change is appropriate for this finding.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ea1957b356
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if method := c.find_comptime_eval_infix_method(expr) { | ||
| return c.eval_comptime_infix_method(method, left, right, nlevel) |
There was a problem hiding this comment.
Defer evaluating forward operator bodies
When an overloaded operator method is declared after the $if that uses it, this dispatch evaluates the method's unchecked AST, whose nested infix expressions do not yet have promoted_type populated. For example, a type A = u8 condition $if A(1) + A(1) == A(2) followed later by fn (a A) + (b A) A { return A(u8(a) + u8(b)) } now fails with invalid $if condition, whereas the same valid overload works when declared first. Fresh evidence beyond the previously covered forward @[comptime] call is that operator bodies bypass the checked-function guard here; ensure the method body has been checked or defer the condition without making declaration order significant.
Useful? React with 👍 / 👎.
|
Fixed forward-declared operator evaluation in f3118ab. When an unchecked forward operator body lacks nested promoted_type metadata, the evaluator now derives operand types from its AST and constant values before applying normal promotion. Added the exact forward numeric alias overload regression. Validation: 172/172 comptime tests, 5/5 checker tests, and all 94 diagnostic fixtures pass. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f3118ab01e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| fn (mut c Checker) fn_decl(mut node ast.FnDecl) { | ||
| nr_errors_before_fn_decl := c.nr_errors | ||
| defer { | ||
| c.comptime_eval_checked_fns[node.name] = c.nr_errors == nr_errors_before_fn_decl |
There was a problem hiding this comment.
Key checked overloads by receiver
When one + overload is checked successfully before a range and another type's + overload is declared after it, both declarations use the same node.name (+), so the earlier method leaves this map entry true. Range folding then treats the later method as checked, evaluates its still-unchecked body, and can emit a spurious empty range diagnostic before an invalid return or expression in that body is reported. Key this state by the full method identity, including its receiver, rather than the shared operator name.
AGENTS.md reference: AGENTS.md:L713-L713
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in abd7013. Checked-function state now uses FnDecl.fkey() when recorded and the resolved Fn.fkey() when queried, so operator overloads are isolated by receiver even before source_fn is updated. Added a cross-receiver forward-overload regression.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: abd70131d1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if backend_has_distinct_range_conversions | ||
| && (!range_value_fits_int(evaluated_low) || !range_value_fits_int(evaluated_high)) { | ||
| return |
There was a problem hiding this comment.
Retain wide literal diagnostics on non-C backends
When targeting JavaScript or WASM, a plain literal range such as 4294967295 .. 4294967294 reaches this guard, but range_value_fits_int rejects both bounds because they exceed 32-bit int, so the established empty-range diagnostic disappears. The inspected JS loop compares these exactly representable numbers directly (vlib/v/gen/js/js.v:1817-1821), while WASM unpacks int_literal to i64 (vlib/v/gen/wasm/gen.v:144-146,1371-1391), making the range empty on both backends; the parent checker also rejected this literal-only case. Restrict the bailout to values whose backend conversion can actually change the comparison and cover wide empty literals in the targeted JS/WASM tests.
AGENTS.md reference: AGENTS.md:L516-L519
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 7038d54. Plain JS/WASM integer-literal ranges are now compared using their backend representation (JavaScript Number and WASM i64), retaining wide empty-range diagnostics without regressing conversion-sensitive ranges. Added focused JS and WASM regressions.
| // Forward operator bodies have not been checked yet, so their nested | ||
| // infix expressions do not have a promoted type assigned. | ||
| promoted_type = c.promote(c.comptime_const_expr_type(expr.left, left), c.comptime_const_expr_type(expr.right, | ||
| right)) |
There was a problem hiding this comment.
Resolve nested overloads before evaluating forward bodies
When a $if invokes a forward-declared operator whose unchecked body contains another overloaded arithmetic expression, the nested expression has neither left_type nor promoted_type populated, so find_comptime_eval_infix_method misses its overload and this fallback evaluates the unaliased builtin operation instead. For example, an A.+ body return A(B(a) + B(b) + 1) where B.+ returns its left operand should make A(1) + A(1) == A(2) true, but the unchecked fallback computes the inner addition as 1 + 1, returns 3, and selects the wrong $if branch. Fresh evidence beyond the earlier forward-overload report is the unresolved nested overload inside the forward body; defer evaluation until that body is checked or resolve nested operators before folding.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 944f48c. Forward evaluation now validates source_fn by full function identity, locates declarations by fkey, and derives unresolved nested infix types so their overloads are evaluated instead of falling back to builtin arithmetic. Added a nested-forward-overload regression.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7038d54c4b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .right_shift { return left >> right } | ||
| .unsigned_right_shift { return left >>> right } | ||
| else { return none } | ||
| if promoted_type.is_pure_float() { |
There was a problem hiding this comment.
Skip floating range folding under
-nofloat
When the compiler is invoked with -nofloat, this branch still evaluates operands using IEEE floating-point semantics. That mode replaces f32/f64 with integer types (vlib/v/pref/pref.v:259), and C generation truncates each float literal before emitting it (vlib/v/gen/c/cgen.v:7068-7073). Consequently, for _ in 0 .. int(f64(2.1) - f64(1.9)) {} is rejected here as 0 .. 0, while its -nofloat high bound is lowered as 2 - 1 and the loop enters. Gate floating range folding on c.pref.nofloat or model that lowering.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in a2362e6. Range-only comptime evaluation now declines floating arithmetic under -nofloat so the backend lowering decides the loop bounds. Added a focused -nofloat compile-and-run regression.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a2362e6a8a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| else { | ||
| ast.void_type |
There was a problem hiding this comment.
Resolve parameter types in forward overload bodies
Fresh evidence beyond the earlier nested-forward-overload case is an unchecked body whose nested operation uses its parameters directly: with A.- returning zero and a forward A.+ implemented as return a - b, $if A(2) + A(1) == A(0) should be true. The parameter identifiers still have no checked Ident.info.typ, and this fallback does not consult their parser scope, so find_comptime_eval_infix_method misses A.- and evaluates builtin subtraction instead, selecting the wrong $if branch. Resolve unresolved identifiers from their scope or carry their declared parameter types through local evaluation.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in c3cf954. Unresolved identifiers now derive their types from the parser scope, so parameter operations inside forward overload bodies dispatch the correct overload. Added the direct a-minus-b forward-body regression.
| if cast_typ == ast.u64_type && (value is f32 || value is f64) { | ||
| float_value := value.f64()? | ||
| if !(float_value >= 0) { | ||
| return u64(0) |
There was a problem hiding this comment.
Skip nofloat float-to-u64 casts during range folding
Fresh evidence after the -nofloat arithmetic bailout is that casts alone still use floating semantics here. Under -nofloat, f64 is emitted as u64 and float literals are emitted as truncated integers, so the high bound in for _ in u64(1) .. u64(f64(-1.0)) { break } becomes max_u64 and the generated loop enters; the evaluator instead retains f64(-1.0), saturates it to zero in this branch, and incorrectly reports an empty range. Decline range folding for float conversions under -nofloat, not only for floating infix expressions.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in c3cf954. Range-only evaluation now declines any floating value conversion under -nofloat, including direct float-to-u64 casts. Added the exact compile-and-run regression.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c3cf954ea9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if is_untyped_int && expr.op in [.div, .mod] { | ||
| use_signed_arithmetic = literal_runtime_type.is_signed() |
There was a problem hiding this comment.
Keep untyped division and modulo signed
When untyped division or modulo mixes a negative literal with a positive literal represented as u32, this switches folding to unsigned even though the checked expression type remains the signed int_literal. For example, for _ in (-1 % 4294967295) .. 0 { break } is folded here to 0 .. 0 and rejected, while Transformer.infix_expr evaluates the literals as i64 and produces -1 (and the unoptimized C path uses signed VSAFE_MOD_int_literal), so the generated loop starts at -1 and executes. Base signedness on the actual promoted expression type rather than the literals' represented widths.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 935384f. Untyped division/modulo now uses signed arithmetic when either operand is an actual negative signed value, while retaining represented unsigned semantics for values such as max-u64. Added the exact negative-modulo range regression; the existing wide-u64 compiler fixture remains unchanged.
|
@codex review |
|
Codex Review: Didn't find any major issues. Another round soon, please! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Fixes #27889
Problem
The compile-time check that detects empty numeric ranges (e.g.
for i in 4 .. 2) only worked when both bounds were raw integer literals. Ranges whose bounds were comptime constant cast expressions (int(2),u8(2),u16(4)) or operation results (4 - 2,int(4) - int(2)) silently compiled even when the range could never execute.Cause
checker/for.vmatched the bounds againstast.IntegerLiteralonly, so any other AST node type (ast.CastExpr, etc.) bypassed the emptiness check entirely, even though its value was fully known at compile time.Fix
Replaced the
ast.IntegerLiteralpattern match withc.eval_comptime_const_expr(), the same comptime evaluator already used elsewhere in the checker, to resolve both bounds to concrete values before comparing them. The check is restricted to cases where both bounds resolve to actual integer types, so it doesn't misfire alongside the existing "range types do not match" / "range type can only be an integer type" errors (e.g.10 .. 10.5,'a' .. 'b'). A fallback unsigned comparison handles literals that overflowi64.Known remaining limitations (left as TODOs, out of scope for this fix):
0 .. max_u8 + 1)4 .. int(2) + u8(1))Test
Coverage + regression tests added.