From e94e0140de1950a80fe2ff7fbf6d7b5dcf420069 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 1 Aug 2026 09:20:25 +0800 Subject: [PATCH] ci: add clippy step and fix clippy warnings Add a clippy check (--all-targets --all-features -- -D warnings) to the CI workflow and resolve all resulting lint warnings across crates and examples (type_complexity, cloned_ref_to_slice_refs, unnecessary_sort_by, collapsible_if, needless_return, enum_variant_names, unused imports, etc.). --- .github/workflows/ci.yml | 3 +++ crates/langgraph-checkpoint-sqlite/src/saver.rs | 5 ++++- crates/langgraph-checkpoint/src/cache/memory.rs | 8 +++++--- .../langgraph-checkpoint/src/checkpoint/memory.rs | 11 +++++++---- crates/langgraph-checkpoint/src/store/memory.rs | 2 +- crates/langgraph-core/src/channels/base.rs | 1 + crates/langgraph-core/src/graph/state.rs | 15 ++++++++------- crates/langgraph-core/src/pregel/algo.rs | 11 +++++------ crates/langgraph-derive/src/lib.rs | 2 +- crates/langgraph-prebuilt/src/traits.rs | 5 ++++- crates/langgraph-providers/src/anthropic/model.rs | 4 +++- crates/langgraph-providers/src/openai/model.rs | 15 ++++++--------- crates/langgraph-tracing/src/store.rs | 2 +- examples/interactive_chat_with_tracing.rs | 1 - examples/parallel_interrupt_hitl.rs | 1 - 15 files changed, 49 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 090549e..f1ce940 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,3 +20,6 @@ jobs: - name: Check formatting run: cargo fmt --all -- --check + + - name: Clippy + run: cargo clippy --all-targets --all-features -- -D warnings diff --git a/crates/langgraph-checkpoint-sqlite/src/saver.rs b/crates/langgraph-checkpoint-sqlite/src/saver.rs index e77babc..d46ac5b 100644 --- a/crates/langgraph-checkpoint-sqlite/src/saver.rs +++ b/crates/langgraph-checkpoint-sqlite/src/saver.rs @@ -20,6 +20,9 @@ use langgraph_checkpoint::serde::jsonplus::JsonPlusSerializer; use crate::queries::*; +/// A dumped blob row: (thread_id, checkpoint_ns, channel, type, checkpoint_id, data) +type BlobDumpRow = (String, String, String, String, String, Option>); + /// Async SQLite checkpoint saver using sqlx. /// /// Uses a three-table schema (`checkpoints`, `checkpoint_blobs`, @@ -218,7 +221,7 @@ impl SqliteSaver { checkpoint_ns: &str, values: &HashMap, versions: &ChannelVersions, - ) -> Vec<(String, String, String, String, String, Option>)> { + ) -> Vec { let mut result = Vec::new(); for (channel, ver) in versions { let ver_str = match ver { diff --git a/crates/langgraph-checkpoint/src/cache/memory.rs b/crates/langgraph-checkpoint/src/cache/memory.rs index d580c12..41bcbb2 100644 --- a/crates/langgraph-checkpoint/src/cache/memory.rs +++ b/crates/langgraph-checkpoint/src/cache/memory.rs @@ -5,10 +5,12 @@ use parking_lot::RwLock; use serde_json::Value as JsonValue; use std::collections::HashMap; +/// namespace -> key -> (type_tag, bytes, expire_at_unix_secs) +type CacheMap = HashMap, Option)>>; + /// In-memory cache implementation pub struct InMemoryCache { - /// namespace -> key -> (type_tag, bytes, expire_at_unix_secs) - cache: RwLock, Option)>>>, + cache: RwLock, } impl InMemoryCache { @@ -127,7 +129,7 @@ mod tests { cache .set(&[((ns.clone(), "k1".to_string()), serde_json::json!(1), None)]) .unwrap(); - cache.clear(Some(&[ns.clone()])).unwrap(); + cache.clear(Some(std::slice::from_ref(&ns))).unwrap(); let result = cache.get(&[(ns, "k1".to_string())]).unwrap(); assert!(result.is_empty()); } diff --git a/crates/langgraph-checkpoint/src/checkpoint/memory.rs b/crates/langgraph-checkpoint/src/checkpoint/memory.rs index 24bdeec..fbb17da 100644 --- a/crates/langgraph-checkpoint/src/checkpoint/memory.rs +++ b/crates/langgraph-checkpoint/src/checkpoint/memory.rs @@ -10,15 +10,18 @@ use std::collections::HashMap; type StorageKey = (String, String, String); // (thread_id, checkpoint_ns, checkpoint_id) type WriteKey = (String, String, String, i64); // (thread_id, checkpoint_ns, checkpoint_id, idx) +/// (thread_id, checkpoint_ns, checkpoint_id) -> (checkpoint_json, metadata_json, parent_checkpoint_id) +type StorageValue = (JsonValue, JsonValue, Option); +/// (thread_id, checkpoint_ns, checkpoint_id, idx) -> (task_id, channel, value_json, task_path) +type WriteValue = (String, String, JsonValue, String); + /// In-memory checkpoint saver for testing and development. /// /// Stores checkpoints, blobs, and writes in memory using DashMap /// for concurrent access. pub struct InMemorySaver { - // (thread_id, checkpoint_ns, checkpoint_id) -> (checkpoint_json, metadata_json, parent_checkpoint_id) - storage: RwLock)>>, - // (thread_id, checkpoint_ns, checkpoint_id, idx) -> (task_id, channel, value_json, task_path) - writes: RwLock>, + storage: RwLock>, + writes: RwLock>, } impl InMemorySaver { diff --git a/crates/langgraph-checkpoint/src/store/memory.rs b/crates/langgraph-checkpoint/src/store/memory.rs index 7d80a1e..aaa05aa 100644 --- a/crates/langgraph-checkpoint/src/store/memory.rs +++ b/crates/langgraph-checkpoint/src/store/memory.rs @@ -186,7 +186,7 @@ impl BaseStore for InMemoryStore { } } - items.sort_by(|a, b| b.updated_at.cmp(&a.updated_at)); + items.sort_by_key(|b| std::cmp::Reverse(b.updated_at)); let total = items.len(); let start = search_op.offset.min(total); let end = (start + search_op.limit).min(total); diff --git a/crates/langgraph-core/src/channels/base.rs b/crates/langgraph-core/src/channels/base.rs index 692c773..8d877df 100644 --- a/crates/langgraph-core/src/channels/base.rs +++ b/crates/langgraph-core/src/channels/base.rs @@ -12,6 +12,7 @@ pub trait Channel: Send + Sync + 'static { fn checkpoint(&self) -> Option; /// Restore channel state from a checkpoint. + #[allow(clippy::wrong_self_convention)] fn from_checkpoint(&self, checkpoint: Option<&JsonValue>) -> Box; /// Apply a batch of updates. Returns true if the channel was modified. diff --git a/crates/langgraph-core/src/graph/state.rs b/crates/langgraph-core/src/graph/state.rs index 193d71f..750f202 100644 --- a/crates/langgraph-core/src/graph/state.rs +++ b/crates/langgraph-core/src/graph/state.rs @@ -260,6 +260,7 @@ impl StateGraph { } /// Internal: compile with explicit parameters. + #[allow(clippy::too_many_arguments)] fn compile_with( &mut self, checkpointer: Option>, @@ -596,7 +597,7 @@ impl CompiledStateGraph { for (tid, chan, val) in pending { if tid == NULL_TASK_ID { if let Some(ch) = channels.get(chan) { - ch.update(&[val.clone()]).ok(); + ch.update(std::slice::from_ref(val)).ok(); } } } @@ -793,7 +794,7 @@ impl CompiledStateGraph { if let Some(obj) = values.as_object() { for (key, val) in obj { if let Some(ch) = channels.get(key) { - ch.update(&[val.clone()]).ok(); + ch.update(std::slice::from_ref(val)).ok(); // Bump the channel version let new_version = channel_versions .get(key) @@ -883,12 +884,12 @@ impl CompiledStateGraph { } if tid == NULL_TASK_ID { if let Some(ch) = channels.get(chan) { - ch.update(&[val.clone()]).ok(); + ch.update(std::slice::from_ref(val)).ok(); } continue; } if let Some(ch) = channels.get(chan) { - ch.update(&[val.clone()]).ok(); + ch.update(std::slice::from_ref(val)).ok(); } } } @@ -1372,7 +1373,7 @@ impl CompiledStateGraph { for (_task_id, channel, value) in pending { if channel != RESUME { if let Some(ch) = restored.get(channel) { - ch.update(&[value.clone()]).ok(); + ch.update(std::slice::from_ref(value)).ok(); } } } @@ -1443,14 +1444,14 @@ impl CompiledStateGraph { let input_writes = map_input(&[START.to_string()], input); for (chan, val) in &input_writes { if let Some(ch) = channels.get(chan) { - ch.update(&[val.clone()]).ok(); + ch.update(std::slice::from_ref(val)).ok(); } } if let Some(obj) = input.as_object() { for (key, val) in obj { if key != START && !key.starts_with("branch:") && !key.starts_with("join:") { if let Some(ch) = channels.get(key) { - ch.update(&[val.clone()]).ok(); + ch.update(std::slice::from_ref(val)).ok(); } } } diff --git a/crates/langgraph-core/src/pregel/algo.rs b/crates/langgraph-core/src/pregel/algo.rs index 6618a16..c8bb690 100644 --- a/crates/langgraph-core/src/pregel/algo.rs +++ b/crates/langgraph-core/src/pregel/algo.rs @@ -42,6 +42,7 @@ fn version_gt(a: &JsonValue, b: &JsonValue) -> bool { /// /// This is the "Plan" phase of the BSP cycle. It checks which nodes /// have trigger channels with newer versions than what the node last saw. +#[allow(clippy::too_many_arguments)] pub fn prepare_next_tasks( nodes: &HashMap, channels: &HashMap>, @@ -292,12 +293,10 @@ pub fn apply_writes( // This allows ephemeral channels to clear themselves and notify downstream. if bump_step { for (chan, ch) in channels.iter() { - if ch.is_available() && !updated.contains(chan) { - if ch.update(&[]).unwrap_or(false) { - channel_versions.insert(chan.clone(), next_version.clone()); - if ch.is_available() { - updated.insert(chan.clone()); - } + if ch.is_available() && !updated.contains(chan) && ch.update(&[]).unwrap_or(false) { + channel_versions.insert(chan.clone(), next_version.clone()); + if ch.is_available() { + updated.insert(chan.clone()); } } } diff --git a/crates/langgraph-derive/src/lib.rs b/crates/langgraph-derive/src/lib.rs index 58ccd6c..5917ae0 100644 --- a/crates/langgraph-derive/src/lib.rs +++ b/crates/langgraph-derive/src/lib.rs @@ -279,7 +279,7 @@ fn impl_tool_macro(name_lit: &Option, desc_lit: &Option, func: &ItemFn continue; } if !extracted_desc.is_empty() { - extracted_desc.push_str(" "); + extracted_desc.push(' '); } extracted_desc.push_str(trimmed); } diff --git a/crates/langgraph-prebuilt/src/traits.rs b/crates/langgraph-prebuilt/src/traits.rs index 4012c9e..ab40318 100644 --- a/crates/langgraph-prebuilt/src/traits.rs +++ b/crates/langgraph-prebuilt/src/traits.rs @@ -224,12 +224,15 @@ impl BaseChatModel for Box { } } +/// A tool function: maps an input JSON value to an output JSON value. +pub type ToolFn = Box Result + Send + Sync>; + /// A simple tool implemented as a closure. pub struct ClosureTool { tool_name: String, tool_description: String, tool_parameters: Option, - func: Box Result + Send + Sync>, + func: ToolFn, } impl ClosureTool { diff --git a/crates/langgraph-providers/src/anthropic/model.rs b/crates/langgraph-providers/src/anthropic/model.rs index aec1771..d28c3b0 100644 --- a/crates/langgraph-providers/src/anthropic/model.rs +++ b/crates/langgraph-providers/src/anthropic/model.rs @@ -174,6 +174,7 @@ enum ContentBlockStart { #[derive(Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] +#[allow(clippy::enum_variant_names)] enum ContentBlockDelta { TextDelta { text: String }, InputJsonDelta { partial_json: String }, @@ -687,7 +688,8 @@ impl BaseChatModel for AnthropicModel { "message_stop" => { break; } - "ping" | _ => {} + "ping" => {} + _ => {} } } } diff --git a/crates/langgraph-providers/src/openai/model.rs b/crates/langgraph-providers/src/openai/model.rs index c5ef61c..dcd932a 100644 --- a/crates/langgraph-providers/src/openai/model.rs +++ b/crates/langgraph-providers/src/openai/model.rs @@ -447,17 +447,14 @@ impl OpenAIModel { reserved_keys: &[&str], ) -> Option { let mut extra = extra?; - if let Some(obj) = extra.as_object_mut() { - for key in reserved_keys { - obj.remove(*key); - } - if obj.is_empty() { - return None; - } - return Some(serde_json::Value::Object(obj.clone())); - } else { + let obj = extra.as_object_mut()?; + for key in reserved_keys { + obj.remove(*key); + } + if obj.is_empty() { return None; } + Some(serde_json::Value::Object(obj.clone())) } } diff --git a/crates/langgraph-tracing/src/store.rs b/crates/langgraph-tracing/src/store.rs index fcaf3dd..a9b5d0f 100644 --- a/crates/langgraph-tracing/src/store.rs +++ b/crates/langgraph-tracing/src/store.rs @@ -91,7 +91,7 @@ impl TracingStore for InMemoryTracingStore { .collect(); // Sort newest first - summaries.sort_by(|a, b| b.start_time.cmp(&a.start_time)); + summaries.sort_by_key(|b| std::cmp::Reverse(b.start_time)); let offset = filter.offset.unwrap_or(0); let limit = filter.limit.unwrap_or(summaries.len()); diff --git a/examples/interactive_chat_with_tracing.rs b/examples/interactive_chat_with_tracing.rs index 18438a3..ca9b421 100644 --- a/examples/interactive_chat_with_tracing.rs +++ b/examples/interactive_chat_with_tracing.rs @@ -7,7 +7,6 @@ use langgraph::prebuilt::{ use langgraph::prelude::*; use langgraph::providers::openai::{OpenAIModel, OpenAIModelConfig}; use langgraph::{langgraph_state, tool, Traceable}; -use serde::{Deserialize, Serialize}; use serde_json::{json, Value as JsonValue}; use std::io::{self, Write}; use std::sync::Arc; diff --git a/examples/parallel_interrupt_hitl.rs b/examples/parallel_interrupt_hitl.rs index be1f1fc..2f197ba 100644 --- a/examples/parallel_interrupt_hitl.rs +++ b/examples/parallel_interrupt_hitl.rs @@ -76,7 +76,6 @@ async fn worker_b_node( } /// Output node: triggered only by worker_a's edge. - async fn output_node( _input: JsonValue, _config: RunnableConfig,