design/ + source rustdoc: replace opaque shorthand with content phrases + links

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: <title>" 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).
This commit is contained in:
2026-05-20 09:47:33 +02:00
parent 3e087d759a
commit bcd41810f4
46 changed files with 311 additions and 284 deletions
+3 -3
View File
@@ -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`.
+7 -7
View File
@@ -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
+17 -14
View File
@@ -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.
+9 -9
View File
@@ -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
+2 -2
View File
@@ -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
+4 -4
View File
@@ -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
+6 -5
View File
@@ -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 13. 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).
+14 -12
View File
@@ -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:
+8 -6
View File
@@ -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
+2 -1
View File
@@ -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.
+6 -6
View File
@@ -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`.
+2 -1
View File
@@ -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.
+1 -1
View File
@@ -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
+17 -14
View File
@@ -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
+4 -2
View File
@@ -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.
+1 -1
View File
@@ -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)
+5 -4
View File
@@ -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,
+17 -16
View File
@@ -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:
+7 -8
View File
@@ -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).