convention: counter-prefix file naming across docs/specs/, docs/plans/, design/contracts/, design/models/

All 176 files in the four accumulating directories now use a
zero-padded 4-digit counter prefix that reflects creation order
(`NNNN-slug.md`). The counter is assigned per directory in strict
git-log creation order; ties broken alphabetically by original name.
The old `YYYY-MM-DD-` prefix on docs/specs/ and docs/plans/ files is
dropped — the date is recoverable from git log and the counter
carries the ordering.

A file's counter is stable for the life of the file: never reassigned,
never reused, never compacted. Deleted files retire their counter;
subsequent files do not fill the gap. This is the property that lets
cross-references stay literal — refs use the full filename including
the counter (`design/contracts/0007-honesty-rule.md`) so they grep
cleanly and resolve directly without a glob step.

313 cross-references updated across .md/.rs/.toml/.c/.json files
(test pins, include_str! paths, design-INDEX entries, baseline notes,
runtime C comments, inter-contract markdown links incl. bare basename
and `../models/foo.md` forms).

CLAUDE.md gets a new "File-naming convention" section spelling out
the rule and rationale. skills/brainstorm/SKILL.md and
skills/planner/SKILL.md updated so new spec/plan creation produces
counter-prefixed names from the start.

The full test suite (cargo test --workspace) passes.
This commit is contained in:
2026-05-28 13:31:31 +02:00
parent 7b8596cef0
commit 832375f2ac
227 changed files with 785 additions and 751 deletions
@@ -18,7 +18,7 @@ the same time, leaving only diagnostic helpers (`type_to_string`,
The textual surface is **not** a replacement for the JSON-AST. It is
one projection among potentially many. Concretely:
- The [JSON-AST](data-model.md) keeps its role as the canonical, hashable, content-
- The [JSON-AST](0002-data-model.md) keeps its role as the canonical, hashable, content-
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.**
@@ -55,7 +55,7 @@ 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
[Roundtrip Invariant](0009-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
@@ -28,10 +28,10 @@ contract is what makes growing the schema cheap.
are real surface forms. Class and type cross-module references
(canonical-form rule, qualified `<module>.<Class>` /
`<module>.<TypeName>`) follow the scoping rule in
[memory model](memory-model.md); the `class`/`instance` schema
[memory model](0008-memory-model.md); the `class`/`instance` schema
narrative — defaults, superclasses, diagnostics — lives in
[typeclasses](typeclasses.md). Exported `fn` defs interact with
[embedding ABI](embedding-abi.md).
[typeclasses](0013-typeclasses.md). Exported `fn` defs interact with
[embedding ABI](0003-embedding-abi.md).
```jsonc
// fn (the unit that gets a content hash)
@@ -65,7 +65,7 @@ narrative — defaults, superclasses, diagnostics — lives in
"drop-iterative": true // opt-in; omitted when false (hash-stable when omitted)
}
// class (typeclass declaration; narrative in contracts/typeclasses.md)
// class (typeclass declaration; narrative in contracts/0013-typeclasses.md)
{ "kind": "class",
"name": "<id>", // class name (e.g. "Show")
"param": "<id>", // single class parameter, kind *
@@ -80,7 +80,7 @@ narrative — defaults, superclasses, diagnostics — lives in
"doc": "<optional string>"
}
// instance (typeclass instance; narrative in contracts/typeclasses.md)
// instance (typeclass instance; narrative in contracts/0013-typeclasses.md)
{ "kind": "instance",
"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)
@@ -165,7 +165,7 @@ narrative — defaults, superclasses, diagnostics — lives in
// non-`recur` branch. Strictly additive (no `skip_serializing_if`;
// pre-existing fixtures hash bit-identically — none carry the tag).
// No totality claim — an infinite loop is legal. See
// `docs/specs/2026-05-17-loop-recur.md`.
// `docs/specs/0034-loop-recur.md`.
{ "t": "loop",
"binders": [ { "name": "<id>", "type": Type, "init": Term }, ... ],
"body": Term }
@@ -179,7 +179,7 @@ narrative — defaults, superclasses, diagnostics — lives in
```
In the MVP, `do` is only a direct call to a built-in effect op (no
handler); the effect system is described in [effects](../models/effects.md).
handler); the effect system is described in [effects](../models/0002-effects.md).
A `lam` term constructs an anonymous function value; free
variables of its body are captured from the enclosing scope.
@@ -188,7 +188,7 @@ ordinary local scope plus a positional `loop_stack`, and codegen
lowers them as entry-block allocas. Capturing a `loop` binder into a
lambda body is rejected at typecheck via
`CheckError::LoopBinderCapturedByLambda`. See
`docs/specs/2026-05-17-loop-recur.md`.
`docs/specs/0034-loop-recur.md`.
**`Literal`**:
@@ -215,7 +215,7 @@ Patterns are linear: each pattern variable may appear at most once.
The `Type::Con.name` canonical-form rule (bare for same-module /
primitives, qualified `<module>.<TypeName>` for cross-module) lives
in [memory model](memory-model.md); `Type::Fn`'s parameter-mode
in [memory model](0008-memory-model.md); `Type::Fn`'s parameter-mode
metadata is defined and gated there as well.
```jsonc
@@ -228,7 +228,7 @@ metadata is defined and gated there as well.
// 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.
// contract lives in contracts/0008-memory-model.md.
{ "k": "fn",
"params": [Type...],
"paramModes": [ParamMode...],
@@ -239,7 +239,7 @@ metadata is defined and gated there as well.
{ "k": "var", "name": "<id>" }
// Top-level polymorphism only. `constraints` carries class
// constraints (narrative in contracts/typeclasses.md); omitted when
// constraints (narrative in contracts/0013-typeclasses.md); omitted when
// empty (hash-stable when omitted).
{ "k": "forall",
"vars": ["<id>"...],
@@ -248,7 +248,7 @@ metadata is defined and gated there as well.
```
**`ParamMode`** (full contract in
[memory model](memory-model.md)):
[memory model](0008-memory-model.md)):
```
"implicit" — unannotated / back-compat. Treated as `own` by the typechecker.
@@ -260,8 +260,8 @@ metadata is defined and gated there as well.
unannotated fixtures continue to serialize without the mode wrapper and keep their
canonical-JSON hash. The full mode contract (codegen consequences,
the over-strict-mode lint, the `Suppress` mechanism) lives in
[memory model](memory-model.md); the four language-design
[memory model](0008-memory-model.md); the four language-design
preconditions that make RC sound live in
[language constraints](language-constraints.md).
[language constraints](0015-language-constraints.md).
Ratified by: `crates/ailang-core/tests/design_schema_drift.rs`.
@@ -44,12 +44,12 @@ The staticlib swarm artefact is **RC-only**:
stub is leak-only and not swarm-safe; `--alloc=gc` no longer
exists as a CLI value — see the Boehm-retirement iter). The
value/record
layout is **frozen as of M3** (see [Frozen value layout](frozen-value-layout.md)); the
layout is **frozen as of M3** (see [Frozen value layout](0006-frozen-value-layout.md)); the
ctx-threaded C signature is the M2 shape.
Export parameters are written **bare**: a scalar type carries no
`own`/`borrow` mode (a single-constructor record export parameter,
by contrast, carries `own`/`borrow` — see [memory model](memory-model.md)
by contrast, carries `own`/`borrow` — see [memory model](0008-memory-model.md)
for the full ownership contract; the
frozen value layout below specifies how that contract lands at the C ABI). The
canonical M1 export shape:
@@ -30,7 +30,7 @@ A proposed feature ships only if all three hold:
question is whether the construct re-opens a class of mistakes
(iterated-mutable-state reasoning, silently-unbounded recursion,
reference cycles) that a foundational invariant — see
[language constraints](language-constraints.md) — closes. If it does,
[language constraints](0015-language-constraints.md) — closes. If it does,
it is cut even when 1 and 2 hold — *or* it must be reshaped until
the bug class is structurally impossible rather than merely
discouraged. A documentation note is not a reshape; the
@@ -44,7 +44,7 @@ A proposed feature ships only if all three hold:
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
`docs/specs/0032-iteration-discipline-revert.md`) is itself
the bug-class-reintroduction discriminator working as intended.
This is the positive complement to the CLAUDE.md rule that
@@ -12,7 +12,7 @@ no `f32` variant. The runtime / codegen contract:
fmul / fdiv double`, `fneg double`. Float comparison goes through
the named prelude fns `float_eq` / `float_ne` / `float_lt` /
`float_le` / `float_gt` / `float_ge` (Float has no Eq/Ord instance
— see [Prelude (built-in) classes](prelude-classes.md)); each fn
— see [Prelude (built-in) classes](0017-prelude-classes.md)); each fn
lowers to a single `fcmp` instruction (`oeq` / `une` / `olt` /
`ole` / `ogt` / `oge` respectively) via the codegen intercept
`try_emit_primitive_instance_body`, with the `alwaysinline`
@@ -73,7 +73,7 @@ no `f32` variant. The runtime / codegen contract:
The same libc-`%g` rendering applies to `show 1.5` / `show nan` /
`show inf` via `instance Show Float` (which calls `float_to_str`
internally — see [Prelude (built-in) classes](prelude-classes.md) for the Show ship).
internally — see [Prelude (built-in) classes](0017-prelude-classes.md) for the Show ship).
The NaN-spelling caveat above is observable via `do print x` for
Float-typed `x`; the rendering is libc-version-dependent and
target-libc-specific. AILang does NOT canonicalise Float textual
@@ -91,15 +91,15 @@ use case appears.
**Form-A serialisation:** Float literals carry the IEEE-754
bit pattern as a 16-character lowercase hex string in the canonical
JSON: `{"kind":"float","bits":"<16-hex>"}` (see
[Data model](data-model.md) for the literal schema). Routing through the
[Data model](0002-data-model.md) for the literal schema). Routing through the
JSON *string* path (not `serde_json::Number`) preserves bit
stability across `serde_json` versions and lets NaN / ±Inf
round-trip through Form-A — JSON numbers cannot represent them.
This bits-hex encoding is what keeps Floats inside the
[Roundtrip Invariant](roundtrip-invariant.md).
[Roundtrip Invariant](0009-roundtrip-invariant.md).
**Pattern matching:** `Pattern::Lit` on `Literal::Float` (see
[Data model](data-model.md) for the pattern schema) is hard-
[Data model](0002-data-model.md) for the pattern schema) is hard-
rejected at typecheck (`CheckError::FloatPatternNotAllowed`). IEEE
semantics make Float patterns semantically dubious — NaN never
matches via `float_eq` (its `fcmp oeq` lowering is `false` for
@@ -109,7 +109,7 @@ Use the explicit comparison fns (`float_lt`, `float_gt`, ...) and
`float_to_str` (Float → Str) and `int_to_str` (Int → Str) are
fully wired through checker, codegen, and runtime. Both allocate
a fresh heap-Str slab at the call site (see [Str ABI](str-abi.md)
a fresh heap-Str slab at the call site (see [Str ABI](0011-str-abi.md)
for the dual realisation) and carry `ret_mode: Own` so the let-binder
for the call result is RC-tracked and the slab is freed at scope
close.
@@ -4,7 +4,7 @@
For a single-constructor `data T` whose `n` fields are each `Int`
or `Float`, a value of `T` crossing the
[embedding C boundary](embedding-abi.md) is a
[embedding C boundary](0003-embedding-abi.md) is a
bare payload pointer `p`:
| bytes | content |
@@ -29,7 +29,7 @@ offsets above. A raw `malloc` is a contract violation:
header at `p - 8` initialised to `1`.
**Ownership follows the declared mode** (the
[Mode metadata is load-bearing for codegen](memory-model.md)
[Mode metadata is load-bearing for codegen](0008-memory-model.md)
contract, as the C ABI): a `(own (con
T))` parameter transfers ownership in — the kernel consumes it
(Iter-B drop-at-return); the host MUST NOT touch or `dec` it after
@@ -3,16 +3,16 @@
The four language-design constraints that make RC sound without a
cycle-collector backstop (strict evaluation, no recursive value
bindings, no shared mutable refs, acyclic ADTs) live in
[language constraints](language-constraints.md); this file covers
[language constraints](0015-language-constraints.md); this file covers
the schema additions, advisory diagnostics, and codegen contract
that build on them.
## Schema additions
**Parameter modes on `Type::Fn`** (see [Data model](data-model.md)
**Parameter modes on `Type::Fn`** (see [Data model](0002-data-model.md)
for the schema-level definition of `Type::Fn`).
The form-A surface (see [authoring surface](authoring-surface.md))
The form-A surface (see [authoring surface](0001-authoring-surface.md))
for fn signatures gains mode wrappers:
```
@@ -22,7 +22,7 @@ for fn signatures gains mode wrappers:
Internally, this is *not* a new `Type` variant. Modes are
metadata on `Type::Fn``paramModes` and `retMode` fields run
parallel to `params` and `ret` (see [Data model](data-model.md) for the JSON
parallel to `params` and `ret` (see [Data model](0002-data-model.md) for the JSON
schema). The substantive reasons for per-position metadata over a
`Type::Borrow` / `Type::Own` variant approach:
@@ -33,7 +33,7 @@ schema). The substantive reasons for per-position metadata over a
`(con List (borrow Int))` — syntactically possible, semantically
meaningless (you cannot separately own/borrow a list element
from the list it lives in). The
[canonical-schema principle](data-model.md) is "schema = data,
[canonical-schema principle](0002-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
@@ -85,7 +85,7 @@ Three schema fields carry class references in this form:
`TypeDef.name`).
Method dispatch is type-driven (see
[Method dispatch](method-dispatch.md)):
[Method dispatch](0016-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
@@ -150,7 +150,7 @@ until the fixture intentionally adopts the feature.
**`FnDef.suppress`.** The `suppress` field on `FnDef` carries a
list of advisory-diagnostic suppress entries; each entry has a
`code` (the diagnostic being suppressed) and a `because` (a
mandatory non-empty reason). See [Data model](data-model.md) for
mandatory non-empty reason). See [Data model](0002-data-model.md) for
the canonical schema.
Form-A surface: `(suppress (code "...") (because "..."))` clause
@@ -4,8 +4,8 @@
Every well-formed AILang module has a canonical `.ail.json`
representation (the hashable, content-addressed
[JSON-AST](data-model.md)) and a hand-authored `.ail` representation
(Form A, the [authoring projection](authoring-surface.md)). The
[JSON-AST](0002-data-model.md)) and a hand-authored `.ail` representation
(Form A, the [authoring projection](0001-authoring-surface.md)). The
JSON-AST is derived from `.ail` in-process by consumers that need
it; the round-trip is now the *property* of that derivation, not
the byte-level agreement of two on-disk forms.
@@ -47,7 +47,7 @@ approximation. The canonical encoding is
`{"kind":"float","bits":"<16-lowercase-hex>"}` and the surface
emits the same bits-hex string. NaN, ±Inf, signed zero, and subnormals all
round-trip exactly because the JSON-number path is bypassed (see
[Float semantics](float-semantics.md), "Form-A serialisation").
[Float semantics](0005-float-semantics.md), "Form-A serialisation").
Floats are not an exception to the invariant — the bits-hex
encoding is the mechanism that keeps them *inside* it.
@@ -85,7 +85,7 @@ 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 choice. The [authoring surface](authoring-surface.md)
one surface-design choice. The [authoring surface](0001-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:
@@ -5,12 +5,12 @@
Snapshot of the current boundary.
- No effect handlers — only the built-in `IO` op (`io/print_str`);
the effect system is described in [effects](../models/effects.md).
the effect system is described in [effects](../models/0002-effects.md).
`Diverge` is a reserved effect name with no op and no codegen.
- No refinements / SMT escalation.
- No HM inference inside bodies. Top-level def types are explicit;
polymorphism is opt-in via `Type::Forall { vars, body }` (see
[Data model](data-model.md)). Inside a body, lambdas check
[Data model](0002-data-model.md)). Inside a body, lambdas check
monomorphically against their declared type.
- Polymorphic fns must be **directly called** at the use site.
Passing a polymorphic fn as a value (`let f = id in f(42)`) is
@@ -20,11 +20,11 @@ Snapshot of the current boundary.
- No recursive `let` for non-fn values. Plain `let x = … in …` only
sees `x` inside the body, not inside its own RHS — recursive value
bindings would break the
[acyclicity invariant](language-constraints.md). Recursive
[acyclicity invariant](0015-language-constraints.md). Recursive
*fn* bindings are supported via `Term::LetRec` (`{ "t": "letrec",
... }`); the desugar pass lifts most occurrences to a synthetic
top-level fn, with `lift_letrecs` finishing the residue after
typecheck (see [pipeline](../models/pipeline.md)).
typecheck (see [pipeline](../models/0003-pipeline.md)).
- No visibility rules in imports. Every top-level def of an imported module
is reachable; there is no `pub` / `priv`.
@@ -34,7 +34,7 @@ What **is** supported (and used as the smoke test for the pipeline):
- `if`, `let`, function calls, recursion.
- Effects on function signatures, with `do op(args)` for direct effect
ops (`io/print_str`). The polymorphic `print` (see
[prelude classes](prelude-classes.md)) is the canonical output
[prelude classes](0017-prelude-classes.md)) is the canonical output
path for non-Str values.
- **Builtins.** Arithmetic operators (`+`, `-`, `*`, `/`) of type
`forall a. (a, a) -> a` (codegen-restricted to `{Int, Float}`);
@@ -47,7 +47,7 @@ What **is** supported (and used as the smoke test for the pipeline):
`float_to_int_truncate : (Float) -> Int` (saturating, NaN → 0),
`float_to_str : (Float) -> Str`,
`int_to_str : (Int) -> Str` (both allocate a heap-Str slab at
call time and return it with `ret_mode: Own`; see [Str ABI](str-abi.md) for
call time and return it with `ret_mode: Own`; see [Str ABI](0011-str-abi.md) for
the dual heap-/static-Str realisation); inspection
`is_nan : (Float) -> Bool` (LLVM
`fcmp uno`); Float bit-pattern constants `nan : Float`,
@@ -69,7 +69,7 @@ What **is** supported (and used as the smoke test for the pipeline):
ctor; every primitive instance body carries the
`alwaysinline` attribute so the call folds at every use site.
Float has no Eq / Ord instance (see
[Float semantics](float-semantics.md)); explicit comparison
[Float semantics](0005-float-semantics.md)); explicit comparison
is via the named fns `float_eq` / `float_ne` / `float_lt` /
`float_le` / `float_gt` / `float_ge`, each lowering to a
single `fcmp` with the matching predicate. `float_ne` uses
@@ -92,7 +92,7 @@ What **is** supported (and used as the smoke test for the pipeline):
`Pattern::Lit` (top-level or sub-) to a `Term::If` on `eq` (the
class method `prelude.Eq.eq`) before typecheck/codegen — see
[desugar](../../crates/ailang-core/src/desugar.rs) and
[Pipeline](../models/pipeline.md).
[Pipeline](../models/0003-pipeline.md).
- Literal patterns at top level and inside Ctor sub-patterns (via desugar).
`(pat-lit 0)` and `(pat-ctor Cons (pat-lit 0) _)` both
parse and lower; the rewrite is to `Term::If { cond = (eq sv lit) }`,
@@ -100,7 +100,7 @@ What **is** supported (and used as the smoke test for the pipeline):
primitive Eq instance is authorable. The primitive instances
cover `Int` / `Bool` / `Str` / `Unit`; `Float` lit-patterns are
hard-rejected at typecheck (`CheckError::FloatPatternNotAllowed`,
see [float-semantics](float-semantics.md)). `(pat-lit "hi")` over
see [float-semantics](0005-float-semantics.md)). `(pat-lit "hi")` over
a `Str` scrutinee is exercised by `examples/eq_demo.ail.json`.
- **Imports + qualified cross-module references** via dotted names.
Extends to **types and constructors**: a foreign
@@ -109,7 +109,7 @@ What **is** supported (and used as the smoke test for the pipeline):
inside that scrutinee. Std-library demos (`examples/std_*_demo.ail.json`)
exercise this end-to-end.
- **AI-authoring text surface, form (A)** (see
[authoring surface](authoring-surface.md)). The `ailang-surface`
[authoring surface](0001-authoring-surface.md)). The `ailang-surface`
crate parses `.ail` form-A text into a canonical
`ailang-core::ast::Module` and prints any module back as form-A
text. `ail render` and `ail describe` use it as the
@@ -117,7 +117,7 @@ What **is** supported (and used as the smoke test for the pipeline):
identity (text → AST → JSON → AST → text) is gated by
`ailang-surface/tests/round_trip.rs` over every shipped fixture.
- **Memory management via reference counting + uniqueness inference**
(see [RC + uniqueness](../models/rc-uniqueness.md)), with
(see [RC + uniqueness](../models/0004-rc-uniqueness.md)), with
**per-fn arena via stack `alloca` for non-escaping allocations**
layered on top. Every ADT box, lambda env, and closure pair
allocates either via `@ailang_rc_alloc` (escaping; RC-managed
@@ -125,7 +125,7 @@ What **is** supported (and used as the smoke test for the pipeline):
`alloca` (non-escaping; freed at fn return). The decision is
made by an escape-analysis pre-pass over the fn body — see the
"Per-fn arena via stack `alloca`" subsection of
[RC + uniqueness](../models/rc-uniqueness.md). The per-fn-arena
[RC + uniqueness](../models/0004-rc-uniqueness.md). The per-fn-arena
path is exercised end-to-end by
`examples/escape_local_demo.ail.json`.
- **First-class function references.** A top-level fn name (or
@@ -13,14 +13,14 @@ and backed by a `runtime/str.c` C helper.
- `int_to_str : (borrow Int) -> Str` — decimal rendering
of an `Int`. Backs `Show Int` in the
[prelude](prelude-classes.md).
[prelude](0017-prelude-classes.md).
- `bool_to_str : (borrow Bool) -> Str``"true"`/`"false"`.
Backs `Show Bool` in the [prelude](prelude-classes.md).
Backs `Show Bool` in the [prelude](0017-prelude-classes.md).
- `float_to_str : (borrow Float) -> Str`
type-installed; codegen is reserved and not yet shipped.
- `str_clone : (borrow Str) -> Str` — allocates a fresh
heap-Str copy of the input's bytes. Backs `Show Str` in the
[prelude](prelude-classes.md).
[prelude](0017-prelude-classes.md).
- `str_concat : (borrow Str, borrow Str) -> Str` — combines two `Str`
values into a single owned `Str`.
General-purpose; commonly used in Show bodies for labelled output
@@ -34,7 +34,7 @@ instance body that wants to combine fragments.
Primitive output for `Str` values goes through `io/print_str`
directly; values of other primitive types route through the
polymorphic `print` helper (see [prelude classes](prelude-classes.md)),
polymorphic `print` helper (see [prelude classes](0017-prelude-classes.md)),
which feeds the heap-Str result of `show x` into `io/print_str`.
Equality on `Str` dispatches via `prelude.Eq.eq` (Str instance,
@@ -70,7 +70,7 @@ implicit trailing newline (Gitea #29; the earlier `@puts` lowering
appended a newline per call).
The heap-Str realisation participates in standard RC (see
[memory model](memory-model.md)): the
[memory model](0008-memory-model.md)): the
`rc_header` slot eight bytes before the `len` field is managed
by `ailang_rc_alloc` / `ailang_rc_inc` / `ailang_rc_dec` exactly
like any other RC-allocated cell. The static-Str realisation has
@@ -13,7 +13,7 @@ Solution: explicit, verified tail calls.
- **AST.** `Term::App { fn, args, tail: bool }` and
`Term::Do { op, args, tail: bool }` (see
[Data model](data-model.md)) gain a `tail` flag,
[Data model](0002-data-model.md)) gain a `tail` flag,
serde-defaulting to `false` so existing fixtures load with
`tail: false` and their hashes stay bit-identical.
- **Typecheck.** A new pass `verify_tail_positions(fn_body)`
@@ -39,7 +39,7 @@ Solution: explicit, verified tail calls.
`tail: true` set on the resulting term. EBNF gains 2 lines;
total production count goes from ~28 to ~30, still inside
the constraint-1 budget (see
[authoring surface](authoring-surface.md)).
[authoring surface](0001-authoring-surface.md)).
**What this does NOT promise.** Per the tail-call survey of existing fixtures,
many existing recursive calls are *not* in tail position
@@ -2,16 +2,16 @@
The schema, registry, and diagnostic contract for the typeclass
layer. The resolution/monomorphisation whitepaper lives at
[models/typeclasses](../models/typeclasses.md); the call-site
lookup rule at [method dispatch](method-dispatch.md); the built-in
classes the prelude ships at [prelude classes](prelude-classes.md).
[models/typeclasses](../models/0005-typeclasses.md); the call-site
lookup rule at [method dispatch](0016-method-dispatch.md); the built-in
classes the prelude ships at [prelude classes](0017-prelude-classes.md).
## Form-A schema (the JSON authoring surface)
Three additive schema extensions; no existing module becomes invalid.
The canonical schema for `ClassDef`, `InstanceDef`, and the
`FnDef.type` constraints field lives in
[Data model](data-model.md); this section is the typeclass-layer
[Data model](0002-data-model.md); this section is the typeclass-layer
narrative.
**`ClassDef`** — top-level definition kind, declares a class:
@@ -75,7 +75,7 @@ exactly as for any free function.
## Cross-module references in synthesised bodies
The unified mono pass (see
[models/typeclasses](../models/typeclasses.md) for the
[models/typeclasses](../models/0005-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`
@@ -84,7 +84,7 @@ is defined in the prelude), but a user-ADT call site
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
[models/typeclasses](../models/0005-typeclasses.md)). The synthesised
body crosses a module boundary the source template did not.
Three invariants make this work:
@@ -218,7 +218,7 @@ wording is fixed; the categories and their triggers are:
Cross-class method sharing is structurally legal; ambiguity surfaces
at the call site via `AmbiguousMethodResolution` or, for class-fn
name overlap, via the `class-method-shadowed-by-fn` warning. See
[Method dispatch](method-dispatch.md).
[Method dispatch](0016-method-dispatch.md).
**Class-schema diagnostics** (validation of class declarations):
@@ -259,6 +259,6 @@ There is no `AmbiguousInstance` diagnostic at the registry level —
coherence (`DuplicateInstance` at registry build, W2) makes
per-`(class, type)` ambiguity structurally impossible. Cross-class
method ambiguity is a separate concern resolved at the call site
via `AmbiguousMethodResolution` (see [Method dispatch](method-dispatch.md)).
via `AmbiguousMethodResolution` (see [Method dispatch](0016-method-dispatch.md)).
Ratified by: `crates/ail/tests/show_no_instance_e2e.rs`.
@@ -5,7 +5,7 @@
1. **Snapshot tests** for the pretty-printer and IR emit. The diff makes
regressions visible immediately.
2. **Property tests** for the JSON ↔ pretty-print
[roundtrip](roundtrip-invariant.md).
[roundtrip](0009-roundtrip-invariant.md).
3. **End-to-end tests** for `examples/` with expected program output.
4. **Hash stability**: a test ensures the same def always produces the same
hash.
@@ -5,11 +5,11 @@
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 the
[memory model](memory-model.md) makes them load-bearing rather
[memory model](0008-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
[Data model](0002-data-model.md)) argument is fully
evaluated before the call. No laziness, no thunks. (Already
true.)
2. **No recursive value bindings.** `(let x EXPR ...)` evaluates
@@ -31,11 +31,11 @@ proposal proves the cycle is collectible by an extension (e.g.
linear ownership).
These constraints are the precondition for the
[memory model](memory-model.md): a sound RC implementation without
[memory model](0008-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 13. They are also the
load-bearing structural reason behind several rejections under the
[feature-acceptance](feature-acceptance.md) bug-class-reintroduction
[feature-acceptance](0004-feature-acceptance.md) bug-class-reintroduction
discriminator (notably iterated-mutable-state reasoning).
Ratified by: `crates/ailang-check/src/uniqueness.rs` (in-source mod tests).
@@ -3,8 +3,8 @@
## Method dispatch
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).
[typeclasses](0013-typeclasses.md) contract; the `Show`/`Eq`/`Ord` instances
the rule dispatches against live in [prelude classes](0017-prelude-classes.md).
Dispatch is two-mode:
@@ -12,7 +12,7 @@ Dispatch is two-mode:
constraint set): the constraint names the class via the qualified
`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).
[memory model](0008-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.
@@ -3,7 +3,7 @@
## Prelude (built-in) classes
The prelude ships the `Ordering` ADT, the `Eq` and `Ord`
[classes](typeclasses.md), primitive `Eq Int/Bool/Str/Unit` and
[classes](0013-typeclasses.md), primitive `Eq Int/Bool/Str/Unit` and
`Ord Int/Bool/Str` instances, and the five polymorphic free-fn
helpers `ne`/`lt`/`le`/`gt`/`ge`. The primitive `Eq` / `Ord`
instance bodies are placeholder lambdas in `examples/prelude.ail`;
@@ -35,12 +35,12 @@ maintenance; the symmetric extensions are mechanical when the
first such workload appears.
Float has neither `Eq` nor `Ord` instance per [Float semantics](
float-semantics.md); a polymorphic helper invoked at Float fires
0005-float-semantics.md); a polymorphic helper invoked at Float fires
`NoInstance` at typecheck with a Float-aware diagnostic that names
the explicit `float_eq` / `float_lt` alternatives and
cross-references this section. The lookup machinery that turns a
`show x` call site into the right monomorphic instance is
documented in [method dispatch](method-dispatch.md).
documented in [method dispatch](0016-method-dispatch.md).
Float comparison is a separate surface: the six monomorphic prelude
fns `float_eq` / `float_ne` / `float_lt` / `float_le` / `float_gt`
@@ -57,15 +57,15 @@ primitive `Show Int`, `Show Bool`, `Show Str`, `Show Float`
instances. Float **is** included in Show (unlike Eq/Ord) — IEEE-754
makes structural equality and total ordering semantically dubious,
but textual representation of a Float is well-defined modulo the
NaN-spelling caveat in [Float semantics](float-semantics.md). Each
NaN-spelling caveat in [Float semantics](0005-float-semantics.md). Each
`Show <T>` instance body is a single-application lambda invoking the
corresponding runtime primitive (`int_to_str`, `bool_to_str`,
`str_clone`, `float_to_str`; see [Str ABI](str-abi.md) for the
`str_clone`, `float_to_str`; see [Str ABI](0011-str-abi.md) for the
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 the Str carve-out in
[Str ABI](str-abi.md)). The let-binder is
[Str ABI](0011-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.