Files
AILang/design/contracts/0017-prelude-classes.md
T
Brummel 625fe849be docs(contracts): reconcile contracts + honesty-pin with shipped reality
First tranche of a contracts-against-code audit (the inverse of the
usual direction: testing the ledger's claims against the code). Each
fix here is a verified factual divergence between a contract and the
code; the direction of the fix follows which side actually drifted.

Code drifted from the stated goal -> fix the code:

- 0014's claim 6 ("`cargo doc --no-deps` runs warning-free") was the
  design goal; reality had 6 warnings. Demote the offending intra-doc
  links to plain code spans so the docs match the goal:
  - ailang-core: `[`load_workspace`]` cannot resolve from core (the
    fn lives in ailang-surface, which core may not depend on) — three
    sites in workspace.rs.
  - ailang-check: three public-item docs linked the `pub(crate)`
    helpers `qualify_local_types` / `qualify_workspace_types`.
  `cargo doc --no-deps --workspace` is now warning-free.

Contract stale, code legitimately advanced -> fix the contract:

- 0011 stated `float_to_str` "codegen is reserved and not yet
  shipped". It is shipped: lowers to `@ailang_float_to_str(double)`,
  green under the codegen `float_to_str_no_longer_errors_internal`
  unit test and the e2e `float_to_str_smoke`. The docs_honesty_pin
  anchor that protected the stale "reserved" wording moved in lockstep
  to assert the present-tense lowering instead — the pin had been
  guarding a claim the code already falsified.
- 0013 named the diagnostic `ConstraintReferencesUnboundTypeVar`; the
  variant is `UnboundConstraintTypeVar` (workspace.rs), and its scope
  is a class-method signature whose constraint mentions a tyvar bound
  neither by the method's `forall` nor by the class `param`.
- 0017 called the primitive Eq/Ord bodies "placeholder lambdas"; they
  carry the `(intrinsic)` marker — the lockstep partner to the
  INTERCEPTS registry, not a placeholder.
- 0009 said "seven" `.ail.json` carve-outs and described the inventory
  test as pinning seven; the test pins twelve (7 subject-matter + 4
  recur + 1 loop-binder, per carve_out_inventory.rs).

Verified separately: 0001's "pretty-printer code in pretty.rs was
deleted, leaving only diagnostic helpers" is factually correct (an
audit agent misread it as "pretty.rs was deleted"); its only issue is
history phrasing, deferred to the honesty-prose tranche.

Deferred to later tranches: honesty-rule prose (history/rationale that
is not a protected honest-reserved/tiebreaker anchor), stale/mislinked
ratifying-tests (0014's `architect_sweeps.sh`, 0015's uniqueness
in-source tests), 0014's never-existent `tests/expected/`, 0012's
non-exhaustive tail-context list, and the 0016<->0013 redundancy.
2026-06-02 11:14:01 +02:00

75 lines
4.0 KiB
Markdown

# Prelude (built-in) classes
## Prelude (built-in) classes
The prelude ships the `Ordering` ADT, the `Eq` and `Ord`
[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 carry the `(intrinsic)` marker in `examples/prelude.ail`
(the marker is the lockstep partner to the `INTERCEPTS` registry);
the codegen intercept `try_emit_primitive_instance_body` supplies
their single-instruction bodies (`icmp eq i64` for `eq__Int`,
`icmp eq i1` for `eq__Bool`, `@ail_str_eq` for `eq__Str`,
`ret i1 1` for `eq__Unit`; a three-way `icmp` ladder constructing
`LT`/`EQ`/`GT` for `compare__T`) and attaches `alwaysinline` so
the call folds to the single instruction at every use site.
The five Ord-class free helpers (`ne`/`lt`/`le`/`gt`/`ge`) have
source-level bodies that route through `eq` / `compare` plus
`match` over `Ordering` — written that way so the surface form is
LLM-natural and so user-ADT instances of `Eq`/`Ord` automatically
pick up working helpers without any per-type opt-in. At Int the
codegen intercept short-circuits this indirection: `lt__Int`,
`le__Int`, `gt__Int`, `ge__Int`, and `ne__Int` are intercept-
overridden to emit `icmp slt i64` / `sle i64` / `sgt i64` /
`sge i64` / `ne i64` directly, bypassing the `compare__Int`
`Ordering`-ctor → `match` path. Without this short-circuit the
`Ordering` ctor allocates per call inside hot loops — a real
cost surfaced by the `bench_closure_chain` regression at the
operator-routing-eq-ord milestone and pinned by
`crates/ail/tests/ord_int_intercept_ir_pin.rs`. The short-circuit
is *Int-only* by deliberate asymmetry: no current bench or
example fixture exercises Ord-`lt`/`le`/`gt`/`ge` at Bool or
Str at sufficient call frequency to justify the intercept-arm
maintenance; the symmetric extensions are mechanical when the
first such workload appears.
Float has neither `Eq` nor `Ord` instance per [Float semantics](
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](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`
/ `float_ge` each carry the type `(Float, Float) -> Bool` without
a class constraint. They lower via the same intercept machinery as
the primitive `Eq` instances (single `fcmp` instruction with the
matching predicate `oeq` / `une` / `olt` / `ole` / `ogt` / `oge`,
plus `alwaysinline`). They replace what would otherwise be a
polymorphic `==` / `<` on Float — Float gets full comparability
via explicit named fns, not via an implicit class instance.
The prelude ships `class Show a where show : (a borrow) -> Str` and
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](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](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](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.
Ratified by: `crates/ail/tests/show_no_instance_e2e.rs`.