walker: keep optional trailing flags completable after --

Typing `--` to start an optional trailing flag (`--create-fk`
on `add 1:n relationship`, `--cascade` on `drop column`,
`--force-conversion` / `--dont-convert` on `change column`)
made completion go empty: the trailing `--` turns the parse
into a trailing-junk Mismatch, and the Mismatch arm of the
completion expected-set resolution returned only `[EndOfInput]`
— the skipped optional-flag expectations, carried in
`tail_expected`, were dropped.

completion_probe and expected_at_input now merge `tail_expected`
into a Mismatch's expected set. `tail_expected` is empty for a
genuine mid-command mismatch, so this only adds the outer
shape's skipped trailing optionals — exactly the continuations
the trailing `--` is starting to type. This also resolves the
"wrong usage hint" symptom: with `--create-fk` offered as a
candidate, the hint panel shows candidates instead of falling
through to the parse-error usage block.

Audit outcome (the requested scan): usage_key_for_input was
verified correct for every multi-form command — add / drop /
show, including the digit-led `add 1:n relationship` form —
and is now regression-locked. The flag-completion fix covers
the whole optional-trailing-flag class.

6 tests (3 flag-completion, 3 usage-key). 1131 passing.
This commit is contained in:
claude@clouddev1
2026-05-19 10:19:00 +00:00
parent 0e5f226e6b
commit f239ca5ff4
3 changed files with 128 additions and 4 deletions
+27 -4
View File
@@ -276,8 +276,19 @@ pub fn completion_probe(
};
let expected = match result.outcome {
outcome::WalkOutcome::Match { .. } => result.tail_expected,
outcome::WalkOutcome::Incomplete { expected, .. }
| outcome::WalkOutcome::Mismatch { expected, .. } => expected,
// A trailing-junk Mismatch (the shape matched, then the
// user kept typing) still carries the outer shape's
// skipped trailing optionals in `tail_expected` — e.g.
// an optional `--create-fk` flag the trailing `--` is
// starting to type. Merge them so completion still
// offers the optional continuation. A genuine
// mid-command mismatch has an empty `tail_expected`.
outcome::WalkOutcome::Mismatch { expected, .. } => {
let mut merged = expected;
merged.extend(result.tail_expected);
merged
}
outcome::WalkOutcome::Incomplete { expected, .. } => expected,
// Validation failure path: the walker matched the
// structural shape but the AST builder rejected (e.g.
// Form C with column-shaped items). The walker still
@@ -699,8 +710,20 @@ pub fn expected_at_input(source: &str) -> Vec<outcome::Expectation> {
// optional-suffix candidates at the end of a valid
// command (`save` → `as`, etc.).
outcome::WalkOutcome::Match { .. } => result.tail_expected,
outcome::WalkOutcome::Incomplete { expected, .. }
| outcome::WalkOutcome::Mismatch { expected, .. } => expected,
// A trailing-junk Mismatch (the shape matched, then the
// user kept typing) still carries the outer shape's
// skipped trailing optionals in `tail_expected` — e.g.
// an optional `--create-fk` flag the trailing `--` is
// starting to type. Surface those alongside the
// mismatch's own expected set so completion still offers
// them. A genuine mid-command mismatch has an empty
// `tail_expected`, so this is a no-op there.
outcome::WalkOutcome::Mismatch { expected, .. } => {
let mut merged = expected;
merged.extend(result.tail_expected);
merged
}
outcome::WalkOutcome::Incomplete { expected, .. } => expected,
// Validation failure path: the walker matched the
// structural shape but the AST builder rejected (e.g.
// Form C with column-shaped items). The walker still