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:
2026-05-14 00:48:17 +02:00
parent 04258c5cc1
commit abcdd05991
19 changed files with 302 additions and 162 deletions
+7 -5
View File
@@ -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.
///
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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")
+25 -18
View File
@@ -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 } }),
+1 -1
View File
@@ -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)
}
+1 -1
View File
@@ -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(),
}
}
+19 -19
View File
@@ -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;
}
+18 -19
View File
@@ -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.
+7 -7
View File
@@ -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,
+5
View File
@@ -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) {
+2 -7
View File
@@ -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`].
+1 -1
View File
@@ -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
}
+70 -68
View File
@@ -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.
+1 -2
View File
@@ -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!(
+1
View File
@@ -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
+5 -10
View File
@@ -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"),
+1 -1
View File
@@ -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"),