From e54940f4a64402fa73469781ad9e9c5c147e9fe5 Mon Sep 17 00:00:00 2001 From: branchseer Date: Wed, 18 Mar 2026 18:05:52 +0800 Subject: [PATCH] chore: allow `future_not_send` lint at workspace level The task scheduling workflow is a single-threaded async task, so Send bounds are unnecessary. Remove 22 per-item `#[expect]` annotations in favor of a single workspace-level allow in Cargo.toml. Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.toml | 2 ++ crates/fspy/src/arena.rs | 5 --- crates/fspy/src/ipc.rs | 5 --- crates/vite_task/src/session/cache/mod.rs | 8 ----- crates/vite_task/src/session/execute/mod.rs | 4 --- crates/vite_task/src/session/execute/spawn.rs | 1 - crates/vite_task/src/session/mod.rs | 32 ------------------- .../vite_task/src/session/reporter/labeled.rs | 4 --- crates/vite_task_bin/src/main.rs | 1 - crates/vite_task_graph/src/lib.rs | 4 --- crates/vite_task_plan/src/lib.rs | 1 - crates/vite_task_plan/src/plan.rs | 2 -- 12 files changed, 2 insertions(+), 67 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 778c5d4a..624cf584 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,8 @@ nursery = { level = "warn", priority = -1 } cargo = { level = "warn", priority = -1 } cargo_common_metadata = "allow" multiple_crate_versions = "allow" +# The task scheduling workflow is a single-threaded async task, so Send bounds are unnecessary. +future_not_send = "allow" [workspace.dependencies] allocator-api2 = { version = "0.2.21", default-features = false, features = ["alloc", "std"] } diff --git a/crates/fspy/src/arena.rs b/crates/fspy/src/arena.rs index 727e452d..aa6dde78 100644 --- a/crates/fspy/src/arena.rs +++ b/crates/fspy/src/arena.rs @@ -1,8 +1,3 @@ -#![expect( - clippy::future_not_send, - reason = "ouroboros generates async builder methods that cannot satisfy Send bounds" -)] - use allocator_api2::vec::Vec; use bumpalo::Bump; diff --git a/crates/fspy/src/ipc.rs b/crates/fspy/src/ipc.rs index 19d7a7ce..f5e7e7dd 100644 --- a/crates/fspy/src/ipc.rs +++ b/crates/fspy/src/ipc.rs @@ -1,8 +1,3 @@ -#![expect( - clippy::future_not_send, - reason = "ouroboros generates async builder methods that cannot satisfy Send bounds" -)] - use std::io; use bincode::borrow_decode_from_slice; diff --git a/crates/vite_task/src/session/cache/mod.rs b/crates/vite_task/src/session/cache/mod.rs index c2cc4f18..36f1fef3 100644 --- a/crates/vite_task/src/session/cache/mod.rs +++ b/crates/vite_task/src/session/cache/mod.rs @@ -323,10 +323,6 @@ fn detect_globbed_input_change( // Basic database operations impl ExecutionCache { - #[expect( - clippy::future_not_send, - reason = "tokio MutexGuard is !Send but this future only runs on a single-threaded runtime" - )] #[expect( clippy::significant_drop_tightening, reason = "lock guard cannot be dropped earlier because prepared statement borrows connection" @@ -370,10 +366,6 @@ impl ExecutionCache { self.get_key_by_value("task_fingerprints", execution_cache_key).await } - #[expect( - clippy::future_not_send, - reason = "tokio MutexGuard is !Send but this future only runs on a single-threaded runtime" - )] #[expect( clippy::significant_drop_tightening, reason = "lock guard must be held while executing the prepared statement" diff --git a/crates/vite_task/src/session/execute/mod.rs b/crates/vite_task/src/session/execute/mod.rs index e45dd861..be936386 100644 --- a/crates/vite_task/src/session/execute/mod.rs +++ b/crates/vite_task/src/session/execute/mod.rs @@ -78,7 +78,6 @@ impl ExecutionContext<'_> { /// /// Returns `true` if all tasks succeeded, `false` if any task failed. #[tracing::instrument(level = "debug", skip_all)] - #[expect(clippy::future_not_send, reason = "uses !Send types internally")] async fn execute_expanded_graph( &mut self, graph: &ExecutionGraph, @@ -132,7 +131,6 @@ impl ExecutionContext<'_> { /// /// Returns `true` if the execution failed (non-zero exit or infrastructure error). #[tracing::instrument(level = "debug", skip_all)] - #[expect(clippy::future_not_send, reason = "uses !Send types internally")] async fn execute_leaf( &mut self, display: &ExecutionItemDisplay, @@ -196,7 +194,6 @@ impl ExecutionContext<'_> { /// Errors (cache lookup failure, spawn failure, cache update failure) are reported /// through `leaf_reporter.finish()` and do not abort the caller. #[tracing::instrument(level = "debug", skip_all)] -#[expect(clippy::future_not_send, reason = "uses !Send types internally")] #[expect( clippy::too_many_lines, reason = "sequential cache check, execute, and update steps are clearer in one function" @@ -531,7 +528,6 @@ impl Session<'_> { /// Returns `Err(ExitStatus)` to indicate the caller should exit with the given status code. /// Returns `Ok(())` when all tasks succeeded. #[tracing::instrument(level = "debug", skip_all)] - #[expect(clippy::future_not_send, reason = "uses !Send types internally")] pub(crate) async fn execute_graph( &self, execution_graph: ExecutionGraph, diff --git a/crates/vite_task/src/session/execute/spawn.rs b/crates/vite_task/src/session/execute/spawn.rs index 7c78d621..fba313a6 100644 --- a/crates/vite_task/src/session/execute/spawn.rs +++ b/crates/vite_task/src/session/execute/spawn.rs @@ -65,7 +65,6 @@ pub struct TrackedPathAccesses { /// - `path_accesses` if provided, fspy will be used to track file accesses. If `None`, fspy is disabled. /// - `resolved_negatives` - resolved negative glob patterns for filtering fspy-tracked paths. #[tracing::instrument(level = "debug", skip_all)] -#[expect(clippy::future_not_send, reason = "uses !Send dyn AsyncWrite writers internally")] #[expect( clippy::too_many_lines, reason = "spawn logic is inherently sequential and splitting would reduce clarity" diff --git a/crates/vite_task/src/session/mod.rs b/crates/vite_task/src/session/mod.rs index e9eca149..d52c5ecd 100644 --- a/crates/vite_task/src/session/mod.rs +++ b/crates/vite_task/src/session/mod.rs @@ -201,10 +201,6 @@ impl<'a> Session<'a> { /// /// Returns an error if the task graph cannot be loaded from the workspace configuration. #[tracing::instrument(level = "debug", skip_all)] - #[expect( - clippy::future_not_send, - reason = "session is single-threaded, futures do not need to be Send" - )] pub async fn ensure_task_graph_loaded( &mut self, ) -> Result<&IndexedTaskGraph, TaskGraphLoadError> { @@ -251,10 +247,6 @@ impl<'a> Session<'a> { /// /// Returns an error if planning or execution fails. #[tracing::instrument(level = "debug", skip_all)] - #[expect( - clippy::future_not_send, - reason = "session is single-threaded, futures do not need to be Send" - )] pub async fn main(mut self, command: Command) -> anyhow::Result { match self.main_inner(command).await { Ok(()) => Ok(ExitStatus::SUCCESS), @@ -266,10 +258,6 @@ impl<'a> Session<'a> { /// # Panics /// /// Panics if parsing a hardcoded bare `RunCommand` fails (should never happen). - #[expect( - clippy::future_not_send, - reason = "session is single-threaded, futures do not need to be Send" - )] async fn main_inner(&mut self, command: Command) -> Result<(), SessionError> { match command.into_resolved() { ResolvedCommand::Cache { ref subcmd } => self.handle_cache_command(subcmd), @@ -343,10 +331,6 @@ impl<'a> Session<'a> { /// /// In non-interactive mode, prints the task list (or "did you mean" suggestions) /// and returns `Err(SessionError::EarlyExit(_))` — no further execution needed. - #[expect( - clippy::future_not_send, - reason = "session is single-threaded, futures do not need to be Send" - )] #[expect( clippy::too_many_lines, reason = "builds interactive/non-interactive select items and handles selection" @@ -594,10 +578,6 @@ impl<'a> Session<'a> { /// /// Returns an error if planning or execution of the synthetic command fails. #[tracing::instrument(level = "debug", skip_all)] - #[expect( - clippy::future_not_send, - reason = "session is single-threaded, futures do not need to be Send" - )] #[expect( clippy::large_futures, reason = "execution plan future is large but only awaited once" @@ -657,10 +637,6 @@ impl<'a> Session<'a> { /// /// Returns an error if the plan request cannot be parsed or if planning fails. #[tracing::instrument(level = "debug", skip_all)] - #[expect( - clippy::future_not_send, - reason = "session is single-threaded, futures do not need to be Send" - )] pub async fn plan_from_cli_run( &mut self, cwd: Arc, @@ -672,10 +648,6 @@ impl<'a> Session<'a> { /// Internal: plans execution from a resolved run command. #[tracing::instrument(level = "debug", skip_all)] - #[expect( - clippy::future_not_send, - reason = "session is single-threaded, futures do not need to be Send" - )] async fn plan_from_cli_run_resolved( &mut self, cwd: Arc, @@ -711,10 +683,6 @@ impl<'a> Session<'a> { /// /// Used by the interactive task selector, which constructs the request /// directly (bypassing CLI specifier parsing). - #[expect( - clippy::future_not_send, - reason = "session is single-threaded, futures do not need to be Send" - )] async fn plan_from_query( &mut self, request: QueryPlanRequest, diff --git a/crates/vite_task/src/session/reporter/labeled.rs b/crates/vite_task/src/session/reporter/labeled.rs index d43aed8d..f41d4241 100644 --- a/crates/vite_task/src/session/reporter/labeled.rs +++ b/crates/vite_task/src/session/reporter/labeled.rs @@ -347,10 +347,6 @@ mod tests { reporter.new_leaf_execution(display, leaf_kind, all_ancestors_single_node) } - #[expect( - clippy::future_not_send, - reason = "LeafExecutionReporter futures are !Send in single-threaded reporter tests" - )] async fn suggestion_for( display: &ExecutionItemDisplay, leaf_kind: &LeafExecutionKind, diff --git a/crates/vite_task_bin/src/main.rs b/crates/vite_task_bin/src/main.rs index f20215a7..e5c213a6 100644 --- a/crates/vite_task_bin/src/main.rs +++ b/crates/vite_task_bin/src/main.rs @@ -15,7 +15,6 @@ async fn main() -> anyhow::Result { Ok(exit_status.0.into()) } -#[expect(clippy::future_not_send, reason = "Session contains !Send types; single-threaded runtime")] async fn run() -> anyhow::Result { let args = Args::parse(); let mut owned_config = OwnedSessionConfig::default(); diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index d5b86c92..103987a0 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -215,10 +215,6 @@ impl IndexedTaskGraph { clippy::too_many_lines, reason = "graph loading is inherently sequential and multi-step" )] - #[expect( - clippy::future_not_send, - reason = "UserConfigLoader uses async_trait(?Send) so the future is intentionally not Send" - )] pub async fn load( workspace_root: &WorkspaceRoot, config_loader: &dyn loader::UserConfigLoader, diff --git a/crates/vite_task_plan/src/lib.rs b/crates/vite_task_plan/src/lib.rs index 8ab7aa7e..159a3d0d 100644 --- a/crates/vite_task_plan/src/lib.rs +++ b/crates/vite_task_plan/src/lib.rs @@ -188,7 +188,6 @@ pub trait TaskGraphLoader { /// # Errors /// Returns an error if task graph loading, query, or execution planning fails. #[tracing::instrument(level = "debug", skip_all)] -#[expect(clippy::future_not_send, reason = "PlanRequestParser and TaskGraphLoader are !Send")] #[expect(clippy::implicit_hasher, reason = "FxHashMap is the only hasher used in this codebase")] pub async fn plan_query( query_plan_request: QueryPlanRequest, diff --git a/crates/vite_task_plan/src/plan.rs b/crates/vite_task_plan/src/plan.rs index 832c4185..be95b2d3 100644 --- a/crates/vite_task_plan/src/plan.rs +++ b/crates/vite_task_plan/src/plan.rs @@ -83,7 +83,6 @@ fn effective_cache_config( /// `false` when the task itself is being executed as a hook, so that hooks are /// never expanded more than one level deep (matching npm behavior). #[expect(clippy::too_many_lines, reason = "sequential planning steps are clearer in one function")] -#[expect(clippy::future_not_send, reason = "PlanContext contains !Send dyn PlanRequestParser")] async fn plan_task_as_execution_node( task_node_index: TaskNodeIndex, mut context: PlanContext<'_>, @@ -626,7 +625,6 @@ fn plan_spawn_execution( /// `vp run build` produces a different query than the script's `vp run -r build`, /// so the skip rule doesn't fire, but the prune rule catches root in the result). /// Like the skip rule, extra args don't affect this — only the `TaskQuery` matters. -#[expect(clippy::future_not_send, reason = "PlanContext contains !Send dyn PlanRequestParser")] pub async fn plan_query_request( query: Arc, plan_options: PlanOptions,