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:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 1–3. 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).
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user