Skip to content

fix: #3621 give range lower precedence than conditional expressions (breaking)#3680

Open
mvanhorn wants to merge 1 commit into
josdejong:v16from
mvanhorn:fix/3621-range-precedence
Open

fix: #3621 give range lower precedence than conditional expressions (breaking)#3680
mvanhorn wants to merge 1 commit into
josdejong:v16from
mvanhorn:fix/3621-range-precedence

Conversation

@mvanhorn

@mvanhorn mvanhorn commented Jul 8, 2026

Copy link
Copy Markdown

Summary

Range : now has a precedence between assignment and conditional, so the parser matches a strict precedence hierarchy. This targets v16 because it changes how a few expressions parse.

Why this matters

The documented precedence table lists conditional ?: < or < range ::, but the parser didn't follow it: true ? 3 : -1 : 2 : 5 bound the conditional tighter than range, while false or 1:3 bound range tighter than or. That relation is non-transitive, so the parser wasn't strictly precedence-based, and the docs never said so (#3621).

In the issue thread you worked through the options and settled the direction: range can't be higher than conditional (that breaks a ? b : c, since b : c would parse as a range), so range moves below conditional and above assignment. This PR implements that, with the consequences you called out.

Changes

  • src/expression/parse.js: reordered the recursive-descent chain to parseAssignment -> parseRange -> parseConditional -> parseLogicalOr -> .... parseConversion now calls parseAddSubtract (range left that slot). Removed the conditionalLevel bookkeeping entirely (all 8 uses) — a : at range level is now unambiguously a range separator because conditional branches parse at a higher level that never consumes :. Net −27 lines in the parser.
  • src/expression/operators.js: moved the range band directly above conditional (and below assignment) so toString() / toTex() parenthesization stays consistent with parse precedence and round-tripping remains lossless.
  • docs/expressions/syntax.md and HISTORY.md: updated the precedence table and documented the breaking behavior changes.

Accepted behavior changes:

  • true ? 3 : -1 : 2 : 5 -> (true ? 3 : -1) : 2 : 5
  • a:b==c:d -> a:(b==c):d
  • true ? a = 1 : 7 is now a syntax error; use true ? (a=1) : 7
  • a = 1:5 still works (range stays above assignment)

Testing

Added #3621 regression tests and updated the tests that encoded the old ordering:

  • true ? 3 : -1 : 2 : 5 parses as a RangeNode whose start is true ? 3 : (-1), evaluating to [3, 5]
  • false or true ? 1 : 2 still returns 1 (conditional below or)
  • false or 1:3 parses as a range with start false or 1 -> [1, 2, 3]
  • a:b==c:d -> a:(b == c):d; true ? a = 1 : 7 throws; true ? (a=1) : 7 works; a = 1:5, A[1:3], f(1:3), 1:2:10 unchanged
  • toString() round-trip and toTex() parenthesization for the reordered operators

Full expression suite (npx mocha test/unit-tests/expression/**/*.test.js): 2213 passing, no regressions.

Closes #3621

…essions (breaking)

The documented precedence table lists conditional < or < range, but the parser
did not follow it: 'true ? 3 : -1 : 2 : 5' bound the conditional tighter than
range while 'false or 1:3' bound range tighter than or, a non-transitive
relation the docs never described.

Per the owner's analysis in josdejong#3621, range cannot sit above conditional (that
breaks 'a ? b : c', since 'b : c' would parse as a range), so range moves to a
precedence between assignment and conditional. Reorder the recursive-descent
chain to parseAssignment -> parseRange -> parseConditional -> ..., remove the
now-redundant conditionalLevel bookkeeping (all 8 uses), and move the range
band in operators.js so toString()/toTex() parenthesization matches.

Accepted behavior changes (documented in HISTORY and syntax.md):
- 'true ? 3 : -1 : 2 : 5' parses as '(true ? 3 : -1) : 2 : 5'
- 'a:b==c:d' parses as 'a:(b==c):d'
- 'true ? a = 1 : 7' now requires parentheses: 'true ? (a=1) : 7'
- 'a = 1:5' still works (range stays above assignment)

Targets v16 (breaking).

Closes josdejong#3621
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