From 224f6219fb44fb52a833fd435280eb2904b418a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Dr=C3=A9an?= Date: Mon, 16 Feb 2026 16:08:54 +0100 Subject: [PATCH 1/5] fix(missing_type): avoid false positives on async fn generated closures --- rules/missing_type/src/lib.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/rules/missing_type/src/lib.rs b/rules/missing_type/src/lib.rs index 14140b1..8740c93 100644 --- a/rules/missing_type/src/lib.rs +++ b/rules/missing_type/src/lib.rs @@ -84,6 +84,12 @@ impl<'tcx> LateLintPass<'tcx> for MissingType { context: &LateContext<'tcx>, expression: &'tcx Expr<'tcx>, ) { + // Skip if the expression is from a macro expansion, as it may not be + // possible to determine the type annotation in that case. + if expression.span.from_expansion() { + return; + } + // Only check closure expressions. let ExprKind::Closure(closure): &ExprKind<'tcx> = &expression.kind else { @@ -97,6 +103,13 @@ impl<'tcx> LateLintPass<'tcx> for MissingType { // check if it has an explicit type annotation. If not, and if // the parameter pattern is not `_`, emit a warning. for param in body.params { + // Skip if the parameter pattern is from a macro expansion, as it + // may not be possible to determine the type annotation in that + // case. + if param.pat.span.from_expansion() { + continue; + } + // Skip if the parameter pattern is `_`. if matches!(param.pat.kind, PatKind::Wild) { continue; From e98f4c51b322ecc90dec8f1612e9fee09b7b24e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Dr=C3=A9an?= Date: Mon, 16 Feb 2026 16:16:55 +0100 Subject: [PATCH 2/5] fix(missing_type): ignore async fn desugared bindings and closures --- rules/indexing_usage/src/lib.rs | 2 +- rules/missing_type/src/lib.rs | 28 +++++++++++++--------------- rules/missing_type/ui/main.rs | 16 ++++++++++++++++ rules/panic_usage/src/lib.rs | 2 +- rules/panic_usage/ui/main.stderr | 2 +- rules/unsafe_usage/src/lib.rs | 2 +- 6 files changed, 33 insertions(+), 19 deletions(-) diff --git a/rules/indexing_usage/src/lib.rs b/rules/indexing_usage/src/lib.rs index 1936252..ac50efc 100644 --- a/rules/indexing_usage/src/lib.rs +++ b/rules/indexing_usage/src/lib.rs @@ -149,7 +149,7 @@ mod tests { #[test] fn ui() { Test::src_base(env!("CARGO_PKG_NAME"), "ui") - .rustc_flags(["-Z", "ui-testing"]) + .rustc_flags(["--edition=2024", "-Z", "ui-testing"]) .run(); } } diff --git a/rules/missing_type/src/lib.rs b/rules/missing_type/src/lib.rs index 8740c93..3cbd9ac 100644 --- a/rules/missing_type/src/lib.rs +++ b/rules/missing_type/src/lib.rs @@ -7,7 +7,7 @@ extern crate rustc_middle; extern crate rustc_session; use rustc_errors::Diag; -use rustc_hir::{Body, Expr, ExprKind, LetStmt, PatKind}; +use rustc_hir::{Body, ClosureKind, Expr, ExprKind, LetStmt, PatKind}; use rustc_lint::{LateContext, LateLintPass, LintContext, LintStore}; use rustc_middle::ty::TyCtxt; use rustc_session::{Session, declare_lint, declare_lint_pass}; @@ -55,6 +55,11 @@ impl<'tcx> LateLintPass<'tcx> for MissingType { if matches!(local.pat.kind, PatKind::Wild) { return; } + // Skip if the let statement is from a macro expansion, as it may not + // be possible to determine the type annotation in that case. + if local.span.desugaring_kind().is_some() { + return; + } // Check if the let statement has an explicit type annotation. If not, // emit a warning. @@ -84,18 +89,18 @@ impl<'tcx> LateLintPass<'tcx> for MissingType { context: &LateContext<'tcx>, expression: &'tcx Expr<'tcx>, ) { - // Skip if the expression is from a macro expansion, as it may not be - // possible to determine the type annotation in that case. - if expression.span.from_expansion() { - return; - } - // Only check closure expressions. let ExprKind::Closure(closure): &ExprKind<'tcx> = &expression.kind else { return; }; + // Skip if the expression is from a macro expansion, as it may not be + // possible to determine the type annotation in that case. + if matches!(closure.kind, ClosureKind::Coroutine(_)) { + return; + } + // Get the body of the closure to access its parameters. let body: &Body<'_> = context.tcx.hir_body(closure.body); @@ -103,13 +108,6 @@ impl<'tcx> LateLintPass<'tcx> for MissingType { // check if it has an explicit type annotation. If not, and if // the parameter pattern is not `_`, emit a warning. for param in body.params { - // Skip if the parameter pattern is from a macro expansion, as it - // may not be possible to determine the type annotation in that - // case. - if param.pat.span.from_expansion() { - continue; - } - // Skip if the parameter pattern is `_`. if matches!(param.pat.kind, PatKind::Wild) { continue; @@ -167,7 +165,7 @@ mod tests { #[test] fn ui() { Test::src_base(env!("CARGO_PKG_NAME"), "ui") - .rustc_flags(["-Z", "ui-testing"]) + .rustc_flags(["--edition=2024", "-Z", "ui-testing"]) .run(); } } diff --git a/rules/missing_type/ui/main.rs b/rules/missing_type/ui/main.rs index fd7b57c..7f61ec7 100644 --- a/rules/missing_type/ui/main.rs +++ b/rules/missing_type/ui/main.rs @@ -29,3 +29,19 @@ fn main() { // Closure with `_` pattern (should not trigger). let ignore: fn(i32) -> i32 = |_| 0; } + +/// Asynchronous function example to demonstrate that the `missing_type` lint +/// does not trigger for async functions, as they may have implicit return +/// types and parameters that are not explicitly annotated. This function takes +/// an `i32` parameter and returns a future that resolves to an `i32`. The lint +/// should not emit a warning for this function, as it is common for async +/// functions to have implicit return types and parameters without explicit +/// type annotations, especially when using async/await syntax. +async fn async_example(x: i32) -> i32 { + x + 1 +} + +async fn async_with_let() -> i32 { + let value: i32 = 10; + value +} diff --git a/rules/panic_usage/src/lib.rs b/rules/panic_usage/src/lib.rs index 0d7829c..d146f00 100644 --- a/rules/panic_usage/src/lib.rs +++ b/rules/panic_usage/src/lib.rs @@ -162,7 +162,7 @@ mod tests { #[test] fn ui() { Test::src_base(env!("CARGO_PKG_NAME"), "ui") - .rustc_flags(["-Z", "ui-testing"]) + .rustc_flags(["--edition=2024", "-Z", "ui-testing"]) .run(); } } diff --git a/rules/panic_usage/ui/main.stderr b/rules/panic_usage/ui/main.stderr index ff079cb..87ea6c2 100644 --- a/rules/panic_usage/ui/main.stderr +++ b/rules/panic_usage/ui/main.stderr @@ -16,7 +16,7 @@ warning: Call to panic backend `Expect` detected. LL | x.expect(""); // should trigger. | ^^^^^^^^^^^^ -warning: Call to panic backend `BeginPanic` detected. +warning: Call to panic backend `PanicFmt` detected. --> $DIR/main.rs:13:5 | LL | panic!(""); // Should trigger. diff --git a/rules/unsafe_usage/src/lib.rs b/rules/unsafe_usage/src/lib.rs index 345dabc..f91d6e4 100644 --- a/rules/unsafe_usage/src/lib.rs +++ b/rules/unsafe_usage/src/lib.rs @@ -159,7 +159,7 @@ mod tests { #[test] fn ui() { Test::src_base(env!("CARGO_PKG_NAME"), "ui") - .rustc_flags(["-Z", "ui-testing"]) + .rustc_flags(["--edition=2024", "-Z", "ui-testing"]) .run(); } } From 8c1ef0ca7f40f47d2ca991ae4b32fd9938f0de0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Dr=C3=A9an?= Date: Mon, 16 Feb 2026 16:20:07 +0100 Subject: [PATCH 3/5] fix(missing_type): ignore macro expansions and async_trait generated bindings --- .vscode/settings.json | 1 + rules/missing_type/src/lib.rs | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 11ad9c6..b1e494b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,6 +26,7 @@ "callsite", "catthehacker", "ctxt", + "desugared", "dylint", "qpath", "rustsec", diff --git a/rules/missing_type/src/lib.rs b/rules/missing_type/src/lib.rs index 3cbd9ac..531d8e5 100644 --- a/rules/missing_type/src/lib.rs +++ b/rules/missing_type/src/lib.rs @@ -7,7 +7,7 @@ extern crate rustc_middle; extern crate rustc_session; use rustc_errors::Diag; -use rustc_hir::{Body, ClosureKind, Expr, ExprKind, LetStmt, PatKind}; +use rustc_hir::{Body, Expr, ExprKind, LetStmt, PatKind}; use rustc_lint::{LateContext, LateLintPass, LintContext, LintStore}; use rustc_middle::ty::TyCtxt; use rustc_session::{Session, declare_lint, declare_lint_pass}; @@ -57,6 +57,13 @@ impl<'tcx> LateLintPass<'tcx> for MissingType { } // Skip if the let statement is from a macro expansion, as it may not // be possible to determine the type annotation in that case. + // Ignore anything coming from macro expansion (async_trait, derives, + // etc.) + if local.span.from_expansion() { + return; + } + + // Ignore desugared constructs (async lowering, ?, for loops, etc.) if local.span.desugaring_kind().is_some() { return; } @@ -89,6 +96,10 @@ impl<'tcx> LateLintPass<'tcx> for MissingType { context: &LateContext<'tcx>, expression: &'tcx Expr<'tcx>, ) { + if expression.span.from_expansion() { + return; + } + // Only check closure expressions. let ExprKind::Closure(closure): &ExprKind<'tcx> = &expression.kind else { @@ -97,7 +108,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingType { // Skip if the expression is from a macro expansion, as it may not be // possible to determine the type annotation in that case. - if matches!(closure.kind, ClosureKind::Coroutine(_)) { + if matches!(closure.kind, rustc_hir::ClosureKind::Coroutine(_)) { return; } From 72a5869e5b9386642b3874c7ab3df5bc2825fc62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Dr=C3=A9an?= Date: Mon, 16 Feb 2026 16:38:18 +0100 Subject: [PATCH 4/5] fix(panic_usage): correctly handle macro expansions and detect --- rules/panic_usage/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rules/panic_usage/src/lib.rs b/rules/panic_usage/src/lib.rs index d146f00..84b4dce 100644 --- a/rules/panic_usage/src/lib.rs +++ b/rules/panic_usage/src/lib.rs @@ -86,6 +86,11 @@ impl<'tcx> LateLintPass<'tcx> for SecurityPanicUsage { context: &LateContext<'tcx>, expression: &'tcx Expr<'tcx>, ) { + // Ignore external macro expansions. + if expression.span.source_callsite().from_expansion() { + return; + } + // Detect direct calls to `unwrap` and `expect` methods. if let ExprKind::MethodCall(segment, _, _, _) = &expression.kind && let Some(kind) = From 9f1ad5fdebaa06c254553797002dda318fe374d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Dr=C3=A9an?= Date: Mon, 16 Feb 2026 16:50:22 +0100 Subject: [PATCH 5/5] fix(panic_usage): use diagnostic items to detect std unwrap/expect --- .vscode/settings.json | 2 ++ rules/panic_usage/src/lib.rs | 61 ++++++++++++-------------------- rules/panic_usage/ui/main.stderr | 4 +-- 3 files changed, 27 insertions(+), 40 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b1e494b..4d3b473 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -28,9 +28,11 @@ "ctxt", "desugared", "dylint", + "krate", "qpath", "rustsec", "rustup", + "typeck", "usize" ] } \ No newline at end of file diff --git a/rules/panic_usage/src/lib.rs b/rules/panic_usage/src/lib.rs index 84b4dce..367941a 100644 --- a/rules/panic_usage/src/lib.rs +++ b/rules/panic_usage/src/lib.rs @@ -12,6 +12,7 @@ use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass, LintContext, LintStore}; use rustc_middle::ty::TyCtxt; use rustc_session::{Session, declare_lint, declare_lint_pass}; +use rustc_span::sym; declare_lint! { pub SECURITY_PANIC_USAGE, @@ -21,26 +22,6 @@ declare_lint! { declare_lint_pass!(SecurityPanicUsage => [SECURITY_PANIC_USAGE]); -/// Enum representing the different kinds of panic-related constructs that can -/// be detected by the `SECURITY_PANIC_USAGE` lint, such as calls to `unwrap` -/// and `expect` methods, as well as calls to panic-related functions in the -/// standard library. -#[derive(Debug, Clone, Copy)] -enum PanicKind { - Unwrap, - Expect, -} - -impl PanicKind { - fn from_method(name: &str) -> Option { - match name { - "unwrap" => Some(Self::Unwrap), - "expect" => Some(Self::Expect), - _ => None, - } - } -} - /// Enum representing the different panic backends that can be detected by the /// `SECURITY_PANIC_USAGE` lint, such as the `panicking` module, the /// `panic_fmt` function, the `panic_display` function, the `assert_failed` @@ -86,26 +67,30 @@ impl<'tcx> LateLintPass<'tcx> for SecurityPanicUsage { context: &LateContext<'tcx>, expression: &'tcx Expr<'tcx>, ) { - // Ignore external macro expansions. - if expression.span.source_callsite().from_expansion() { - return; - } - // Detect direct calls to `unwrap` and `expect` methods. - if let ExprKind::MethodCall(segment, _, _, _) = &expression.kind - && let Some(kind) = - PanicKind::from_method(segment.ident.name.as_str()) + // This checks for method calls where the method name is `unwrap` or + // `expect`, and the method is defined in the local crate (to avoid + // false positives from external crates). + if let ExprKind::MethodCall(_, _, _, _) = &expression.kind + && let Some(def_id) = context + .typeck_results() + .type_dependent_def_id(expression.hir_id) { - context.span_lint( - SECURITY_PANIC_USAGE, - expression.span, - |diagnostic: &mut Diag<'_, ()>| { - diagnostic.primary_message(format!( - "Call to panic backend `{kind:?}` detected." - )); - }, - ); - return; + if context.tcx.is_diagnostic_item(sym::unwrap, def_id) + || context.tcx.is_diagnostic_item(sym::option_unwrap, def_id) + || context.tcx.is_diagnostic_item(sym::except, def_id) + || context.tcx.is_diagnostic_item(sym::option_expect, def_id) + { + context.span_lint( + SECURITY_PANIC_USAGE, + expression.span, + |diagnostic: &mut Diag<'_, ()>| { + diagnostic.primary_message( + "Call to panic backend `unwrap/expect` detected.", + ); + }, + ); + } } // Detect calls to panic-related functions in the standard library. diff --git a/rules/panic_usage/ui/main.stderr b/rules/panic_usage/ui/main.stderr index 87ea6c2..5675c85 100644 --- a/rules/panic_usage/ui/main.stderr +++ b/rules/panic_usage/ui/main.stderr @@ -1,4 +1,4 @@ -warning: Call to panic backend `Unwrap` detected. +warning: Call to panic backend `unwrap/expect` detected. --> $DIR/main.rs:10:5 | LL | x.unwrap(); // Should trigger. @@ -10,7 +10,7 @@ note: the lint level is defined here LL | #![warn(security_panic_usage)] | ^^^^^^^^^^^^^^^^^^^^ -warning: Call to panic backend `Expect` detected. +warning: Call to panic backend `unwrap/expect` detected. --> $DIR/main.rs:11:5 | LL | x.expect(""); // should trigger.