Skip to content

Warn about backwards ranges only when the step was not written - #108

Merged
revarbat merged 1 commit into
mainfrom
implicit-step-warning
Aug 22, 2026
Merged

Warn about backwards ranges only when the step was not written#108
revarbat merged 1 commit into
mainfrom
implicit-step-warning

Conversation

@revarbat

Copy link
Copy Markdown
Member

Follow-up to #107, which added the backwards-range warning in 0.40.0 and got two things wrong.

Wrong place

The reference reports this against the range literal:

r = [5:0];      // real OpenSCAD warns here -- nothing ever iterates r

We warned at iteration, so we missed that case entirely.

Wrong scope

[5:1:0] and [0:-1:5] warned too. Writing the step out is a statement of intent — the warning is for the author who wrote [5:0] meaning [5:-1:0] and got a silent empty loop, not for someone who spelled out a step and meant it.

for (x=[3:1])     // WARNING
for (x=[3:-1:1])  // silent
for (x=[1:3])     // silent
for (x=[1:1:3])   // silent

This is a deliberate divergence from the reference, which warns for the explicit forms as well.

One move fixes both

The check moves to applyRange — the single point where a range literal becomes a value in either engine — gated on the parser's new RangeLiteral::implicitStep (BelfrySCAD/openscad_cpp_parser#6). Construction-time is where the reference has it, and it is the only place the flag exists.

It deletes more than it adds: RangeDirectionFn, rangeDirectionWarning() and seven per-call-site lambdas all go, because one construction site covers for, list comprehensions, intersection_for, chr() and children() at once.

An implicit step is always exactly 1, so the reference's second wording ("begin is smaller than the end, but step is negative") can no longer arise. Removed, with a test pinning that it stays unreachable — if it ever fires again, the gate has been widened back to explicit steps.

Verification

  • 989 tests pass under both engines (OSCAD_BYTECODE_VM=0 and =1)
  • Placement checked against OpenSCAD-dev.app — that is how the never-iterated case surfaced
  • End-to-end through the CLI, byte-identical output from both engines

🤖 Generated with Claude Code

0.40.0 added the reference's backwards-range warning, and put it in
expandIterable so every iterating construct got it from one place. Both
halves of that turn out to be wrong.

Wrong place: the reference reports this against the range LITERAL, so
`r = [5:0];` warns even though nothing ever iterates r. We reported it at
iteration and missed that case entirely.

Wrong scope: `[5:1:0]` and `[0:-1:5]` warned too. Writing the step out is a
statement of intent -- the warning exists for the author who wrote `[5:0]`
meaning `[5:-1:0]` and got a silent empty loop, not for someone who spelled
out a step and meant it.

Both are fixed by the same move. The check now lives in applyRange, the
single point where a range literal becomes a value in either engine, gated
on the parser's new RangeLiteral::implicitStep. Construction-time is where
the reference has it, and it is the only place the flag exists.

That deletes more than it adds: RangeDirectionFn, rangeDirectionWarning()
and seven per-call-site lambdas all go, since one construction site covers
for, list comprehensions, intersection_for, chr() and children() at once.

An implicit step is always exactly 1, so the reference's second wording
("begin is smaller than the end, but step is negative") can no longer arise
and is gone. A test pins that it stays unreachable.

Verified against the real binary for placement, and end-to-end through the
CLI under both engines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@revarbat
revarbat merged commit eeb113f into main Aug 22, 2026
3 checks passed
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.

1 participant