Iter 15e: render is the exact inverse of parse — CLI hygiene

The CLI help text claimed `render` was symmetric to `parse`, but
`render` was wired to ailang_core::pretty::module (an older
human-pretty form) while `parse` consumed form (A). Two different
"text projections" coexisted under one name.

Fix: Cmd::Render and Cmd::Describe (both branches) now call
ailang_surface::print, the actual inverse of `parse`. Round-trip
gate via `ail render | ail parse` is now an e2e test in
crates/ail/tests/e2e.rs in addition to the existing unit-level
round-trip across all 25 fixtures in ailang-surface/tests.

Cleanup: `ailang_core::pretty::module` and its three private
helpers (`def_block`, `term_block`, `term_inline`) deleted —
~260 LOC of duplicate-purpose code removed. pretty.rs goes
from 457 to 196 LOC. Surviving surface is intentionally narrow:
manifest, type_to_string, pattern_to_string — the diagnostic
stringification used in error messages, where one-line ML
notation reads better than form (A)'s nested S-expressions.
Module-level doc rewritten to nail down the single-purpose
framing.

Tests: 89/89 (e2e +1, ailang-core unit -1 since the deleted
test exercised the deleted fn).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-07 18:54:07 +02:00
parent df5b76bd65
commit 0b1b39f829
5 changed files with 140 additions and 286 deletions
+64
View File
@@ -2606,3 +2606,67 @@ the CLI. Logged as 15e.
**Queue update.** 15d done; remaining queue: 15e (CLI render/parse
symmetry — small, just discovered), then `std_pair` if more stdlib
is wanted, then 16a/16b language gaps.
---
## Iter 15e — `render` is the inverse of `parse` (CLI hygiene)
**Trigger.** Discovered during 15d: `ail render | ail parse` did not
round-trip. The help text claimed they were inverses but `render`
called `ailang_core::pretty::module` (an old human-readable
S-expression-ish form), while `parse` consumes form (A). Two
different "text projections" lived in the codebase, one of them
exposed via the CLI under a name that promised inversion.
The form-(A) printer in `ailang_surface::print` was already correct
and gated by `crates/ailang-surface/tests/round_trip.rs` across
every fixture (25 modules at the time of writing) — it was simply
not exposed at the CLI surface.
**Changes.**
1. `Cmd::Render` now calls `ailang_surface::print(&m)`. Help text
updated to state explicitly: form (A), exact inverse of `parse`,
round-trippable.
2. `Cmd::Describe` (both single-module and `--workspace` branches)
likewise switched to `ailang_surface::print` so that "text view
of one def" means the same thing everywhere in the CLI.
3. `ailang_core::pretty::module` deleted. With `module` gone its
helpers (`def_block`, `term_block`, `term_inline`) became dead
weight — also deleted. ~260 LOC of duplicate-purpose code gone;
`crates/ailang-core/src/pretty.rs` shrank from 457 to 196 LOC.
4. The remaining surface of `ailang_core::pretty` — `manifest`,
`type_to_string`, `pattern_to_string` — is the diagnostic
stringification surface (used in error messages and the
`ail manifest` summary, where one-line ML notation is more
readable than form (A)'s nested S-expression). Module-level
doc rewritten to make this single-purpose framing explicit;
no more "pretty-printer" / "render" overloading.
5. New e2e test `render_parse_round_trip_canonical` (in
`crates/ail/tests/e2e.rs`) gates the CLI shell pipeline:
shell out `ail render <fixture>` → write to tmp .ailx →
shell out `ail parse <tmp>` → assert canonical-byte equality
with the original fixture. The unit-level round-trip in
`ailang-surface/tests/round_trip.rs` covered the printer
function directly; this new test additionally guards the
CLI wiring.
6. The `ailang_core::pretty` unit test `pretty_print_does_not_panic`
exercised the deleted `module` fn — removed. `manifest_contains_
type_and_hash` stays.
**Doctrinal pin.** Form (A) is the only round-trippable text form.
The diagnostic stringification in `ailang_core::pretty` is
asymmetric and lossy by design — it exists for the *limited*
purpose of sticking a type or pattern into an error message in
human-friendlier shape than form (A). It does not roundtrip and
must not be used as an authoring or persistence target. Any
future add to it should add to that purpose, not back-fill toward
"another text projection".
**Tests: 89/89.** (e2e went from 31 to 32; ailang-core unit
tests went from 11 to 10 — the deleted unit test exercised the
removed fn. Net same.)
**Cumulative state, post-15e.** No new language features. Two
canonical text projections collapsed to one (form A). Internal
cleanup; no behavioural change for any program in the repo.