walker: F5 — drop preceding-clause keywords from committed-child Incomplete sets
walk_seq's Incomplete arm unconditionally merged the accumulated skipped-Optional expectations (pending_skipped) into the child's expected set. When a child committed terminals before going Incomplete (e.g. `order by` consumed, now awaiting a sort item), this leaked ~13 clause keywords from clauses positioned *before* the committed child — WHERE/GROUP BY/HAVING, the FROM's JOIN options, set-ops — into the ORDER BY completion list, shoving the actual columns off-screen. Merge pending_skipped only when the Incomplete-producing child consumed nothing (path length unchanged): the cursor still sits at the optional boundary, so those optionals are genuine alternatives. A committed child means the cursor is past them. Tests: walker expected-set guard (+ over-correction guard) and a full-stack completion-layer regression test.
This commit is contained in:
@@ -850,6 +850,7 @@ fn walk_seq(
|
||||
// engine see optional connectives that haven't been typed.
|
||||
let mut pending_skipped: Vec<Expectation> = Vec::new();
|
||||
for child in children {
|
||||
let path_before = path.items.len();
|
||||
match walk_node(source, cur, child, ctx, path, per_byte) {
|
||||
NodeWalkResult::Matched { end, skipped } => {
|
||||
if end == cur {
|
||||
@@ -900,9 +901,23 @@ fn walk_seq(
|
||||
position,
|
||||
mut expected,
|
||||
} => {
|
||||
for e in std::mem::take(&mut pending_skipped) {
|
||||
if !expected.contains(&e) {
|
||||
expected.push(e);
|
||||
// Only merge the skipped-Optional expectations when
|
||||
// the Incomplete-producing child consumed nothing
|
||||
// (path didn't grow): the cursor still sits at the
|
||||
// optional boundary, so those optionals are genuine
|
||||
// alternatives. If the child committed terminals
|
||||
// (e.g. `order by` consumed, now awaiting a sort
|
||||
// item) the cursor has moved *past* the skipped
|
||||
// optionals — clauses positioned before this child
|
||||
// are no longer valid continuations, so dropping
|
||||
// `pending_skipped` keeps them out of the expected
|
||||
// set (handoff 30 §3.3, F5).
|
||||
let child_consumed = path.items.len() > path_before;
|
||||
if !child_consumed {
|
||||
for e in std::mem::take(&mut pending_skipped) {
|
||||
if !expected.contains(&e) {
|
||||
expected.push(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return NodeWalkResult::Incomplete { position, expected };
|
||||
|
||||
Reference in New Issue
Block a user