1use std::sync::Arc;
2
3use crate::violation::CheckRationale;
4
5const NESTED_CONDITIONAL_PROBLEM: &str = "Conditional-in-conditional creates combinatorial complexity. A 2-branch if inside a 3-branch match is 6 paths. Hard to ensure all paths are tested.";
6const NESTED_CONDITIONAL_FIX: &str = "Use tuple patterns `match (a, b) { ... }`, match guards `Some(x) if x > 0 => ...`, or extract to functions.";
7const NESTED_CONDITIONAL_EXCEPTION: &str = "None. Refactoring is always possible.";
8
9#[derive(Debug, Clone, Copy)]
11pub struct CheckInfo {
12 pub code: &'static str,
14 pub description: &'static str,
16 pub category: &'static str,
18 pub llm_specific: bool,
20}
21
22macro_rules! define_checks {
30 (
32 $(
33 $variant:ident $({ $field:ident : $ftype:ty })? => {
34 code: $code:expr,
35 description: $desc:literal,
36 category: $cat:expr,
37 problem: $problem:expr,
38 fix: $fix:expr,
39 exception: $exception:expr,
40 llm_specific: $llm:expr $(,)?
41 }
42 ),+ $(,)?
43 ) => {
44 #[derive(Debug, Clone, PartialEq, Eq)]
46 pub enum ViolationType {
47 $(
48 #[doc = $desc]
49 $variant $({
50 #[doc = "The matched pattern."]
51 $field: $ftype
52 })?,
53 )+
54 }
55
56 impl ViolationType {
57 pub fn code(&self) -> &'static str {
59 match self {
60 $(
61 Self::$variant $({ $field: _ })? => $code,
62 )+
63 }
64 }
65
66 pub fn check_name(&self) -> &'static str {
68 match self {
69 $(
70 Self::$variant $({ $field: _ })? => $cat,
71 )+
72 }
73 }
74
75 pub fn rationale(&self) -> CheckRationale {
77 match self {
78 $(
79 Self::$variant $({ $field: _ })? => CheckRationale {
80 problem: $problem,
81 fix: $fix,
82 exception: $exception,
83 llm_specific: $llm,
84 },
85 )+
86 }
87 }
88 }
89
90 pub fn lookup_rationale(code: &str) -> Option<CheckRationale> {
92 match code {
93 $(
94 $code => Some(CheckRationale {
95 problem: $problem,
96 fix: $fix,
97 exception: $exception,
98 llm_specific: $llm,
99 }),
100 )+
101 _ => None,
102 }
103 }
104
105 pub const ALL_CHECKS: &[CheckInfo] = &[
107 $(
108 CheckInfo {
109 code: $code,
110 description: $desc,
111 category: $cat,
112 llm_specific: $llm,
113 },
114 )+
115 ];
116 };
117}
118
119define_checks! {
120 MaxDepth => {
121 code: "max-depth",
122 description: "Excessive nesting depth",
123 category: "nesting",
124 problem: "Deeply nested code is hard to read, test, and modify. Each nesting level adds cognitive load. Bugs hide in deep branches.",
125 fix: "Extract functions, use early returns, flatten with guard clauses.",
126 exception: "Complex parsers or state machines may need deeper nesting locally.",
127 llm_specific: false,
128 },
129 NestedIf => {
130 code: "nested-if",
131 description: "If nested inside if",
132 category: "nesting",
133 problem: NESTED_CONDITIONAL_PROBLEM,
134 fix: NESTED_CONDITIONAL_FIX,
135 exception: NESTED_CONDITIONAL_EXCEPTION,
136 llm_specific: false,
137 },
138 IfInMatch => {
139 code: "if-in-match",
140 description: "If inside match arm",
141 category: "nesting",
142 problem: NESTED_CONDITIONAL_PROBLEM,
143 fix: NESTED_CONDITIONAL_FIX,
144 exception: NESTED_CONDITIONAL_EXCEPTION,
145 llm_specific: false,
146 },
147 NestedMatch => {
148 code: "nested-match",
149 description: "Match nested inside match",
150 category: "nesting",
151 problem: NESTED_CONDITIONAL_PROBLEM,
152 fix: NESTED_CONDITIONAL_FIX,
153 exception: NESTED_CONDITIONAL_EXCEPTION,
154 llm_specific: false,
155 },
156 MatchInIf => {
157 code: "match-in-if",
158 description: "Match inside if branch",
159 category: "nesting",
160 problem: NESTED_CONDITIONAL_PROBLEM,
161 fix: NESTED_CONDITIONAL_FIX,
162 exception: NESTED_CONDITIONAL_EXCEPTION,
163 llm_specific: false,
164 },
165 ElseChain => {
166 code: "else-chain",
167 description: "Long if/else if chain",
168 category: "nesting",
169 problem: "Long if/else if/else if chains are unordered match arms in disguise. Easy to miss cases, hard to verify exhaustiveness.",
170 fix: "Use `match` on boolean tuples. Precedence becomes explicit, compiler checks exhaustiveness.",
171 exception: "None. Any boolean chain can be refactored to a tuple match.",
172 llm_specific: false,
173 },
174 ForbiddenAttribute { pattern: Arc<str> } => {
175 code: "forbidden-attribute",
176 description: "Forbidden attribute pattern",
177 category: "forbid_attributes",
178 problem: "Silences warnings that indicate real problems. Dead code is maintenance burden. Unused variables often signal logic errors.",
179 fix: "Remove dead code. Use `_` prefix for intentionally unused bindings. Address the underlying issue rather than suppressing.",
180 exception: "Generated code, FFI bindings, conditional compilation.",
181 llm_specific: true,
182 },
183 ForbiddenType { pattern: Arc<str> } => {
184 code: "forbidden-type",
185 description: "Forbidden type pattern",
186 category: "forbid_types",
187 problem: "Certain type patterns indicate suboptimal design. Arc<String> has double indirection. Box<dyn Error> is superseded by better alternatives.",
188 fix: "Use Arc<str> instead of Arc<String>. Use thiserror or anyhow instead of Box<dyn Error>.",
189 exception: "When mutation methods are needed via Arc::make_mut(), or legacy API interop.",
190 llm_specific: true,
191 },
192 ForbiddenCall { pattern: Arc<str> } => {
193 code: "forbidden-call",
194 description: "Forbidden method call pattern",
195 category: "forbid_calls",
196 problem: ".unwrap() and .expect() panic on failure with no recovery. .clone() hides allocations.",
197 fix: "Use `?` for propagation. Use .unwrap_or(), .unwrap_or_default() for defaults. Restructure ownership to avoid clone.",
198 exception: "Human-authored code may use .unwrap() on provably infallible paths with documented invariants. Does not apply to LLM-generated code.",
199 llm_specific: true,
200 },
201 ForbiddenMacro { pattern: Arc<str> } => {
202 code: "forbidden-macro",
203 description: "Forbidden macro pattern",
204 category: "forbid_macros",
205 problem: "panic!/todo!/unimplemented! crash at runtime. dbg!/println! are debug artifacts that shouldn't be committed.",
206 fix: "Return Result instead of panicking. Use proper logging (tracing, log) for diagnostics. Implement functionality instead of stubbing.",
207 exception: "Invariant assertions for bugs (not expected failures). CLI tools where stdout is the interface.",
208 llm_specific: true,
209 },
210 ForbiddenElse => {
211 code: "forbidden-else",
212 description: "Use of `else` keyword (style preference)",
213 category: "forbid_else",
214 problem: "`else` creates implicit branches. `match` makes all branches explicit and compiler-checked.",
215 fix: "Use `match` for multi-way branches. Use early return with guard clauses instead of if/else.",
216 exception: "This is a style preference. Clippy recommends if/else for simple boolean conditions (match_bool lint). Disable with `forbid_else = false` if you disagree.",
217 llm_specific: false,
218 },
219 ForbiddenUnsafe => {
220 code: "forbidden-unsafe",
221 description: "Use of `unsafe` keyword",
222 category: "forbid_unsafe",
223 problem: "`unsafe` bypasses Rust's safety guarantees. Memory corruption, undefined behavior, and security vulnerabilities become possible.",
224 fix: "Use safe abstractions. Wrap unsafe in minimal, well-audited modules with safe public APIs.",
225 exception: "FFI bindings, performance-critical code with proven safety invariants, implementing safe abstractions over unsafe primitives.",
226 llm_specific: false,
227 },
228 DynReturn => {
229 code: "dyn-return",
230 description: "Dynamic dispatch in return type (`Box<dyn T>`, `Arc<dyn T>`)",
231 category: "dispatch",
232 problem: "Returning Box<dyn Trait> or Arc<dyn Trait> forces vtable dispatch on every call. The vtable lookup prevents inlining and all downstream optimizations.",
233 fix: "Use enum dispatch for a closed set of types. Use `impl Trait` when the caller doesn't need to store heterogeneously. Use a generic type parameter when the concrete type varies per call site.",
234 exception: "Plugin systems or FFI boundaries where the set of concrete types is truly open-ended and unknown at compile time.",
235 llm_specific: true,
236 },
237 DynParam => {
238 code: "dyn-param",
239 description: "Dynamic dispatch in function parameter (`&dyn T`, `Box<dyn T>`)",
240 category: "dispatch",
241 problem: "Accepting &dyn Trait or Box<dyn Trait> as a parameter forces vtable dispatch per call. The compiler cannot monomorphize or inline the callee's methods.",
242 fix: "Use a generic parameter `T: Trait` or `impl Trait` to enable monomorphization. The compiler generates specialized code for each concrete type, enabling inlining.",
243 exception: "When the function is called with many distinct concrete types and binary size is a concern, or when storing heterogeneous collections.",
244 llm_specific: true,
245 },
246 VecBoxDyn => {
247 code: "vec-box-dyn",
248 description: "`Vec<Box<dyn T>>` prevents cache locality and inlining",
249 category: "dispatch",
250 problem: "Vec<Box<dyn Trait>> incurs per-element heap allocation, vtable dispatch on every access, and scattered memory that defeats cache prefetching.",
251 fix: "Use an enum wrapping the known concrete types. Elements are stored inline in the Vec with no vtable and no per-element allocation.",
252 exception: "Plugin systems where concrete types are loaded at runtime and cannot be enumerated at compile time.",
253 llm_specific: true,
254 },
255 DynField => {
256 code: "dyn-field",
257 description: "Dynamic dispatch in struct field (`Box<dyn T>`, `Arc<dyn T>`)",
258 category: "dispatch",
259 problem: "A Box<dyn Trait> or Arc<dyn Trait> struct field permanently commits every method call on that field to vtable dispatch. This prevents inlining for the lifetime of the struct.",
260 fix: "Make the struct generic over the trait: `struct Foo<T: Trait> { field: T }`. The compiler monomorphizes each instantiation, enabling static dispatch and inlining.",
261 exception: "When the struct must hold different concrete types at different times, or when the concrete type is determined at runtime (e.g., configuration-driven).",
262 llm_specific: true,
263 },
264 CloneInLoop => {
265 code: "clone-in-loop",
266 description: "clone() called inside loop body (Arc/Rc suppressed when type is visible)",
267 category: "performance",
268 problem: ".clone() inside a loop body means N heap allocations where N is the iteration count. LLMs add .clone() to satisfy the borrow checker without considering the per-iteration cost. Arc/Rc clones are automatically suppressed when the type is visible (explicit type annotations or containers with Arc/Rc generic args). Type aliases that hide Arc/Rc (e.g., type MyMap = BTreeMap<Arc<str>, Arc<str>>) cannot be resolved and may cause false positives.",
269 fix: "Borrow instead of cloning. Use Cow<T> for conditional ownership. Use Rc/Arc for shared ownership. Restructure to move ownership before the loop.",
270 exception: "When the cloned value is mutated independently per iteration and borrowing is not possible.",
271 llm_specific: true,
272 },
273 DefaultHasher => {
274 code: "default-hasher",
275 description: "HashMap/HashSet with default SipHash hasher",
276 category: "performance",
277 problem: "HashMap/HashSet default to SipHash, designed for HashDoS resistance. SipHash is 2-5x slower than FxHash or AHash for typical keys (integers, short strings).",
278 fix: "Use rustc_hash::FxHashMap for integer keys. Use ahash::AHashMap for general-purpose fast hashing. Specify the hasher explicitly: HashMap<K, V, S>.",
279 exception: "When keys come from untrusted input (network, user-provided) and HashDoS resistance is required.",
280 llm_specific: true,
281 },
282 MixedConcerns => {
283 code: "mixed-concerns",
284 description: "Disconnected type groups indicate mixed concerns",
285 category: "structure",
286 problem: "Disconnected type groups in a single file indicate mixed concerns. Types that share no fields, trait bounds, or function signatures belong in separate modules.",
287 fix: "Split the file along connected components. Each group of related types becomes its own module.",
288 exception: "Re-export modules or files that intentionally collect small, independent items (e.g., error enums).",
289 llm_specific: true,
290 },
291 InlineTests => {
292 code: "inline-tests",
293 description: "Test module embedded in source file",
294 category: "structure",
295 problem: "Test modules embedded in source files mix production code with test code. This inflates source files and makes test organization harder to navigate.",
296 fix: "Move tests to the tests/ directory as integration tests, or to a separate test file alongside the source.",
297 exception: "Small utility modules where colocated unit tests are preferred for locality.",
298 llm_specific: true,
299 },
300 GenericNaming => {
301 code: "generic-naming",
302 description: "High ratio of generic variable names in a function",
303 category: "naming",
304 problem: "LLMs generate generic names like `tmp`, `data`, `val` because training data is saturated with them. System prompt rules like 'use descriptive names' compete with this statistical bias and lose.",
305 fix: "Use domain-specific names that describe what the value represents: `user_id` not `val`, `retry_count` not `tmp`, `response_body` not `data`.",
306 exception: "Small utility functions (fewer than 2 generic names) where short names are conventional.",
307 llm_specific: true,
308 },
309 LetUnderscoreResult => {
310 code: "let-underscore-result",
311 description: "let _ = discards a potentially fallible Result",
312 category: "structure",
313 problem: "Silently discarding a Result hides errors that surface only in production.",
314 fix: "Handle the error with `?`, `match`, or `if let Err`; or use `.expect()` with a reason if the error is truly impossible.",
315 exception: "`write!`/`writeln!` to a `String` binding — fmt::Write for String is infallible.",
316 llm_specific: true,
317 },
318 HighParamCount => {
319 code: "high-param-count",
320 description: "Function has too many parameters",
321 category: "structure",
322 problem: "Functions with many parameters are hard to call correctly. Callers must remember argument order, and adding parameters is a breaking change at every call site.",
323 fix: "Group related parameters into a struct. Use the builder pattern for optional configuration. Split the function if parameters serve different concerns.",
324 exception: "FFI bindings that must match an external C signature.",
325 llm_specific: true,
326 },
327}