treewide: enforce strict clippy check

This commit is contained in:
2026-08-22 18:41:23 +08:00
parent 5f494983b8
commit cb38031f85
51 changed files with 1237 additions and 568 deletions
+3
View File
@@ -17,3 +17,6 @@ fix-error = { path = "../fix-error" }
fix-lang = { path = "../fix-lang" }
fix-runtime = { path = "../fix-runtime" }
tracing = "0.1"
[lints]
workspace = true
+42 -18
View File
@@ -114,8 +114,13 @@ impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> {
source,
);
let ir = downgrade_ctx.downgrade_toplevel(expr)?;
// SAFETY: `ir` borrows from `bump`, which is moved into the returned
// `OwnedIr` alongside the reference. The `'static` lifetime is a
// storage token that is re-narrowed to the `OwnedIr`'s own borrow in
// `as_ref`, so no reference ever outlives the arena backing it.
let ir = unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) };
Ok(OwnedIr { _bump: bump, ir })
// SAFETY: `ir` borrows from `bump`
Ok(unsafe { OwnedIr::new(bump, ir) })
})
}
}
@@ -153,15 +158,7 @@ impl<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> {
Bool(x) => StaticValue::new(x),
String(x) => StaticValue::new(x),
Path(x) => StaticValue::new(fix_runtime::Path(x)),
PrimOp {
id,
arity,
dispatch_ip,
} => StaticValue::new(fix_runtime::PrimOp {
id,
arity,
dispatch_ip,
}),
PrimOp(id) => StaticValue::new(fix_runtime::PrimOp::from(id)),
Null => StaticValue::default(),
};
self.runtime.add_const(val)
@@ -288,6 +285,10 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
self.runtime.resolve_string(id).into()
}
#[expect(
clippy::unwrap_in_result,
reason = "exceeding u8::MAX thunk scope layers is a compiler invariant violation, not a user-facing error"
)]
fn lookup(
&mut self,
sym: StringId,
@@ -364,6 +365,10 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
self.source.clone()
}
#[expect(
clippy::panic_in_result_fn,
reason = "assert_eq guards an internal invariant (bindings and values have equal length); a mismatch is a compiler bug, not a user error"
)]
fn with_let_scope<F, Ret>(&mut self, keys: &[StringId], f: F) -> Result<Ret>
where
F: FnOnce(
@@ -425,6 +430,10 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
ret
}
#[expect(
clippy::panic,
reason = "exceeding u8::MAX thunk scope layers is a compiler invariant violation, not a user-facing error"
)]
fn with_thunk_scope<F, Ret>(
&mut self,
f: F,
@@ -490,7 +499,6 @@ enum Scope<'ctx, 'id, 'ir> {
Repl(&'ctx HashSet<StringId>),
ScopedImport {
keys: HashSet<StringId>,
#[allow(dead_code)]
slot_id: u32,
},
Let(HashMap<StringId, GhostMaybeThunkRef<'id, 'ir>>),
@@ -534,13 +542,29 @@ impl<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> ScopeGuard<'a, 'ctx, 'id, 'ir, R> {
}
}
struct OwnedIr {
_bump: Bump,
ir: RawIrRef<'static>,
}
mod sealed {
use super::*;
impl OwnedIr {
fn as_ref<'ir>(&'ir self) -> RawIrRef<'ir> {
unsafe { std::mem::transmute::<RawIrRef<'static>, RawIrRef<'ir>>(self.ir) }
pub struct OwnedIr {
_bump: Bump,
ir: RawIrRef<'static>,
}
impl OwnedIr {
/// # Safety
/// `ir` must borrows from `bump`
pub unsafe fn new(bump: Bump, ir: RawIrRef<'static>) -> Self {
Self { _bump: bump, ir }
}
pub fn as_ref<'ir>(&'ir self) -> RawIrRef<'ir> {
// SAFETY: `self.ir`'s `'static` lifetime is a storage token; the IR is
// backed by `self._bump`, which lives as long as `self`. Narrowing to
// `'ir` (tied to `&self`) hands out a reference that cannot outlive the
// arena.
unsafe { std::mem::transmute::<RawIrRef<'static>, RawIrRef<'ir>>(self.ir) }
}
}
}
pub use sealed::OwnedIr;
+3 -5
View File
@@ -3,7 +3,7 @@ use std::marker::PhantomData;
use bumpalo::Bump;
use bumpalo::collections::Vec;
use fix_lang::{BUILTINS, BuiltinId, StringId};
use fix_lang::{BuiltinId, StringId};
use ghost_cell::{GhostCell, GhostToken};
use rnix::{TextRange, ast};
use string_interner::DefaultStringInterner;
@@ -222,7 +222,6 @@ pub enum Ir<'ir, R: RefExt<'ir> + ?Sized + 'ir> {
pub struct ThunkId(pub usize);
/// Represents a key in an attribute path.
#[allow(unused)]
#[derive(Debug)]
pub enum Attr<Ref> {
/// A dynamic attribute key, which is an expression that must evaluate to a string.
@@ -291,9 +290,8 @@ pub fn new_global_env(
let builtins_sym = StringId(strings.get_or_intern("builtins"));
global_env.insert(builtins_sym, MaybeThunk::Builtins);
for (idx, &(name, _)) in BUILTINS.iter().enumerate() {
let id = BuiltinId::try_from(idx as u8).expect("infallible");
let name = StringId(strings.get_or_intern(name));
for id in BuiltinId::ALL {
let name = StringId(strings.get_or_intern(id.info().global_name));
global_env.insert(name, MaybeThunk::Builtin(id));
}
+30 -15
View File
@@ -156,6 +156,10 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
let path = {
let temp = self.content().require(ctx, span)?;
let text = temp.text();
#[expect(
clippy::string_slice,
reason = "PathSearch text is <...> wrapped in ASCII angle brackets, so byte indices 1 and len-1 are char boundaries"
)]
let id = ctx.intern_string(&text[1..text.len() - 1]);
let expr = ctx.new_expr(Ir::Str(id));
ctx.maybe_thunk(expr)
@@ -463,15 +467,13 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
}
let (ret, thunks) = ctx.with_thunk_scope(|ctx| {
let param;
let body;
match raw_param {
let (param, body) = match raw_param {
ast::Param::IdentParam(id) => {
let param_sym = ctx.intern_string(id.to_string());
param = None;
body = ctx.with_param_scope(param_sym, |ctx| body_ast.downgrade(ctx))?;
(
None,
ctx.with_param_scope(param_sym, |ctx| body_ast.downgrade(ctx))?,
)
}
ast::Param::Pattern(pattern) => {
let alias = pattern
@@ -493,15 +495,16 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
body_ast.clone().downgrade(ctx)
})?;
param = Some(Param {
required,
optional,
ellipsis,
});
body = inner_body;
(
Some(Param {
required,
optional,
ellipsis,
}),
inner_body,
)
}
}
};
Result::Ok(Ret { param, body })
});
@@ -550,6 +553,10 @@ impl<'id: 'ir, 'ir> PendingAttrSet<'ir> {
}
}
#[expect(
clippy::indexing_slicing,
reason = "path is non-empty here: path.first() was just unwrapped above, so path[1..] is in bounds"
)]
fn insert(
&mut self,
path: &[ast::Attr],
@@ -651,6 +658,10 @@ impl<'id: 'ir, 'ir> PendingAttrSet<'ir> {
) -> Result<()> {
if !path.is_empty() {
let mut nested = PendingAttrSet::new_in(ctx.bump());
#[expect(
clippy::indexing_slicing,
reason = "path is non-empty in this branch, so path[0] and path[1..] are in bounds"
)]
nested.insert_dynamic(
path[0].clone(),
path[0].syntax().text_range(),
@@ -665,6 +676,10 @@ impl<'id: 'ir, 'ir> PendingAttrSet<'ir> {
Ok(())
}
#[expect(
clippy::unreachable,
reason = "value was just reassigned to PendingValue::Set on the line above, so the re-match always hits the Set arm"
)]
fn ensure_pending_set<'a>(
value: &'a mut PendingValue<'ir>,
ctx: &mut impl DowngradeContext<'id, 'ir>,
+32 -13
View File
@@ -1,5 +1,5 @@
use fix_bytecode::{Const, Continuation, InstructionPtr, Op, OperandType};
use fix_lang::{BUILTINS, StringId};
use fix_bytecode::{Const, InstructionPtr, Op, OperandType};
use fix_lang::StringId;
use hashbrown::HashMap;
use rnix::TextRange;
use string_interner::Symbol as _;
@@ -54,7 +54,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
}
}
#[must_use]
fn inline_maybe_thunk(&self, val: &MaybeThunk) -> InlineOperand {
use MaybeThunk::*;
match *val {
@@ -75,14 +74,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
InlineOperand::Local { layer, local }
}
Arg { layer } => InlineOperand::Local { layer, local: 0 },
Builtin(id) => {
let (_, arity) = BUILTINS[id as usize];
InlineOperand::Const(Const::PrimOp {
id,
arity,
dispatch_ip: Continuation::entry_for_builtin(id).ip(),
})
}
Builtin(id) => InlineOperand::Const(Const::PrimOp(id)),
BuiltinConst(id) => InlineOperand::BuiltinConst(id),
Builtins => InlineOperand::Builtins,
ReplBinding(id) => InlineOperand::ReplBinding(id),
@@ -186,6 +178,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
offset
}
#[inline]
#[expect(
clippy::indexing_slicing,
reason = "offset addresses a 4-byte i32 placeholder this compiler previously emitted, so it is in bounds"
)]
fn patch_i32(&mut self, offset: usize, val: i32) {
self.ctx.get_code_mut()[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
}
@@ -214,6 +210,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.scope_stack.last().map_or(0, |s| s.depth)
}
#[expect(
clippy::panic,
reason = "a ThunkId must resolve in some enclosing scope; failure indicates a compiler bug"
)]
fn resolve_thunk(&self, id: ThunkId) -> (u8, u32) {
for scope in self.scope_stack.iter().rev() {
if let Some(&local_idx) = scope.thunk_map.get(&id) {
@@ -520,6 +520,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
}
}
#[expect(
clippy::unreachable,
reason = "the outer match only reaches this arm for the binary operator kinds enumerated above"
)]
fn emit_binop(&mut self, lhs: RawIrRef<'_>, rhs: RawIrRef<'_>, kind: BinOpKind) {
use BinOpKind::*;
match kind {
@@ -712,14 +716,25 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
}
if let Some(default) = default {
let before: i32 = self.ctx.get_code().len().try_into().unwrap();
// FIXME: i32???
let before: i32 = self
.ctx
.get_code()
.len()
.try_into()
.expect("emitted code length fits in i32");
for patch in dynamic_patches {
self.patch_jump_target(patch);
}
self.emit_op(Op::JumpIfSelectSucceeded);
let placeholder = self.emit_i32_placeholder();
self.emit_expr(default);
let after: i32 = self.ctx.get_code().len().try_into().unwrap();
let after: i32 = self
.ctx
.get_code()
.len()
.try_into()
.expect("emitted code length fits in i32");
// Offset is relative to after the placeholder, so subtract the
// size of JumpIfSelectSucceeded (1) + placeholder (4).
self.patch_i32(placeholder, after - before - 5);
@@ -730,6 +745,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
}
}
#[expect(
clippy::panic,
reason = "a hasAttr attrpath always has at least one attr by construction of the AST"
)]
fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) {
self.emit_expr(lhs);