Skip to content

refactor!: give expression parse failures a typed cause - #178

Merged
lomigmegard merged 1 commit into
mainfrom
feat/typed-expression-errors
Aug 26, 2026
Merged

refactor!: give expression parse failures a typed cause#178
lomigmegard merged 1 commit into
mainfrom
feat/typed-expression-errors

Conversation

@lomigmegard

Copy link
Copy Markdown
Contributor

parse_expression returned Option, so explain_expression worked the reason out afterwards by re-dispatching through the parser's own predicates. The two could drift, and had.

parse_expression_detailed returns Result<Expression, ExprError>; parse_expression is its .ok(), so callers are untouched. The explain_* family is deleted. A source is dispatched on its shape before anything is parsed, so a failure names what the author reached for.

Fixes a panic on main: a lone quote satisfied both ends of the old literal check and sliced &s[1..0]. ${concat(",", param.a)} is enough to hit it, since the comma inside the string splits the argument list, and it takes rite-ls down on a keystroke.

Diagnostics gained near-miss namespaces (${paramm.x} suggests param), argument positions, and separate causes for unclosed text and a missing pipeline source.

Breaking: ResolveError gains three variants, InvalidReferenceSyntax.reason is now ExprError, and explain_expression is removed.

Closes #173

parse_expression returned Option, so the reason a `${...}` did not parse
was worked out afterwards by explain_expression, which re-dispatched
through the parser's own predicates. The two could disagree, and a
consumer could only tell an unknown namespace from a malformed name by
matching on message text.

parse_expression_detailed returns Result<Expression, ExprError> and
parse_expression is its .ok(). ResolveError::InvalidReferenceSyntax
carries the ExprError. The explain_* family is deleted.

A source is dispatched on its shape before anything is parsed, so a
failure reports what the author reached for rather than the last of three
alternatives to fail. This also fixes a panic: a lone quote satisfied
both ends of the old literal check and sliced `&s[1..0]`, crashing `rite
check` and the language server. It was reachable from an ordinary value,
since a comma inside a string splits the argument list: `${concat(",",
param.a)}` was enough.

Diagnostics gained what the typed cause makes cheap. An unknown namespace
within two edits of a real one is named, so `${paramm.x}` suggests
`param`. A failing function argument reports its position and its own
cause. Text that opens with `${` and never closes reports Unclosed, where
it used to read as advice for text that never opened one. A pipeline with
nothing before its first `|` reports MissingSource rather than claiming
the expression is empty. The material guidance matches within one edit,
so the plural that names the declaring key lands on it too, and
suggestion() offers the artifact namespace as the fix.

One splitter serves both delimiters the grammar has, and one call parser
serves both positions a call appears in. parse_ref takes the text already
split at its first dot, so the unreachable missing-namespace arm and the
second scan for that dot both go away, along with a Vec per reference on
the parse path. Empty has one owner instead of three. RefType::from_str
reads the namespace spellings off ALL rather than repeating them.

Three failures that were riding InvalidReferenceSyntax as hand-written
strings get their own variants: ExpectedReference for a pipeline where
one reference belongs, and ReadsInputNotAString and
ReadsNotAReferenceOrMap for a `reads:` value of the wrong YAML type.
@lomigmegard
lomigmegard merged commit bd21e2b into main Aug 26, 2026
9 checks passed
@lomigmegard
lomigmegard deleted the feat/typed-expression-errors branch August 26, 2026 18:39
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.

Expression parse failures carry a sentence, not a cause

1 participant