docs: ADR-0032 Amendment 2 + §10.6 regression tests

Amendment 2 records the §10.6 fixup-pass mechanism choice. §10.6
prescribes "rewriting the highlight class" on projection-list
idents at end-of-walk; the actual implementation uses a different
mechanism that achieves the identical user-visible behavior:

1. 2d's two-pass schema-existence diagnostic collects every FROM
   binding from the matched path first, then resolves projection
   idents against the complete scope. The post-walk re-resolve
   §10.6 calls for, just embedded in the diagnostic emitter.

2. input_render.rs's diagnostic-overlay path colors each
   diagnostic span Error/Warning, achieving the visual change
   §10.6 describes without needing a new HighlightClass variant.

The completion-mid-typing piece is improved by the §10.5
look-ahead probe (sub-phase 2e earlier).

Four new regression tests in `projection_before_from_tests` pin
the behavior so a future refactor can't silently regress it:
correct ident resolves silently, unknown ident flags via
diagnostic on its span, multi-projection only flags unknowns,
projection-without-FROM is silent.

ADR index entry updated to reference Amendment 2.

Test totals: 1424 → 1428 passing (+4). Clippy clean.
This commit is contained in:
claude@clouddev1
2026-05-20 21:19:57 +00:00
parent 0fc7b082b2
commit ee0dafd86b
3 changed files with 216 additions and 1 deletions
+90
View File
@@ -1390,6 +1390,96 @@ This amendment narrows the honest limitation in §12 from
and recursive CTE result columns" — a tighter, factually
verified carve-out.
## Amendment 2 — §10.6 fixup-pass mechanism (2026-05-20)
§10.6's prescription for the post-walk fixup is written in
terms of "rewriting the highlight class" on projection-list
`Ident` terminals — downgrading "column" → "unknown identifier"
when an ident doesn't belong to the eventual `from_scope`, or
upgrading the reverse direction once a `FROM` is typed. The
implementation chose a different mechanism that achieves the
identical user-visible effect; this Amendment records the
choice so a reader of §10.6 doesn't go looking for a literal
`per_byte_class` rewrite step that does not exist.
### Mechanism actually used
Two pieces, both already in the codebase by the end of
sub-phase 2d:
1. **Two-pass schema-existence diagnostic.** The 2d rewrite of
`schema_existence_diagnostics` (`src/dsl/walker/mod.rs`)
runs a pre-pass over the matched path that collects every
`IdentSource::Tables` / `cte_name` / `table_alias` ident
into a single binding vec, regardless of where in the path
it sits. The main pass then resolves each `sql_expr_ident`
against the **complete** binding set. A projection ident
that resolves under the eventual FROM scope produces no
diagnostic; one that doesn't produces an
`unknown_column` diagnostic on its own span.
2. **Diagnostic-overlay renderer.** `src/input_render.rs`
reads the walker's diagnostic list at every keystroke and
overlays each diagnostic's span with the appropriate
colour (Error red for unknown-column, Warning for
type-mismatch / `LIKE`-on-numeric / etc.). The overlay
sits on top of the walker's `per_byte_class` (which keeps
all idents at `HighlightClass::Identifier`).
Combined, the two yield the §10.6 user-visible behaviour:
typing `select bogus_col`, the diagnostic emits and the
overlay paints the ident red as soon as a FROM appears that
shows the column doesn't exist; typing `select real_col`, no
diagnostic emits and the ident stays Identifier-coloured.
Within one debounce cycle.
### Why this is equivalent
§10.6's stated goal is correctness of the end-of-walk
classification — "rewriting the highlight class" is one
implementation strategy for that goal. The HighlightClass
enum in the codebase has only one identifier slot
(`Identifier`); the Error tint comes from diagnostic overlay,
not from a separate `Column` vs `UnknownIdentifier` class.
The two-pass diagnostic pass is the "post-walk fixup" that
§10.6 calls for — it just runs inside the diagnostic emitter
rather than as a separate rewrite step. The integration
point (§10.6's "final stage of the walk itself") still
holds: `schema_existence_diagnostics` runs after the walk's
main work, mutating the walker's accumulated diagnostic
vector in place. Consumers see a single coherent snapshot.
### Completion mid-typing
§10.6's second user-visible promise — "during-typing
completion of projection-list column names uses the global
fallback" — is preserved as a posture, but improved at the
edges in sub-phase 2e by a look-ahead probe in
`src/completion.rs`. When the leading walk produces no
`from_scope` (the projection-before-FROM state) **and** the
full input does have a FROM after the cursor, a second walk
on the full input populates the binding set, and column
candidates narrow to that scope. The fallback to global
`SchemaCache.columns` remains the path when the full input
doesn't parse cleanly (e.g., the user deleted `*` and is
mid-edit). This is a strict improvement: the realistic
"edit an existing query" workflow now narrows correctly.
### What §10.6's prescription becomes
The "rewrite the highlight class" wording is superseded by:
**the post-walk diagnostic pass re-resolves projection
idents against the complete scope and emits / withholds the
unknown-column diagnostic accordingly; the renderer's
diagnostic-overlay path achieves the visual change**. No
new `HighlightClass` variant is required.
§10.6's other prescriptions stand verbatim — the integration
point (final walk stage, in-place mutation of walker
accumulators), the per-keystroke re-walk (ADR-0027's
debounced cadence), and the ORDER BY no-fixup-needed
clarification.
## See also
- ADR-0005 — the ten-type vocabulary §10 resolves back to.