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
+59
View File
@@ -498,3 +498,62 @@ pub fn command_for_entry_word(word: &str) -> Option<(usize, &'static CommandNode
.find(|(_, c)| c.entry.matches(word))
.map(|(i, c)| (i, *c))
}
#[cfg(test)]
mod usage_key_tests {
use super::usage_key_for_input;
/// Every multi-form command resolves a typed form to its
/// own usage key — a parse error in one form must never
/// show another form's usage (the handoff-18 `151ed08` fix;
/// regression-locked here, including the `add 1:n
/// relationship` digit-led form).
#[test]
fn multi_form_commands_resolve_to_the_typed_form() {
let cases = [
("add column to T: c (int)", "parse.usage.add_column"),
("add index on T (c)", "parse.usage.add_index"),
(
"add 1:n relationship from A.x to B.y",
"parse.usage.add_relationship",
),
// Trailing junk must not change the resolved form.
(
"add 1:n relationship from A.x to B.y --",
"parse.usage.add_relationship",
),
("drop table T", "parse.usage.drop_table"),
("drop column from table T: c", "parse.usage.drop_column"),
("drop index i", "parse.usage.drop_index"),
(
"drop relationship r",
"parse.usage.drop_relationship",
),
("show data T", "parse.usage.show_data"),
("show table T", "parse.usage.show_table"),
];
for (input, expected) in cases {
assert_eq!(
usage_key_for_input(input),
Some(expected),
"usage key for {input:?}",
);
}
}
#[test]
fn a_bare_multi_form_entry_word_resolves_to_no_single_form() {
// `add` / `drop` alone — no form chosen; the caller
// shows the whole family rather than guessing.
assert_eq!(usage_key_for_input("add "), None);
assert_eq!(usage_key_for_input("drop "), None);
}
#[test]
fn a_single_form_command_resolves_to_its_one_key() {
assert_eq!(
usage_key_for_input("create table T with pk"),
Some("parse.usage.create_table"),
);
}
}