Skip to content
Documentation GitHub
Build Errors

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

  1. Remove blanket #[allow(dead_code)] — exposed 4 warnings in MCP module
  2. Check each item manually — confirmed 3 of 4 are used at runtime via proc-macro dispatch, 1 was genuinely unused
  3. 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:

ItemFrameworkHow It’s Used at Runtime
McpError enum variantsrmcp #[tool_handler]Error variants returned by tool methods, matched by macro-generated dispatch
BearerToken struct fieldaxum FromRequest deriveField populated by extractor, read by macro-generated code
require_bearer_token fnaxum middlewareScaffolded 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 rationale
pub(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:

  1. Is the item referenced only through proc-macro expansion? → Targeted #[allow(dead_code)] with comment explaining which macro uses it
  2. Is the item scaffolded but not yet wired? → Targeted #[allow(dead_code)] with TODO comment for when to remove
  3. 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?