Skip to content

children() accepts a vector or range; warn on backwards ranges - #107

Merged
revarbat merged 1 commit into
mainfrom
children-index-list
Aug 22, 2026
Merged

children() accepts a vector or range; warn on backwards ranges#107
revarbat merged 1 commit into
mainfrom
children-index-list

Conversation

@revarbat

Copy link
Copy Markdown
Member

Two fixes, both found from the question "can children() take a range?"

children([3:1:5]) is children(3); children(4); children(5)

A vector or range was accepted syntactically and then silently ignored: toDoubleLenient collapses both to 0, so every vector/range form rendered child 0. Wrong geometry, no warning — and the out-of-range path returned std::nullopt without saying anything either.

Ranges now go through expandIterable, the same path a for-loop uses, so step direction, fractional steps and naturally-empty ranges behave identically in both places rather than growing a second interpretation here.

form result
children([3:1:5]) children 3, 4, 5
children([3:-1:1]) children 3, 2, 1 — in that order
children([5:-2:0]) 5, 3, 1
children([2,2,2]) child 2 three times
children([1.7]) child 1 — truncates
children([0,99]) child 0, plus an out-of-bounds warning
children([]), children([1:0]) nothing

Out-of-range and bad-type now warn, quoting the reference's wording verbatim.

A range whose step points away from its end now warns

[1:0] is almost always a typo for [1:-1:0], and the reference says so rather than iterating zero times in silence. We said nothing, anywhere:

WARNING: begin is greater than the end, but step is positive
WARNING: begin is smaller than the end, but step is negative

Wired into expandIterable behind a RangeDirectionFn callback, mirroring the existing RangeTooManyFn so the function stays free of Evaluator coupling.

Why doing it there rather than at one caller mattered

There are seven call sites, and my first pass found five. The test suite then failed on the interpreter alone, at intersection_for — a sixth site an earlier grep | head had truncated away. Searching properly turned up a seventh, chr(), where the reference warns too (chr([70:1:65]) — checked, not assumed).

Two of seven paths would have stayed silently wrong.

A zero step is deliberately not reported as a direction problem: the reference calls that "too many elements", and it shares rangeElementCount's nullopt with this case only by coincidence.

Testing

988 pass, 21 new, under both OSCAD_BYTECODE_VM=0 and =1. Every case diffed against OpenSCAD 2026.02.01 — values, order, and warning text — across for-loops, list comprehensions, intersection_for, chr() and children().

🤖 Generated with Claude Code

Two fixes, found from the question "can children() take a range?"

**children([3:1:5]) is children(3); children(4); children(5).**

A vector or a range was accepted syntactically and then silently ignored:
toDoubleLenient collapses both to 0, so EVERY vector/range form rendered
child 0. Wrong geometry, no warning, since the out-of-range path returned
std::nullopt without saying anything either.

Ranges now go through expandIterable, the same path a for-loop uses, so
step direction, fractional steps and naturally-empty ranges behave
identically in both places rather than growing a second interpretation.
children([3:-1:1]) is 3, 2, 1 in that order; children([2,2,2]) really does
evaluate child 2 three times; children([1.7]) truncates to child 1; an
out-of-range index is skipped rather than fatal, so children([0,99]) still
draws child 0. Out-of-range and bad-type now warn, quoting the reference's
wording verbatim.

**A range whose step points away from its end now warns.**

[1:0] is almost always a typo for [1:-1:0], and the reference says so
rather than iterating zero times in silence. We said nothing, anywhere.

Wired into expandIterable behind a RangeDirectionFn callback, mirroring the
existing RangeTooManyFn so the function stays free of Evaluator coupling.

That last part is why it is worth doing there rather than at one caller:
there are SEVEN call sites, and an initial pass found only five. The test
suite then failed on the interpreter alone, at intersection_for -- a sixth
site an earlier `grep | head` had truncated away. Searching properly turned
up a seventh, chr(), where the reference warns too (checked, not assumed:
chr([70:1:65]) warns). Two of seven paths would have stayed silently wrong.

A zero step is deliberately NOT reported as a direction problem: the
reference calls that "too many elements", and it shares rangeElementCount's
nullopt with this case only by coincidence.

Every case diffed against OpenSCAD 2026.02.01 under both engines. 988 tests
pass, 21 new.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@revarbat
revarbat merged commit c231f61 into main Aug 22, 2026
3 checks passed
@revarbat
revarbat deleted the children-index-list branch August 22, 2026 19:48
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