Proc-Macro Generated Code Triggers dead_code Warnings
Proc-Macro Generated Code Triggers dead_code Warnings
Problem
After removing a blanket #[allow(dead_code)] from a module, cargo clippy reports dead_code warnings on items that
ARE used at runtime — but only through proc-macro-generated dispatch code that clippy cannot trace.
Symptoms:
- Removing
#[allow(dead_code)]from a module causes clippy to flag items that work correctly at runtime - Affected items are referenced exclusively through proc-macro-generated code (e.g.,
#[tool_handler],#[derive(FromRequest)]) - The warnings appear on error enums, middleware functions, and trait implementations consumed by framework macros
Investigation
Steps Tried
- Remove blanket
#[allow(dead_code)]— exposed 4 warnings in MCP module - Check each item manually — confirmed 3 of 4 are used at runtime via proc-macro dispatch, 1 was genuinely unused
- Targeted
#[allow(dead_code)]with comments — correct solution for proc-macro false positives
Root Cause
Rust’s dead code analysis operates on the HIR (High-level Intermediate Representation) BEFORE proc macros expand their
generated code. When a proc macro like rmcp’s #[tool_handler] generates match arms that dispatch to error variants, or
axum’s FromRequest derive generates code that reads struct fields, clippy sees no direct references in the source code
and flags them as unused.
Specific cases encountered:
| Item | Framework | How It’s Used at Runtime |
|---|---|---|
McpError enum variants | rmcp #[tool_handler] | Error variants returned by tool methods, matched by macro-generated dispatch |
BearerToken struct field | axum FromRequest derive | Field populated by extractor, read by macro-generated code |
require_bearer_token fn | axum middleware | Scaffolded for .layer() wiring — genuinely not yet called (different case) |
Solution
Use targeted #[allow(dead_code)] with explanatory comments on each item, rather than blanket module-level
suppression.
Code Changes
// Before — blanket suppression hides real issues#[allow(dead_code)]pub(crate) mod mcp;
// After — targeted suppression with clear rationalepub(crate) mod mcp;
// In the specific files:
/// MCP-specific errors returned by tool handlers.////// Variants are constructed in `#[tool]` methods and dispatched by/// rmcp's `#[tool_handler]` proc-macro — clippy cannot trace through/// the generated match arms.#[allow(dead_code)]#[derive(Debug, thiserror::Error)]pub enum McpError { ... }
/// Bearer token extracted from Authorization header./// Field is read by axum's `FromRequest` derive — invisible to clippy.#[allow(dead_code)]pub struct BearerToken(pub String);Decision Framework
When clippy reports dead_code after removing a blanket suppression:
- Is the item referenced only through proc-macro expansion? → Targeted
#[allow(dead_code)]with comment explaining which macro uses it - Is the item scaffolded but not yet wired? → Targeted
#[allow(dead_code)]withTODOcomment for when to remove - Is the item genuinely unused? → Delete it
Implementation Notes
- Always add a comment explaining WHY the item appears unused but isn’t
- Reference the specific proc macro that generates the runtime dispatch
- Distinguish between “false positive” (macro-used) and “scaffolded” (not-yet-wired) — both get
#[allow(dead_code)]but different comments - Prefer targeted per-item suppression over module-level blanket
#[allow(dead_code)]
Prevention
Best Practices
- Never use blanket
#[allow(dead_code)]on modules — it masks real dead code as the module grows - When introducing proc-macro-heavy modules (rmcp, axum, serde), anticipate that items consumed by macros will need targeted suppression
- Review dead_code warnings individually when cleaning up blanket suppressions
- Add the explanatory comment at the same time as the
#[allow]— future readers need to know why
Warning Signs
- A module has
#[allow(dead_code)]at the module level — likely hiding real unused code - Removing
#[allow(dead_code)]produces warnings on items decorated with proc-macro attributes - Items flagged as unused are error types, extractor structs, or handler functions in framework code
Known Proc Macros That Cause This
- rmcp:
#[tool_handler],#[tool_router],#[tool]— tool dispatch and error handling - axum:
#[derive(FromRequest)],#[derive(FromRequestParts)]— extractor field access - serde:
#[derive(Deserialize)]with#[serde(default)]— default functions - tauri-specta: Command registration macros — function references in generated code
References
- Commit:
5c38792— Cleaned up dead_code warnings in MCP module (INK-251) - Rust issue: rust-lang/rust#56750 — dead_code lint doesn’t account for proc macros
- Related:
docs/solutions/build-errors/rust-clippy-idioms-catalog.md
Was this page helpful?
Thanks for your feedback!