children() accepts a vector or range; warn on backwards ranges - #107
Merged
Conversation
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>
This was referenced Aug 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two fixes, both found from the question "can
children()take a range?"children([3:1:5])ischildren(3); children(4); children(5)A vector or range was accepted syntactically and then silently ignored:
toDoubleLenientcollapses both to0, so every vector/range form rendered child 0. Wrong geometry, no warning — and the out-of-range path returnedstd::nulloptwithout 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.children([3:1:5])children([3:-1:1])children([5:-2:0])children([2,2,2])children([1.7])children([0,99])children([]),children([1: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
expandIterablebehind aRangeDirectionFncallback, mirroring the existingRangeTooManyFnso the function stays free ofEvaluatorcoupling.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 earliergrep | headhad 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'snulloptwith this case only by coincidence.Testing
988 pass, 21 new, under both
OSCAD_BYTECODE_VM=0and=1. Every case diffed against OpenSCAD 2026.02.01 — values, order, and warning text — across for-loops, list comprehensions,intersection_for,chr()andchildren().🤖 Generated with Claude Code