Iter 13d: rustdoc polish for ailang-core + new docwriter agent

Adds ailang-docwriter to /agents/ — a recurring role for keeping
crate-, module-, and pub-item-level rustdoc accurate. First mission:
ailang-core. Crate root, every module root, every pub item documented;
intra-doc links throughout; Iter-13a additions (TypeDef.vars, Type::Con.args)
get an explicit backwards-compat note. Two stale broken-link warnings in
ailang-check fixed in passing. cargo doc --no-deps now warning-free across
the workspace; promoted to verification invariant 6 in DESIGN.md.
This commit is contained in:
2026-05-07 15:02:35 +02:00
parent 3d9fbc68c6
commit c90926dbba
12 changed files with 536 additions and 36 deletions
+52 -5
View File
@@ -1,10 +1,35 @@
//! Workspace loader: loads an entry module and recursively follows its
//! `imports`. Convention: an `import { module: "foo" }` is resolved
//! relative to the entry file's directory as `<root_dir>/foo.ail.json`.
//! `imports`.
//!
//! Iter 5a is responsible for **finding** and consistently **loading**
//! all reachable modules. Cross-module typecheck (5b) and codegen (5c)
//! build on top, but are not part of this step.
//! Convention: an `import { module: "foo" }` is resolved relative to
//! the entry file's directory as `<root_dir>/foo.ail.json`. Module
//! names must match the file stem (the loader rejects mismatches).
//!
//! The single entry point is [`load_workspace`]; it returns a fully
//! populated [`Workspace`] or a structured [`WorkspaceLoadError`].
//! [`module_hash`] is the module-granularity counterpart to
//! [`crate::def_hash`] and is used both internally (to detect a module
//! re-loaded from disk with different content) and by the CLI.
//!
//! This module is responsible only for **finding** and consistently
//! **loading** all reachable modules. Cross-module typechecking lives
//! in `ailang-check`; codegen in `ailang-codegen`. Neither is run from
//! here.
//!
//! # Examples
//!
//! ```ignore
//! use ailang_core::load_workspace;
//! use std::path::Path;
//!
//! let ws = load_workspace(Path::new("examples/ws_main.ail.json"))?;
//! assert_eq!(ws.entry, "ws_main");
//! // All transitively imported modules are now in `ws.modules`.
//! for (name, m) in &ws.modules {
//! println!("{name}: {} defs", m.defs.len());
//! }
//! # Ok::<(), ailang_core::workspace::WorkspaceLoadError>(())
//! ```
use crate::ast::Module;
use crate::canonical;
@@ -20,8 +45,14 @@ use std::path::{Path, PathBuf};
/// in; all imports are resolved relative to it.
#[derive(Debug, Clone)]
pub struct Workspace {
/// Name of the entry module (the one passed to [`load_workspace`]).
pub entry: String,
/// Every module reachable from `entry`, indexed by module name.
/// `BTreeMap` is used so iteration order is deterministic, which
/// matters for downstream codegen and reporting.
pub modules: BTreeMap<String, Module>,
/// Directory the entry file lives in; all imports are resolved
/// relative to it.
pub root_dir: PathBuf,
}
@@ -31,6 +62,8 @@ pub struct Workspace {
/// closed — the last element is the name already present in `visiting`.
#[derive(Debug, thiserror::Error)]
pub enum WorkspaceLoadError {
/// File I/O failed while reading a module file (typically: file
/// missing, permission denied).
#[error("io error for {path}: {source}")]
Io {
path: PathBuf,
@@ -38,6 +71,9 @@ pub enum WorkspaceLoadError {
source: std::io::Error,
},
/// File contents failed to parse as a [`Module`] or had the wrong
/// schema tag. Wraps a [`CoreError`] (the single-module loader's
/// error type).
#[error("schema/parse error in {path}: {source}")]
Schema {
path: PathBuf,
@@ -45,9 +81,14 @@ pub enum WorkspaceLoadError {
source: CoreError,
},
/// An `import { module: "foo" }` did not resolve to an existing
/// file at the expected path.
#[error("module `{name}` not found (expected at {expected_path})")]
ModuleNotFound { name: String, expected_path: PathBuf },
/// The `name` field in a loaded module file disagrees with the
/// file-stem-derived name the loader expected (i.e. the
/// `<name>.ail.json` convention is broken).
#[error(
"module name in file ({name_in_file:?}) does not match expected name from path ({name_from_path:?})"
)]
@@ -56,9 +97,15 @@ pub enum WorkspaceLoadError {
name_from_path: String,
},
/// An import cycle was detected. `path` is the chain of module
/// names in which the cycle was closed — the last element is the
/// name that was already on the visit stack.
#[error("import cycle detected: {}", path.join(" -> "))]
Cycle { path: Vec<String> },
/// A module was reachable through two import paths, and its
/// on-disk content (compared via [`module_hash`]) differs between
/// the two reads. This typically means the file changed mid-load.
#[error(
"module `{name}` was loaded twice with differing content (hashes differ)"
)]