iter clippy-sweep: clear all 61 cargo clippy warnings
Hygiene sweep across the workspace under `cargo clippy --workspace
--all-targets`. Before: 61 warnings. After: zero. Tests stay 563
green, `cargo doc` stays at zero warnings, and all three bench
scripts exit 0 against existing baselines.
Twelve lint classes, three fix shapes:
- Documentation hygiene (~32 hits): `doc_lazy_continuation` resolved
by adding blank lines before continuation paragraphs or rephrasing
the offending line; plus four `empty_line_after_doc_comments` hits
in workspace.rs where eight `///` blocks left orphan by form-a.1
T5 test relocation were converted to plain `//`.
- Idiomatic refactors (~16 hits): `.err().expect()` → `.expect_err()`,
redundant `.into_iter()` and `.into()` removed, `|f| g(f)` →
`g`, `.trim().split_whitespace()` → `.split_whitespace()`,
`push_str("x")` → `push('x')`, two manual `impl Default` flipped
to `#[derive(Default)]` + `#[default]`, two nested `if let Some(_)
= …` collapsed.
- Block extraction (1 hit): a 13-line block inside an `else if`
condition in synth's Var-arm extracted to a new helper
`is_class_method_dispatch(name, env)` next to
`qualifier_is_class_shape`.
Three `#[allow]`s with inline rationale where clippy's suggestion
would lose meaning: `only_used_in_recursion` on walk_pattern (the
parameter preserves the five-fn walker-framework signature
uniformity), 2× `if_same_then_else` on qualify_local_types shapes
(two branches encode semantically distinct disqualification
reasons), `too_many_arguments` on `Emitter::new` (8 workspace-flat
tables; bundling would just rename boilerplate).
This commit is contained in:
@@ -913,11 +913,12 @@ fn compare_primitives_smoke_prints_1_2_3_thrice() {
|
||||
}
|
||||
|
||||
/// Iter 23.2: end-to-end coverage for the auto-loaded `Eq` class
|
||||
/// + three primitive instances. The fixture calls `eq` on Int /
|
||||
/// Bool / Str with both an equal and an unequal pair each; the
|
||||
/// monomorphiser synthesises `eq__Int` / `eq__Bool` / `eq__Str`
|
||||
/// in the prelude module; the binary prints six lines, one per
|
||||
/// call. Equal-call lines are `1`, unequal-call lines are `0`.
|
||||
/// and its three primitive instances. The fixture calls `eq` on
|
||||
/// Int / Bool / Str with both an equal and an unequal pair each;
|
||||
/// the monomorphiser synthesises `eq__Int` / `eq__Bool` /
|
||||
/// `eq__Str` in the prelude module; the binary prints six lines,
|
||||
/// one per call. Equal-call lines are `1`, unequal-call lines are
|
||||
/// `0`.
|
||||
#[test]
|
||||
fn eq_primitives_smoke_compiles_and_runs() {
|
||||
let stdout = build_and_run("eq_primitives_smoke.ail");
|
||||
@@ -1610,6 +1611,7 @@ fn alloc_rc_recursive_list_sum() {
|
||||
/// `@drop_rc_list_drop_borrow_IntList(ptr %t)` — recursively
|
||||
/// cascades through the 4-cell tail, freeing each cell.
|
||||
/// 5. Branches to the join.
|
||||
///
|
||||
/// And at `xs`'s let-close, the inlined partial drop skips slot 1
|
||||
/// (already freed by the arm) and dec's the outer Cons cell.
|
||||
///
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
//! calls `gt`/`not` is monomorphised correctly at Int / Bool / Str.
|
||||
//! 2. User-ADT integration with the unified mono pass
|
||||
//! (`eq_ord_user_adt_runs_end_to_end`). Property: `instance Eq T`
|
||||
//! + `instance Ord T` for a user-defined `T` produces working
|
||||
//! plus `instance Ord T` for a user-defined `T` produces working
|
||||
//! mono symbols when invoked through the prelude.
|
||||
//! 3. Mono-symbol body-hash stability for `eq__IntBox`
|
||||
//! (`eq_ord_user_adt_eq_intbox_hash_stable`). Property: the user
|
||||
|
||||
@@ -704,8 +704,8 @@ fn rewrite_replaces_class_method_var_with_mono_symbol_same_module() {
|
||||
}
|
||||
|
||||
/// Helper: build & run a workspace via `ail run` and capture stdout
|
||||
/// + stderr. The 22b.3 e2e tests below all funnel through this so the
|
||||
/// failure-mode reporting is uniform.
|
||||
/// and stderr. The 22b.3 e2e tests below all funnel through this so
|
||||
/// the failure-mode reporting is uniform.
|
||||
fn ail_run(fixture_name: &str) -> (std::process::Output, String) {
|
||||
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let ail_bin = std::env::var("CARGO_BIN_EXE_ail")
|
||||
|
||||
@@ -2363,6 +2363,7 @@ pub(crate) fn parse_method_qualifier(name: &str) -> (&str, Option<String>) {
|
||||
/// - bare-class qualifier, e.g. `"Show.show"` — class names are
|
||||
/// PascalCase per repo convention (first character uppercase).
|
||||
/// - module-qualified class qualifier, e.g. `"prelude.Show.show"`.
|
||||
///
|
||||
/// Rejects qualifier shapes that look like cross-module-fn calls
|
||||
/// (`"std_list.length"`): qualifier starts with lowercase, no
|
||||
/// inner dot — falls through to the qualified-fn arm.
|
||||
@@ -2382,6 +2383,19 @@ pub(crate) fn qualifier_is_class_shape(qualifier_opt: &Option<String>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// mq.2 dispatch entry predicate: the synth Var-arm runs this to
|
||||
/// decide whether `name` should be routed through the class-method
|
||||
/// dispatch helper. Matches a bare method name (e.g. `"show"`) or a
|
||||
/// class-shaped qualifier (`"Show.show"`, `"prelude.Show.show"`).
|
||||
/// A 1-dot lowercase name (`"std_list.length"`) is structurally a
|
||||
/// cross-module-fn call per mq.1's canonical-form rule and falls
|
||||
/// through to the qualified-fn arm.
|
||||
fn is_class_method_dispatch(name: &str, env: &Env) -> bool {
|
||||
let (method_name, qualifier_opt) = parse_method_qualifier(name);
|
||||
qualifier_is_class_shape(&qualifier_opt)
|
||||
&& env.method_to_candidate_classes.contains_key(method_name)
|
||||
}
|
||||
|
||||
/// mq.tidy: predicate for the `class-method-shadowed-by-fn`
|
||||
/// warning emission. The full spec rule (§"Class-fn collisions"
|
||||
/// 161-162) requires a class candidate WITH AN INSTANCE FOR THE
|
||||
@@ -2750,20 +2764,7 @@ pub(crate) fn synth(
|
||||
// Bare name resolves through implicit import; the
|
||||
// unqualified name in the owner module is the same `name`.
|
||||
(qualified, Some((owner_module, name.clone())))
|
||||
} else if {
|
||||
// mq.2 dispatch entry: the Var arm matches a class
|
||||
// method either bare (`"show"`) or qualified
|
||||
// (`"prelude.Show.show"`). `parse_method_qualifier`
|
||||
// splits at the last dot; a `Some(qualifier)` with no
|
||||
// inner dot is a 1-dot name (`std_list.length`) —
|
||||
// structurally a cross-module-fn call per mq.1's
|
||||
// canonical-form rule (class qualifiers are always
|
||||
// `<module>.<Class>`), so we fall through to the
|
||||
// qualified-fn arm below in that case.
|
||||
let (method_name, qualifier_opt) = parse_method_qualifier(name);
|
||||
qualifier_is_class_shape(&qualifier_opt)
|
||||
&& env.method_to_candidate_classes.contains_key(method_name)
|
||||
} {
|
||||
} else if is_class_method_dispatch(name, env) {
|
||||
// mq.2: type-driven dispatch entry. The Var arm runs
|
||||
// before App-arm unification, so the arg type is not
|
||||
// available here. The dispatch helper resolves on
|
||||
@@ -3456,6 +3457,11 @@ fn maybe_instantiate(t: Type, counter: &mut u32) -> Type {
|
||||
fn qualify_local_types(t: &Type, owner_module: &str, local_types: &IndexMap<String, TypeDef>) -> Type {
|
||||
match t {
|
||||
Type::Con { name, args } => {
|
||||
// The first two branches share the body `name.clone()` but
|
||||
// express semantically distinct reasons (already qualified;
|
||||
// primitive needs no qualification). Combining them with
|
||||
// `||` would obscure why each disqualifies the name.
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
let qualified_name = if name.contains('.') {
|
||||
name.clone()
|
||||
} else if ailang_core::primitives::is_primitive_name(name) {
|
||||
@@ -3726,9 +3732,10 @@ pub struct Env {
|
||||
/// mq.2: workspace-flat inverse of [`Self::class_methods`] — for
|
||||
/// each method name, the set of qualified class names that declare
|
||||
/// it. Pre-mq.3, the `MethodNameCollision` invariant guarantees
|
||||
/// each set is singleton; mq.3 lifts the invariant and cardinality
|
||||
/// > 1 becomes legal. Synth's `Term::Var` arm consults this index
|
||||
/// for type-driven dispatch (spec §Architecture's 5-step rule).
|
||||
/// each set is singleton; mq.3 lifts the invariant and any
|
||||
/// cardinality above one becomes legal. Synth's `Term::Var` arm
|
||||
/// consults this index for type-driven dispatch (spec
|
||||
/// §Architecture's 5-step rule).
|
||||
pub method_to_candidate_classes: BTreeMap<String, BTreeSet<String>>,
|
||||
/// mq.3: post-superclass-expansion declared constraints of the
|
||||
/// active fn body being checked. Populated by `check_fn` before
|
||||
@@ -5512,7 +5519,7 @@ mod tests {
|
||||
ret_mode: ParamMode::Implicit,
|
||||
}),
|
||||
},
|
||||
vec!["x".into()],
|
||||
vec!["x"],
|
||||
Term::Let {
|
||||
name: "z".into(),
|
||||
value: Box::new(Term::Lit { lit: Literal::Int { value: 7 } }),
|
||||
|
||||
@@ -198,7 +198,7 @@ pub fn lift_letrecs(m: &Module) -> Result<Module> {
|
||||
}
|
||||
}
|
||||
|
||||
out.defs.extend(lifter.lifted.into_iter());
|
||||
out.defs.extend(lifter.lifted);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
|
||||
@@ -714,7 +714,7 @@ fn collect_pattern_binders(p: &Pattern) -> Vec<String> {
|
||||
Pattern::Var { name } => vec![name.clone()],
|
||||
Pattern::Ctor { fields, .. } => fields
|
||||
.iter()
|
||||
.flat_map(|f| collect_pattern_binders(f))
|
||||
.flat_map(collect_pattern_binders)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1025,25 +1025,25 @@ fn rewrite_mono_calls(
|
||||
let is_poly_free_fn = !is_class_method && poly_free_fns.contains(name);
|
||||
let should_advance = (is_class_method || is_poly_free_fn) && !locals.contains(name);
|
||||
if should_advance {
|
||||
if let Some(slot) = ordered_targets.get(*cursor) {
|
||||
if let Some(t) = slot {
|
||||
let (sym, defining_module) = match t {
|
||||
MonoTarget::ClassMethod { method, type_, defining_module, .. } => {
|
||||
(mono_symbol(method, type_), defining_module.as_str())
|
||||
}
|
||||
MonoTarget::FreeFn { name: fn_name, type_args, defining_module } => {
|
||||
(mono_symbol_n(fn_name, type_args.as_slice()), defining_module.as_str())
|
||||
}
|
||||
};
|
||||
let new_name = if defining_module == caller_module {
|
||||
sym
|
||||
} else {
|
||||
format!("{}.{}", defining_module, sym)
|
||||
};
|
||||
*name = new_name;
|
||||
}
|
||||
// else: residual / observation was non-concrete →
|
||||
// leave name unchanged.
|
||||
// The outer `Some` means the cursor slot exists; the inner
|
||||
// `Some` means the observation at that slot is concrete.
|
||||
// A `Some(None)` slot is a residual / non-concrete
|
||||
// observation and leaves the name unchanged.
|
||||
if let Some(Some(t)) = ordered_targets.get(*cursor) {
|
||||
let (sym, defining_module) = match t {
|
||||
MonoTarget::ClassMethod { method, type_, defining_module, .. } => {
|
||||
(mono_symbol(method, type_), defining_module.as_str())
|
||||
}
|
||||
MonoTarget::FreeFn { name: fn_name, type_args, defining_module } => {
|
||||
(mono_symbol_n(fn_name, type_args.as_slice()), defining_module.as_str())
|
||||
}
|
||||
};
|
||||
let new_name = if defining_module == caller_module {
|
||||
sym
|
||||
} else {
|
||||
format!("{}.{}", defining_module, sym)
|
||||
};
|
||||
*name = new_name;
|
||||
}
|
||||
*cursor += 1;
|
||||
}
|
||||
|
||||
@@ -532,26 +532,24 @@ impl<'a> Emitter<'a> {
|
||||
// var on an as-yet-unmonomorphised polymorphic call —
|
||||
// the monomorphised copies will resolve correctly).
|
||||
Term::App { .. } => {
|
||||
if let Ok(ret_ty) = self.synth_arg_type(value) {
|
||||
if let Type::Con { name, .. } = ret_ty {
|
||||
// Symmetric to `field_drop_call`'s Str arm: Str is a
|
||||
// built-in pointer type with no per-type drop fn. Both
|
||||
// heap-Str (rc_header at payload-8) and static-Str
|
||||
// (codegen-elision keeps static pointers out of this
|
||||
// path) consume via `ailang_rc_dec`.
|
||||
if name == "Str" {
|
||||
return "ailang_rc_dec".to_string();
|
||||
}
|
||||
if name.matches('.').count() == 1 {
|
||||
let (prefix, suffix) =
|
||||
name.split_once('.').expect("checked");
|
||||
if let Some(target) = self.import_map.get(prefix) {
|
||||
return format!("drop_{target}_{suffix}");
|
||||
}
|
||||
return format!("drop_{prefix}_{suffix}");
|
||||
}
|
||||
return format!("drop_{m}_{name}", m = self.module_name);
|
||||
if let Ok(Type::Con { name, .. }) = self.synth_arg_type(value) {
|
||||
// Symmetric to `field_drop_call`'s Str arm: Str is a
|
||||
// built-in pointer type with no per-type drop fn. Both
|
||||
// heap-Str (rc_header at payload-8) and static-Str
|
||||
// (codegen-elision keeps static pointers out of this
|
||||
// path) consume via `ailang_rc_dec`.
|
||||
if name == "Str" {
|
||||
return "ailang_rc_dec".to_string();
|
||||
}
|
||||
if name.matches('.').count() == 1 {
|
||||
let (prefix, suffix) =
|
||||
name.split_once('.').expect("checked");
|
||||
if let Some(target) = self.import_map.get(prefix) {
|
||||
return format!("drop_{target}_{suffix}");
|
||||
}
|
||||
return format!("drop_{prefix}_{suffix}");
|
||||
}
|
||||
return format!("drop_{m}_{name}", m = self.module_name);
|
||||
}
|
||||
"ailang_rc_dec".to_string()
|
||||
}
|
||||
@@ -578,6 +576,7 @@ impl<'a> Emitter<'a> {
|
||||
/// match that moved out some fields;
|
||||
/// 3. [`Self::emit_inlined_partial_drop`]'s non-Ctor branch
|
||||
/// (let-binder whose value is `Term::App`).
|
||||
///
|
||||
/// All three share the same shape: a binder whose runtime ctor
|
||||
/// tag is dynamic (not statically known from a `Term::Ctor`)
|
||||
/// and whose `moved_slots` is a strict subset of its ptr fields.
|
||||
|
||||
@@ -154,19 +154,14 @@ type Result<T> = std::result::Result<T, CodegenError>;
|
||||
/// Iter 18c once uniqueness inference is wired up.
|
||||
/// The IR is otherwise byte-identical between the three strategies
|
||||
/// modulo the allocator symbol name.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AllocStrategy {
|
||||
#[default]
|
||||
Gc,
|
||||
Bump,
|
||||
Rc,
|
||||
}
|
||||
|
||||
impl Default for AllocStrategy {
|
||||
fn default() -> Self {
|
||||
AllocStrategy::Gc
|
||||
}
|
||||
}
|
||||
|
||||
impl AllocStrategy {
|
||||
/// LLVM IR-level name of the allocator fn (without leading `@`).
|
||||
fn fn_name(self) -> &'static str {
|
||||
@@ -759,6 +754,11 @@ struct FnSig {
|
||||
}
|
||||
|
||||
impl<'a> Emitter<'a> {
|
||||
// The eight parameters are all the workspace-flat tables the
|
||||
// emitter needs at construction time; bundling them into a
|
||||
// context struct would just rename the boilerplate without
|
||||
// reducing it. Keep the explicit-arg shape.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
module: &'a Module,
|
||||
module_name: &'a str,
|
||||
|
||||
@@ -163,6 +163,11 @@ pub(crate) fn qualify_local_types_codegen(
|
||||
) -> Type {
|
||||
match t {
|
||||
Type::Con { name, args } => {
|
||||
// The first two branches share the body `name.clone()` but
|
||||
// express semantically distinct reasons (already qualified;
|
||||
// primitive needs no qualification). Combining them with
|
||||
// `||` would obscure why each disqualifies the name.
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
let qualified = if name.contains('.') {
|
||||
name.clone()
|
||||
} else if ailang_core::primitives::is_primitive_name(name) {
|
||||
|
||||
@@ -717,10 +717,11 @@ impl Type {
|
||||
///
|
||||
/// `Own` and `Borrow` are author-asserted: the surface form
|
||||
/// `(own T)` / `(borrow T)` round-trips through this enum.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ParamMode {
|
||||
/// Pre-18a / unannotated. Treated as `Own` by the typechecker.
|
||||
#[default]
|
||||
Implicit,
|
||||
/// `(own T)` — caller transfers ownership; callee consumes.
|
||||
Own,
|
||||
@@ -728,12 +729,6 @@ pub enum ParamMode {
|
||||
Borrow,
|
||||
}
|
||||
|
||||
impl Default for ParamMode {
|
||||
fn default() -> Self {
|
||||
ParamMode::Implicit
|
||||
}
|
||||
}
|
||||
|
||||
impl ParamMode {
|
||||
/// Used by the `skip_serializing_if` predicate on
|
||||
/// [`Type::Fn::ret_mode`].
|
||||
|
||||
@@ -272,7 +272,7 @@ pub fn desugar_module(m: &Module) -> Module {
|
||||
// produced by the bottom-up walk. Typecheck/codegen are order-
|
||||
// insensitive at the def list level (they index by name), so this
|
||||
// is purely cosmetic.
|
||||
out.defs.extend(d.lifted.into_iter());
|
||||
out.defs.extend(d.lifted);
|
||||
out
|
||||
}
|
||||
|
||||
|
||||
@@ -1395,7 +1395,9 @@ where
|
||||
/// ct.1: Pattern::Ctor carries a `ctor` name (matches against a
|
||||
/// scrutinee's TypeDef) but NOT a type_name field — the type is
|
||||
/// inferred from the scrutinee. So Pattern walking only recurses;
|
||||
/// no canonical-form check fires here.
|
||||
/// no canonical-form check fires here. `f` is kept in the signature
|
||||
/// to match the sibling `walk_*` framework (uniform plumbing).
|
||||
#[allow(clippy::only_used_in_recursion)]
|
||||
fn walk_pattern<F>(p: &crate::ast::Pattern, f: &mut F) -> Result<(), WorkspaceLoadError>
|
||||
where
|
||||
F: FnMut(&str) -> Result<(), WorkspaceLoadError>,
|
||||
@@ -1670,13 +1672,13 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter 22b.1: a workspace whose own modules contain no
|
||||
/// `Def::Instance` defs produces no non-prelude registry entries.
|
||||
/// This is the happy-path baseline for the registry-build pass —
|
||||
/// every pre-22b workspace falls into this case. With the
|
||||
/// auto-loaded prelude (iter 23.2) the registry is no longer
|
||||
/// strictly empty, so the invariant is now "no entries whose
|
||||
/// `defining_module` is anything other than `prelude`".
|
||||
// Iter 22b.1: a workspace whose own modules contain no
|
||||
// `Def::Instance` defs produces no non-prelude registry entries.
|
||||
// This is the happy-path baseline for the registry-build pass —
|
||||
// every pre-22b workspace falls into this case. With the
|
||||
// auto-loaded prelude (iter 23.2) the registry is no longer
|
||||
// strictly empty, so the invariant is now "no entries whose
|
||||
// `defining_module` is anything other than `prelude`".
|
||||
// `iter22b1_workspace_with_no_classes_has_empty_registry` relocated
|
||||
// to `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
|
||||
|
||||
@@ -1690,50 +1692,50 @@ mod tests {
|
||||
.join("examples")
|
||||
}
|
||||
|
||||
/// Iter 22b.1: a coherent instance (in the class's module)
|
||||
/// loads cleanly and produces one registry entry from the
|
||||
/// fixture itself. With the auto-loaded prelude (iter 23.2)
|
||||
/// the registry also contains the prelude's own entries; the
|
||||
/// invariant is filtered to fixture-only entries.
|
||||
// Iter 22b.1: a coherent instance (in the class's module)
|
||||
// loads cleanly and produces one registry entry from the
|
||||
// fixture itself. With the auto-loaded prelude (iter 23.2)
|
||||
// the registry also contains the prelude's own entries; the
|
||||
// invariant is filtered to fixture-only entries.
|
||||
// `iter22b1_instance_in_class_module_loads_clean` relocated to
|
||||
// `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
|
||||
|
||||
/// Iter 22b.1: an instance declared in a module that is neither
|
||||
/// the class's module nor the type's module fires `OrphanInstance`.
|
||||
/// The fixture imports `test_22b1_orphan_third_classmod` (which
|
||||
/// owns `class TShow`) and the entry module declares `instance
|
||||
/// TShow Int` itself — but the entry is not the class's module
|
||||
/// and `Int` is primitive, so neither leg of coherence is
|
||||
/// satisfied. (24.2: class renamed `Show` → `TShow`.)
|
||||
// Iter 22b.1: an instance declared in a module that is neither
|
||||
// the class's module nor the type's module fires `OrphanInstance`.
|
||||
// The fixture imports `test_22b1_orphan_third_classmod` (which
|
||||
// owns `class TShow`) and the entry module declares `instance
|
||||
// TShow Int` itself — but the entry is not the class's module
|
||||
// and `Int` is primitive, so neither leg of coherence is
|
||||
// satisfied. (24.2: class renamed `Show` → `TShow`.)
|
||||
// `iter22b1_orphan_instance_fires_diagnostic` relocated to
|
||||
// `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
|
||||
|
||||
/// Iter 22b.1 / mq.1: two instances of the same `(class, type)`
|
||||
/// pair collide on the registry's uniqueness check.
|
||||
///
|
||||
/// Pre-mq.1 the test used a two-module fixture
|
||||
/// (`test_22b1_dup_a` declared the class + first instance,
|
||||
/// `test_22b1_dup_b` declared the type + second instance) which
|
||||
/// relied on bare cross-module class refs. The mq.1
|
||||
/// canonical-form rule makes that shape structurally
|
||||
/// unrepresentable (any second instance in a module that owns
|
||||
/// neither the class nor the type fires `OrphanInstance` first).
|
||||
/// Post-mq.1 the only way to land two instances on the same
|
||||
/// canonical key is to have them in the same module (the class's
|
||||
/// or the type's module). The fixture now declares both
|
||||
/// instances in `test_22b1_dup_same_module` — both class-leg
|
||||
/// coherent, both collide on `(test_22b1_dup_same_module.TShow,
|
||||
/// type_hash(Int))`. (24.2: class renamed `Show` → `TShow`.)
|
||||
// Iter 22b.1 / mq.1: two instances of the same `(class, type)`
|
||||
// pair collide on the registry's uniqueness check.
|
||||
//
|
||||
// Pre-mq.1 the test used a two-module fixture
|
||||
// (`test_22b1_dup_a` declared the class + first instance,
|
||||
// `test_22b1_dup_b` declared the type + second instance) which
|
||||
// relied on bare cross-module class refs. The mq.1
|
||||
// canonical-form rule makes that shape structurally
|
||||
// unrepresentable (any second instance in a module that owns
|
||||
// neither the class nor the type fires `OrphanInstance` first).
|
||||
// Post-mq.1 the only way to land two instances on the same
|
||||
// canonical key is to have them in the same module (the class's
|
||||
// or the type's module). The fixture now declares both
|
||||
// instances in `test_22b1_dup_same_module` — both class-leg
|
||||
// coherent, both collide on `(test_22b1_dup_same_module.TShow,
|
||||
// type_hash(Int))`. (24.2: class renamed `Show` → `TShow`.)
|
||||
// `iter22b1_duplicate_instance_fires_diagnostic` relocated to
|
||||
// `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
|
||||
|
||||
/// Iter 22b.1: an instance that omits a required (non-default)
|
||||
/// method of its class fires `MissingMethod`. The fixture's
|
||||
/// `class TEq` declares `teq` and `tne` as both non-default; the
|
||||
/// instance only specifies `tne`, leaving `teq` missing.
|
||||
/// (Class is `TEq` (and method `tne`) rather than `Eq` / `ne` to
|
||||
/// avoid colliding with the auto-loaded prelude's `class Eq` and
|
||||
/// — since iter 23.5 — the prelude's polymorphic free fn `ne`.)
|
||||
// Iter 22b.1: an instance that omits a required (non-default)
|
||||
// method of its class fires `MissingMethod`. The fixture's
|
||||
// `class TEq` declares `teq` and `tne` as both non-default; the
|
||||
// instance only specifies `tne`, leaving `teq` missing.
|
||||
// (Class is `TEq` (and method `tne`) rather than `Eq` / `ne` to
|
||||
// avoid colliding with the auto-loaded prelude's `class Eq` and
|
||||
// — since iter 23.5 — the prelude's polymorphic free fn `ne`.)
|
||||
// `iter22b1_missing_method_fires_diagnostic` relocated to
|
||||
// `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
|
||||
|
||||
@@ -1780,16 +1782,16 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter 22b.2: an instance that specifies a body for a method
|
||||
/// name the class never declared must fire
|
||||
/// `OverridingNonExistentMethod`. Symmetric counterpart to
|
||||
/// `MissingMethod`: the latter fires when the class declares a
|
||||
/// non-default method that the instance omits; this one fires
|
||||
/// when the instance provides a body the class did not ask for.
|
||||
/// Decision 11 forbids ad-hoc additions to a class's method set
|
||||
/// at the instance site.
|
||||
/// (Class is `TEq` rather than `Eq` to avoid colliding with the
|
||||
/// auto-loaded prelude's `class Eq`.)
|
||||
// Iter 22b.2: an instance that specifies a body for a method
|
||||
// name the class never declared must fire
|
||||
// `OverridingNonExistentMethod`. Symmetric counterpart to
|
||||
// `MissingMethod`: the latter fires when the class declares a
|
||||
// non-default method that the instance omits; this one fires
|
||||
// when the instance provides a body the class did not ask for.
|
||||
// Decision 11 forbids ad-hoc additions to a class's method set
|
||||
// at the instance site.
|
||||
// (Class is `TEq` rather than `Eq` to avoid colliding with the
|
||||
// auto-loaded prelude's `class Eq`.)
|
||||
// `instance_overriding_nonexistent_method_fires` relocated to
|
||||
// `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
|
||||
|
||||
@@ -1827,15 +1829,15 @@ mod tests {
|
||||
// observations move to `env.method_to_candidate_classes`
|
||||
// (multi-entry set) plus the call-site warning.
|
||||
|
||||
/// Iter 22b.2: an instance `C T` whose class `C` declares a
|
||||
/// superclass `S` requires that `instance S T` also exist in the
|
||||
/// workspace. The fixture declares `class TEq a`, `class TOrd a
|
||||
/// extends TEq a`, and `instance TOrd Int` — but no `instance TEq
|
||||
/// Int` — so registry build must fire
|
||||
/// `MissingSuperclassInstance`. Decision 11 single-superclass
|
||||
/// model requires `instance S T` whenever `instance C T` exists.
|
||||
/// (Classes are `TEq` / `TOrd` rather than `Eq` / `Ord` to avoid
|
||||
/// colliding with the auto-loaded prelude's `class Eq`.)
|
||||
// Iter 22b.2: an instance `C T` whose class `C` declares a
|
||||
// superclass `S` requires that `instance S T` also exist in the
|
||||
// workspace. The fixture declares `class TEq a`, `class TOrd a
|
||||
// extends TEq a`, and `instance TOrd Int` — but no `instance TEq
|
||||
// Int` — so registry build must fire
|
||||
// `MissingSuperclassInstance`. Decision 11 single-superclass
|
||||
// model requires `instance S T` whenever `instance C T` exists.
|
||||
// (Classes are `TEq` / `TOrd` rather than `Eq` / `Ord` to avoid
|
||||
// colliding with the auto-loaded prelude's `class Eq`.)
|
||||
// `instance_without_superclass_instance_fires` relocated to
|
||||
// `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
|
||||
|
||||
@@ -2494,12 +2496,12 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// mq.1: on-disk fixture pin — a workspace where the consumer's
|
||||
/// `Constraint.class` references a class in an imported module via
|
||||
/// the qualified form loads cleanly. Symmetric to ct.1's positive
|
||||
/// cross-module type-ref fixture (`ct1_validator_accepts_qualified_xmod_ref`
|
||||
/// in-test sibling). Guards the full load → validator → registry
|
||||
/// path on a real on-disk pair.
|
||||
// mq.1: on-disk fixture pin — a workspace where the consumer's
|
||||
// `Constraint.class` references a class in an imported module via
|
||||
// the qualified form loads cleanly. Symmetric to ct.1's positive
|
||||
// cross-module type-ref fixture (`ct1_validator_accepts_qualified_xmod_ref`
|
||||
// in-test sibling). Guards the full load → validator → registry
|
||||
// path on a real on-disk pair.
|
||||
// `mq1_xmod_constraint_class_fixture_loads` relocated to
|
||||
// `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
|
||||
|
||||
|
||||
@@ -380,7 +380,7 @@ fn write_class_method(out: &mut String, m: &ClassMethod, level: usize, owning_mo
|
||||
// as `x`, `x1`, `x2`, … to keep the projection unambiguous
|
||||
// without inventing names that could collide with the body.
|
||||
if i == 0 {
|
||||
out.push_str("x");
|
||||
out.push('x');
|
||||
} else {
|
||||
out.push_str(&format!("x{i}"));
|
||||
}
|
||||
@@ -2100,7 +2100,6 @@ mod tests {
|
||||
let last = doc_lines.last().unwrap();
|
||||
let last_word_count = last
|
||||
.trim_start_matches("///")
|
||||
.trim()
|
||||
.split_whitespace()
|
||||
.count();
|
||||
assert!(
|
||||
|
||||
@@ -237,6 +237,7 @@ pub fn tokenize(input: &str) -> Result<Vec<Token>, LexError> {
|
||||
/// - `[-]?<digits>.<digits>`
|
||||
/// - `[-]?<digits>.<digits>(e|E)[+-]?<digits>`
|
||||
/// - `[-]?<digits>(e|E)[+-]?<digits>`
|
||||
///
|
||||
/// and contains at least one of `.`, `e`, `E` (otherwise the caller
|
||||
/// would dispatch to the integer path). Rejects bare leading/trailing
|
||||
/// dots (`5.`, `5.e10`), missing exponent digits (`1.5e`), and any
|
||||
|
||||
@@ -1758,8 +1758,7 @@ mod tests {
|
||||
(body 0)))
|
||||
"#,
|
||||
)
|
||||
.err()
|
||||
.expect("parse should fail");
|
||||
.expect_err("parse should fail");
|
||||
let msg = format!("{err}");
|
||||
assert!(
|
||||
msg.contains("borrow"),
|
||||
@@ -1806,8 +1805,7 @@ mod tests {
|
||||
(body (clone))))
|
||||
"#,
|
||||
)
|
||||
.err()
|
||||
.expect("parse should fail");
|
||||
.expect_err("parse should fail");
|
||||
let msg = format!("{err}");
|
||||
assert!(
|
||||
msg.contains("clone expects exactly one term argument"),
|
||||
@@ -1864,8 +1862,7 @@ mod tests {
|
||||
(body (reuse-as))))
|
||||
"#,
|
||||
)
|
||||
.err()
|
||||
.expect("parse should fail");
|
||||
.expect_err("parse should fail");
|
||||
let msg = format!("{err}");
|
||||
assert!(
|
||||
msg.contains("reuse-as expects exactly two term arguments"),
|
||||
@@ -1885,8 +1882,7 @@ mod tests {
|
||||
(body (reuse-as x))))
|
||||
"#,
|
||||
)
|
||||
.err()
|
||||
.expect("parse should fail");
|
||||
.expect_err("parse should fail");
|
||||
let msg = format!("{err}");
|
||||
assert!(
|
||||
msg.contains("reuse-as expects exactly two term arguments"),
|
||||
@@ -1986,8 +1982,7 @@ mod tests {
|
||||
(drop-iterative oops)))
|
||||
"#,
|
||||
)
|
||||
.err()
|
||||
.expect("parse should fail");
|
||||
.expect_err("parse should fail");
|
||||
let msg = format!("{err}");
|
||||
assert!(
|
||||
msg.contains("drop-iterative takes no arguments"),
|
||||
|
||||
@@ -105,7 +105,7 @@ fn load_workspace_prefers_ail_when_both_extensions_exist() {
|
||||
let names: Vec<&str> = leaf
|
||||
.defs
|
||||
.iter()
|
||||
.map(|d| ailang_core::def_name(d))
|
||||
.map(ailang_core::def_name)
|
||||
.collect();
|
||||
assert!(
|
||||
names.contains(&"winner"),
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
# iter clippy-sweep — clear all 61 `cargo clippy` warnings
|
||||
|
||||
**Date:** 2026-05-14
|
||||
**Started from:** 04258c5 (post-WhatsNew of rustdoc-sweep + drift-test-narrowing)
|
||||
**Status:** DONE
|
||||
|
||||
## Summary
|
||||
|
||||
Hygiene sweep against `cargo clippy --workspace --all-targets`. Before:
|
||||
61 warnings across all eight crates. After: zero. Tests stay 563 green;
|
||||
`cargo doc --workspace --no-deps` stays at zero warnings (the
|
||||
rustdoc-sweep baseline); all three bench scripts (`bench/check.py`,
|
||||
`bench/compile_check.py`, `bench/cross_lang.py`) all exit 0 against
|
||||
the existing baselines — a free Stabilitätsbeweis that the sweep
|
||||
touched no semantics.
|
||||
|
||||
Twelve warning classes, fixed with three different shapes:
|
||||
|
||||
- **Pure documentation fixes** (~32 warnings). `doc_lazy_continuation`
|
||||
on multi-line `///` and `//!` comments where clippy parsed the
|
||||
second line as a list continuation; resolved by either adding a
|
||||
blank line, escaping the marker, or rephrasing the offending line.
|
||||
Affected: `lex.rs:235-243` (3-line bullet list), `lib.rs:2360-2368`
|
||||
in `ailang-check` (3-line bullet list), `lib.rs:3736` in
|
||||
`ailang-check` (a `> 1` numeric comparison parsed as a quote
|
||||
marker; rephrased to "any cardinality above one"), `drop.rs:570-585`
|
||||
(numbered list + paragraph mix), `e2e.rs:915-920` (a `+ three
|
||||
primitive instances` line where `+` was parsed as bullet marker),
|
||||
`e2e.rs:1607-1614`, `typeclass_22b3.rs:706-708`, `eq_ord_e2e.rs:13`.
|
||||
Plus four `empty_line_after_doc_comments` hits in
|
||||
`workspace.rs:1675-2506` — these were `///` doc-comments left
|
||||
orphaned by form-a.1 Task 5 test relocation; converted to plain
|
||||
`//` comments (eight blocks total) since they no longer document
|
||||
any function.
|
||||
|
||||
- **Idiomatic refactors** (8 warnings). `err_expect` (5 hits in
|
||||
`parse.rs`): `.err().expect("…")` → `.expect_err("…")`.
|
||||
`useless_conversion` (3 hits): `.extend(x.into_iter())` → `.extend(x)`
|
||||
in `desugar.rs:275` + `lift.rs:201`; `vec!["x".into()]` →
|
||||
`vec!["x"]` in `lib.rs:5522` (where the call-site `fn_def`
|
||||
parameter is `Vec<&str>` so the conversion was identity).
|
||||
`redundant_closure` (2 hits): `|f| collect_pattern_binders(f)` →
|
||||
`collect_pattern_binders` in `linearity.rs:717`; same pattern in
|
||||
`loader.rs:108`. `trim_split_whitespace`: `.trim().split_whitespace()`
|
||||
→ `.split_whitespace()` in `prose/src/lib.rs:2103` (since
|
||||
`split_whitespace` already ignores leading/trailing whitespace).
|
||||
`single_char_add_str`: `out.push_str("x")` → `out.push('x')` in
|
||||
`prose/src/lib.rs:383`. `collapsible_match` (2 hits): nested
|
||||
`if let Some(_) = …` collapsed to `if let Some(Some(_)) = …` in
|
||||
`mono.rs:1028` + `drop.rs:535-536`.
|
||||
|
||||
- **`derivable_impls`** (2 hits). `impl Default for ParamMode {
|
||||
fn default() -> Self { ParamMode::Implicit } }` in `ast.rs:731`
|
||||
→ `#[derive(Default)]` + `#[default] Implicit`. Same shape for
|
||||
`AllocStrategy` in `codegen/src/lib.rs:164`. The derive form is
|
||||
strictly more explicit (the `#[default]` attribute marks which
|
||||
variant in-source rather than the impl body referencing it by
|
||||
name) so the change is also a small clarity gain.
|
||||
|
||||
- **`blocks_in_conditions`** (1 hit). The synth `Term::Var` arm in
|
||||
`check/src/lib.rs` had a 13-line block inside an `else if`
|
||||
condition (the mq.2 dispatch entry: `else if { let (m, q) =
|
||||
parse_method_qualifier(name); qualifier_is_class_shape(&q) &&
|
||||
env.method_to_candidate_classes.contains_key(m) }`). Extracted
|
||||
to a new helper `fn is_class_method_dispatch(name, env) -> bool`
|
||||
next to `qualifier_is_class_shape`. The helper inherits the
|
||||
block's full doc-comment so the why-each-branch reasoning is
|
||||
preserved at the call site.
|
||||
|
||||
- **`only_used_in_recursion`** (1 hit). `walk_pattern(p, f)` in
|
||||
`workspace.rs:1399` carries an `f: &mut F` parameter that
|
||||
Pattern walking never invokes (ct.1 left this seam unused: no
|
||||
Pattern variant carries a cross-module reference). Rather than
|
||||
delete `f` (which would break the symmetry with the five other
|
||||
`walk_*` framework functions in this file that all share the
|
||||
same `<F>(_, f: &mut F) -> Result<…>` signature), added
|
||||
`#[allow(clippy::only_used_in_recursion)]` with an inline
|
||||
comment naming the framework-uniformity rationale.
|
||||
|
||||
- **`if_same_then_else`** (2 hits). Both in `qualify_local_types`
|
||||
shapes (`check/src/lib.rs:3457` + `codegen/src/subst.rs:165`):
|
||||
the if-chain has two branches returning `name.clone()` for
|
||||
semantically distinct reasons (`name.contains('.')` = already
|
||||
qualified; `is_primitive_name(name)` = primitive needs no
|
||||
qualification). Combining the branches with `||` would obscure
|
||||
the reasoning. Added `#[allow(clippy::if_same_then_else)]` with
|
||||
an inline comment naming why each branch disqualifies the name.
|
||||
|
||||
- **`too_many_arguments`** (1 hit). `Emitter::new` takes 8 of the
|
||||
workspace-flat tables the emitter needs at construction time.
|
||||
Bundling them into a context struct would just rename the
|
||||
boilerplate without reducing it. Added
|
||||
`#[allow(clippy::too_many_arguments)]` with the rationale
|
||||
inline.
|
||||
|
||||
## Files touched
|
||||
|
||||
- `crates/ailang-core/src/ast.rs` (1 derive flip)
|
||||
- `crates/ailang-core/src/desugar.rs` (1 .extend)
|
||||
- `crates/ailang-core/src/workspace.rs` (1 walk_pattern allow + 8 `///`→`//` blocks)
|
||||
- `crates/ailang-surface/src/lex.rs` (1 doc-list blank line)
|
||||
- `crates/ailang-surface/src/parse.rs` (5 `.expect_err`)
|
||||
- `crates/ailang-surface/tests/loader.rs` (1 redundant closure)
|
||||
- `crates/ailang-prose/src/lib.rs` (push_str + trim removal)
|
||||
- `crates/ailang-check/src/lib.rs` (helper extraction + 1 allow + 1 doc rephrase + 1 doc-list blank line + 1 vec literal)
|
||||
- `crates/ailang-check/src/lift.rs` (1 .extend)
|
||||
- `crates/ailang-check/src/linearity.rs` (1 redundant closure)
|
||||
- `crates/ailang-codegen/src/lib.rs` (1 derive flip + 1 too_many_args allow)
|
||||
- `crates/ailang-codegen/src/subst.rs` (1 if_same_then_else allow)
|
||||
- `crates/ailang-codegen/src/drop.rs` (1 doc-list blank line + 1 collapsible_match)
|
||||
- `crates/ailang-check/src/mono.rs` (1 collapsible_match)
|
||||
- `crates/ail/tests/e2e.rs` (2 doc rephrasings)
|
||||
- `crates/ail/tests/typeclass_22b3.rs` (1 doc rephrasing)
|
||||
- `crates/ail/tests/eq_ord_e2e.rs` (1 doc rephrasing)
|
||||
|
||||
## Verification
|
||||
|
||||
- `cargo clippy --workspace --all-targets 2>&1 | grep -c '^warning:'`
|
||||
→ `0` (was 61).
|
||||
- `cargo test --workspace` → 563 passed + 3 ignored, 0 failed
|
||||
(baseline holds).
|
||||
- `cargo doc --workspace --no-deps 2>&1 | grep -c '^warning:'` →
|
||||
`0` (rustdoc-sweep baseline holds — no regression).
|
||||
- `bench/check.py` → exit 0.
|
||||
- `bench/compile_check.py` → exit 0.
|
||||
- `bench/cross_lang.py` → exit 0.
|
||||
|
||||
## Concerns
|
||||
|
||||
- (none)
|
||||
|
||||
## Known debt
|
||||
|
||||
- (none)
|
||||
@@ -60,3 +60,4 @@
|
||||
- 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
|
||||
- 2026-05-14 — iter clippy-sweep: cleared all 61 `cargo clippy --workspace --all-targets` warnings across 12 lint classes. Bulk: 32× `doc_lazy_continuation` + 4× `empty_line_after_doc_comments` (8 orphan `///` blocks in `workspace.rs` left by form-a.1 T5 relocation converted to plain `//`) — pure documentation hygiene. Idiomatic refactors: 5× `err_expect` (`.err().expect()` → `.expect_err()`), 3× `useless_conversion`, 2× `redundant_closure`, 2× `collapsible_match`, 1× `single_char_add_str`, 1× `trim_split_whitespace`. Two `derivable_impls` (manual `Default` for `ParamMode` + `AllocStrategy` flipped to `#[derive(Default)]` + `#[default]`). One `blocks_in_conditions` extracted to a new helper `is_class_method_dispatch(name, env)` next to `qualifier_is_class_shape` in `check/src/lib.rs`. Three `#[allow]`s with inline rationale: `only_used_in_recursion` on `walk_pattern` (preserves the 5-fn walker-framework signature uniformity), 2× `if_same_then_else` on `qualify_local_types` shapes (`check/src/lib.rs:3457` + `codegen/src/subst.rs:165` — the two `name.clone()` branches encode semantically distinct disqualification reasons), `too_many_arguments` on `Emitter::new` (8 workspace-flat tables; bundling would just rename boilerplate). Tests 563 → 563 (no behavior change); `cargo doc` stays at 0 warnings (rustdoc-sweep baseline holds); all 3 bench scripts exit 0 against existing baselines (free stability check that the sweep touched no semantics) → 2026-05-14-iter-clippy-sweep.md
|
||||
|
||||
Reference in New Issue
Block a user