feat: bring simple-mode insert arity diagnostics to parity with advanced
A wrong-count simple-mode insert now shows the friendly per-column arity message at typing time (instead of a bare "expected `,`/`)`") and is blocked from dispatch at submit — unifying simple and advanced mode onto the one ADR-0027 model (structural parse + ERROR diagnostic), where they had diverged. Grammar: a simple-mode-only arity gate (dsl_insert_value_list) routes a wrong-count DSL insert tuple to the type-blind fallback so it matches structurally and the per-tuple arity diagnostic fires. The gate is gated to simple mode, so advanced behaviour is unchanged. count_tuple_values and the target-column selection (insert_target_columns) are now shared by both grammars. Diagnostic: dml_insert_arity_diagnostics is mode-aware — advanced Form B expects all columns; simple Form B/C expects the user-fillable columns (serial/shortid auto-fill). It counts the DSL Form A role and scans the keyword-less Form C tuple. New catalog keys name the fillable/auto split and the all-auto-table case. Submit: a wrong-count DSL insert now parses Ok + carries the ERROR diagnostic, so a unified Ok-arm pre-flight (dsl_insert_count_mismatch_notes) blocks dispatch and teaches; the previous Err-arm note retires. advanced_alternative_note's gate now reads the validity verdict so it still fires for the parse-Ok-with-error shape. Docs: ADR-0036 Amendment 2 (+ README index) and requirements.md H1a.
This commit is contained in:
+90
-4
@@ -323,10 +323,20 @@ pub fn advanced_alternative_note(
|
||||
input: &str,
|
||||
cache: &crate::completion::SchemaCache,
|
||||
) -> Option<String> {
|
||||
let definite_dsl_error = matches!(
|
||||
classify_input_with_schema_in_mode(input, cache, Mode::Simple),
|
||||
InputState::DefiniteErrorAt(_)
|
||||
);
|
||||
// The line must be *definitely* invalid in simple mode — a definite
|
||||
// parse error, or (issue #17) a parse that succeeds structurally but
|
||||
// carries a blocking ERROR diagnostic such as a value-count
|
||||
// mismatch. Incomplete input (still being typed) and empty input are
|
||||
// excluded so the pointer doesn't flicker mid-keystroke.
|
||||
let definite_dsl_error = match classify_input_with_schema_in_mode(input, cache, Mode::Simple)
|
||||
{
|
||||
InputState::DefiniteErrorAt(_) => true,
|
||||
InputState::Valid => {
|
||||
crate::dsl::walker::input_verdict_in_mode(input, Some(cache), Mode::Simple)
|
||||
== Some(crate::dsl::walker::outcome::Severity::Error)
|
||||
}
|
||||
InputState::Empty | InputState::IncompleteAtEof => false,
|
||||
};
|
||||
if !definite_dsl_error {
|
||||
return None;
|
||||
}
|
||||
@@ -363,6 +373,82 @@ pub fn advanced_alternative_note(
|
||||
/// without adding pedagogy. The cross-mode pointer wins because it
|
||||
/// only fires when switching modes actually works (issue #1 sub-task
|
||||
/// 1's gate); when it doesn't fire, this note steps in.
|
||||
/// Submit-time pre-flight for a simple-mode (DSL) `Command::Insert`
|
||||
/// whose positional value count doesn't match the expected count
|
||||
/// (issue #17). Returns the advice line(s) to display when there is a
|
||||
/// mismatch — the caller (`dispatch_dsl`) blocks dispatch whenever this
|
||||
/// is `Some`, so a wrong-count insert never reaches the worker. `None`
|
||||
/// when the command isn't an insert, the table is unknown, or the count
|
||||
/// already matches.
|
||||
///
|
||||
/// This is the simple-mode counterpart of the advanced Ok-arm pre-flight
|
||||
/// (`form_b_positional_count_mismatch_note`). Both modes now parse a
|
||||
/// wrong-count insert as `Ok` (so the typing-time arity diagnostic can
|
||||
/// fire — issue #17), so dispatch is gated here, uniformly, rather than
|
||||
/// by a parse error.
|
||||
///
|
||||
/// Expected count: Form A (explicit `(col, …)`) → the listed count;
|
||||
/// Form B/C (no list) → the user-fillable (non-auto-generated) count,
|
||||
/// since the dispatch auto-fills serial/shortid (ADR-0018 §3).
|
||||
///
|
||||
/// Advice selection mirrors the previous Err-arm logic: the cross-mode
|
||||
/// pointer wins when the same text is valid in advanced mode; otherwise
|
||||
/// Form B/C shows the rich teaching note (names the fillable + auto
|
||||
/// columns and the Form-A override) and Form A shows the column-list
|
||||
/// arity message.
|
||||
#[must_use]
|
||||
pub fn dsl_insert_count_mismatch_notes(
|
||||
input: &str,
|
||||
cmd: &crate::dsl::command::Command,
|
||||
cache: &crate::completion::SchemaCache,
|
||||
) -> Option<Vec<String>> {
|
||||
use crate::dsl::command::Command;
|
||||
use crate::dsl::types::Type;
|
||||
let Command::Insert {
|
||||
table,
|
||||
columns,
|
||||
values,
|
||||
} = cmd
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
let table_cols = cache.table_columns.get(table)?;
|
||||
let is_auto = |t: Type| matches!(t, Type::Serial | Type::ShortId);
|
||||
let expected = columns.as_ref().map_or_else(
|
||||
|| table_cols.iter().filter(|c| !is_auto(c.user_type)).count(),
|
||||
Vec::len,
|
||||
);
|
||||
if values.len() == expected {
|
||||
return None; // counts match — nothing to flag, dispatch proceeds
|
||||
}
|
||||
// Count mismatch → the caller blocks dispatch. Build the advice.
|
||||
// The cross-mode pointer is the single most useful line when the
|
||||
// same text is valid in advanced mode, so it suppresses the rest.
|
||||
if let Some(pointer) = advanced_alternative_note(input, cache) {
|
||||
return Some(vec![pointer]);
|
||||
}
|
||||
let note = if columns.is_some() {
|
||||
// Form A: the column-list arity message.
|
||||
crate::t!(
|
||||
"diagnostic.insert_arity_mismatch",
|
||||
expected = expected,
|
||||
actual = values.len()
|
||||
)
|
||||
} else {
|
||||
// Form B/C: the rich teaching note. Falls back to the all-auto
|
||||
// explanation for a table whose columns are all auto-generated
|
||||
// (the override note doesn't apply there).
|
||||
form_b_extra_values_note(input, cache).unwrap_or_else(|| {
|
||||
crate::t!(
|
||||
"diagnostic.insert_arity_mismatch_all_auto",
|
||||
table = table,
|
||||
actual = values.len()
|
||||
)
|
||||
})
|
||||
};
|
||||
Some(vec![note])
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn form_b_extra_values_note(
|
||||
input: &str,
|
||||
|
||||
Reference in New Issue
Block a user