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).
11 KiB
What is not (yet) supported
What is not (yet) supported
Snapshot of the current boundary.
- No effect handlers — only the built-in
IOop (io/print_str); the effect system is described in effects.Divergeis 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). 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 not yet supported. - No higher-rank polymorphism. Passing a polymorphic fn to another
polymorphic fn (
apply(id, 42)) is not supported. - No recursive
letfor non-fn values. Plainlet x = … in …only seesxinside the body, not inside its own RHS — recursive value bindings would break the acyclicity invariant. Recursive fn bindings are supported viaTerm::LetRec({ "t": "letrec", ... }); the desugar pass lifts most occurrences to a synthetic top-level fn, withlift_letrecsfinishing the residue after typecheck (see pipeline). - No visibility rules in imports. Every top-level def of an imported module
is reachable; there is no
pub/priv.
What is supported (and used as the smoke test for the pipeline):
-
Int, Bool, Unit, Str, Float as primitive types.
-
if,let, function calls, recursion. -
Effects on function signatures, with
do op(args)for direct effect ops (io/print_str). The polymorphicprint(see prelude classes) is the canonical output path for non-Str values. -
Builtins. Arithmetic operators (
+,-,*,/) of typeforall a. (a, a) -> a(codegen-restricted to{Int, Float});%of type(Int, Int) -> Int(Int-only —fmodsemantics for Float deferred); ordering operators and!=(!=,<,<=,>,>=) of typeforall a. (a, a) -> Bool(codegen-restricted to{Int, Float}); polymorphicneg : forall a. (a) -> a(codegen-restricted to{Int, Float}; Float arm uses LLVMfneg doublefor correct-0.0handling); logicalnot : (Bool) -> Bool; conversionsint_to_float : (Int) -> Float,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 withret_mode: Own; see Str ABI for the dual heap-/static-Str realisation); inspectionis_nan : (Float) -> Bool(LLVMfcmp uno); Float bit-pattern constantsnan : Float,inf : Float,neg_inf : Float(resolved as bare values, lower to direct hex-floatdoubleSSA constants at use site); the IO effect opio/print_str;==: forall a. (a, a) -> Bool; and__unreachable__ : forall a. a.==is polymorphic. The typechecker accepts==at any type whose two sides agree (the rigidaof theForallis unified by HM at the use site). Codegen monomorphises and dispatches on the resolved AIL arg type:Int→icmp eq i64;Bool→icmp eq i1;Str→call @strcmp(ptr, ptr)thenicmp eq i32 0(@strcmpis declared in the LLVM IR header alongside@printf/@GC_malloc);Unit→ constanti1 true(Unit has a single inhabitant; both sides are still evaluated for any side effects);Float→fcmp oeq double. ADT andFnarg types are rejected at codegen with aCodegenError::Internalmentioning==and the offending type — neither has a canonical structural-equality scheme yet, and the language deliberately does not silently elide the check.!=for Float usesfcmp UNE double(NOTone) —oneis "ordered and not equal" and would return false fornan != nan, violating IEEE-!=.__unreachable__is a polymorphic bottom value: a use of__unreachable__typechecks against any expected type at the use site and codegens to the LLVMunreachableinstruction (UB if ever executed). It is the chain machinery's deepest fall-through for matches that the typechecker proved exhaustive, and it is available to user code as an explicit panic primitive ((if cond __unreachable__ ...)for assertions or impossible branches). Reference site isTerm::Var { name = "__unreachable__" }/ form-A bare__unreachable__.
-
ADTs + pattern matching. Sub-patterns of a Ctor pattern may be
Var,Wild, anotherCtor, or a literal. The desugar pass flattens nested Ctor patterns into a chain of let + match and rewrites everyPattern::Lit(top-level or sub-) to aTerm::Ifon==before typecheck/codegen — see desugar and Pipeline. -
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 toTerm::If { cond = (== sv lit) }, so any literal kind whose==is supported is authorable. With==polymorphic overInt/Bool/Str/Unit, that covers every lit kind the AST ships — including(pat-lit "hi")over aStrscrutinee, exercised byexamples/eq_demo.ail.json. -
Imports + qualified cross-module references via dotted names. Extends to types and constructors: a foreign module's ADT is referenced as
(con std_pair.Pair a b), its ctors as(term-ctor std_pair.Pair MkPair x y)and(pat-ctor MkPair x y)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). The
ailang-surfacecrate parses.ailform-A text into a canonicalailang-core::ast::Moduleand prints any module back as form-A text.ail renderandail describeuse it as the sole text projection;ail parseis the inverse direction. Round-trip identity (text → AST → JSON → AST → text) is gated byailang-surface/tests/round_trip.rsover every shipped fixture. -
Memory management via Boehm conservative GC (see RC + uniqueness), with per-fn arena via stack
allocafor non-escaping allocations layered on top. Every ADT box, lambda env, and closure pair allocates either via@GC_malloc(escaping; Boehm-managed) or via LLVMalloca(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 stackalloca" subsection of RC + uniqueness. Boehm-only soak tests are unchanged:examples/gc_stress.ail.jsonandexamples/std_list_stress.ail.jsonstill allocate via@GC_mallocbecause their boxes flow into other fns and escape. The per-fn-arena path is exercised end-to-end byexamples/escape_local_demo.ail.json. -
First-class function references. A top-level fn name (or qualified
prefix.def) used as aTerm::Varis a fn-value. -
Anonymous lambdas with capture.
Term::Lamconstructs a closure that captures any free variables of its body from the enclosing scope. All fn-values share a single ABI: aptrto a closure pair{ thunk_ptr, env_ptr }. Top-level fns get an auto- generated adapter and a static closure pair (env = null) so they remain passable as values without heap overhead. -
Polymorphism via
Type::Forallat top-level def types. Use sites instantiate fresh metavars; unification pins them against the concrete types of the call args. Codegen monomorphises on demand: each unique instantiation emits a specialised LLVM fn mangled@ail_<m>_<def>__<descriptor>(e.g.id__IforidatInt,apply__I_Iforapplyat(Int, Int)). -
Parameterised ADTs.
TypeDef.vars: Vec<String>declares type parameters;Type::Con.args: Vec<Type>carries the type arguments at use sites. Both fields default to empty and are skipped during serialization, so canonical-JSON hashes of every existing definition stay bit-identical (regression test incrates/ailang-core/src/hash.rs). Ctor and match codegen stay inline at every use site — there is no specialised ADT symbol — but LLVM field types are derived per use site by substituting throughcdef.ail_fields. The substitution is read off the call's arg types (ctor) or the scrutinee'sType::Con.args(match). An unresolvedType::Varreachingllvm_typeis a hard error rather than a silent fallback toptr. Pipeline regression smoke tests: -
examples/sum.ail.json→ prints 55 (recursion, arithmetic). -
examples/list.ail.json→ prints 42 (ADTs + match). -
examples/hof.ail.json→ prints 42 (first-class fn-refs, indirect call). -
examples/closure.ail.json→ prints 42 (lambda capturing a let-bound var). -
examples/list_map.ail.json→ prints 2/4/6 (ADTs + closure + recursive HOF + IO; the dogfood smoke test). -
examples/sort.ail.json→ prints sorted [3,1,4,1,5,9,2,6,5,3,5] one-per-line (insertion sort over an 11-element list). -
examples/poly_id.ail.json→ prints 42 then "true" (polymorphic identity atIntandBool; two specialised fns emitted). -
examples/poly_apply.ail.json→ prints 42 (polymorphicapplywith a fn-typed parameter;apply(succ, 41)). -
examples/box.ail.json→ prints 42 (parameterised ADT round- trip:MkBox(42)constructed, then projected by a polymorphicunbox : forall a. (Box<a>) -> aand printed). -
examples/maybe_int.ail.json→ prints 7 then 99 (pattern match overMaybe<Int>:or_else(Some(7), 99)thenor_else(None, 99)). -
examples/std_list_demo.ail.json→ exercisesstd_list's combinators (length, sum, reverse, take/drop-style uses) end-to-end againststd_list'sList<a>. -
examples/std_maybe_demo.ail.json→ exercisesstd_maybecombinators overMaybe<Int>, includingfrom_maybeandmap. -
examples/std_either_demo.ail.json→ first program with three distinct type variables in a single fn (theeithereliminator), monomorphised six different ways in the IR. -
examples/std_pair_demo.ail.json→ drives everystd_paircombinator (fst, snd, swap, map_first, map_second); expected output 7, 9, 9, 7, 8, 18. -
examples/nested_pat.ail.json→ first program to use a nested(pat-ctor Cons a (pat-ctor Cons b _)); the desugar pass flattens it into a chain that the existing flat-match codegen consumes. Prints 30 for a 3-element input list.
Ratified by: crates/ailang-core/tests/effect_doc_honesty_pin.rs.