parser: leftover-token hints diagnose the actual mistake (#732) - #801
Merged
Conversation
The four most likely model-generated mistakes — print("hello"),
def f(a):, f is x => x + 1, say x — all landed in p_end_statement,
whose blanket " — one statement per line" named a rule none of them
broke, while docs/llms.txt taught the same misdiagnosis (agreement
reading as confirmation). The hint is now specialized on the leftover
and predecessor tokens:
- leftover '(' after an identifier -> "calls use 'of', not parentheses:
write f of x (or f of [a, b] for several arguments)"
- leftover '=>' -> "lambda parameters need parentheses: (x) => ..."
- leftover identifier after a LONE-identifier statement -> name-check
hint, with 'def' specifically pointed at "define f(...) as:"
- everything else keeps the original text: x is 2 x is 3 still says
"one statement per line" (the lone-ident guard is what keeps the new
arms from misdiagnosing genuine statement splits like y is w z is 3)
Cascade guard from the same pass: a statement that already reported a
parse error resyncs silently instead of stacking a spurious
"unexpected X after statement" on its debris — add2 of (1, 2) now gets
exactly one error, the real "expected ')', got ','".
The recorded --lint --json / LSP message is unchanged ("unexpected X
after statement") — the hint is display-layer, so diagnostic codes and
consumers are stable.
docs/llms.txt validation-ladder entries rewritten to describe the
specialized hints (and that the caret sits one token AFTER the
mistake). Five new examples/errors/ demos pin the hints; the error-
examples harness gains an optional "# expect-error-count: N" header
pinning the single-error cascade (suite [90], 14 -> 19 checks).
Validation: release suite 3355/3355; ASan+UBSan detect_leaks=1 suite
3359/3359, leak tally 0; doc_drift_check clean.
Closes #732
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR improves EigenScript’s parser diagnostics for “leftover-token after statement” errors by tailoring the hint text to the specific leftover token (and its predecessor), addressing the misleading catch-all “one statement per line” guidance described in #732. It also prevents a known cascade where a real parse error would be followed by a second spurious “unexpected … after statement” error, and it updates the documentation and error-example corpus to lock in the new behavior.
Changes:
- Specialize
p_end_statement’s hint based on the leftover token (e.g.,(/=>/ identifier-after-lone-ident statement) and suppress follow-on leftover-token errors when the current statement already reported a parse error. - Extend the error-example test harness to optionally assert the number of “Parse error …” lines (
# expect-error-count:) and add five newexamples/errors/*.eigsdemos. - Update
docs/llms.txt, the test-suite check count, and the changelog to reflect the new diagnostics behavior.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
src/parser.c |
Adds statement-entry snapshots for cascade suppression and uses leftover-token context to generate a more accurate diagnostic hint. |
tests/test_error_examples.sh |
Adds optional # expect-error-count: support to pin error-cascade behavior in examples. |
tests/run_all_tests.sh |
Updates suite [90] label to reflect the increased number of error-example checks. |
examples/errors/unknown_statement_word.eigs |
New pinned demo for the “unknown statement word” specialized hint. |
examples/errors/paren_tuple_call.eigs |
New pinned demo ensuring the add2 of (1, 2) case yields exactly one parse error. |
examples/errors/paren_call.eigs |
New pinned demo for paren-style call hinting (print("hello")). |
examples/errors/def_not_define.eigs |
New pinned demo for def vs define … as: hinting. |
examples/errors/bare_lambda_params.eigs |
New pinned demo for unparenthesized lambda parameter hinting (x => …). |
docs/llms.txt |
Updates the validation ladder to reflect the specialized hints and caret positioning semantics. |
CHANGELOG.md |
Documents the diagnostics fix and the new error-example pinning behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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.
Closes #732.
The fix
p_end_statement's hint is now specialized on the leftover token and its predecessor — exactly the shape the issue prescribed. All five table rows from the issue now read:print("hello")n is len(xs)lendef f(a):f is x => x + 1say xThe specialized identifier arms require the statement to have been a lone bare identifier (
p->pos == stmt_start + 1), so genuine statement splits keep the original diagnosis:x is 2 x is 3andy is w z is 3both still say "one statement per line". That guard is what keeps the new hints from misdiagnosing the case the old hint was actually right about.Note 1 from the issue (the
add2 of (1, 2)cascade) — also fixedp_end_statementnow resyncs silently when the current statement already reported a parse error (error-count snapshot taken at statement entry).add2 of (1, 2)produces exactly one error — the realexpected ')', got ','— instead of that plus a spurious "unexpected number after statement" with the misleading hint.Note 2 (precedence errors printing to stdout before failing) is evaluation-order semantics, not diagnostics — left to its own issue.
Stability
The recorded
--lint --json/ LSP message is unchanged (unexpected X after statement); the hint is display-layer only, so E-codes and downstream consumers see no difference.Docs + tests
docs/llms.txtladder entries (the E002 note and the run-it signature) rewritten to describe the specialized hints instead of asserting the single wrong cause — including that the caret sits one token after the mistake.examples/errors/demos pin each hint via# expect-error:; the harness gains an optional# expect-error-count: Nheader, used to pin the single-error cascade so the suppressed spurious error can't silently return. Suite [90] goes 14 → 19 checks.Validation
detect_leaks=1suite 3359/3359, leak tally 0.tools/doc_drift_check.shclean.two_statements_one_line.eigsexpect-error (original hint text) passes unchanged — the control case is pinned both ways.🤖 Generated with Claude Code