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:
@@ -4801,3 +4801,64 @@ mod dispatch_3a_tests {
|
||||
assert!(matches!(outcome, WalkOutcome::Match { .. }), "got {outcome:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod order_by_expected_set_tests {
|
||||
//! F5 (handoff 30 §3.3) — when ORDER BY has consumed `order
|
||||
//! by` and is awaiting a sort item, the expected set must not
|
||||
//! be padded with clause keywords belonging to clauses that
|
||||
//! sit *before* ORDER BY (the FROM's JOIN options, WHERE /
|
||||
//! GROUP BY / HAVING, set-ops). Those optionals were skipped
|
||||
//! earlier in the seq; once ORDER BY commits past them they
|
||||
//! are no longer valid continuations at the cursor.
|
||||
use super::*;
|
||||
use crate::dsl::walker::outcome::Expectation;
|
||||
use crate::mode::Mode;
|
||||
|
||||
fn expected_words(source: &str) -> Vec<&'static str> {
|
||||
expected_at_input_in_mode(source, Mode::Advanced)
|
||||
.iter()
|
||||
.filter_map(|e| match e {
|
||||
Expectation::Word(w) => Some(*w),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn order_by_excludes_preceding_clause_keywords() {
|
||||
let words = expected_words("select Name from T order by ");
|
||||
let preceding_clause_kw = [
|
||||
"where", "group", "having", "join", "union", "intersect",
|
||||
"except", "left", "right", "full", "cross", "inner", "as",
|
||||
];
|
||||
let leaked: Vec<&str> = preceding_clause_kw
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|k| words.contains(k))
|
||||
.collect();
|
||||
assert!(
|
||||
leaked.is_empty(),
|
||||
"ORDER BY expected set leaked preceding-clause keywords \
|
||||
{leaked:?}; full word set: {words:?}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn order_by_still_offers_a_sort_item() {
|
||||
// Guard against over-correction: the legitimate sort-item
|
||||
// continuation (a column identifier) must survive the
|
||||
// pending-skipped suppression.
|
||||
let expected = expected_at_input_in_mode(
|
||||
"select Name from T order by ",
|
||||
Mode::Advanced,
|
||||
);
|
||||
assert!(
|
||||
expected.iter().any(|e| matches!(
|
||||
e,
|
||||
Expectation::Ident { .. } | Expectation::NumberLit
|
||||
)),
|
||||
"ORDER BY must still offer a sort item; got {expected:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user