Accept prefix-binop expressions as call arguments#159
Merged
Conversation
The most-cited ergonomic friction in the assessment doc was call args bailing too eagerly when a position started with a prefix-binary expression. `slc ls i +i 1` produced an arity error because parse_call_or_atom broke the arg loop on seeing `+` (infix-eligible) without checking that `+` actually looks prefix-binary. Every program ended up with 5-10 intermediate names like `ip=+i 1;slc ls i ip` just to satisfy this. The pre-loop guard at the top of the arg-collection block already used looks_like_prefix_binary to distinguish prefix vs infix when the first token after the function name was an operator. The inner break check inside the loop didn't, so once one arg had been consumed, any following operator broke out unconditionally. Mirror the pre-loop guard inside the break: only break when the operator looks infix (1 atom ahead), not when it looks prefix-binary (2+ atoms ahead). parse_operand already handles a leading prefix-binop correctly via its Plus/Star/etc arm calling parse_prefix_binop, so once the loop doesn't break, the next iteration consumes the prefix-binop expression as one arg. Also extended looks_like_prefix_binary to recognise nested prefix-binops as atoms. Previously it only counted simple terminals, so a shape like `h +a +b c` (two prefix-binop args back-to-back) would still bail at the second operator. Refactored into scan_prefix_binary_end which walks the lookahead recursively; when an operator-headed sub-expression is itself prefix-binary, it counts as one atom and the scan advances past it. Recursion is bounded because each step consumes at least one operator plus one atom. Resolves the doc-cited pattern across `slc xs i +i 1`, `slc xs fi +fi 1`, `*/n 0.25` inside calls, and the broader class of "intermediate-name tax on every program". The separate function-as-arg variant (`prnt str nc` -> `prnt(str(nc))`) needs parser-time arity lookup and is a follow-up.
15 cross-engine regression tests covering the canonical repro
(`slc ls i +i 1`), 3-arg user functions with a prefix-binop in
each position, nested `h +a +b c` shape that exercises the
recursive lookahead in scan_prefix_binary_end, real infix on a
call result still parsing as `(f a) + b`, guard expressions like
`>a 0{a}{- 0 a}` for abs, and a characterisation test pinning the
behaviour of `f +x` (single atom after operator, falls through as
infix).
examples/prefix-arg.ilo demonstrates the now-allowed pattern with
a `window` helper that slices a list using `+lo 2` as the
computed end index. Two run/out cases use distinct `lo` values
(0 and 3) so the prefix arithmetic actually evaluates at both
ends of the list rather than constant-folding.
The infix-on-call test routes around a separate pre-existing
parser quirk where two function declarations on one line can
swallow the second's return type into the first's body, by
writing multi-fn source to a temp .ilo file. Flagged as a
follow-up at the bottom of the test file.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This was referenced May 11, 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.
Summary
Resolves the most-cited ergonomic friction in the assessment doc: call args bailing too eagerly when a position started with a prefix-binary expression. The doc calls this "the single most repetitive ilo friction across both sessions; every program has 10+ intermediate names just for this."
Across 11 rounds of feedback and 4 personas, agents kept hitting:
Every fix produces one or two saved intermediate bindings per call. Across a typical program that compounds into 10+ named temporaries the agent wouldn't otherwise need. Through the manifesto lens this is a recurring per-program token tax — exactly the kind of friction the language exists to remove.
Root cause
parse_call_or_atom's arg-collection loop (src/parser/mod.rs:1571-1585):The break fires whenever the next token is infix-eligible, even when it actually starts a prefix-binary expression. The parser already has
looks_like_prefix_binaryand uses it at the top of the arg-collection block to disambiguate prefix vs infix when the first token after the function name is an operator. The inner break check didn't.The fix
parser: accept prefix-binop expressions as call arguments— mirror the pre-loop guard inside the break. Only break when the operator looks infix (1 atom ahead), not when it looks prefix-binary (2+ atoms ahead). Also extendedlooks_like_prefix_binaryto recognise nested prefix-binops as atoms via a newscan_prefix_binary_endhelper that walks the lookahead recursively — this is needed for shapes likeh +a +b c(two back-to-back prefix-binop args). Recursion is bounded because each step consumes at least one operator + one atom.tests + example: pin prefix-binop call-arg behaviour across engines— 15 cross-engine regression tests covering the canonical repro, 3-arg user functions with prefix-binops in each position, nestedh +a +b c, real infix on a call result still parsing as(f a) + b, guard expressions, and a characterisation test for thef +xsingle-atom-after-operator shape.examples/prefix-arg.ilodemonstrates the now-allowed pattern with two distinctrun:/out:cases that exercise the prefix arithmetic at both ends of a list.Test plan
cargo test --release --features cranelift— full suite green (baseline 3419 + 15 new = 3434)cargo fmt --all -- --checkcleancargo clippy --all-targets --features cranelift -- -D warningscleanf a + b) still parses asBinOp(Add, Call(f, a), b)— no regressionlooks_like_prefix_binaryshallow-counting gap (nested prefix-binops) was caught and fixed before commitFollow-ups
prnt str nc→prnt(str(nc)),flr +*n 0.25etc) — needs the parser to know callee arities at parse time. Different fix shape than this PR. Separate design decision (symbol-table lookup vs required parens).;can swallow the second's return type into the first's body) — surfaced while writing tests; flagged as a// FOLLOW-UP:line intests/regression_prefix_arg_depth.rs.