Skip to content

checker: detect empty ranges with comptime constant bounds - #27890

Open
rilaaax wants to merge 6 commits into
vlang:masterfrom
rilaaax:checker/fix-for-in-empty-ranges
Open

checker: detect empty ranges with comptime constant bounds#27890
rilaaax wants to merge 6 commits into
vlang:masterfrom
rilaaax:checker/fix-for-in-empty-ranges

Conversation

@rilaaax

@rilaaax rilaaax commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

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.v matched the bounds against ast.IntegerLiteral only, 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.IntegerLiteral pattern match with c.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 overflow i64.

Known remaining limitations (left as TODOs, out of scope for this fix):

  • Ranges that are empty only due to a bound silently overflowing its target integer type (e.g. 0 .. max_u8 + 1)
  • Bounds combining two different cast types in one expression (e.g. 4 .. int(2) + u8(1))

Test

Coverage + regression tests added.

@medvednikov

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread vlib/v/checker/for.v Outdated
@GGRei

GGRei commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Please remember to add a regression test whenever you submit a valid codex review fix. Thanks!

@GGRei

GGRei commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread vlib/v/checker/comptime.v Outdated
Comment thread vlib/v/checker/for.v Outdated
Comment thread vlib/v/checker/for.v Outdated
Comment thread vlib/v/checker/comptime.v Outdated
@medvednikov

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread vlib/v/checker/comptime.v
Comment on lines +1263 to +1264
left_f := left.f64()
right_f := right.f64()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Round operands before folding f32 arithmetic

When an integer operand is not exactly representable as f32, these lines preserve it as an exact f64 and only round the final result, whereas generated code converts each operand to the promoted f32 type first. For example, for i in int(i32(16777217) - f32(16777215.0)) .. 2 {} executes once at runtime because the left operand rounds to 16777216, but the evaluator computes 2 and rejects the range as empty. The new final-result rounding therefore does not cover operand conversion; convert both operands to f32 before performing an f32 operation.

Useful? React with 👍 / 👎.

Comment thread vlib/v/checker/for.v

// Check for empty range with comptime constant integer bounds
fn (mut c Checker) check_for_empty_range(low ast.Expr, high ast.Expr) {
if low_val := c.eval_comptime_const_expr(low, 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply the promoted type when folding power bounds

The fresh remaining narrow-integer case is **: evaluating a range bound now reaches comptime_power_value, which returns before the generic promoted-type wrapping path. Thus for i in u8(16) ** u8(2) .. 1 {} is rejected using a folded low bound of 256, although codegen casts the power result to u8, producing 0, so the loop executes once. Power folding needs to honor expr.promoted_type just like the other arithmetic operators.

Useful? React with 👍 / 👎.

Comment thread vlib/v/checker/for.v
Comment on lines +102 to +104
range_error := c.errors.map(it.pos.line_nr).any(it == node.pos.line_nr)
if !range_error {
c.check_for_empty_range(node.cond, node.high)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Track range errors independently of source lines

The new guard only finds errors whose position is on the for statement's starting line, so an invalid bound placed on a later line still gets evaluated. For example, a multiline for i in 4.0 .. 2 reports its range-type error at the bound and then adds an incorrect empty-range diagnostic, while a multiline invalid shift also reaches the evaluator. Record the error count before checking the two bounds and skip this check if that count increases instead of correlating errors by line.

AGENTS.md reference: AGENTS.md:L713-L713

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

checker: for-in loop empty ranges (part. 2)

3 participants