Skip to content

Commit 2994db1

Browse files
fix(lint): clear the clippy backlog the revived Rust CI exposed (#361)
## Why main is currently red PR #358 revived Rust CI. It merged **before** this commit landed on the branch, so main now has a *working* Rust CI that immediately fails its `Cargo check + clippy + fmt` step. That is not a regression — it is the gate doing its job for the first time since 2026-06-27. This PR is the backlog it found. Current state of the revived gate on main (`d7f1b49`): ``` success wasm-tools validate (emitted modules) success Cargo build + test (ephapax-cli, --no-default-features) success rust-ci / Detect Cargo.toml failure rust-ci / Cargo check + clippy + fmt <-- this PR ``` ## What this fixes `cargo clippy --all-targets -- -D warnings` failed crate after crate. All resolved; it now exits 0. **Mechanical, behaviour-preserving:** | Lint | Fix | |---|---| | `derivable_impls` | `impl Default for Visibility` → `#[derive(Default)]` + `#[default]` | | `manual_try_fold` ×2 | `.fold(Ok(acc), ..)` → `.try_fold(acc, ..)` in the surface and core parsers' statement-sequence desugaring | | `collapsible_if` ×2, `match_like_matches_macro` | via `cargo clippy --fix` | | `ptr_arg` | `&PathBuf` → `&Path` in two `ephapax-cli` signatures | | `unnecessary_unsafe` | removed an **empty** `unsafe {}` block wrapping only comments in a placeholder test | That last one is worth a second look in a language whose entire thesis is memory safety: `ephapax-runtime::list::tests::test_list_new` contained `unsafe { /* comments only */ }`. **Deliberately allowed, each with an inline reason** (no blanket crate-level allow): - `approx_constant` — `lex("3.14")` is a float literal *under test*, not an approximation of PI - `type_complexity` — replaced with a `TokenPredicate` type alias - `too_many_arguments` ×3 — 8 params vs clippy's default threshold of 7, on option-heavy CLI entry points and the recursive import resolver - `large_enum_variant` — `Value::Closure` is ~264 bytes vs ~48 for the next largest, so every `Value` pays for it. The real fix is boxing the payload, which touches every construction and match site **in the interpreter**. That is a behavioural-risk refactor that does not belong in a CI-infrastructure change. Allowed here with a comment, tracked in **#360**. ## Verification — all local, all green ``` cargo check --locked --all-targets ok cargo clippy --all-targets -- -D warnings exit 0 cargo fmt --all -- --check clean cargo test --workspace 285 passed, 0 failed ``` The 285 passing tests matter here: the `try_fold` rewrites and the `unsafe` removal are the only changes that could alter behaviour, and the parser/interpreter suites cover both. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents d7f1b49 + c5e14a2 commit 2994db1

15 files changed

Lines changed: 65 additions & 87 deletions

File tree

ephapax-linear/src/tests.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ fn p2p_single_consume_always_accepted_both_disciplines() {
383383
let expr = e(ExprKind::LetLin {
384384
name: name.clone().into(),
385385
ty: Some(Ty::Base(BaseTy::I32)),
386-
value: Box::new(i32_lit(i as i32)),
386+
value: Box::new(i32_lit(i)),
387387
body: Box::new(var(&name)),
388388
});
389389

@@ -413,7 +413,7 @@ fn p2p_contraction_always_rejected_both_disciplines() {
413413
let expr = e(ExprKind::LetLin {
414414
name: name.clone().into(),
415415
ty: Some(Ty::Base(BaseTy::I32)),
416-
value: Box::new(i32_lit(i as i32)),
416+
value: Box::new(i32_lit(i)),
417417
body: Box::new(e(ExprKind::Pair {
418418
left: Box::new(var(&name)),
419419
right: Box::new(var(&name)),
@@ -462,7 +462,7 @@ fn p2p_weakening_linear_forbidden_affine_allowed() {
462462
let expr = e(ExprKind::LetLin {
463463
name: name.clone().into(),
464464
ty: Some(Ty::Base(BaseTy::I32)),
465-
value: Box::new(i32_lit(i as i32)),
465+
value: Box::new(i32_lit(i)),
466466
body: Box::new(unit()),
467467
});
468468

@@ -485,7 +485,7 @@ fn p2p_weakening_linear_forbidden_affine_allowed() {
485485
let affine_expr = e(ExprKind::Let {
486486
name: name.clone().into(),
487487
ty: Some(Ty::Base(BaseTy::I32)),
488-
value: Box::new(i32_lit(i as i32)),
488+
value: Box::new(i32_lit(i)),
489489
body: Box::new(unit()),
490490
});
491491
let mut affine = AffineChecker::new();
@@ -510,7 +510,7 @@ fn p2p_unified_api_consistent_with_direct_checkers() {
510510
let good_expr = e(ExprKind::LetLin {
511511
name: name.clone().into(),
512512
ty: Some(Ty::Base(BaseTy::I32)),
513-
value: Box::new(i32_lit(i as i32)),
513+
value: Box::new(i32_lit(i)),
514514
body: Box::new(var(&name)),
515515
});
516516

src/ephapax-cli/src/import_resolver.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ fn first_module_declaration(source: &str) -> Option<String> {
162162
None
163163
}
164164

165+
#[allow(clippy::too_many_arguments)] // recursive resolver: 8 params vs the 7 default threshold
165166
fn visit(
166167
logical: &str,
167168
explicit_file: Option<&Path>,

src/ephapax-cli/src/main.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,8 +419,9 @@ fn check_files(files: &[PathBuf], _mode_str: &str, verbose: bool) -> Result<(),
419419
}
420420
}
421421

422+
#[allow(clippy::too_many_arguments)] // CLI entry point: 8 options vs the 7 default threshold
422423
fn compile_file(
423-
path: &PathBuf,
424+
path: &Path,
424425
output: Option<PathBuf>,
425426
opt_level: u8,
426427
debug: bool,
@@ -570,10 +571,11 @@ fn compile_file(
570571

571572
/// Codegen + optional ownership verification + output write. Shared by
572573
/// the multi-module and legacy single-file compile paths.
574+
#[allow(clippy::too_many_arguments)] // CLI entry point: 8 options vs the 7 default threshold
573575
fn finish_compile(
574576
module: ephapax_syntax::Module,
575577
filename: &str,
576-
path: &PathBuf,
578+
path: &Path,
577579
output: Option<PathBuf>,
578580
opt_level: u8,
579581
debug: bool,

src/ephapax-cli/tests/integration.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,8 @@ fn assert_checks(source: &str) {
3232

3333
/// Helper: assert that source fails type-checking
3434
fn assert_rejects(source: &str) {
35-
match check(source) {
36-
Ok(ty) => panic!("Expected type-check failure for `{source}`, got: {ty:?}"),
37-
Err(_) => {}
35+
if let Ok(ty) = check(source) {
36+
panic!("Expected type-check failure for `{source}`, got: {ty:?}")
3837
}
3938
}
4039

src/ephapax-desugar/src/lib.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,7 @@ fn module_qualifier_of(base: &SurfaceExpr) -> Option<SmolStr> {
8787
/// (`lib/math` → `math`, `Foo.Bar` → `Bar`, `Coproc` → `Coproc`). This is
8888
/// the name a `M.member` qualifier must use.
8989
fn last_segment(path: &str) -> SmolStr {
90-
path.rsplit(|c| c == '/' || c == '.')
91-
.next()
92-
.unwrap_or(path)
93-
.into()
90+
path.rsplit(['/', '.']).next().unwrap_or(path).into()
9491
}
9592

9693
/// Desugaring errors.
@@ -2014,7 +2011,7 @@ mod tests {
20142011
assert_eq!(params[0].0.as_str(), "title");
20152012
assert_eq!(*ret_ty, Ty::Base(BaseTy::I32));
20162013
} else {
2017-
panic!("expected ExternItem::Fn, got {:?}", &items[1]);
2014+
panic!("expected ExternItem::Fn, got {:?}", items[1]);
20182015
}
20192016
}
20202017
other => panic!("expected Decl::Extern, got {other:?}"),

src/ephapax-interp/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ pub enum RuntimeError {
106106
}
107107

108108
/// Runtime values
109+
//
110+
// clippy::large_enum_variant — `Closure` is ~264 bytes against ~48 for the next
111+
// largest variant. The fix is to box the closure payload, but that changes every
112+
// construction and match site in the interpreter, so it is deliberately NOT bundled
113+
// into the CI-revival change that first surfaced this lint. Tracked separately;
114+
// remove this allow when the boxing lands.
115+
#[allow(clippy::large_enum_variant)]
109116
#[derive(Debug, Clone)]
110117
pub enum Value {
111118
Unit,

src/ephapax-lexer/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,8 @@ mod tests {
584584
assert_eq!(lex("12345"), vec![TokenKind::Integer(12345)]);
585585
}
586586

587+
// `3.14` here is a float literal under test, not an approximation of PI.
588+
#[allow(clippy::approx_constant)]
587589
#[test]
588590
fn test_floats() {
589591
assert_eq!(lex("3.14"), vec![TokenKind::Float(3.14)]);
@@ -785,6 +787,9 @@ mod tests {
785787
mod property_tests {
786788
use super::*;
787789

790+
/// Predicate over a token kind, used by the table-driven P2P tests.
791+
type TokenPredicate = fn(&TokenKind) -> bool;
792+
788793
// -------------------------------------------------------------------------
789794
// P2P: round-trip token spans
790795
// -------------------------------------------------------------------------
@@ -891,7 +896,7 @@ mod property_tests {
891896
/// exactly one content token of the Integer kind.
892897
#[test]
893898
fn p2p_integer_literals_tokenise_to_one_token() {
894-
let inputs: Vec<(&str, fn(&TokenKind) -> bool)> = vec![
899+
let inputs: Vec<(&str, TokenPredicate)> = vec![
895900
("0", |t| matches!(t, TokenKind::Integer(_))),
896901
("1", |t| matches!(t, TokenKind::Integer(_))),
897902
("42", |t| matches!(t, TokenKind::Integer(_))),

src/ephapax-lsp/src/main.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,7 @@ impl Backend {
173173
if let Some(module) = &state.module {
174174
for d in &module.decls {
175175
if let Decl::Fn {
176-
name,
177-
body,
178-
params,
179-
type_params: _,
180-
..
176+
name, body, params, ..
181177
} = d
182178
{
183179
// Check if word matches a parameter
@@ -572,7 +568,7 @@ fn extract_declarations(module: &Module, _source: &str) -> Vec<DeclInfo> {
572568
.unwrap_or_default()
573569
),
574570
params: Vec::new(),
575-
return_type: ty.as_ref().map(|t| format_ty(t)),
571+
return_type: ty.as_ref().map(format_ty),
576572
}),
577573
Decl::Extern { abi, items } => {
578574
for item in items {

src/ephapax-parser/src/lib.rs

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -287,10 +287,8 @@ fn parse_extern_item(pair: pest::iterators::Pair<Rule>) -> Result<ExternItem, Pa
287287
}
288288
}
289289
}
290-
Rule::ty => {
291-
if ret_ty.is_none() {
292-
ret_ty = Some(parse_type(sub)?);
293-
}
290+
Rule::ty if ret_ty.is_none() => {
291+
ret_ty = Some(parse_type(sub)?);
294292
}
295293
_ => {}
296294
}
@@ -772,8 +770,7 @@ fn parse_seq_expr_core(pair: pest::iterators::Pair<Rule>) -> Result<Expr, ParseE
772770

773771
// invariant: loop above ensures parsed is not empty before we enter this block
774772
let last = parsed.pop().expect("invariant: parsed is not empty");
775-
parsed.into_iter().rev().fold(Ok(last), |acc, expr| {
776-
let acc = acc?;
773+
parsed.into_iter().rev().try_fold(last, |acc, expr| {
777774
let s = expr.span;
778775
Ok(Expr::new(
779776
ExprKind::Let {
@@ -1261,34 +1258,29 @@ fn parse_handle_expr(pair: pest::iterators::Pair<Rule>) -> Result<Expr, ParseErr
12611258
match item.as_rule() {
12621259
Rule::handler_param_list => {
12631260
for param_item in item.into_inner() {
1264-
match param_item.as_rule() {
1265-
Rule::handler_param => {
1266-
let hp_inner = param_item.into_inner().next();
1267-
if let Some(hp) = hp_inner {
1268-
match hp.as_rule() {
1269-
Rule::resume_param => {
1270-
// Check for mode
1271-
if let Some(mode_pair) = hp.into_inner().next()
1272-
{
1273-
resume_mode =
1274-
Some(match mode_pair.as_str() {
1275-
"once" => ResumeMode::Once,
1276-
"multi" => ResumeMode::Multi,
1277-
_ => ResumeMode::Once,
1278-
});
1279-
} else {
1280-
resume_mode = Some(ResumeMode::Once);
1281-
// default
1282-
}
1283-
}
1284-
Rule::identifier => {
1285-
params.push(parse_identifier(hp));
1261+
if param_item.as_rule() == Rule::handler_param {
1262+
let hp_inner = param_item.into_inner().next();
1263+
if let Some(hp) = hp_inner {
1264+
match hp.as_rule() {
1265+
Rule::resume_param => {
1266+
// Check for mode
1267+
if let Some(mode_pair) = hp.into_inner().next() {
1268+
resume_mode = Some(match mode_pair.as_str() {
1269+
"once" => ResumeMode::Once,
1270+
"multi" => ResumeMode::Multi,
1271+
_ => ResumeMode::Once,
1272+
});
1273+
} else {
1274+
resume_mode = Some(ResumeMode::Once);
1275+
// default
12861276
}
1287-
_ => {}
12881277
}
1278+
Rule::identifier => {
1279+
params.push(parse_identifier(hp));
1280+
}
1281+
_ => {}
12891282
}
12901283
}
1291-
_ => {}
12921284
}
12931285
}
12941286
}
@@ -2359,7 +2351,7 @@ mod tests {
23592351
assert_eq!(name.as_str(), "window_open");
23602352
assert_eq!(params.len(), 2);
23612353
} else {
2362-
panic!("expected ExternItem::Fn, got {:?}", &items[1]);
2354+
panic!("expected ExternItem::Fn, got {:?}", items[1]);
23632355
}
23642356
}
23652357
other => panic!("expected Decl::Extern, got {other:?}"),

src/ephapax-parser/src/surface.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,8 @@ fn parse_extern_item(pair: pest::iterators::Pair<Rule>) -> Result<SurfaceExternI
201201
}
202202
}
203203
}
204-
Rule::ty => {
205-
if ret_ty.is_none() {
206-
ret_ty = Some(parse_type(sub)?);
207-
}
204+
Rule::ty if ret_ty.is_none() => {
205+
ret_ty = Some(parse_type(sub)?);
208206
}
209207
_ => {}
210208
}
@@ -507,8 +505,7 @@ fn parse_seq_expr(pair: pest::iterators::Pair<Rule>) -> Result<SurfaceExpr, Pars
507505
// let _ = e1 in (let _ = e2 in (... eN))
508506
// invariant: loop above ensures parsed is not empty before we enter this block
509507
let last = parsed.pop().expect("invariant: parsed is not empty");
510-
parsed.into_iter().rev().fold(Ok(last), |acc, expr| {
511-
let acc = acc?;
508+
parsed.into_iter().rev().try_fold(last, |acc, expr| {
512509
let s = expr.span;
513510
Ok(SurfaceExpr::new(
514511
SurfaceExprKind::Let {

0 commit comments

Comments
 (0)