tidy: rustdoc-sweep + drift-test-narrowing — autonomous batch
Two unrelated hygiene iters bundled because they shipped together in one autonomous-while-Boss-away batch: - iter rustdoc-sweep: cleared all 23 `cargo doc --workspace --no-deps` warnings (17 in ailang-check + 4 in ailang-core + 2 in ailang-surface). Two warning classes: pub-doc-comment links to pub(crate)/private items (15 hits — replaced with plain backtick-code-spans), and unresolved/ambiguous links (8 hits — fully-qualified, disambiguated to fn-form, or escaped). No behaviour change; tests 562 → 562. - iter drift-test-narrowing: design_schema_drift.rs now scans §"Data model" only via new helper data_model_section(), instead of full DESIGN.md. Closes the audit-form-a-precursor [high] "anchor-elsewhere-passes-silently" failure mode. All 38 anchors verified pre-edit to live in §"Data model" so the tightening doesn't regress to red. New pin data_model_section_is_bounded guards the extractor against silent regression. Tests 562 → 563.
This commit is contained in:
@@ -167,7 +167,7 @@ pub struct SuggestedRewrite {
|
||||
/// Form-A AILang snippet to substitute at the offending site. For
|
||||
/// the linearity-check diagnostics this is typically `(clone <n>)`
|
||||
/// or a small enclosing rewrite. The string MUST parse via the
|
||||
/// surface `parse_term` entrypoint; [`crate::linearity`] tests
|
||||
/// surface `parse_term` entrypoint; `crate::linearity` tests
|
||||
/// guard the round-trip.
|
||||
pub replacement: String,
|
||||
}
|
||||
|
||||
@@ -2016,7 +2016,7 @@ pub(crate) fn is_fully_concrete(t: &Type) -> bool {
|
||||
}
|
||||
|
||||
/// Iter 22b.2 (Task 9): per-call-site residual class constraint
|
||||
/// recorded by [`synth`] when a `Term::Var` resolves to a class method.
|
||||
/// recorded by `synth` when a `Term::Var` resolves to a class method.
|
||||
/// Carries the method name (for diagnostic rendering), the class, and
|
||||
/// the type the class is applied to at the call site. The type is
|
||||
/// typically a fresh metavar (which unification then ties to the
|
||||
@@ -2290,7 +2290,7 @@ pub fn refine_multi_candidate_residual(
|
||||
}
|
||||
|
||||
/// Iter 23.4: per-call-site polymorphic-free-fn observation recorded by
|
||||
/// [`synth`] when a `Term::Var` resolves to a `Type::Forall`-quantified
|
||||
/// `synth` when a `Term::Var` resolves to a `Type::Forall`-quantified
|
||||
/// top-level def (NOT a class method — those are pushed to
|
||||
/// [`ResidualConstraint`] instead). Carries the bare name, the owning
|
||||
/// module (resolved through the same lookup ladder as `synth`'s Var arm),
|
||||
@@ -2298,7 +2298,7 @@ pub fn refine_multi_candidate_residual(
|
||||
/// fresh metavars instantiated for those vars at the call site.
|
||||
///
|
||||
/// Consumed by the mono pass: after `synth` completes a body, each
|
||||
/// `metas[i]` is `subst.apply`'d to recover the concrete type at vars[i].
|
||||
/// `metas\[i\]` is `subst.apply`'d to recover the concrete type at `vars\[i\]`.
|
||||
/// Fully-concrete observations become [`crate::mono::MonoTarget::FreeFn`]
|
||||
/// targets; non-concrete observations are silently dropped (covered by
|
||||
/// the missing-constraint check on residuals if relevant).
|
||||
@@ -3693,7 +3693,7 @@ pub struct Env {
|
||||
/// actual-module-name). Sibling-shape to [`Self::module_globals`];
|
||||
/// populated workspace-flat by [`crate::build_check_env`] so the
|
||||
/// mono pass's per-fn entry points can re-seed the local
|
||||
/// [`Self::imports`] before re-running [`synth`] on a body. Both
|
||||
/// [`Self::imports`] before re-running `synth` on a body. Both
|
||||
/// `check_in_workspace` and `mono::build_workspace_env` consume
|
||||
/// the same env shape; `check_in_workspace` then overwrites
|
||||
/// [`Self::imports`] with the current module's map at the call
|
||||
@@ -3718,7 +3718,7 @@ pub struct Env {
|
||||
/// [`ModuleGlobals::class_methods`], re-keyed to
|
||||
/// `(qualified-class-name, method-name)` so post-retirement of
|
||||
/// `MethodNameCollision` two classes can share a method name.
|
||||
/// Consumed by [`synth`]'s `Term::Var` arm via the resolved class
|
||||
/// Consumed by `synth`'s `Term::Var` arm via the resolved class
|
||||
/// (from `method_to_candidate_classes` + dispatch) plus the method
|
||||
/// name. Mono's presence checks consult
|
||||
/// [`Self::method_to_candidate_classes`] (method-keyed natively).
|
||||
@@ -3742,13 +3742,13 @@ pub struct Env {
|
||||
/// Iter 22b.2 (Task 9): one-step superclass expansion table.
|
||||
/// Maps each class name to its superclass class name. Absence from
|
||||
/// the map means the class has no superclass — populated only for
|
||||
/// classes that declare one. Used by [`check_fn`] to expand
|
||||
/// classes that declare one. Used by `check_fn` to expand
|
||||
/// `Forall.constraints` for the missing-constraint check
|
||||
/// (Decision 11: max one step). The schema permits at most one
|
||||
/// superclass per class.
|
||||
pub class_superclasses: BTreeMap<String, String>,
|
||||
/// Iter 22b.2 (Task 10): workspace-wide instance registry, threaded
|
||||
/// through from [`Workspace::registry`]. Read by [`check_fn`] in the
|
||||
/// through from [`Workspace::registry`]. Read by `check_fn` in the
|
||||
/// concrete-residual path: when a class-method call at a fully-
|
||||
/// concrete type produces a residual `(class, type_)` whose
|
||||
/// `(class, type_hash(type_))` key is absent from `registry.entries`,
|
||||
|
||||
@@ -23,16 +23,16 @@
|
||||
//!
|
||||
//! See `docs/plans/2026-05-09-22b3-monomorphisation.md` for the
|
||||
//! full task list. The eventual contract is for the pass to
|
||||
//! consume the workspace's typeclass [`Registry`] and the per-
|
||||
//! consume the workspace's typeclass [`ailang_core::workspace::Registry`] and the per-
|
||||
//! module class-method tables (already populated by
|
||||
//! [`crate::check_in_workspace`]) and produce a workspace with:
|
||||
//! `crate::check_in_workspace`) and produce a workspace with:
|
||||
//!
|
||||
//! 1. Synthesised [`Def::Fn`] entries — one per unique
|
||||
//! `(class, method, type-hash)` triple observed at any
|
||||
//! class-method call site — appended to the [`Registry`]'s
|
||||
//! class-method call site — appended to the registry's
|
||||
//! `defining_module` for that instance.
|
||||
//! 2. Rewritten call sites — every `Term::Var { name }` whose
|
||||
//! `name` resolves through [`Env::class_methods`] is replaced
|
||||
//! `name` resolves through `crate::Env::class_methods` is replaced
|
||||
//! by the corresponding mono-symbol name (qualified with the
|
||||
//! instance's `defining_module` iff that module differs from
|
||||
//! the calling module).
|
||||
@@ -403,7 +403,7 @@ fn workspace_has_specialisable_targets(ws: &Workspace) -> bool {
|
||||
///
|
||||
/// Determinism: `ailang_core::canonical::type_hash` is the same
|
||||
/// function `workspace::build_registry` uses to key
|
||||
/// [`Registry::entries`], so a registry-key match implies a
|
||||
/// [`ailang_core::workspace::Registry::entries`], so a registry-key match implies a
|
||||
/// `mono_symbol` match.
|
||||
pub fn mono_symbol(base: &str, ty: &Type) -> String {
|
||||
mono_symbol_n(base, std::slice::from_ref(ty))
|
||||
@@ -511,7 +511,7 @@ pub fn mono_target_key(t: &MonoTarget) -> (String, String, String) {
|
||||
/// and apply per-fn overlay (current_module, globals from
|
||||
/// module_globals, imports from module_imports, rigid_vars) plus the
|
||||
/// per-module env.types overlay
|
||||
/// (see [`apply_per_module_types_overlay`]).
|
||||
/// (see `apply_per_module_types_overlay`).
|
||||
pub fn build_workspace_env(ws: &Workspace) -> crate::Env {
|
||||
crate::build_check_env(ws)
|
||||
}
|
||||
@@ -773,7 +773,7 @@ fn contains_rigid_var(t: &Type) -> bool {
|
||||
/// 1. has name [`mono_symbol`]`(target.method, target.type_)`,
|
||||
/// 2. has type = the class's method type with the class param
|
||||
/// substituted to `target.type_` (rigid-var substitution via
|
||||
/// [`crate::substitute_rigids`]); the result is a plain
|
||||
/// `crate::substitute_rigids`); the result is a plain
|
||||
/// `Type::Fn` with no `Forall`,
|
||||
/// 3. has params + body taken from the instance's matching
|
||||
/// `InstanceMethod.body` if present and Lam-shaped; from the
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! This pass classifies every binder of every fn body as either
|
||||
//! [`Uniqueness::Unique`] or [`Uniqueness::Shared`]. Unlike the
|
||||
//! [`crate::linearity`] check it is **not** a user-facing diagnostic
|
||||
//! `crate::linearity` check it is **not** a user-facing diagnostic
|
||||
//! pass — it is internal codegen input. Codegen consults the side
|
||||
//! table to decide where to emit `ailang_rc_inc` and `ailang_rc_dec`
|
||||
//! calls under `--alloc=rc` (Iter 18c.3).
|
||||
@@ -39,7 +39,7 @@
|
||||
//!
|
||||
//! ## Position model
|
||||
//!
|
||||
//! Identical to [`crate::linearity`]'s, modulo that this pass has no
|
||||
//! Identical to `crate::linearity`'s, modulo that this pass has no
|
||||
//! activation gate (it runs on every fn). See that module's docs for
|
||||
//! the propagation rules. `Term::Lam` captures are conservatively
|
||||
//! treated as Consume — the same conservative decision the linearity
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
//! desugar pass eliminates it before typecheck/codegen by **lifting**
|
||||
//! the LetRec to a synthetic top-level fn in the same module. The
|
||||
//! lifted name has the form `<hint>$lr_N`, fresh against both the
|
||||
//! existing module-top-level def names and the [`Desugarer::used`] set.
|
||||
//! existing module-top-level def names and the `Desugarer::used` set.
|
||||
//! Inside the LetRec's `body` and `in_term`, every reference to the
|
||||
//! original local name is rewritten via [`subst_var`] to the lifted
|
||||
//! name. The lifted [`FnDef`] is appended to the module's `defs` so
|
||||
|
||||
@@ -90,7 +90,7 @@ pub struct Registry {
|
||||
/// defining module. Used by [`Self::normalize_type_for_lookup`] to
|
||||
/// rewrite a bare `Type::Con.name` to its always-qualified form
|
||||
/// before computing the registry key. Primitives are not present.
|
||||
/// Populated in [`build_registry`] from the same scan that builds
|
||||
/// Populated in `build_registry` from the same scan that builds
|
||||
/// the entry map.
|
||||
///
|
||||
/// Keyed by `(owning_module, bare_name)`, value is the
|
||||
@@ -99,7 +99,7 @@ pub struct Registry {
|
||||
/// module M is `(M, "Foo")`, bare `Foo` from module N is
|
||||
/// `(N, "Foo")`, and the two carry distinct canonical
|
||||
/// qualifications under
|
||||
/// [`normalize_type_for_registry`]. Pre-ctt.2 the key was
|
||||
/// `normalize_type_for_registry`. Pre-ctt.2 the key was
|
||||
/// the bare name alone, and a workspace with two `type Foo`
|
||||
/// declarations silently overwrote one entry, then tripped
|
||||
/// `DuplicateInstance` on the loser-side instance after
|
||||
@@ -441,7 +441,7 @@ pub fn load_workspace(entry_path: &Path) -> Result<Workspace, WorkspaceLoadError
|
||||
/// Load a workspace using a caller-supplied per-module loader.
|
||||
///
|
||||
/// The standard entry point [`load_workspace`] is a thin wrapper that
|
||||
/// passes [`load_one`] as the loader and so only accepts `.ail.json`
|
||||
/// passes `load_one` as the loader and so only accepts `.ail.json`
|
||||
/// files. Pass a different loader (for example, an extension-dispatching
|
||||
/// loader from `ailang-surface`) to accept `.ail` source files as well.
|
||||
///
|
||||
|
||||
@@ -8,7 +8,11 @@
|
||||
//! The exhaustive `match` per enum is the load-bearing mechanism: adding a
|
||||
//! new variant without a matching arm fails compilation before the test runs.
|
||||
//! Once the variant is matched, the test asserts the anchor is present in
|
||||
//! DESIGN.md.
|
||||
//! §"Data model" specifically — not anywhere in DESIGN.md. The narrow scope
|
||||
//! is load-bearing: before this tightening, the test scanned the whole
|
||||
//! document, so an anchor that appeared only in §"Decision 11" (or any
|
||||
//! other discussion section) was treated as "present" even though
|
||||
//! §"Data model" — the canonical schema reference — was missing it.
|
||||
|
||||
use ailang_core::ast::{
|
||||
ClassDef, ClassMethod, Constraint, ConstDef, Ctor, Def, FnDef, InstanceDef,
|
||||
@@ -17,6 +21,21 @@ use ailang_core::ast::{
|
||||
|
||||
const DESIGN_MD: &str = include_str!("../../../docs/DESIGN.md");
|
||||
|
||||
/// Slice of `DESIGN_MD` covering only §"Data model": from the `## Data model`
|
||||
/// header to the next top-level `## ` header. Returned as a `&'static str`
|
||||
/// because `DESIGN_MD` is itself static. Panics if §"Data model" is missing
|
||||
/// — that itself would be drift.
|
||||
fn data_model_section() -> &'static str {
|
||||
let start = DESIGN_MD
|
||||
.find("## Data model")
|
||||
.expect("DESIGN.md must contain `## Data model` header");
|
||||
let from_start = &DESIGN_MD[start..];
|
||||
match from_start.find("\n## ") {
|
||||
Some(end) => &from_start[..end],
|
||||
None => from_start,
|
||||
}
|
||||
}
|
||||
|
||||
/// Every `Term` variant must have its JSON-schema anchor present in
|
||||
/// DESIGN.md §"Data model". An LLM author cannot produce a term variant
|
||||
/// whose `"t"` tag is absent from the canonical schema document.
|
||||
@@ -139,7 +158,7 @@ fn design_md_anchors_every_term_variant() {
|
||||
Term::ReuseAs { .. } => "reuse-as",
|
||||
};
|
||||
assert!(
|
||||
DESIGN_MD.contains(anchor),
|
||||
data_model_section().contains(anchor),
|
||||
"DESIGN.md §Data-model is missing anchor `{anchor}` for a Term variant — \
|
||||
add it to docs/DESIGN.md"
|
||||
);
|
||||
@@ -169,7 +188,7 @@ fn design_md_anchors_every_pattern_variant() {
|
||||
Pattern::Ctor { .. } => "ctor",
|
||||
};
|
||||
assert!(
|
||||
DESIGN_MD.contains(anchor),
|
||||
data_model_section().contains(anchor),
|
||||
"DESIGN.md §Data-model is missing anchor `{anchor}` for a Pattern variant"
|
||||
);
|
||||
}
|
||||
@@ -202,7 +221,7 @@ fn design_md_anchors_every_type_variant() {
|
||||
Type::Forall { .. } => "forall",
|
||||
};
|
||||
assert!(
|
||||
DESIGN_MD.contains(anchor),
|
||||
data_model_section().contains(anchor),
|
||||
"DESIGN.md §Data-model is missing anchor `{anchor}` for a Type variant"
|
||||
);
|
||||
}
|
||||
@@ -231,7 +250,7 @@ fn design_md_anchors_every_literal_variant() {
|
||||
Literal::Float { .. } => "float",
|
||||
};
|
||||
assert!(
|
||||
DESIGN_MD.contains(anchor),
|
||||
data_model_section().contains(anchor),
|
||||
"DESIGN.md §Data-model is missing anchor `{anchor}` for a Literal variant"
|
||||
);
|
||||
}
|
||||
@@ -302,7 +321,7 @@ fn design_md_anchors_every_def_kind() {
|
||||
Def::Instance(_) => "instance",
|
||||
};
|
||||
assert!(
|
||||
DESIGN_MD.contains(anchor),
|
||||
data_model_section().contains(anchor),
|
||||
"DESIGN.md §Data-model is missing anchor `{anchor}` for a Def kind"
|
||||
);
|
||||
}
|
||||
@@ -326,7 +345,7 @@ fn design_md_anchors_every_parammode_variant() {
|
||||
ParamMode::Borrow => "borrow",
|
||||
};
|
||||
assert!(
|
||||
DESIGN_MD.contains(anchor),
|
||||
data_model_section().contains(anchor),
|
||||
"DESIGN.md §Data-model is missing anchor `{anchor}` for a ParamMode variant"
|
||||
);
|
||||
}
|
||||
@@ -363,8 +382,35 @@ fn design_md_anchors_nested_struct_keys() {
|
||||
|
||||
for anchor in anchors {
|
||||
assert!(
|
||||
DESIGN_MD.contains(anchor),
|
||||
data_model_section().contains(anchor),
|
||||
"DESIGN.md §Data-model is missing nested-struct-key anchor `{anchor}`"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pin the §"Data model" section extractor itself: it must return a
|
||||
/// non-empty slice starting with `## Data model`, and it must NOT bleed
|
||||
/// into the next top-level section (`## Pipeline` today). A bug in the
|
||||
/// extractor would otherwise silently widen every drift test's scope
|
||||
/// back toward full-document scanning — the failure mode this file's
|
||||
/// tightening exists to prevent.
|
||||
#[test]
|
||||
fn data_model_section_is_bounded() {
|
||||
let section = data_model_section();
|
||||
assert!(
|
||||
section.starts_with("## Data model"),
|
||||
"section must start at the §Data model header; got first 40 bytes: {:?}",
|
||||
§ion[..40.min(section.len())]
|
||||
);
|
||||
assert!(
|
||||
!section.contains("\n## Pipeline"),
|
||||
"section must stop before the next top-level header (\\n## Pipeline); \
|
||||
extractor regressed to whole-document scanning"
|
||||
);
|
||||
assert!(
|
||||
section.len() > 1000,
|
||||
"§Data model is the canonical schema reference; a slice shorter than \
|
||||
1 KB suggests the extractor truncated; got {} bytes",
|
||||
section.len()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! AILang has two file forms today: Form A (`.ail`, the LLM
|
||||
//! authoring surface) and Form B (`.ail.json`, the canonical
|
||||
//! JSON-AST). These loaders accept both: for `.ail` they read the
|
||||
//! source text and parse it via [`crate::parse`]; for `.ail.json`
|
||||
//! source text and parse it via [`crate::parse()`]; for `.ail.json`
|
||||
//! they delegate to [`ailang_core::load_module`].
|
||||
//!
|
||||
//! These functions live in `ailang-surface` rather than
|
||||
@@ -24,7 +24,7 @@ fn is_ail_source(path: &Path) -> bool {
|
||||
///
|
||||
/// Dispatches on file extension:
|
||||
///
|
||||
/// - `.ail` — read source text, parse via [`crate::parse`], return
|
||||
/// - `.ail` — read source text, parse via [`crate::parse()`], return
|
||||
/// the in-memory [`Module`].
|
||||
/// - anything else — delegate to [`ailang_core::load_module`] and
|
||||
/// convert its `Error` into the corresponding
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# iter drift-test-narrowing — design_schema_drift.rs scans §"Data model" only
|
||||
|
||||
**Date:** 2026-05-13
|
||||
**Started from:** rustdoc-sweep working-tree state
|
||||
**Status:** DONE
|
||||
|
||||
## Summary
|
||||
|
||||
`crates/ailang-core/tests/design_schema_drift.rs` is the
|
||||
drift-detection layer between AST enum definitions in
|
||||
`crates/ailang-core/src/ast.rs` and the canonical schema
|
||||
documentation in `docs/DESIGN.md` §"Data model". Each AST variant
|
||||
must have its JSON-schema anchor (e.g. `"t": "lit"`, `"k": "fn"`)
|
||||
documented; the tests assert presence of each anchor.
|
||||
|
||||
Pre-iter, the assertion was `DESIGN_MD.contains(anchor)` —
|
||||
substring search over the whole document. That was loose: an
|
||||
anchor present only in §"Decision 11" (or any other discussion
|
||||
section) was treated as documented even though §"Data model" —
|
||||
the canonical schema reference section — was missing it. The
|
||||
audit-form-a-precursor `[high]` drift report flagged this as the
|
||||
silent-pass failure mode.
|
||||
|
||||
This iter narrows the scan. New helper `data_model_section()`
|
||||
extracts the slice of `DESIGN_MD` from the `## Data model` header
|
||||
to the next top-level `## ` header. The 7 existing anchor tests
|
||||
now call `data_model_section().contains(anchor)` instead of
|
||||
`DESIGN_MD.contains(anchor)`. All 38 anchors verified present in
|
||||
§"Data model" pre-edit (no regressions). A new test
|
||||
`data_model_section_is_bounded` pins the extractor itself
|
||||
(starts-with `## Data model`, does not bleed past `\n## Pipeline`,
|
||||
length > 1 KB) so a future refactor cannot silently regress the
|
||||
extractor to whole-document scanning.
|
||||
|
||||
## Files touched
|
||||
|
||||
- `crates/ailang-core/tests/design_schema_drift.rs` (M)
|
||||
|
||||
## Verification
|
||||
|
||||
- `cargo test -p ailang-core --test design_schema_drift` →
|
||||
8 passed (was 7; +1 for the bounded-section pin), 0 failed.
|
||||
- `cargo test --workspace` → 563 passed (was 562; +1 from the
|
||||
new pin), 0 failed.
|
||||
|
||||
## Concerns
|
||||
|
||||
- (none)
|
||||
|
||||
## Known debt
|
||||
|
||||
- (none)
|
||||
@@ -0,0 +1,61 @@
|
||||
# iter rustdoc-sweep — clear all 23 cargo-doc warnings
|
||||
|
||||
**Date:** 2026-05-13
|
||||
**Started from:** 48b1f77 (post-WhatsNew str-concat done-state)
|
||||
**Status:** DONE
|
||||
|
||||
## Summary
|
||||
|
||||
Hygiene sweep against `cargo doc --workspace --no-deps`. Before: 23
|
||||
warnings (17 in `ailang-check` + 4 in `ailang-core` + 2 in
|
||||
`ailang-surface`). After: zero. All edits are documentation-only —
|
||||
no production code, no test code changed. Tests stay 562 green.
|
||||
|
||||
Two warning classes, fixed with two different shapes:
|
||||
|
||||
- **Public doc links to private items** (15 warnings). Pattern:
|
||||
a `pub` item's `///` doc references `[`fn_name`]` where `fn_name`
|
||||
is `pub(crate)` or private. Fix: replace ``[`fn_name`]`` with
|
||||
the plain backtick-code-span ``` `fn_name` ```. The identifier
|
||||
stays readable in rendered docs but rustdoc no longer tries to
|
||||
resolve the link.
|
||||
- **Unresolved links** (8 warnings). Sub-classes:
|
||||
- ``[`Registry`]`` in `mono.rs` (×3) — `Registry` lives in
|
||||
`ailang_core::workspace` and the path was bare. Fixed by
|
||||
fully-qualifying as `[`ailang_core::workspace::Registry`]` /
|
||||
`[`ailang_core::workspace::Registry::entries`]`.
|
||||
- ``[`Env::class_methods`]`` — `Env` is in this crate but
|
||||
bare; the doc string was demoted to plain backticks
|
||||
(`` `crate::Env::class_methods` ``) because `Env` itself has
|
||||
no `pub` re-export at the crate root that rustdoc could
|
||||
follow.
|
||||
- ``[`crate::parse`]`` ambiguous in `loader.rs` (×2) — rustdoc
|
||||
saw both a function and a module with that path. Disambiguated
|
||||
to the function form via ``[`crate::parse()`]`` per
|
||||
rustdoc's own suggestion.
|
||||
- `metas[i]` in `lib.rs` — rustdoc parsed `[i]` as a link.
|
||||
Escaped as `metas\[i\]` (same fix for the sibling `vars\[i\]`).
|
||||
|
||||
## Files touched
|
||||
|
||||
- `crates/ailang-core/src/desugar.rs` (1 line)
|
||||
- `crates/ailang-core/src/workspace.rs` (3 lines)
|
||||
- `crates/ailang-surface/src/loader.rs` (2 lines)
|
||||
- `crates/ailang-check/src/uniqueness.rs` (2 lines)
|
||||
- `crates/ailang-check/src/diagnostic.rs` (1 line)
|
||||
- `crates/ailang-check/src/mono.rs` (5 lines)
|
||||
- `crates/ailang-check/src/lib.rs` (8 lines)
|
||||
|
||||
## Verification
|
||||
|
||||
- `cargo doc --workspace --no-deps 2>&1 | grep "^warning:" | wc -l`
|
||||
→ `0`.
|
||||
- `cargo test --workspace` → 562 passed, 0 failed (baseline holds).
|
||||
|
||||
## Concerns
|
||||
|
||||
- (none)
|
||||
|
||||
## Known debt
|
||||
|
||||
- (none)
|
||||
@@ -58,3 +58,5 @@
|
||||
- 2026-05-13 — iter bugfix-instance-body-unbound-var: instance method bodies now walk through `check_fn` — `check_def`'s `Def::Class(_) | Def::Instance(_)` arm at `crates/ailang-check/src/lib.rs:1574-1595` was early-returning `Ok(())` for both variants (the comment claimed iter 22b.2 landed instance-body typechecking; the wiring was never implemented), so an unbound identifier inside an instance-method lambda body slipped past `ail check` (false-OK exit 0 with `ok (N symbols across M modules)`) and surfaced only at `ail build` as the degraded internal-error diagnostic `monomorphise_workspace: unknown identifier: <name>` without source location, symbol kind, or "did you mean" candidates. Fix: split the combined arm so `Def::Class(_)` stays schema-only at this layer while `Def::Instance(inst)` routes through a new helper `check_instance` that, for each `InstanceMethod`, lifts the body `Term::Lam` into a synthetic `FnDef` and applies class-method substitution (class param → instance type via the existing `substitute_rigids` / `substitute_rigids_in_term` helpers — looked up in `env.class_methods[(qualify_class_ref_in_check(&inst.class, &env.current_module), im.name)]` for the class's `param` name) before handing to `check_fn`. The reuse of `check_fn` is what gets the body walked through the same identifier-resolution path as fn bodies, so an unbound name fires `[unbound-var]` at `ail check` with exit 1 and the standard structured diagnostic. RED: 2 tests in `crates/ail/tests/unbound_in_instance_method_pin.rs` (text-mode + `--json` shape, both committed in 72f3f65 ahead of the GREEN side per the audit-trail flow). GREEN: 557+2 = 559 green; both pins PASS; fn-body-level unbound-var path stays green; all 8 carve-out fixtures classify unchanged. Known debt (out of scope per "minimal fix" constraint, recorded in journal): `check_instance` does not yet cross-check the substituted method signature against the class's `ClassMethodEntry.method_ty` — a malformed instance declaring wrong types in its Lam would still typecheck cleanly. Closes the bug finding from fieldtest-form-a → 2026-05-13-iter-bugfix-instance-body-unbound-var.md
|
||||
- 2026-05-13 — iter form-a.tidy: post-fieldtest documentary tidy — 6 tasks; closes 2/3 fieldtest-form-a spec_gaps + 3/4 audit-form-a drift items. T1-T3 add three new sections to `crates/ailang-core/specs/form_a.md`: `### Class — (class ...)` (EBNF with optional `(superclass …)?` schema-matched cardinality + abstract-required vs default-bearing method clauses, anchored to `examples/test_22c_user_class_e2e.ail` ok 24/2), `### Instance — (instance ...)` (EBNF with canonical-form CLASS-REF rule explicitly stated + same-module bare example abbreviated from `mq3_class_eq_vs_fn_eq_classmod.ail` + cross-module qualified example from `show_user_adt.ail` + `bare-cross-module-class-ref` diagnostic anchor), and `(forall ...)` extension with optional `(constraints (constraint CLASS-REF TYPE)+)?` clause + `no-instance` diagnostic anchor + fifth Examples-block entry anchored to `cmp_max_smoke.ail` ok 22/2. §Definitions intro flipped `Three kinds` → `Five kinds`. T4 fixes 6 contradictory "seven carve-outs" sites in `docs/specs/2026-05-13-form-a-default-authoring.md` (preamble + §C1 + §C2 + §C3 + §"Data flow" two sites) to match the §C4(b) amendment commit 9fcda8b; post-edit grep returns 4 correctly-scoped surviving "seven" mentions (lines 233, 238, 463, 469 — all §C4(a)-scope or arithmetic/future-state). T5 deletes empty `#[cfg(test)] mod tests {}` placeholder + 6-line relocation-comment block from `crates/ailang-core/src/hash.rs:50-57` (the unit tests live in `crates/ailang-core/tests/hash_pin.rs` since form-a.1 T5 relocation; placeholder served no purpose). T6 rewrites `crates/ailang-surface/tests/round_trip.rs` module-level `//!` (lines 1-26) and inner `///` (lines 63-72) docstrings to the post-T10 four-property framing (parse-determinism + idempotency-under-print + CLI-pipeline-idempotency + carve-out-anchor) instead of retired Direction-1/Direction-2 framing, with sibling-crate breadcrumbs pointing at the other two enforcement points (`crates/ail/tests/roundtrip_cli.rs::cli_parse_then_render_then_parse_is_idempotent` for CLI-pipeline-idempotency, `crates/ailang-core/tests/carve_out_inventory.rs::examples_ail_json_inventory_matches_carve_outs` for carve-out-anchor). Drift item B2 ("plan-file seven-orphans, two sites" per audit-form-a) dropped from the iter: recon verified `docs/plans/2026-05-13-iter-form-a.1.md` contains zero defective `seven` mentions; all four hits are internally scoped to §C4(a), arithmetic, or future-state — the audit-form-a journal's "two sites" claim against the plan file did not match its contents at HEAD; decision recorded in journal §"Decision recorded — Drift item B2". Tests 559 green at every per-task gate (no test-file changes; T5 deletes a no-op `mod tests {}`). Pure-documentary iter, Phase 3 deliberately skipped per carrier; verification was per-task `cargo test --workspace` + `ail check` on the four corpus fixtures cited in T1-T3. Remaining open from form-a fieldtest queue: friction (no `str_concat` primitive — symmetric tidy iter queued) → 2026-05-13-iter-form-a.tidy.md
|
||||
- 2026-05-13 — iter str-concat: `str_concat : (borrow Str, borrow Str) -> Str` heap-Str concatenation primitive shipped in four-site lockstep, closing fieldtest-form-a friction finding #4. T1 RED-pin `crates/ail/tests/str_concat_e2e.rs` + new corpus fixture `examples/show_user_adt_with_label.ail` (Show user-ADT body using `(app str_concat "Item " (app int_to_str n))` through prelude `print`; ok 23/2). T2 `runtime/str.c` C helper `ailang_str_concat(a, b)` appended after `ailang_str_clone`: reads `len` headers from both source payloads, `str_alloc(la+lb)`, memcpys both bytes, NUL-terminates. T3 `crates/ailang-check/src/builtins.rs` install entry (Type::Fn arity-2 Borrow-Borrow params, Own return, effects empty) + list() row + `install_str_concat_signature` unit test reaching into `env.globals` directly (deeper than synth_in_builtins_env-based siblings because first builtin with arity-2 Borrow params). T4 `crates/ailang-codegen/src/lib.rs` four-site lockstep: extern `declare ptr @ailang_str_concat(ptr, ptr)` after str_clone declare + lower_app arm after str_clone arm (arity-2 check + two `lower_term` + `fresh_ssa` + body push) + `is_builtin_callable` match-list extended with `"str_concat"` + IR-pin unit test `str_concat_emits_call_to_ailang_str_concat` asserting both extern declaration AND call instruction in emitted IR. Five IR snapshots regenerated (`hello.ll`, `list.ll`, `max3.ll`, `sum.ll`, `ws_main.ll`) to absorb the new unconditional declare line in IR header — same upkeep pattern as hs.4 (sole diff verified at IR line 17 across all snapshots). T5 lockstep-collision repair: `examples/bug_unbound_in_instance_method.ail` had used `str_concat` as the literal UNBOUND name (because that was the fieldtester's LLM-natural repro); renamed to `format_label` (LLM-author-realistic helper name that will never become a builtin) and updated `crates/ail/tests/unbound_in_instance_method_pin.rs` (`replace_all` on `str_concat` → `format_label`, including one test name flipped to `check_fires_unbound_var_for_format_label_in_instance_method_body`), preserving the regression guard's intent (instance-method-body walked through unbound-var check) while substituting a name with stable unbound semantics. T6 `docs/DESIGN.md` new §"Heap-Str primitives" subsection (line 1994) between the milestone-24 Show-backer enumeration and the existing `Primitive output goes through ...` paragraph, cataloguing all five heap-Str primitives (`int_to_str`, `bool_to_str`, `float_to_str`, `str_clone`, `str_concat`) with type signatures, iter origins, and the user-visible-vs-prelude-internal distinction; Show-backer block unchanged. T7 `docs/roadmap.md` struck `[x] [feature] str_concat` line at end of P2 cluster. Two minor plan-vs-actual discrepancies recorded in journal Concerns: (a) T3 Step 5 test-count prediction `passed: 560 failed: 1` was actual `passed: 558 failed: 3` because the unbound_in_instance_method_pin tests turn RED at checker registration not at codegen (assertion is on the `[unbound-var]` diagnostic which depends only on the checker registry) — implementation correct, T5 fully repairs; (b) IR snapshot regen for T4 was not explicitly scripted in the plan but follows hs.4 precedent. Final iter state: 562/562 green (559 baseline + 3 new pins), zero re-loops across all 7 tasks. Bench impact none (no new code paths touched by bench corpus; bench fixtures use `int_to_str` / `bool_to_str` only). Closes fieldtest-form-a queue: bug (closed bugfix-instance-body-unbound-var), spec_gaps 2+3 (closed form-a.tidy), friction (closed here). Remaining open: spec_gap 1 dropped per recon (audit-form-a "plan two sites" claim did not match HEAD) → 2026-05-13-iter-str-concat.md
|
||||
- 2026-05-13 — iter rustdoc-sweep: cleared all 23 `cargo doc --workspace --no-deps` warnings (17 in `ailang-check` + 4 in `ailang-core` + 2 in `ailang-surface`). Two warning classes: (a) `pub` doc-comments that linked-via-brackets to `pub(crate)`/private items (15 hits) — replaced ``[`fn`]`` with plain backtick-code-span ``` `fn` ``` so the identifier stays readable but rustdoc no longer tries to resolve the link; (b) unresolved/ambiguous links (8 hits) — `mono.rs` `[`Registry`]`/`[`Registry::entries`]` ×3 fully-qualified to `[`ailang_core::workspace::Registry…`]`; `loader.rs` `[`crate::parse`]` ×2 (ambiguous between fn + mod) disambiguated to `[`crate::parse()`]`; `lib.rs` `metas[i]` (parsed as a link) escaped to `metas\[i\]`. Tests 562 → 562, no behaviour change → 2026-05-13-iter-rustdoc-sweep.md
|
||||
- 2026-05-13 — iter drift-test-narrowing: `crates/ailang-core/tests/design_schema_drift.rs` now scans §"Data model" only, not the whole DESIGN.md. New helper `data_model_section()` slices `DESIGN_MD` from `## Data model` to the next top-level `## ` header; all 7 anchor-presence tests switched from `DESIGN_MD.contains(anchor)` to `data_model_section().contains(anchor)`. Closes the audit-form-a-precursor `[high]` "anchors-elsewhere-pass-silently" failure mode (an anchor present only in §"Decision 11" or another discussion section was previously treated as documented). All 38 anchors verified pre-edit to live in §"Data model" so the tightening doesn't regress to red. New pin `data_model_section_is_bounded` (starts-with `## Data model`, no bleed past `\n## Pipeline`, > 1 KB) guards the extractor itself against silent regression to whole-document scanning. Tests 562 → 563 (+1) → 2026-05-13-iter-drift-test-narrowing.md
|
||||
|
||||
Reference in New Issue
Block a user