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.
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); 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; and__unreachable__ : forall a. a.- Equality + ordering are not builtins. The class method
eq(prelude.Eq) dispatches via the per-type Eq instance; the class methodcompare(prelude.Ord) dispatches via the per-type Ord instance, returning a three-ctorOrderingADT (LT/EQ/GT). The five free helpersne/lt/le/gt/geare defined in the prelude in terms ofeq/compare. The primitive Eq/Ord instance bodies are emitted by the codegen intercepttry_emit_primitive_instance_body: Eq Int →icmp eq i64, Eq Bool →icmp eq i1, Eq Str →call @ail_str_eq(...), Eq Unit →ret i1 1, Ord Int / Bool / Str → three-wayicmp slt/icmp eqladder constructing theOrderingctor; every primitive instance body carries thealwaysinlineattribute so the call folds at every use site. Float has no Eq / Ord instance (see Float semantics); explicit comparison is via the named fnsfloat_eq/float_ne/float_lt/float_le/float_gt/float_ge, each lowering to a singlefcmpwith the matching predicate.float_neusesfcmp une double(NOTone) sonan != nanreturnstrueper 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__.
- Equality + ordering are not builtins. The class method
-
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::Ifoneq(the class methodprelude.Eq.eq) 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 = (eq sv lit) }, so any literal kind whoseeqdispatch resolves to a known primitive Eq instance is authorable. The primitive instances coverInt/Bool/Str/Unit;Floatlit-patterns are hard-rejected at typecheck (CheckError::FloatPatternNotAllowed, see float-semantics).(pat-lit "hi")over aStrscrutinee is 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 reference counting + uniqueness inference (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@ailang_rc_alloc(escaping; RC-managed with inc/dec instrumentation per the memory model) 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. 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.