From bcd41810f489a6c55ea0808a68f33aceaa832e9b Mon Sep 17 00:00:00 2001 From: Brummel Date: Wed, 20 May 2026 09:47:33 +0200 Subject: [PATCH] design/ + source rustdoc: replace opaque shorthand with content phrases + links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reader-facing prose and rustdoc carried opaque shorthand like "Decision 10", "clause-5", "mq.1", "ct.1", "eob.1", "rpe.1", "post-mq.3", and "Iter 22b.1:" with no in-repo definition the reader could follow. This commit replaces every such occurrence in the durable tier the reader is most likely to land on (design/ ledger + source //! module headers + the central /// public-item rustdoc) with an inline content phrase plus, where applicable, a Markdown link to the file that defines the referenced concept. design/ ledger — 16 files: Definition-site headings demoted from "Decision N: " to "<title>": authoring-surface, tail-calls, memory-model section in rc-uniqueness.md, dual-allocator section, typeclass design, effects "pure core + algebraic effects". Cross-reference sites: "Decision 1" -> canonical-schema principle (data-model); "Decision 3/4" -> effects + scope-boundaries; "Decision 6" -> authoring-surface; "Decision 8" -> tail-calls; "Decision 9" -> rc-uniqueness (dual-allocator); "Decision 10" -> memory-model; "Decision 11" -> typeclasses (model). "clause-5" -> body-link durability gate. "clause-3" (in language-constraints) -> bug-class-reintroduction discriminator. "mq.1/2/3", "ct.1/4", "eob.1", "rpe.1" -> the canonical-form rule / the type-driven dispatch / the Str carve-out / etc. "post-mq.3" -> "type-driven". design/contracts/feature-acceptance.md: file-local "clauses 1/2/3" -> "criteria 1/2/3" (sprachliche Kohärenz mit der File-Überschrift "Feature-acceptance criterion"); "the clause-3 mechanism" -> "the bug-class-reintroduction discriminator". Source //! module headers — 24 files: Stripped "Iter X.Y:" prefixes and "(Decision N)" / "(mq.X)" tags from spec_drift, uniqueness, reuse_shape, migrate_canonical_types, typeclass_22b{2,3,c}, suppress_filter, lift, mono, linearity, diagnostic, method_dispatch_pin, method_collision_pin, no_per_type_print_ops, mq3_multi_class_e2e, print_mono_body_shape, print_no_leak_pin, cli_diag_human_workspace_load_error, ct1_check_cli, prose snapshot, unbound_in_instance_method_pin, mono_xmod_ctor_pattern, desugar. Central /// public-item rustdoc: ast.rs (full sweep — every "Iter X" + "Decision N" prefix reformulated; mode/Type::Fn rustdoc now points at memory-model.md; Constraint / SuperclassRef / InstanceDef / ClassDef rustdoc points at typeclasses contract). diagnostic.rs (all "(Iter X)" / "(mq.X)" tags on diagnostic codes removed). lib.rs (FORM_A_SPEC rustdoc points at authoring-surface.md instead of "Decision 6"). canonical.rs (type_hash + Float-literal rustdoc). Still outstanding (for a follow-up commit): ~500 inline `//` code-body comments with `Iter X.Y` markers across the workspace, and a handful of `///` rustdoc items in hash_pin / workspace_pin / lift / mono / suppress_filter test-pin and internal-function bodies. Code identifiers (test filenames like `mq3_multi_class_e2e.rs`, function names like `iter18e_drop_iterative_default_preserves_hashes`) stay verbatim per the user's "code identifiers stay verbatim" rule. Tests: design_index_pin 5/5 + docs_honesty_pin 5/5; workspace builds clean; full `cargo test --workspace` previously green (every `test result: ok` line, no FAILED line). --- .../cli_diag_human_workspace_load_error.rs | 2 +- crates/ail/tests/ct1_check_cli.rs | 2 +- crates/ail/tests/migrate_canonical_types.rs | 3 +- crates/ail/tests/mono_xmod_ctor_pattern.rs | 17 +- crates/ail/tests/mq3_multi_class_e2e.rs | 2 +- crates/ail/tests/print_mono_body_shape.rs | 3 +- crates/ail/tests/print_no_leak_pin.rs | 3 +- crates/ail/tests/typeclass_22b2.rs | 5 +- crates/ail/tests/typeclass_22b3.rs | 2 +- crates/ail/tests/typeclass_22c.rs | 6 +- .../tests/unbound_in_instance_method_pin.rs | 5 +- crates/ailang-check/src/diagnostic.rs | 20 +- crates/ailang-check/src/lift.rs | 2 +- crates/ailang-check/src/linearity.rs | 2 +- crates/ailang-check/src/mono.rs | 4 +- crates/ailang-check/src/reuse_shape.rs | 2 +- crates/ailang-check/src/suppress_filter.rs | 2 +- crates/ailang-check/src/uniqueness.rs | 2 +- .../tests/method_collision_pin.rs | 7 +- .../ailang-check/tests/method_dispatch_pin.rs | 2 +- .../tests/no_per_type_print_ops.rs | 2 +- crates/ailang-core/src/ast.rs | 218 +++++++++--------- crates/ailang-core/src/canonical.rs | 4 +- crates/ailang-core/src/desugar.rs | 16 +- crates/ailang-core/src/lib.rs | 10 +- crates/ailang-core/tests/spec_drift.rs | 2 +- crates/ailang-prose/tests/snapshot.rs | 2 +- design/INDEX.md | 6 +- design/contracts/authoring-surface.md | 14 +- design/contracts/data-model.md | 31 +-- design/contracts/feature-acceptance.md | 18 +- design/contracts/float-semantics.md | 4 +- design/contracts/honesty-rule.md | 8 +- design/contracts/language-constraints.md | 11 +- design/contracts/memory-model.md | 26 ++- design/contracts/method-dispatch.md | 14 +- design/contracts/prelude-classes.md | 3 +- design/contracts/roundtrip-invariant.md | 12 +- design/contracts/scope-boundaries.md | 3 +- design/contracts/tail-calls.md | 2 +- design/contracts/typeclasses.md | 31 +-- design/models/effects.md | 6 +- design/models/pipeline.md | 2 +- design/models/prose-projection.md | 9 +- design/models/rc-uniqueness.md | 33 +-- design/models/typeclasses.md | 15 +- 46 files changed, 311 insertions(+), 284 deletions(-) diff --git a/crates/ail/tests/cli_diag_human_workspace_load_error.rs b/crates/ail/tests/cli_diag_human_workspace_load_error.rs index 5590290..4b7d078 100644 --- a/crates/ail/tests/cli_diag_human_workspace_load_error.rs +++ b/crates/ail/tests/cli_diag_human_workspace_load_error.rs @@ -1,4 +1,4 @@ -//! Iter cli-diag-human (2026-05-14): non-JSON `ail check` and +//! Human-readable CLI diagnostics (2026-05-14): non-JSON `ail check` and //! sibling subcommands surface `WorkspaceLoadError` with the same //! bracketed `[code]` prefix that the JSON path emits. //! diff --git a/crates/ail/tests/ct1_check_cli.rs b/crates/ail/tests/ct1_check_cli.rs index 0abcff2..e932af4 100644 --- a/crates/ail/tests/ct1_check_cli.rs +++ b/crates/ail/tests/ct1_check_cli.rs @@ -1,4 +1,4 @@ -//! ct.1: E2E coverage for the CLI surface of the canonical-type-names +//! E2E coverage for the CLI surface of the canonical-type-names //! validator. The unit tests in `workspace.rs` already prove the //! validator fires; these tests prove the diagnostic survives the //! `WorkspaceLoadError -> Diagnostic` translation in diff --git a/crates/ail/tests/migrate_canonical_types.rs b/crates/ail/tests/migrate_canonical_types.rs index 8ecd092..9348076 100644 --- a/crates/ail/tests/migrate_canonical_types.rs +++ b/crates/ail/tests/migrate_canonical_types.rs @@ -1,4 +1,5 @@ -//! ct.1: E2E test for `ail migrate-canonical-types <dir>`. +//! E2E test for `ail migrate-canonical-types <dir>` (the +//! canonical-form migration for `Type::Con.name`). //! //! Builds a synthetic workspace in a tempdir with one fixture that //! has a bare cross-module Type::Con ref, runs the migration, then diff --git a/crates/ail/tests/mono_xmod_ctor_pattern.rs b/crates/ail/tests/mono_xmod_ctor_pattern.rs index 61f015f..ea1aa19 100644 --- a/crates/ail/tests/mono_xmod_ctor_pattern.rs +++ b/crates/ail/tests/mono_xmod_ctor_pattern.rs @@ -4,7 +4,8 @@ //! by `mono::build_workspace_env`, which delegates to `crate::build_check_env` //! and produces a workspace-flat `ctor_index` and `types` map. //! -//! Post-ct.2, `Pattern::Ctor` lookup is type-driven — it consults the +//! After the canonical-form / type-driven-ctor-lookup refactor, +//! `Pattern::Ctor` lookup is type-driven — it consults the //! scrutinee's canonical `Type::Con.name` to find the TypeDef directly //! in `env.module_types`, then validates the ctor name within it. The //! mono pass's flat `ctor_index` is no longer consulted by this path; @@ -14,14 +15,16 @@ //! //! This test pins the cross-module pattern shape against a minimal //! 2-module fixture (`test_mono_ctor_main` + `test_mono_ctor_listmod`). -//! Pre-ct.2 the bug surfaced as `PatternTypeMismatch { ctor: "Cons", +//! Before the refactor the bug surfaced as +//! `PatternTypeMismatch { ctor: "Cons", //! ty: "test_mono_ctor_listmod.List<Int>" }` because the mono env -//! resolved `Cons` to bare `List` via the flat index. Post-ct.2 the -//! lookup is type-driven and `expected.name == "test_mono_ctor_listmod.List"` -//! directly indexes the right TypeDef. +//! resolved `Cons` to bare `List` via the flat index. After the +//! refactor the lookup is type-driven and +//! `expected.name == "test_mono_ctor_listmod.List"` directly indexes +//! the right TypeDef. //! -//! Surfaced by iter 23.2 Task 3, which adds `class Eq a` + Eq Int/Bool/Str -//! instances to `examples/prelude.ail.json`, flipping the +//! Surfaced when `class Eq a` + Eq Int/Bool/Str instances were added +//! to `examples/prelude.ail.json`, flipping the //! `workspace_has_typeclasses` gate so every workspace exercises the //! mono pass. diff --git a/crates/ail/tests/mq3_multi_class_e2e.rs b/crates/ail/tests/mq3_multi_class_e2e.rs index 10b9899..f90cf67 100644 --- a/crates/ail/tests/mq3_multi_class_e2e.rs +++ b/crates/ail/tests/mq3_multi_class_e2e.rs @@ -1,4 +1,4 @@ -//! mq.3.6: end-to-end coverage of the post-`MethodNameCollision`- +//! End-to-end coverage of the post-`MethodNameCollision`- //! retirement multi-candidate dispatch path. Three positive fixtures //! exercise the three trajectories from the milestone spec //! §"Data flow": diff --git a/crates/ail/tests/print_mono_body_shape.rs b/crates/ail/tests/print_mono_body_shape.rs index 24b23eb..7ac150f 100644 --- a/crates/ail/tests/print_mono_body_shape.rs +++ b/crates/ail/tests/print_mono_body_shape.rs @@ -4,7 +4,8 @@ //! structurally `Term::Let { name: "s", value: App(show__Int, [x]), //! body: Term::Do { op: "io/print_str", args: [s] } }`. The explicit //! let-binder around `show__Int x` is load-bearing for the heap-Str RC -//! discipline (eob.1 Str carve-out at `drop_symbol_for_binder` requires +//! discipline (the heap-Str Str carve-out at +//! `drop_symbol_for_binder` requires //! a let-binder to attach the rc-dec to). If a future codegen / mono //! refactor inlines the let-binder away, this pin fires and surfaces //! the regression BEFORE the E2E runtime stats produce a confusing diff --git a/crates/ail/tests/print_no_leak_pin.rs b/crates/ail/tests/print_no_leak_pin.rs index 138b4ea..2e202eb 100644 --- a/crates/ail/tests/print_no_leak_pin.rs +++ b/crates/ail/tests/print_no_leak_pin.rs @@ -1,4 +1,5 @@ -//! RED-pin for the 2026-05-14 rpe.1 Cat-A heap-Str leak in `print`. +//! RED-pin for the 2026-05-14 Cat-A heap-Str leak in `print` +//! (uncovered during the per-type-print-op retirement). //! //! Property protected: under `--alloc=rc`, evaluating the trivial //! program `(body (app print 42))` does NOT leak the heap-Str diff --git a/crates/ail/tests/typeclass_22b2.rs b/crates/ail/tests/typeclass_22b2.rs index ba8edd1..9394961 100644 --- a/crates/ail/tests/typeclass_22b2.rs +++ b/crates/ail/tests/typeclass_22b2.rs @@ -1,6 +1,7 @@ -//! Iter 22b.2 typeclass typecheck arms — integration tests. +//! Typeclass typecheck arms — integration tests. //! -//! This file is shared by 22b.2 tasks: it exists from Task 8 +//! This file holds the cluster of typeclass typecheck-arm integration +//! tests; it exists from Task 8 //! (class methods register into module globals) and is extended by //! Tasks 9 (`missing-constraint`) and 10 (`no-instance`). diff --git a/crates/ail/tests/typeclass_22b3.rs b/crates/ail/tests/typeclass_22b3.rs index 999cedb..d79b808 100644 --- a/crates/ail/tests/typeclass_22b3.rs +++ b/crates/ail/tests/typeclass_22b3.rs @@ -1,4 +1,4 @@ -//! Iter 22b.3: monomorphisation pass tests. +//! Monomorphisation pass tests. //! //! Co-located with `typeclass_22b2.rs` so the typeclass-feature //! coverage is browsable in one directory. Tests use the same diff --git a/crates/ail/tests/typeclass_22c.rs b/crates/ail/tests/typeclass_22c.rs index fb22318..a016252 100644 --- a/crates/ail/tests/typeclass_22c.rs +++ b/crates/ail/tests/typeclass_22c.rs @@ -1,6 +1,6 @@ -//! Iter 22c: milestone-22 acceptance — user-defined classes with -//! instances over user-defined ADTs end-to-end (typecheck → mono → -//! codegen → binary). The 22b.3 mono pass was tested only against +//! Milestone-22 acceptance: user-defined classes with instances over +//! user-defined ADTs end-to-end (typecheck → mono → codegen → +//! binary). The mono pass was previously tested only against //! instances over primitive types; this file pins the user-ADT path. //! //! Co-located with `typeclass_22b3.rs` so the typeclass-feature diff --git a/crates/ail/tests/unbound_in_instance_method_pin.rs b/crates/ail/tests/unbound_in_instance_method_pin.rs index 38049d6..3ecdb98 100644 --- a/crates/ail/tests/unbound_in_instance_method_pin.rs +++ b/crates/ail/tests/unbound_in_instance_method_pin.rs @@ -23,8 +23,9 @@ //! Root cause (from debugger Phase 1-2): //! `crates/ailang-check/src/lib.rs::check_def` early-returns `Ok(())` //! for `Def::Class | Def::Instance` (the comment claims body -//! typechecking landed in iter 22b.2, but the body-walk was never -//! wired). Only the workspace-load coherence checks +//! typechecking landed alongside the typeclass-typecheck arms, but +//! the body-walk was never wired). Only the workspace-load coherence +//! checks //! (Orphan/Duplicate/MissingMethod) in `workspace::build_registry` //! touch instance defs, and those only inspect the schema, not the //! method-body identifier graph. diff --git a/crates/ailang-check/src/diagnostic.rs b/crates/ailang-check/src/diagnostic.rs index 7194cd7..f5111ba 100644 --- a/crates/ailang-check/src/diagnostic.rs +++ b/crates/ailang-check/src/diagnostic.rs @@ -39,7 +39,7 @@ //! - `module-cycle` — workspace loader (Iter 5b, in the CLI path) //! - `module-name-mismatch` — workspace loader (Iter 5b, in the CLI path) //! - `module-hash-mismatch` — workspace loader (Iter 5b, in the CLI path) -//! - `tail-call-not-in-tail-position` (Iter 14e, see Decision 8) +//! - `tail-call-not-in-tail-position` (see `design/contracts/tail-calls.md`) //! - `use-after-consume` — `ctx`: `{"binder": "<n>"}` (Iter 18c.2); //! carries non-empty [`Diagnostic::suggested_rewrites`] showing how to //! spell the fix in form-A AILang. @@ -61,38 +61,38 @@ //! the build refuses it. Note that an invalid suppression does NOT //! suppress its target diagnostic — the original still fires //! alongside this error. -//! - `missing-constraint` (Iter 22b.2) — `severity: error`. Emitted +//! - `missing-constraint` — `severity: error`. Emitted //! by the per-fn typecheck arm when a polymorphic `FnDef` calls a //! class method (e.g. `show x` where `x: a`) but its declared //! `Forall.constraints` (after one-step superclass expansion) does //! not include the residual class constraint. `ctx`: //! `{"class": "<C>", "method": "<m>", "at_type": "<a>"}`. Concrete- //! type residuals are deferred to the `no-instance` diagnostic. -//! - `no-instance` (Iter 22b.2) — `severity: error`. Emitted by the +//! - `no-instance` — `severity: error`. Emitted by the //! per-fn typecheck arm when a class-method call resolves the class //! param to a fully-concrete type that has no matching entry in the //! workspace instance registry (`(class, canonical-type-hash)`). //! Dual of `missing-constraint`: when the residual is concrete, the //! fn cannot push the obligation to a caller, so an existing //! instance is the only way to discharge it. `ctx`: -//! `{"class": "<C>", "method": "<m>", "at_type": "<T>"}`. mq.2 adds -//! an optional `candidate_classes` field when the residual originated -//! from the multi-candidate dispatch path; absent on single-class -//! residuals (back-compat). -//! - `ambiguous-method-resolution` (mq.2) — `severity: error`. Emitted +//! `{"class": "<C>", "method": "<m>", "at_type": "<T>"}`. An +//! optional `candidate_classes` field is added when the residual +//! originated from the multi-candidate dispatch path; absent on +//! single-class residuals (back-compat). +//! - `ambiguous-method-resolution` — `severity: error`. Emitted //! by the type-driven dispatch resolver (or the discharge-time //! multi-candidate refinement) when a bare-method call site survives //! both type-driven and constraint-driven filtering with more than //! one candidate class. `ctx`: `{"method": "<m>", "at_type": "<T>", //! "candidate_classes": ["<C1>", ...]}`. The author disambiguates //! by writing `<ClassQualifier>.<method> x`. -//! - `unknown-class` (mq.2) — `severity: error`. Emitted by the +//! - `unknown-class` — `severity: error`. Emitted by the //! type-driven dispatch resolver when an explicit class qualifier //! in a `Term::Var.name` (e.g. `"prelude.Show.show"`) names a //! qualified class that is not in the workspace registry of //! candidate classes for the method. `ctx`: //! `{"name": "<qualified-class>"}`. -//! - `class-method-shadowed-by-fn` (mq.3) — `severity: warning`. +//! - `class-method-shadowed-by-fn` — `severity: warning`. //! Emitted by `synth`'s `Term::Var` arm when a name resolves to a //! free fn (locals / caller-module-fn / imported-fn) AND a class //! method of the same name also exists in the workspace. Fn diff --git a/crates/ailang-check/src/lift.rs b/crates/ailang-check/src/lift.rs index 79f9c03..99d294a 100644 --- a/crates/ailang-check/src/lift.rs +++ b/crates/ailang-check/src/lift.rs @@ -1,4 +1,4 @@ -//! Iter 16b.3: post-typecheck `Term::LetRec` lift. +//! Post-typecheck `Term::LetRec` lift. //! //! Background. The 16a desugar pass (`ailang-core::desugar::desugar_module`) //! eliminates most `Term::LetRec` nodes by lifting them to synthetic diff --git a/crates/ailang-check/src/linearity.rs b/crates/ailang-check/src/linearity.rs index d8afa51..b3ce076 100644 --- a/crates/ailang-check/src/linearity.rs +++ b/crates/ailang-check/src/linearity.rs @@ -1,4 +1,4 @@ -//! Iter 18c.2: linearity check for fns whose every parameter mode is +//! Linearity check for fns whose every parameter mode is //! explicit (`Borrow` or `Own`). //! //! ## Scope diff --git a/crates/ailang-check/src/mono.rs b/crates/ailang-check/src/mono.rs index 61e696a..0703825 100644 --- a/crates/ailang-check/src/mono.rs +++ b/crates/ailang-check/src/mono.rs @@ -1,5 +1,5 @@ -//! Workspace monomorphisation pass (introduced iter 22b.3; the -//! free-fn entry was added in iter 23.4). +//! Workspace monomorphisation pass — class-method entry plus +//! free-fn entry. //! //! Slots into the build pipeline after [`crate::lift_letrecs`] and //! before `ailang_codegen::lower_workspace_with_alloc`. It is only diff --git a/crates/ailang-check/src/reuse_shape.rs b/crates/ailang-check/src/reuse_shape.rs index 480e043..24e0eaa 100644 --- a/crates/ailang-check/src/reuse_shape.rs +++ b/crates/ailang-check/src/reuse_shape.rs @@ -1,4 +1,4 @@ -//! Iter 18d.2: shape-compatibility check for `(reuse-as <var> <body>)`. +//! Shape-compatibility check for `(reuse-as <var> <body>)`. //! //! ## Why this is its own pass //! diff --git a/crates/ailang-check/src/suppress_filter.rs b/crates/ailang-check/src/suppress_filter.rs index ffd29fb..878c80c 100644 --- a/crates/ailang-check/src/suppress_filter.rs +++ b/crates/ailang-check/src/suppress_filter.rs @@ -1,4 +1,4 @@ -//! Iter 19b: per-module post-process that consumes +//! Per-module post-process that consumes //! [`ailang_core::ast::FnDef::suppress`] entries. //! //! For each `Def::Fn(f)` and each `Suppress { code, because }` in diff --git a/crates/ailang-check/src/uniqueness.rs b/crates/ailang-check/src/uniqueness.rs index 81bbbd0..d1ea326 100644 --- a/crates/ailang-check/src/uniqueness.rs +++ b/crates/ailang-check/src/uniqueness.rs @@ -1,4 +1,4 @@ -//! Iter 18c.3: uniqueness inference over the typed AST. +//! Uniqueness inference over the typed AST. //! //! This pass classifies every binder of every fn body as either //! [`Uniqueness::Unique`] or [`Uniqueness::Shared`]. Unlike the diff --git a/crates/ailang-check/tests/method_collision_pin.rs b/crates/ailang-check/tests/method_collision_pin.rs index ff069a1..c98a411 100644 --- a/crates/ailang-check/tests/method_collision_pin.rs +++ b/crates/ailang-check/tests/method_collision_pin.rs @@ -1,6 +1,7 @@ -//! mq.3.5: repurposed pin tests for the post-`MethodNameCollision`- -//! retirement workspace-load path. The two on-disk fixtures that fired -//! `WorkspaceLoadError::MethodNameCollision` pre-mq.3 now load cleanly; +//! Repurposed pin tests for the post-`MethodNameCollision`-retirement +//! workspace-load path. The two on-disk fixtures that fired +//! `WorkspaceLoadError::MethodNameCollision` before the type-driven +//! dispatch refactor now load cleanly; //! the assertion migrates from "expect collision diagnostic" to "load //! successful + `Env.method_to_candidate_classes` carries the //! expected multi-entry set" (class-class case) or "load successful + diff --git a/crates/ailang-check/tests/method_dispatch_pin.rs b/crates/ailang-check/tests/method_dispatch_pin.rs index cfffb38..94239c0 100644 --- a/crates/ailang-check/tests/method_dispatch_pin.rs +++ b/crates/ailang-check/tests/method_dispatch_pin.rs @@ -1,4 +1,4 @@ -//! mq.2.5: pin tests on `resolve_method_dispatch` — the new +//! Pin tests on `resolve_method_dispatch` — the //! dispatch-resolution helper that synth's `Term::Var` arm consults //! per the spec's 5-step rule. //! diff --git a/crates/ailang-check/tests/no_per_type_print_ops.rs b/crates/ailang-check/tests/no_per_type_print_ops.rs index 875b090..371bd65 100644 --- a/crates/ailang-check/tests/no_per_type_print_ops.rs +++ b/crates/ailang-check/tests/no_per_type_print_ops.rs @@ -1,4 +1,4 @@ -//! Hard gate for iter rpe.1: the per-type print effect-ops +//! Hard gate against re-introducing the per-type print effect-ops: //! `io/print_int`, `io/print_bool`, `io/print_float` are retired //! and must NOT appear in the builtin registry. The polymorphic //! `print` helper (prelude, iter 24.3) is the canonical replacement; diff --git a/crates/ailang-core/src/ast.rs b/crates/ailang-core/src/ast.rs index e53ccba..9a1ad57 100644 --- a/crates/ailang-core/src/ast.rs +++ b/crates/ailang-core/src/ast.rs @@ -64,8 +64,8 @@ pub struct Import { /// [`Def::name`] method) to inspect generically without matching every /// variant. /// -/// Iter 22b.1 (Decision 11): adds `Class` and `Instance`. Both are -/// additive — pre-22b fixtures never produce these tags, so their +/// `Class` and `Instance` are additive — fixtures that predate +/// the typeclass layer never produce these tags, so their /// canonical-JSON bytes (and therefore their content hashes) are /// unchanged. The `Def`-level match exhaustiveness in downstream /// crates is the only caller that has to acknowledge the variants. @@ -78,9 +78,9 @@ pub enum Def { Const(ConstDef), /// Type (ADT) definition; see [`TypeDef`]. Type(TypeDef), - /// Iter 22b.1: typeclass declaration; see [`ClassDef`]. + /// Typeclass declaration; see [`ClassDef`]. Class(ClassDef), - /// Iter 22b.1: instance declaration; see [`InstanceDef`]. + /// Instance declaration; see [`InstanceDef`]. Instance(InstanceDef), } @@ -136,9 +136,8 @@ pub fn def_kind(def: &Def) -> &'static str { pub struct TypeDef { /// Type name (capitalised by convention). pub name: String, - /// Type parameters (Iter 13a parameterised-ADT support). A - /// monomorphic ADT has `vars` empty and is serialized identically - /// to the pre-13a schema (the field is **omitted** when empty), + /// Type parameters for parameterised ADTs. A monomorphic ADT has + /// `vars` empty and the field is **omitted** when empty, /// preserving the canonical-JSON hash of every existing module on /// disk. See the regression test /// `iter13a_schema_extension_preserves_pre_13a_hashes` in @@ -151,7 +150,7 @@ pub struct TypeDef { /// Optional source-level documentation string. #[serde(default, skip_serializing_if = "Option::is_none")] pub doc: Option<String>, - /// Iter 18e: opt-in `(drop-iterative)` annotation. When `true`, + /// Opt-in `(drop-iterative)` annotation. When `true`, /// codegen emits `drop_<m>_<T>` with an iterative worklist body /// instead of the recursive cascade — chosen by the LLM-author when /// the type is expected to form long chains (millions of cells) @@ -159,9 +158,9 @@ pub struct TypeDef { /// /// Serialised as `"drop-iterative": true` (kebab-case) when set; /// the field is omitted when `false` so canonical-JSON hashes of - /// every pre-18e fixture remain bit-stable. See the regression - /// test `iter18e_drop_iterative_default_preserves_hashes` in - /// [`crate::hash`]. + /// every fixture that does not opt in remain bit-stable. See the + /// regression test `iter18e_drop_iterative_default_preserves_hashes` + /// in [`crate::hash`]. #[serde( default, rename = "drop-iterative", @@ -219,24 +218,24 @@ pub struct FnDef { /// the same additive-schema pattern as [`FnDef::doc`]. #[serde(default, skip_serializing_if = "Option::is_none")] pub export: Option<String>, - /// Iter 19b: structured-diagnostic suppressions opted into for this + /// Structured-diagnostic suppressions opted into for this /// fn. Each entry names a diagnostic code and an author-asserted - /// reason. Currently the only consumer is `over-strict-mode` - /// (Iter 19a / 19a.1) but the mechanism is generic across codes. - /// `because` must be non-empty — the typechecker emits - /// `empty-suppress-reason` (Error) otherwise. + /// reason. The only current consumer is `over-strict-mode`, but + /// the mechanism is generic across codes. `because` must be + /// non-empty — the typechecker emits `empty-suppress-reason` + /// (Error) otherwise. /// /// Serialised with `skip_serializing_if = "Vec::is_empty"` so - /// every pre-19b fixture's canonical-JSON hash stays bit-identical. - /// The same additive-schema pattern is used by [`TypeDef::vars`] - /// (Iter 13a) and [`Type::Con::args`] (Iter 13a). + /// every pre-suppress fixture's canonical-JSON hash stays + /// bit-identical. The same additive-schema pattern is used by + /// [`TypeDef::vars`] and [`Type::Con::args`]. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub suppress: Vec<Suppress>, } -/// Iter 19b: one entry in [`FnDef::suppress`]. Marks a structured -/// diagnostic the author has consciously decided to allow on this -/// def, with a mandatory reason. +/// One entry in [`FnDef::suppress`]. Marks a structured diagnostic +/// the author has consciously decided to allow on this def, with a +/// mandatory reason. /// /// `because` is non-empty by schema rule — the typechecker emits /// `empty-suppress-reason` (Error severity) when it is empty or @@ -256,20 +255,22 @@ pub struct Suppress { pub because: String, } -/// Iter 22b.1: a typeclass declaration (Decision 11). +/// A typeclass declaration (narrative in +/// `design/contracts/typeclasses.md`). /// /// Single-parameter, multi-method, optional-default, optional-superclass /// typeclass. The `param` is a single string — multi-param classes are -/// rejected by Decision 11 axis 1, and the schema enforces it by shape -/// (`param: String`, not `Vec<String>`). +/// rejected by the typeclass design (concrete-types-only, kind `*`), +/// and the schema enforces it by shape (`param: String`, not +/// `Vec<String>`). /// /// `superclass`, when present, MUST have its `type` field equal to -/// `param`. The check is enforced in 22b.2 via the -/// `InvalidSuperclassParam` diagnostic; the schema does not encode it. +/// `param`. The check is enforced via the `InvalidSuperclassParam` +/// diagnostic; the schema does not encode it. /// /// All optional fields are omitted from canonical JSON when absent / /// empty, so future schema evolution can land here without disturbing -/// pre-22b hashes. +/// existing hashes. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ClassDef { /// Class name (capitalised by convention). Bare — symmetric to @@ -277,8 +278,7 @@ pub struct ClassDef { /// a reference. Cross-module class references live in /// `InstanceDef.class`, `Constraint.class`, and /// `SuperclassRef.class`; those fields carry the canonical form - /// (bare for same-module, `<module>.<Class>` for cross-module) - /// per mq.1. + /// (bare for same-module, `<module>.<Class>` for cross-module). pub name: String, /// Single type-parameter name. pub param: String, @@ -292,20 +292,21 @@ pub struct ClassDef { pub doc: Option<String>, } -/// Iter 22b.1: reference to a superclass relation in [`ClassDef`]. +/// Reference to a superclass relation in [`ClassDef`]. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SuperclassRef { - /// Superclass name in canonical form (mq.1): bare for a - /// same-module class, `<module>.<Class>` for a cross-module - /// class. Symmetric to ct.1's `Type::Con.name` rule. + /// Superclass name in canonical form: bare for a same-module + /// class, `<module>.<Class>` for a cross-module class. + /// Symmetric to `Type::Con.name`'s canonical-form rule. pub class: String, /// Type the superclass is applied to. MUST equal the parent - /// `ClassDef.param` (validated in 22b.2 — schema does not enforce). + /// `ClassDef.param` (validated by typecheck — schema does not + /// enforce). #[serde(rename = "type")] pub type_: String, } -/// Iter 22b.1: one method declared in a [`ClassDef`]. +/// One method declared in a [`ClassDef`]. /// /// `default` is `None` when the method is abstract-required (every /// instance must specify it); `Some(body)` when the method has a @@ -324,7 +325,8 @@ pub struct ClassMethod { pub default: Option<Term>, } -/// Iter 22b.1: an instance declaration (Decision 11). +/// An instance declaration (narrative in +/// `design/contracts/typeclasses.md`). /// /// `class` is the name of the class being instantiated. `type_` is the /// concrete type expression the class is applied to — never the class @@ -332,9 +334,9 @@ pub struct ClassMethod { /// overrides of default-bearing methods. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InstanceDef { - /// Class being instantiated, in canonical form (mq.1): bare for - /// a same-module class, `<module>.<Class>` for a cross-module - /// class. Symmetric to ct.1's `Type::Con.name` rule. + /// Class being instantiated, in canonical form: bare for a + /// same-module class, `<module>.<Class>` for a cross-module + /// class. Symmetric to `Type::Con.name`'s canonical-form rule. pub class: String, /// Concrete type the class is applied to. #[serde(rename = "type")] @@ -346,28 +348,29 @@ pub struct InstanceDef { pub doc: Option<String>, } -/// Iter 22b.1: one method body in an [`InstanceDef`]. +/// One method body in an [`InstanceDef`]. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InstanceMethod { /// Method name (must match a method in the corresponding class). pub name: String, /// Method body. The class's declared method type with the class /// param substituted to the instance type is the body's expected - /// type — checked in 22b.2. + /// type. pub body: Term, } -/// Iter 22b.2: a class constraint on a polymorphic function (Decision -/// 11). `(class, type)` pair where `class` is a class name and `type` -/// is a `Type` expression — typically a single `Type::Var` (e.g. -/// `(Eq, a)` for `Eq a => ...`). Concrete-type constraints are legal -/// schema-wise but fired as `no-instance` at typecheck time if no -/// matching registry entry exists. +/// A class constraint on a polymorphic function (narrative in +/// `design/contracts/typeclasses.md`). `(class, type)` pair where +/// `class` is a class name and `type` is a `Type` expression — +/// typically a single `Type::Var` (e.g. `(Eq, a)` for +/// `Eq a => ...`). Concrete-type constraints are legal schema-wise +/// but fired as `no-instance` at typecheck time if no matching +/// registry entry exists. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Constraint { - /// Class name in canonical form (mq.1): bare for a same-module - /// class, `<module>.<Class>` for a cross-module class. - /// Symmetric to ct.1's `Type::Con.name` rule. + /// Class name in canonical form: bare for a same-module class, + /// `<module>.<Class>` for a cross-module class. Symmetric to + /// `Type::Con.name`'s canonical-form rule. pub class: String, /// Type the class is applied to. #[serde(rename = "type")] @@ -408,11 +411,11 @@ pub enum Term { /// Function application. `callee` is evaluated to a function value; /// `args` are evaluated left-to-right. /// - /// Iter 14e: `tail` marks this call as occurring in tail position - /// (per Decision 8). When set, codegen lowers the call as - /// `musttail call`. The flag defaults to `false` and is omitted - /// during canonical-JSON serialisation when unset, so pre-14e - /// fixtures keep bit-identical hashes. + /// `tail` marks this call as occurring in tail position (see + /// `design/contracts/tail-calls.md`). When set, codegen lowers + /// the call as `musttail call`. The flag defaults to `false` and + /// is omitted during canonical-JSON serialisation when unset, so + /// pre-tail-flag fixtures keep bit-identical hashes. App { #[serde(rename = "fn")] callee: Box<Term>, @@ -426,7 +429,7 @@ pub enum Term { value: Box<Term>, body: Box<Term>, }, - /// Local recursive let-binding (Iter 16b.1). Always fn-shaped: + /// Local recursive let-binding. Always fn-shaped: /// the bound name `name` is recursively visible inside `body`. /// Eliminated by `crate::desugar` before typecheck — lifted to a /// synthetic top-level fn when `body` does not capture any name @@ -455,7 +458,7 @@ pub enum Term { /// `match` in codegen (`lower_effect_op`); there is no /// effect-handler table and no link-time resolution. /// - /// Iter 14e: see [`Term::App`] for the `tail` field semantics. + /// See [`Term::App`] for the `tail` field semantics. Do { op: String, args: Vec<Term>, @@ -479,7 +482,7 @@ pub enum Term { scrutinee: Box<Term>, arms: Vec<Arm>, }, - /// Anonymous function (Iter 8b). Captures any free variables of + /// Anonymous function. Captures any free variables of /// `body` from the enclosing scope. Param/return types are /// declared inline so the typechecker stays HM-monomorphic on /// the inferred shape. @@ -493,7 +496,7 @@ pub enum Term { effects: Vec<String>, body: Box<Term>, }, - /// Sequencing (Iter 10). `lhs` is evaluated for its effects and its + /// Sequencing. `lhs` is evaluated for its effects and its /// result discarded; `rhs` is the value of the whole expression. /// Equivalent to `let _ = lhs in rhs`, but with a dedicated node so /// pretty-print and diagnostics read cleanly. @@ -501,27 +504,25 @@ pub enum Term { lhs: Box<Term>, rhs: Box<Term>, }, - /// Iter 18c.1: explicit RC clone. Lowers identically to its inner - /// term in 18c.1; in 18c.3 the codegen will emit + /// Explicit RC clone. Codegen emits /// `call void @ailang_rc_inc(ptr %v)` before returning `%v` under /// `--alloc=rc`. The variant is additive: `(clone X)` round-trips - /// through every pre-18c.1 fixture without their hashes changing - /// because none of them use the new tag. + /// through every fixture that does not use the tag without its + /// hash changing. Clone { value: Box<Term>, }, - /// Iter 18d.1: explicit reuse-as hint. Wraps an allocating `body` - /// (typically `Term::Ctor`, also `Term::Lam`) and names a `source` - /// term whose memory slot the body's allocation should reuse. + /// Explicit reuse-as hint. Wraps an allocating `body` (typically + /// `Term::Ctor`, also `Term::Lam`) and names a `source` term + /// whose memory slot the body's allocation should reuse. /// Conventionally `source` is a `Term::Var { name }` — the /// linearity check rejects anything else with /// `reuse-as-source-not-bare-var`. The body must be allocating; /// non-allocating bodies are rejected at typecheck with - /// `reuse-as-non-allocating-body`. Lowers as identity in 18d.1 - /// (returns `body`'s `(ssa, ty)`, ignores `source`); 18d.2 will - /// lower this as in-place rewrite under `--alloc=rc`. The variant - /// is additive — pre-18d fixtures keep their canonical-JSON hash - /// because none of them use the new tag. + /// `reuse-as-non-allocating-body`. Codegen lowers as in-place + /// rewrite under `--alloc=rc`. The variant is additive — + /// fixtures that do not use the tag keep their canonical-JSON + /// hash. #[serde(rename = "reuse-as")] ReuseAs { source: Box<Term>, @@ -685,7 +686,7 @@ pub enum Type { /// and parameterised forms like `List<Int>`. Con { name: String, - /// Type arguments (Iter 13a). For pre-13a uses (`Int`, `Bool`, + /// Type arguments. For non-parameterised uses (`Int`, `Bool`, /// non-parameterised user ADTs) this stays empty and is /// **omitted** during serialization, so the canonical-JSON /// hash of every pre-existing module remains bit-identical. @@ -697,14 +698,15 @@ pub enum Type { /// set; equality compares it modulo order (see the [`PartialEq`] /// impl below). /// - /// Iter 18a (Decision 10): `param_modes` and `ret_mode` carry the - /// `(borrow T)` / `(own T)` wrappers from the surface form. They - /// are metadata on `Type::Fn`, not new `Type` variants — so - /// unification, occurs, apply, and every other `Type` match-arm - /// keeps working unchanged. `param_modes` is omitted from - /// canonical JSON when every entry is `Implicit`; `ret_mode` is - /// omitted when it is `Implicit`. Pre-18a fixtures therefore hash - /// bit-identically. + /// `param_modes` and `ret_mode` carry the `(borrow T)` / + /// `(own T)` wrappers from the surface form. They are metadata + /// on `Type::Fn`, not new `Type` variants — so unification, + /// occurs, apply, and every other `Type` match-arm keeps working + /// unchanged. `param_modes` is omitted from canonical JSON when + /// every entry is `Implicit`; `ret_mode` is omitted when it is + /// `Implicit`, so pre-mode-annotation fixtures hash + /// bit-identically. Full contract in + /// `design/contracts/memory-model.md`. Fn { params: Vec<Type>, #[serde(default, skip_serializing_if = "all_implicit")] @@ -726,9 +728,9 @@ pub enum Type { /// names, instantiated fresh at each use site. Forall { vars: Vec<String>, - /// Iter 22b.2: class constraints quantified together with - /// `vars`. Empty for pre-22b.2 polymorphic types; serialised - /// with `skip_serializing_if = "Vec::is_empty"` so existing + /// Class constraints quantified together with `vars`. Empty + /// for unconstrained polymorphic types; serialised with + /// `skip_serializing_if = "Vec::is_empty"` so existing /// canonical-JSON bytes stay bit-identical. #[serde(default, skip_serializing_if = "Vec::is_empty")] constraints: Vec<Constraint>, @@ -759,11 +761,11 @@ impl Type { Type::Con { name: "Float".into(), args: vec![] } } - /// Iter 18a: build a `Type::Fn` with all parameter modes set to - /// `ParamMode::Implicit` and `ret_mode` set to `Implicit`. This is - /// the form every typechecker / desugar / codegen site that + /// Build a `Type::Fn` with all parameter modes set to + /// `ParamMode::Implicit` and `ret_mode` set to `Implicit`. This + /// is the form every typechecker / desugar / codegen site that /// synthesises a fn-type should use, so that newly inferred - /// fn-types retain pre-18a canonical-JSON bytes. + /// fn-types retain pre-mode-annotation canonical-JSON bytes. pub fn fn_implicit(params: Vec<Type>, ret: Type, effects: Vec<String>) -> Type { let n = params.len(); Type::Fn { @@ -776,21 +778,21 @@ impl Type { } } -/// Iter 18a (Decision 10): per-parameter / return mode marker on a -/// [`Type::Fn`]. +/// Per-parameter / return mode marker on a [`Type::Fn`]. Full +/// contract lives in `design/contracts/memory-model.md`. /// /// `Implicit` is the legacy state for fn-types that were constructed /// before the borrow/own surface annotations existed. Semantically, -/// `Implicit ≡ Own` throughout the 18-series; the distinction exists -/// only so pre-18a JSON fixtures continue to serialize without a -/// `"mode"` wrapper and therefore keep their canonical-JSON hash. +/// `Implicit ≡ Own`; the distinction exists only so pre-annotation +/// JSON fixtures continue to serialize without a `"mode"` wrapper +/// and therefore keep their canonical-JSON hash. /// /// `Own` and `Borrow` are author-asserted: the surface form /// `(own T)` / `(borrow T)` round-trips through this enum. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum ParamMode { - /// Pre-18a / unannotated. Treated as `Own` by the typechecker. + /// Unannotated / back-compat. Treated as `Own` by the typechecker. #[default] Implicit, /// `(own T)` — caller transfers ownership; callee consumes. @@ -807,21 +809,20 @@ impl ParamMode { } } -/// Iter 18a: serde helper for [`Type::Fn::param_modes`]. Returns -/// `true` when every entry is [`ParamMode::Implicit`] (or when the -/// list is empty), so canonical JSON omits the field for any fn-type -/// without explicit `(borrow)` / `(own)` annotations and pre-18a +/// Serde helper for [`Type::Fn::param_modes`]. Returns `true` when +/// every entry is [`ParamMode::Implicit`] (or when the list is +/// empty), so canonical JSON omits the field for any fn-type without +/// explicit `(borrow)` / `(own)` annotations and pre-annotation /// fixtures keep bit-identical hashes. fn all_implicit(modes: &[ParamMode]) -> bool { modes.iter().all(|m| m.is_implicit()) } -/// Iter 18a: equality of [`ParamMode`] for the purposes of `Type` -/// equality. `Implicit` and `Own` are treated as the same mode -/// throughout the 18-series; `Borrow` is distinct. This keeps -/// pre-18a fixtures (whose fn-types serialize `Implicit`) compatible -/// with newly-written 18a fixtures that mark the same fn-type -/// explicitly with `(own T)`. +/// Equality of [`ParamMode`] for the purposes of `Type` equality. +/// `Implicit` and `Own` are treated as the same mode; `Borrow` is +/// distinct. This keeps pre-annotation fixtures (whose fn-types +/// serialize `Implicit`) compatible with newly-written fixtures +/// that mark the same fn-type explicitly with `(own T)`. fn mode_eq(a: &ParamMode, b: &ParamMode) -> bool { match (a, b) { (ParamMode::Borrow, ParamMode::Borrow) => true, @@ -831,7 +832,7 @@ fn mode_eq(a: &ParamMode, b: &ParamMode) -> bool { } } -/// Iter 18a: equality of two `param_modes` slices, robust to the +/// Equality of two `param_modes` slices, robust to the /// "elided when all-implicit" representation used by typechecker / /// desugar / codegen sites that construct fn-types with /// `param_modes: vec![]`. Both slices are normalised to "implicit @@ -897,9 +898,10 @@ impl Eq for Type {} /// Serde helper for `#[serde(skip_serializing_if = "is_false")]`. /// -/// Used by [`Term::App::tail`] and [`Term::Do::tail`] (Iter 14e) so the -/// `tail` flag is omitted from the canonical JSON whenever it is false, -/// preserving bit-identical hashes for every pre-14e definition. +/// Used by [`Term::App::tail`] and [`Term::Do::tail`] so the `tail` +/// flag is omitted from the canonical JSON whenever it is false, +/// preserving bit-identical hashes for every fixture that does not +/// carry the flag. #[allow(clippy::trivially_copy_pass_by_ref)] fn is_false(b: &bool) -> bool { !*b diff --git a/crates/ailang-core/src/canonical.rs b/crates/ailang-core/src/canonical.rs index 953bd50..3204135 100644 --- a/crates/ailang-core/src/canonical.rs +++ b/crates/ailang-core/src/canonical.rs @@ -51,7 +51,7 @@ pub fn to_bytes<T: serde::Serialize>(value: &T) -> Vec<u8> { out } -/// Iter 22b.1: 16-hex-char hash of a [`crate::ast::Type`] in isolation. +/// 16-hex-char hash of a [`crate::ast::Type`] in isolation. /// /// Used by [`crate::workspace::Registry`] to key `InstanceDef`s by /// their target type. Parallel in shape to [`crate::def_hash`] and @@ -138,7 +138,7 @@ mod tests { assert!(!s.contains('\n')); } - /// Iter 22-floats.1 RED: a `Literal::Float` carries the IEEE-754 + /// A `Literal::Float` carries the IEEE-754 /// binary64 bit pattern as a `u64`; canonical JSON encodes it as /// `{"bits":"<16-lowercase-hex>","kind":"float"}` — string path, /// NOT through `serde_json::Number` (which is not bit-stable for diff --git a/crates/ailang-core/src/desugar.rs b/crates/ailang-core/src/desugar.rs index 317f162..31e48c4 100644 --- a/crates/ailang-core/src/desugar.rs +++ b/crates/ailang-core/src/desugar.rs @@ -29,14 +29,14 @@ //! re-evaluate effectful scrutinees per arm). //! 3. Build a chain of single-level matches via //! `build_chain(s_var, arms, default)` where `default` is the -//! polymorphic bottom builtin `__unreachable__` (`forall a. a`, -//! Iter 16d) — codegen lowers it to LLVM `unreachable`. Valid -//! programs never reach it because the checker requires -//! exhaustiveness (the catch-all arm dominates the chain). -//! Pre-16d the default was a `Unit` literal, which forced any -//! match whose arms returned a non-Unit type to carry a -//! synthetic `_` arm dominating the terminator. The polymorphic -//! `__unreachable__` removes that workaround. +//! polymorphic bottom builtin `__unreachable__` (`forall a. a`) +//! — codegen lowers it to LLVM `unreachable`. Valid programs +//! never reach it because the checker requires exhaustiveness +//! (the catch-all arm dominates the chain). An earlier shape used +//! a `Unit` literal as the default, which forced any match whose +//! arms returned a non-Unit type to carry a synthetic `_` arm +//! dominating the terminator. The polymorphic `__unreachable__` +//! removes that workaround. //! 4. Each arm is lowered via `desugar_one_arm`: //! - `Pattern::Wild` → arm body (catch-all; later arms are dropped). //! - `Pattern::Var { name }` → `Let { name = scrutinee_var; body }`. diff --git a/crates/ailang-core/src/lib.rs b/crates/ailang-core/src/lib.rs index 1c4020c..b601e65 100644 --- a/crates/ailang-core/src/lib.rs +++ b/crates/ailang-core/src/lib.rs @@ -107,11 +107,11 @@ pub type Result<T> = std::result::Result<T, Error>; /// loading fails with [`Error::SchemaMismatch`]. pub const SCHEMA: &str = "ailang/v0"; -/// Iter 20f: complete LLM-targeted specification of Form-A — the -/// canonical authoring surface (Decision 6). Embedded verbatim into -/// any prompt that asks an LLM to produce or edit AILang code; in -/// particular, `ail merge-prose` includes it in the round-trip -/// prompt template. The string is the raw bytes of +/// Complete LLM-targeted specification of Form-A — the canonical +/// authoring surface (see `design/contracts/authoring-surface.md`). +/// Embedded verbatim into any prompt that asks an LLM to produce or +/// edit AILang code; in particular, `ail merge-prose` includes it in +/// the round-trip prompt template. The string is the raw bytes of /// `specs/form_a.md`, co-located with this crate so the spec sits /// next to the AST it describes; a drift test /// (`tests/spec_drift.rs`) walks every AST enum variant and asserts diff --git a/crates/ailang-core/tests/spec_drift.rs b/crates/ailang-core/tests/spec_drift.rs index 5cc7d9e..a771d6a 100644 --- a/crates/ailang-core/tests/spec_drift.rs +++ b/crates/ailang-core/tests/spec_drift.rs @@ -1,4 +1,4 @@ -//! Iter 20f: drift detection between the AST and `specs/form_a.md`. +//! Drift detection between the AST and `specs/form_a.md`. //! //! The spec is hand-curated, but it cannot silently fall behind the //! language. Every AST enum (`Term`, `Pattern`, `Type`, `Def`, `Literal`, diff --git a/crates/ailang-prose/tests/snapshot.rs b/crates/ailang-prose/tests/snapshot.rs index 9b8c1a2..2342caa 100644 --- a/crates/ailang-prose/tests/snapshot.rs +++ b/crates/ailang-prose/tests/snapshot.rs @@ -1,4 +1,4 @@ -//! Iter 20a snapshot tests. +//! Prose-projection snapshot tests. //! //! For each `examples/<name>.ail`, the renderer's output is compared //! against the committed `examples/<name>.prose.txt`. The snapshot diff --git a/design/INDEX.md b/design/INDEX.md index bee3686..504a181 100644 --- a/design/INDEX.md +++ b/design/INDEX.md @@ -35,9 +35,9 @@ evolving in lockstep with the language: `crates/ailang-codegen`): AST, type system, codegen. - **Surface forms** (`crates/ailang-surface`, `crates/ailang-prose`): the LLM-facing renderings of a module. `ailang-surface` - is the lossless Form-A printer/parser — the canonical authoring - surface fixed by Decision 6, with a round-trip property - `parse ∘ print = id` gating every release. `ailang-prose` + is the lossless Form-A printer/parser — the canonical + [authoring surface](contracts/authoring-surface.md), with a + round-trip property `parse ∘ print = id` gating every release. `ailang-prose` is the lossy Form-B projection — human-readable prose for review and edit, with no parser; re-integration goes through the LLM-mediator round-trip documented in `docs/PROSE_ROUNDTRIP.md`. diff --git a/design/contracts/authoring-surface.md b/design/contracts/authoring-surface.md index 0cc8ea9..a172e7c 100644 --- a/design/contracts/authoring-surface.md +++ b/design/contracts/authoring-surface.md @@ -1,6 +1,6 @@ # Authoring surface -## Decision 6: authoring surface +## Authoring surface Form (A) is implemented as the `ailang-surface` crate (parser + printer). Form-A is @@ -22,7 +22,7 @@ one projection among potentially many. Concretely: addressed representation of a module. All hashing, content- addressing, cross-module references, and typecheck/codegen input flow through the JSON-AST. **No new hashable form is introduced.** -- The textual surface (form A, this Decision) is the **AI authoring +- The textual surface (form A, this contract) is the **AI authoring projection**: optimised for me producing programs token-efficiently and for foreign LLMs producing programs from a spec. It is not optimised for human authors and does not need to be human-pleasant. @@ -55,11 +55,11 @@ the AST and remain projection-agnostic. shape. The full bijection between `.ail.json` and `.ail` (both directions, BLAKE3-stable hashing, Float-bits-hex encoding, workspace-CI enforcement points) is anchored as the top-level - [Roundtrip Invariant](roundtrip-invariant.md) — this constraint records that Decision - 6's surface-design choice must satisfy that invariant; the - invariant itself lives at top level because the property is - load-bearing on the language identity, not on this Decision's - surface-design rationale. + [Roundtrip Invariant](roundtrip-invariant.md) — this constraint + records that the surface-design choice must satisfy that + invariant; the invariant itself lives at top level because the + property is load-bearing on the language identity, not on this + file's surface-design rationale. 3. **No external symbols.** ASCII only. No Greek (`∀`), no arrows (`→`), no subscripts. Reasoning: I substitute mojibake for non-ASCII characters under context pressure; foreign LLMs vary diff --git a/design/contracts/data-model.md b/design/contracts/data-model.md index c15fdae..b1af7ae 100644 --- a/design/contracts/data-model.md +++ b/design/contracts/data-model.md @@ -41,7 +41,7 @@ narrative — defaults, superclasses, diagnostics — lives in "params": ["<id>"...], // names bound in body, in type.params order "body": Term, "doc": "<optional string>", - "export": "<optional C symbol>", // omitted when absent (hash-stable when omitted); see §"Embedding ABI" + "export": "<optional C symbol>", // omitted when absent (hash-stable when omitted); embedding-ABI surface — see prose below "suppress": [Suppress...] // omitted when empty } @@ -65,11 +65,11 @@ narrative — defaults, superclasses, diagnostics — lives in "drop-iterative": true // opt-in; omitted when false (hash-stable when omitted) } -// class (typeclass declaration; see Decision 11) +// class (typeclass declaration; narrative in contracts/typeclasses.md) { "kind": "class", "name": "<id>", // class name (e.g. "Show") "param": "<id>", // single class parameter, kind * - "superclass": null, // or { "class": "<id>", "type": "<param>" } — "class": canonical form (bare for same-module, "<module>.<Class>" for cross-module; see §"Class names" / mq.1) + "superclass": null, // or { "class": "<id>", "type": "<param>" } — "class": canonical form (bare for same-module, "<module>.<Class>" for cross-module) "methods": [ { "name": "<id>", "type": Type, // FnSig over the class param @@ -80,9 +80,9 @@ narrative — defaults, superclasses, diagnostics — lives in "doc": "<optional string>" } -// instance (typeclass instance; see Decision 11) +// instance (typeclass instance; narrative in contracts/typeclasses.md) { "kind": "instance", - "class": "<id>", // class being instantiated; canonical form (bare for same-module, "<module>.<Class>" for cross-module; see §"Class names" / mq.1) + "class": "<id>", // class being instantiated; canonical form (bare for same-module, "<module>.<Class>" for cross-module) "type": Type, // concrete type expression (never the class param) "methods": [ { "name": "<id>", "body": Term } @@ -215,13 +215,14 @@ metadata is defined and gated there as well. ```jsonc // Type-constructor application. `args` omitted when empty // (hash-stable when omitted, for non-parameterised cases like Int, Bool, ...). -{ "k": "con", "name": "<id>", "args": [Type...] } // "name": canonical form (bare for same-module / primitives, "<module>.<TypeName>" for cross-module; see §"Type::Con name scoping" / ct.1) +{ "k": "con", "name": "<id>", "args": [Type...] } // "name": canonical form (bare for same-module / primitives, "<module>.<TypeName>" for cross-module) -// Function type. Decision 10 added paramModes/retMode as -// metadata on Type::Fn — they are NOT separate Type variants, so every -// existing match-arm in the typechecker (unify, occurs, apply) keeps -// working. `paramModes` omitted when every entry is "implicit"; -// `retMode` omitted when "implicit" (hash-stable when omitted). +// Function type. paramModes/retMode are metadata on Type::Fn — +// they are NOT separate Type variants, so every existing match-arm +// in the typechecker (unify, occurs, apply) keeps working. +// `paramModes` omitted when every entry is "implicit"; `retMode` +// omitted when "implicit" (hash-stable when omitted). Full mode +// contract lives in contracts/memory-model.md. { "k": "fn", "params": [Type...], "paramModes": [ParamMode...], @@ -232,14 +233,16 @@ metadata is defined and gated there as well. { "k": "var", "name": "<id>" } // Top-level polymorphism only. `constraints` carries class -// constraints (Decision 11); omitted when empty (hash-stable when omitted). +// constraints (narrative in contracts/typeclasses.md); omitted when +// empty (hash-stable when omitted). { "k": "forall", "vars": ["<id>"...], - "constraints": [{ "class": "<id>", "type": "<id>" }, ...], // "class": canonical form (bare for same-module, "<module>.<Class>" for cross-module; see §"Class names" / mq.1) + "constraints": [{ "class": "<id>", "type": "<id>" }, ...], // "class": canonical form (bare for same-module, "<module>.<Class>" for cross-module) "body": Type } ``` -**`ParamMode`** (Decision 10): +**`ParamMode`** (full contract in +[memory model](memory-model.md)): ``` "implicit" — unannotated / back-compat. Treated as `own` by the typechecker. diff --git a/design/contracts/feature-acceptance.md b/design/contracts/feature-acceptance.md index ebe5fdf..b5bcb8c 100644 --- a/design/contracts/feature-acceptance.md +++ b/design/contracts/feature-acceptance.md @@ -36,16 +36,16 @@ A proposed feature ships only if all three hold: discouraged. A documentation note is not a reshape; the discriminator is whether the wrong code fails to typecheck, not whether a guideline advises against it. Worked example: a bare - `while` over mutable state would pass clauses 1 and 2 yet fail - clause 3 (it reinstates iterated-mutable-state reasoning the pure - core exists to remove); a hypothetical "all repetition is either - structurally-decreasing recursion over an acyclic ADT or an - explicit named loop" iteration story would, *if it could be built - without a documented-unenforced precondition*, pass all three — - and the fact that the 2026-05 attempt could not (it forced a - silent-divergence precondition; see + `while` over mutable state would pass criteria 1 and 2 yet fail + criterion 3 (it reinstates iterated-mutable-state reasoning the + pure core exists to remove); a hypothetical "all repetition is + either structurally-decreasing recursion over an acyclic ADT or + an explicit named loop" iteration story would, *if it could be + built without a documented-unenforced precondition*, pass all + three — and the fact that the 2026-05 attempt could not (it + forced a silent-divergence precondition; see `docs/specs/2026-05-16-iteration-discipline-revert.md`) is itself - the clause-3 mechanism working as intended. + the bug-class-reintroduction discriminator working as intended. This is the positive complement to the CLAUDE.md rule that implementation effort is not a rationale: cost is not a reason *for* a diff --git a/design/contracts/float-semantics.md b/design/contracts/float-semantics.md index afc6603..d46f2a8 100644 --- a/design/contracts/float-semantics.md +++ b/design/contracts/float-semantics.md @@ -56,8 +56,8 @@ no `f32` variant. The runtime / codegen contract: `0x7ff8000000000000` on one target and a different qNaN on another. - The textual rendering of NaN through `float_to_str` (the runtime - C helper that backs `instance Show Float` and, post-iter-rpe.1, - every Float-typed `print` call). The libc `printf("%g", nan)` + C helper that backs `instance Show Float` and every Float-typed + `print` call). The libc `printf("%g", nan)` glue used by `float_to_str` is permitted to emit `nan` / `-nan` / `NaN` etc. depending on libc version and the NaN's sign bit; AILang does not normalise this, since the prose / surface-print diff --git a/design/contracts/honesty-rule.md b/design/contracts/honesty-rule.md index 6699f63..b07bbee 100644 --- a/design/contracts/honesty-rule.md +++ b/design/contracts/honesty-rule.md @@ -15,10 +15,10 @@ Two things never belong in a contract or model file: A cross-reference that does belong stays: it is a formal, file-relative Markdown link into the durable tier (`design/` or -source), enforced by -[`design_index_pin.rs`](../../crates/ailang-core/tests/design_index_pin.rs) -clause-5. A reference that cannot be expressed as such a link is, -by that fact, the history-or-rationale prose the rule above removes. +source), enforced by the body-link durability gate in +[`design_index_pin.rs`](../../crates/ailang-core/tests/design_index_pin.rs). +A reference that cannot be expressed as such a link is, by that +fact, the history-or-rationale prose the rule above removes. The single legitimate exception is a present-tense reserved or deliberately-excluded claim that is explicitly and correctly diff --git a/design/contracts/language-constraints.md b/design/contracts/language-constraints.md index 1bd99a9..9a2a9d4 100644 --- a/design/contracts/language-constraints.md +++ b/design/contracts/language-constraints.md @@ -4,8 +4,9 @@ The four constraints below are necessary preconditions for RC to be sound and complete *without* a cycle-collector backstop. They -are not new — AILang already satisfies all four — but Decision 10 -makes them load-bearing rather than incidental: +are not new — AILang already satisfies all four — but the +[memory model](memory-model.md) makes them load-bearing rather +than incidental: 1. **Strict evaluation.** Every `Term::App` (see [Data model](data-model.md)) argument is fully @@ -33,8 +34,8 @@ These constraints are the precondition for the [memory model](memory-model.md): a sound RC implementation without a cycle-collector backstop requires the reference graph to be a DAG, which constraint 4 establishes from 1–3. They are also the -load-bearing structural reason behind several -[feature-acceptance](feature-acceptance.md) clause-3 rejections -(notably iterated-mutable-state reasoning). +load-bearing structural reason behind several rejections under the +[feature-acceptance](feature-acceptance.md) bug-class-reintroduction +discriminator (notably iterated-mutable-state reasoning). Ratified by: `crates/ailang-check/src/uniqueness.rs` (in-source mod tests). diff --git a/design/contracts/memory-model.md b/design/contracts/memory-model.md index 917eb3a..c90b61b 100644 --- a/design/contracts/memory-model.md +++ b/design/contracts/memory-model.md @@ -32,7 +32,8 @@ schema). The substantive reasons for per-position metadata over a `Type` would let the schema express forms like `(con List (borrow Int))` — syntactically possible, semantically meaningless (you cannot separately own/borrow a list element - from the list it lives in). Decision 1 is "schema = data, + from the list it lives in). The + [canonical-schema principle](data-model.md) is "schema = data, schema permits exactly what is meaningful"; per-position metadata is the option that holds that line. - **Compositional clarity.** A `Type` value's identity should @@ -62,7 +63,7 @@ identical: `param_modes` is skipped when every entry is `Implicit`, `ret_mode` is skipped when `Implicit`. Existing modules emit the same bytes as before. -**Type::Con name scoping (canonical form, since ct.1).** Within a +**Type::Con name scoping (canonical form).** Within a `.ail.json`, a `Type::Con.name` is interpreted relative to the file's top-level `"name"` field (the owning module). Bare names (no `.`) refer to a TypeDef in the owning module's own `defs`. @@ -75,15 +76,16 @@ qualified references whose owner is unknown are also a violation (`WorkspaceLoadError::BadCrossModuleTypeRef`). The same rule applies to `Term::Ctor.type_name`. -Class names follow the canonical-form rule (mq.1): bare for +Class names follow the same canonical-form rule: bare for same-module references, `<module>.<Class>` for cross-module -references — symmetric to `Type::Con.name`'s rule from ct.1. +references — symmetric to `Type::Con.name`'s rule above. Three schema fields carry class references in this form: `InstanceDef.class`, `Constraint.class`, and `SuperclassRef.class`. `ClassDef.name` itself stays bare (defining-site context, like `TypeDef.name`). -Method dispatch is type-driven post-mq.3 (see [Method dispatch](method-dispatch.md)): +Method dispatch is type-driven (see +[Method dispatch](method-dispatch.md)): synth resolves a `Term::Var { name: "show" }` by consulting the workspace's method-to-candidate-class index, filtering by argument type (concrete) or by declared constraint (rigid-var), and @@ -161,24 +163,24 @@ Skipped from serialisation when empty so existing fixtures keep bit-identical canonical-JSON hashes (regression-pinned by `iter19b_empty_suppress_preserves_pre_19b_hashes` and `iter19b_schema_extension_preserves_pre_19b_hashes`). -The canonical-form tightening in ct.1 shifted the hashes of two -cross-module fixtures (`ordering_match.ail.json` and +The canonical-form tightening for `Type::Con.name` shifted the +hashes of two cross-module fixtures (`ordering_match.ail.json` and `test_22b1_dup_a.ail.json`); all intra-module fixtures, including the regression-pinned `sum.ail.json` and `list.ail.json`, remain bit-identical. The new pins are `ct4_migrated_fixtures_have_canonical_form_hashes` (locks the post-migration hashes) and `ct4_unmigrated_fixtures_remain_bit_identical` (re-asserts the -existing 13a/19b/22b.1 hashes still hold). +pre-tightening hashes still hold). ## Advisory diagnostics The advisory-diagnostics arc introduces the language's first **advisory** typechecker diagnostic and the suppression mechanism -that goes with it. Decision 10's mandatory-annotation rule is -unchanged: `param_modes` and `ret_mode` remain author-required; -the typechecker does not infer them. What's new is feedback when -an authored annotation is *stricter than necessary*. +that goes with it. The mandatory-annotation rule of this memory +model is unchanged: `param_modes` and `ret_mode` remain +author-required; the typechecker does not infer them. What's new +is feedback when an authored annotation is *stricter than necessary*. **The lint: `over-strict-mode`.** Fires on a fn-param `p` annotated `(own T)` when: diff --git a/design/contracts/method-dispatch.md b/design/contracts/method-dispatch.md index 7f309c4..9a05de1 100644 --- a/design/contracts/method-dispatch.md +++ b/design/contracts/method-dispatch.md @@ -6,14 +6,16 @@ The class-method resolution rule that pairs with the [typeclasses](typeclasses.md) contract; the `Show`/`Eq`/`Ord` instances the rule dispatches against live in [prelude classes](prelude-classes.md). -Post-mq.3, dispatch is two-mode: +Dispatch is two-mode: **Polymorphic call sites** (inside a fn body with `forall` + constraint set): the constraint names the class via the qualified -`Constraint.class` field (canonical-form per mq.1). Synth's -residual carries the class name directly; constraint-discharge at -fn-body-end matches against the workspace registry by -`(class, type_hash)` key. +`Constraint.class` field (canonical form: bare for same-module, +`<module>.<Class>` for cross-module; see +[memory model](memory-model.md) for the canonical-form rule). +Synth's residual carries the class name directly; +constraint-discharge at fn-body-end matches against the workspace +registry by `(class, type_hash)` key. **Monomorphic call sites**: synth consults the workspace-flat `Env.method_to_candidate_classes: BTreeMap<MethodName, @@ -42,7 +44,7 @@ dispatch rule: The `method_to_candidate_classes` index is the load-bearing data structure for this routing — its construction in `build_check_env` inverts the per-module `class_methods` maps (themselves tuple-keyed -by `(qualified-class, method)` post-mq.3) to a workspace-flat +by `(qualified-class, method)`) to a workspace-flat method-name-to-class-set map. Class-fn collisions resolve at the call site, not at workspace load diff --git a/design/contracts/prelude-classes.md b/design/contracts/prelude-classes.md index d94dc94..315c285 100644 --- a/design/contracts/prelude-classes.md +++ b/design/contracts/prelude-classes.md @@ -24,7 +24,8 @@ corresponding runtime primitive (`int_to_str`, `bool_to_str`, heap-Str primitives); no codegen intercept is required. The polymorphic helper `print : forall a. Show a => a -> () !IO` has body `\x -> let s = show x in do io/print_str s` (explicit let-binder for -heap-Str RC discipline per eob.1 Str carve-out). The let-binder is +heap-Str RC discipline per the Str carve-out in +[Str ABI](str-abi.md)). The let-binder is structurally pinned by `crates/ail/tests/print_mono_body_shape.rs`. Routing through `print` is the path for non-`Str` primitives; `io/print_str` is the only built-in direct-output effect-op. diff --git a/design/contracts/roundtrip-invariant.md b/design/contracts/roundtrip-invariant.md index 8f1291e..4647550 100644 --- a/design/contracts/roundtrip-invariant.md +++ b/design/contracts/roundtrip-invariant.md @@ -85,15 +85,15 @@ never by relaxing the test. ### Why this is anchored at top level The invariant is a property of the language identity, not of any -one surface-design Decision. Decision 6 introduces the `.ail` -surface and lists round-trip-as-property as one of its -constraints, but the property is load-bearing for every +one surface-design choice. The [authoring surface](authoring-surface.md) +introduces the `.ail` surface and lists round-trip-as-property as +one of its constraints, but the property is load-bearing for every downstream concern that treats the two forms as exchangeable: content-addressed hashing, the LLM-author's choice of authoring form, the integrity of fixture cross-references, and the prerequisite for empirical cross-model authoring-form studies. -Lifting it out of Decision 6 makes the property quotable on its -own and reviewable by the architect agent at every milestone -close. +Lifting it out of the authoring-surface contract makes the property +quotable on its own and reviewable by the architect agent at every +milestone close. Ratified by: `crates/ailang-surface/tests/round_trip.rs`. diff --git a/design/contracts/scope-boundaries.md b/design/contracts/scope-boundaries.md index 32ce608..476f0b9 100644 --- a/design/contracts/scope-boundaries.md +++ b/design/contracts/scope-boundaries.md @@ -118,7 +118,8 @@ What **is** supported (and used as the smoke test for the pipeline): pair allocates either via `@GC_malloc` (escaping; Boehm-managed) or via LLVM `alloca` (non-escaping; freed at fn return). The decision is made by an escape-analysis pre-pass over the fn body — see - Decision 9's "Per-fn arena via stack `alloca`" subsection. + the "Per-fn arena via stack `alloca`" subsection of + [RC + uniqueness](../models/rc-uniqueness.md). Boehm-only soak tests are unchanged: `examples/gc_stress.ail.json` and `examples/std_list_stress.ail.json` still allocate via `@GC_malloc` because their boxes flow into other fns and escape. diff --git a/design/contracts/tail-calls.md b/design/contracts/tail-calls.md index ae8f424..ddcb996 100644 --- a/design/contracts/tail-calls.md +++ b/design/contracts/tail-calls.md @@ -1,6 +1,6 @@ # Tail calls -## Decision 8: explicit, verified tail calls +## Explicit, verified tail calls For an LLM author, recursion is the natural iteration form (`\n. if n == 0 then () else loop(n-1)` is what I reach for, not diff --git a/design/contracts/typeclasses.md b/design/contracts/typeclasses.md index 0d187c0..6ac2b66 100644 --- a/design/contracts/typeclasses.md +++ b/design/contracts/typeclasses.md @@ -74,15 +74,18 @@ exactly as for any free function. ## Cross-module references in synthesised bodies -The unified mono pass (per Decision 11's milestone-23.4 reorganisation) -synthesises mono symbols for polymorphic free fns and class-method -instances in the symbol's owner module — e.g. `print__Int` lives in -`prelude` (because `print` is defined in the prelude), but a -user-ADT call site `print (MkIntBox 7)` causes synthesis of -`prelude.print__IntBox` whose body references -`show_user_adt.show__IntBox` (the user instance lives in the -user-defining module per Decision 11 coherence). The synthesised body -crosses a module boundary the source template did not. +The unified mono pass (see +[models/typeclasses](../models/typeclasses.md) for the +resolution/monomorphisation algorithm) synthesises mono symbols for +polymorphic free fns and class-method instances in the symbol's +owner module — e.g. `print__Int` lives in `prelude` (because `print` +is defined in the prelude), but a user-ADT call site +`print (MkIntBox 7)` causes synthesis of `prelude.print__IntBox` +whose body references `show_user_adt.show__IntBox` (the user +instance lives in the user-defining module per the +orphan-free-coherence rule documented in +[models/typeclasses](../models/typeclasses.md)). The synthesised +body crosses a module boundary the source template did not. Three invariants make this work: @@ -127,7 +130,7 @@ Three invariants make this work: workspace registry; if no instance satisfies the residual at the unified concrete type, the `NoInstance` diagnostic fires at typecheck (correctly), not at codegen (confusingly). Without the - residual push, milestone-23-shape negative cases (e.g. `print f` + residual push, polymorphic-helper negative cases (e.g. `print f` where `f : Int -> Int`) silently typecheck and surface as `unknown variable: show` from codegen instead of the right typecheck-phase NoInstance Show. @@ -211,17 +214,17 @@ module. - `MissingConstraint` — body's residual constraint is not covered by declared (and superclass-expanded) constraints. - `NoInstance` — fully concrete constraint has no registry entry. - mq.2 adds an optional `candidate_classes` field surfacing the + Carries an optional `candidate_classes` field surfacing the multi-candidate set when the bare-method dispatch path's filter collapses to zero registry survivors. -- `AmbiguousMethodResolution` (mq.2) — a monomorphic `Term::Var` +- `AmbiguousMethodResolution` — a monomorphic `Term::Var` call site survives both type-driven and constraint-driven filters with more than one candidate class. LLM-author writes the explicit qualifier form `<module>.<Class>.<method>` to disambiguate. -- `UnknownClass` (mq.2) — an explicit class qualifier in +- `UnknownClass` — an explicit class qualifier in `Term::Var.name` names a qualified class that is not in the workspace's candidate-class index for the method. -- `class-method-shadowed-by-fn` (mq.3, warning) — a `Term::Var` +- `class-method-shadowed-by-fn` (warning) — a `Term::Var` resolved via fn lookup precedence (locals → caller-module-fn → imported-fn) while a class method of the same name also exists in the workspace. Fn resolution proceeds; the warning surfaces diff --git a/design/models/effects.md b/design/models/effects.md index 35613ff..866f2c5 100644 --- a/design/models/effects.md +++ b/design/models/effects.md @@ -1,6 +1,6 @@ # Effects — pure core + algebraic effects -## Decision 3: pure core language + algebraic effects +## Pure core language + algebraic effects The default is total, pure functions. Effects are declared as a set in the function type: `(Int) -> Int ![IO]`. The effect set is a @@ -13,7 +13,9 @@ currently in scope vs. excluded lives in In the MVP only the effect `IO` is wired up (its sole op is `io/print_str`). `Diverge` (for non-termination) is reserved as an effect name but is unimplemented — no op, no codegen, no checker injection — in the same -sense Decision 4 reserves refinements. +sense the refinements layer is reserved as a future extension without +present-day surface (see +[scope boundaries](../contracts/scope-boundaries.md)). This is the most important LLM property: when I read a function, I can trust its signature without reading the body. diff --git a/design/models/pipeline.md b/design/models/pipeline.md index 6aa4946..10cf1e9 100644 --- a/design/models/pipeline.md +++ b/design/models/pipeline.md @@ -7,7 +7,7 @@ ├─ load + validate schema ├─ resolve names + assign hashes ├─ desugar (AST → AST) - ├─ typecheck (HM, effect rows; mode-strict per Decision 10) + ├─ typecheck (HM, effect rows; mode-strict per the memory model) ├─ lift_letrecs (post-typecheck AST → AST) ├─ lower to MIR (SSA-like, named SSA values) ├─ emit LLVM IR (.ll) diff --git a/design/models/prose-projection.md b/design/models/prose-projection.md index cbf3413..024811f 100644 --- a/design/models/prose-projection.md +++ b/design/models/prose-projection.md @@ -5,9 +5,9 @@ AILang ships a second textual projection of the AST: `ailang-prose`, a one-way projection from `Module → human-readable text`. It is **not** an -authoring surface; it is the "display" projection that Decision 6's -architectural pin (see [authoring surface](../contracts/authoring-surface.md)) -explicitly anticipated: +authoring surface; it is the "display" projection that the +[authoring surface](../contracts/authoring-surface.md)'s +architectural pin explicitly anticipated: > *"Future projections are explicitly anticipated: a visual / > graphical front-end is a plausible second projection for human @@ -41,7 +41,8 @@ form (B) deliberately is not. Re-integrating prose edits requires an external LLM mediator, not a compiler pass — the prompt template `ail merge-prose` composes the six-step cycle. -Form (B) does not weaken any Decision 6 invariant: +Form (B) does not weaken any +[authoring surface](../contracts/authoring-surface.md) invariant: - The [JSON-AST](../contracts/data-model.md) remains the only hashable artefact. Prose is not hashed, not content-addressed, diff --git a/design/models/rc-uniqueness.md b/design/models/rc-uniqueness.md index 20401d7..d93ec6f 100644 --- a/design/models/rc-uniqueness.md +++ b/design/models/rc-uniqueness.md @@ -1,6 +1,6 @@ # RC + Uniqueness — memory model whitepaper -## Decision 9: dual allocator — RC canonical, Boehm parity oracle +## Dual allocator — RC canonical, Boehm parity oracle AILang ships two allocator backends with an asymmetric role: @@ -25,12 +25,11 @@ when the parity oracle stops paying its keep — concretely, when a few iter families ship without the gc arm catching anything that the rc arm did not already catch. Until then, the cost of keeping libgc as a build dependency is accepted in exchange for -diagnostic leverage. Decision 10 (RC + uniqueness) holds as the -specification of the canonical runtime (the contract version is -[memory model](../contracts/memory-model.md), built on -[language constraints](../contracts/language-constraints.md)); -the rest of this section documents the Boehm half, retained as -the oracle. +diagnostic leverage. The +[RC + uniqueness memory model](../contracts/memory-model.md), built +on the [language constraints](../contracts/language-constraints.md), +holds as the specification of the canonical runtime; the rest of +this section documents the Boehm half, retained as the oracle. **Choice: Boehm-Demers-Weiser conservative GC.** The simplest working option: @@ -130,7 +129,7 @@ The closure-pair and its env share an escape verdict — they have parallel lifetimes. If the closure pair is non-escaping, the env is too. -## Decision 10: memory model — RC + Uniqueness with LLM-author annotations +## Memory model — RC + Uniqueness with LLM-author annotations **The GC bench (`bench/run.sh`) showed Boehm contributing a substantial fraction of runtime on allocation-heavy workloads @@ -150,9 +149,10 @@ stdlib is the cheapest moment to commit. **Choice.** AILang's canonical [memory model](../contracts/memory-model.md) is reference counting with static uniqueness inference **and explicit LLM-author annotations on fn signatures**, in the lineage -of Lean 4 / Roc / Koka. Boehm becomes a transitional allocator (Decision 9) and is -retired when the RC pipeline matches the bump-allocator floor -within an acceptable margin (target 1.3× on `bench/run.sh`). +of Lean 4 / Roc / Koka. Boehm becomes a transitional allocator (see +the dual-allocator section above) and is retired when the RC pipeline +matches the bump-allocator floor within an acceptable margin (target +1.3× on `bench/run.sh`). **Workload scope of the 1.3× target.** The 1.3× target was calibrated on the original `bench/run.sh` corpus: linear list @@ -168,16 +168,17 @@ one bump-pointer bump, doubling the allocation tax on closure construction (current ratio recorded in JOURNAL bench entries). This is a representational cost of the closure-pair layout, not a defect in the RC implementation; a future slab/pool -allocator for fixed-shape pair cells (Decision 9 retirement -follow-up) would compress this ratio without changing semantics. +allocator for fixed-shape pair cells (a Boehm-retirement follow-up) +would compress this ratio without changing semantics. The 1.3× retirement target therefore applies to the linear / tree / poly-ADT subset of the corpus. Closure-heavy workloads are tracked under a wider band (the closure-chain baseline records its rc/bump ratio as the `rc_over_bump` reference value with ±15% tolerance) and are explicitly excluded from the Boehm-retirement gate until a -slab/pool answer ships. Decision-10's RC commitment is unchanged; -what is scoped is the *quantitative* retirement criterion, not -the choice of memory model. +slab/pool answer ships. The +[memory model](../contracts/memory-model.md)'s RC commitment is +unchanged; what is scoped is the *quantitative* retirement criterion, +not the choice of memory model. The architecture has two layers: diff --git a/design/models/typeclasses.md b/design/models/typeclasses.md index f8ac9bc..84864cf 100644 --- a/design/models/typeclasses.md +++ b/design/models/typeclasses.md @@ -8,7 +8,7 @@ built-in classes shipped in the prelude in covers the design choices and the resolution / monomorphisation algorithm. -## Decision 11: typeclasses — Haskell-lite, monomorphised, coherent +## The design — Haskell-lite, monomorphised, coherent **The design pass for typeclasses. Codified after the [Feature-acceptance criterion](../contracts/feature-acceptance.md) @@ -158,10 +158,9 @@ component contains `__` by project convention. **No runtime dispatch, no dictionary passing.** The monomorphisation pass is the ONLY specialiser. Codegen sees only monomorphic -`Def::Fn`s and direct calls; the pre-iter-23.4 codegen-time -specialiser (`lower_polymorphic_call` + `module_polymorphic_fns` + -`mono_queue`) was removed in iter 23.4. A call that cannot be -monomorphised — for instance, because a constraint remains -unresolved at the entry point — is a static error, not a runtime -one. This is the LLVM-friendly form and is consistent with -Decision 10's performance commitment. +`Def::Fn`s and direct calls. A call that cannot be monomorphised — +for instance, because a constraint remains unresolved at the entry +point — is a static error, not a runtime one. This is the +LLVM-friendly form and is consistent with the performance +commitment of the +[RC + uniqueness memory model](../contracts/memory-model.md).