feat: H1a CROSS JOIN ON teaching message; advanced-SQL gaps re-verified (ADR-0042)

Empirically re-checking ADR §3's advanced-SQL "gaps" reversed two of
three — the code survey that produced the list was wrong:
- INSERT…SELECT column-count: already handled (verdict=Error, "the
  column list names N column(s) but M value(s) are given";
  insert_select_arity_mismatch_fires).
- RETURNING scope: already handled (completion offers the table's
  columns; `returning <unknown>` → unknown_column diagnostic).

The one genuine residual is fixed: `select … cross join b on …`
rejected the ON with a bare "expected end of input". Add
parse.cross_join_no_on — "a CROSS JOIN has no ON clause — it pairs
every row; for a join condition use `JOIN … ON`, or filter with
`WHERE`" — rendered when the failing token is `on` and the most
recent consumed join is a CROSS join (a precise signature: every
other join requires `on`, so `on` is expected there, not a failure).
Render-only in format_walker_error; two misfire guards locked (plain
join still asks for ON; a stray `on` with no join does not fire).

ADR-0042 §3 corrected + Implementation-outcome records the advanced-SQL
re-check and the user-confirmed low-priority residual (submit-time
expression first-set at non-projection positions, where typing-time
completion already offers the right candidates).

Full suite green (lib 1578 / it 388 / typing_surface_matrix 192); clippy clean.
This commit is contained in:
claude@clouddev1
2026-06-05 19:02:11 +00:00
parent 1d4923b15b
commit d6e229f0f5
5 changed files with 120 additions and 8 deletions
+31
View File
@@ -255,6 +255,37 @@ fn advanced_mode_usage_block_shows_sql_and_dsl_forms() {
assert!(sql_at < dsl_at, "SQL form should precede the DSL form\n{dump_msg}");
}
#[test]
fn advanced_cross_join_with_on_teaches_no_on_clause() {
// ADR-0042 §3: a CROSS JOIN has no ON clause. The grammar
// rejects a following `on`, but the bare structural error
// ("expected end of input") does not teach why. `on` is
// unexpected here only because the most recent join is a CROSS
// join — every other join flavour *requires* `on` — so the case
// is precisely detectable and gets a teaching message.
let input = "select * from a cross join b on x = y";
let lines = advanced_error_lines_for(input);
let joined = lines.join("\n");
let dump_msg = dump(input, &lines);
assert!(
joined.contains("a CROSS JOIN has no ON clause"),
"cross join + on should teach that CROSS JOIN takes no ON\n{dump_msg}",
);
// Misfire guard 1: a plain JOIN missing its ON still asks for `on`.
let plain = advanced_error_lines_for("select * from a join b").join("\n");
assert!(
plain.contains("expected `on`") && !plain.contains("CROSS JOIN"),
"a plain join must still ask for ON, not the cross-join message: {plain}",
);
// Misfire guard 2: a stray `on` with no join present must NOT
// claim a CROSS JOIN.
let stray = advanced_error_lines_for("select * from a on x = y").join("\n");
assert!(
!stray.contains("CROSS JOIN has no ON"),
"no cross join present — must not fire: {stray}",
);
}
/// The advanced-mode near-miss matrix (ADR-0042 §1/§3). Mirrors
/// the simple-mode matrix for the SQL surface. Every row must show
/// a per-command `usage:` block (never the available-commands