1 | use clippy_utils::diagnostics::span_lint_and_then; |
---|---|
2 | use clippy_utils::ty::{implements_trait, is_must_use_ty, match_type}; |
3 | use clippy_utils::{is_from_proc_macro, is_must_use_func_call, paths}; |
4 | use rustc_hir::{LetStmt, LocalSource, PatKind}; |
5 | use rustc_lint::{LateContext, LateLintPass}; |
6 | use rustc_middle::ty::{GenericArgKind, IsSuggestable}; |
7 | use rustc_session::declare_lint_pass; |
8 | use rustc_span::{BytePos, Span}; |
9 | |
10 | declare_clippy_lint! { |
11 | /// ### What it does |
12 | /// Checks for `let _ = <expr>` where expr is `#[must_use]` |
13 | /// |
14 | /// ### Why restrict this? |
15 | /// To ensure that all `#[must_use]` types are used rather than ignored. |
16 | /// |
17 | /// ### Example |
18 | /// ```no_run |
19 | /// fn f() -> Result<u32, u32> { |
20 | /// Ok(0) |
21 | /// } |
22 | /// |
23 | /// let _ = f(); |
24 | /// // is_ok() is marked #[must_use] |
25 | /// let _ = f().is_ok(); |
26 | /// ``` |
27 | #[clippy::version = "1.42.0"] |
28 | pub LET_UNDERSCORE_MUST_USE, |
29 | restriction, |
30 | "non-binding `let` on a `#[must_use]` expression" |
31 | } |
32 | |
33 | declare_clippy_lint! { |
34 | /// ### What it does |
35 | /// Checks for `let _ = sync_lock`. This supports `mutex` and `rwlock` in |
36 | /// `parking_lot`. For `std` locks see the `rustc` lint |
37 | /// [`let_underscore_lock`](https://doc.rust-lang.org/nightly/rustc/lints/listing/deny-by-default.html#let-underscore-lock) |
38 | /// |
39 | /// ### Why is this bad? |
40 | /// This statement immediately drops the lock instead of |
41 | /// extending its lifetime to the end of the scope, which is often not intended. |
42 | /// To extend lock lifetime to the end of the scope, use an underscore-prefixed |
43 | /// name instead (i.e. _lock). If you want to explicitly drop the lock, |
44 | /// `std::mem::drop` conveys your intention better and is less error-prone. |
45 | /// |
46 | /// ### Example |
47 | /// ```rust,ignore |
48 | /// let _ = mutex.lock(); |
49 | /// ``` |
50 | /// |
51 | /// Use instead: |
52 | /// ```rust,ignore |
53 | /// let _lock = mutex.lock(); |
54 | /// ``` |
55 | #[clippy::version = "1.43.0"] |
56 | pub LET_UNDERSCORE_LOCK, |
57 | correctness, |
58 | "non-binding `let` on a synchronization lock" |
59 | } |
60 | |
61 | declare_clippy_lint! { |
62 | /// ### What it does |
63 | /// Checks for `let _ = <expr>` where the resulting type of expr implements `Future` |
64 | /// |
65 | /// ### Why is this bad? |
66 | /// Futures must be polled for work to be done. The original intention was most likely to await the future |
67 | /// and ignore the resulting value. |
68 | /// |
69 | /// ### Example |
70 | /// ```no_run |
71 | /// async fn foo() -> Result<(), ()> { |
72 | /// Ok(()) |
73 | /// } |
74 | /// let _ = foo(); |
75 | /// ``` |
76 | /// |
77 | /// Use instead: |
78 | /// ```no_run |
79 | /// # async fn context() { |
80 | /// async fn foo() -> Result<(), ()> { |
81 | /// Ok(()) |
82 | /// } |
83 | /// let _ = foo().await; |
84 | /// # } |
85 | /// ``` |
86 | #[clippy::version = "1.67.0"] |
87 | pub LET_UNDERSCORE_FUTURE, |
88 | suspicious, |
89 | "non-binding `let` on a future" |
90 | } |
91 | |
92 | declare_clippy_lint! { |
93 | /// ### What it does |
94 | /// Checks for `let _ = <expr>` without a type annotation, and suggests to either provide one, |
95 | /// or remove the `let` keyword altogether. |
96 | /// |
97 | /// ### Why restrict this? |
98 | /// The `let _ = <expr>` expression ignores the value of `<expr>`, but will continue to do so even |
99 | /// if the type were to change, thus potentially introducing subtle bugs. By supplying a type |
100 | /// annotation, one will be forced to re-visit the decision to ignore the value in such cases. |
101 | /// |
102 | /// ### Known problems |
103 | /// The `_ = <expr>` is not properly supported by some tools (e.g. IntelliJ) and may seem odd |
104 | /// to many developers. This lint also partially overlaps with the other `let_underscore_*` |
105 | /// lints. |
106 | /// |
107 | /// ### Example |
108 | /// ```no_run |
109 | /// fn foo() -> Result<u32, ()> { |
110 | /// Ok(123) |
111 | /// } |
112 | /// let _ = foo(); |
113 | /// ``` |
114 | /// Use instead: |
115 | /// ```no_run |
116 | /// fn foo() -> Result<u32, ()> { |
117 | /// Ok(123) |
118 | /// } |
119 | /// // Either provide a type annotation: |
120 | /// let _: Result<u32, ()> = foo(); |
121 | /// // …or drop the let keyword: |
122 | /// _ = foo(); |
123 | /// ``` |
124 | #[clippy::version = "1.69.0"] |
125 | pub LET_UNDERSCORE_UNTYPED, |
126 | restriction, |
127 | "non-binding `let` without a type annotation" |
128 | } |
129 | |
130 | declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_FUTURE, LET_UNDERSCORE_UNTYPED]); |
131 | |
132 | const SYNC_GUARD_PATHS: [&[&str]; 3] = [ |
133 | &paths::PARKING_LOT_MUTEX_GUARD, |
134 | &paths::PARKING_LOT_RWLOCK_READ_GUARD, |
135 | &paths::PARKING_LOT_RWLOCK_WRITE_GUARD, |
136 | ]; |
137 | |
138 | impl<'tcx> LateLintPass<'tcx> for LetUnderscore { |
139 | fn check_local(&mut self, cx: &LateContext<'tcx>, local: &LetStmt<'tcx>) { |
140 | if matches!(local.source, LocalSource::Normal) |
141 | && let PatKind::Wild = local.pat.kind |
142 | && let Some(init) = local.init |
143 | && !local.span.in_external_macro(cx.tcx.sess.source_map()) |
144 | { |
145 | let init_ty = cx.typeck_results().expr_ty(init); |
146 | let contains_sync_guard = init_ty.walk().any(|inner| match inner.unpack() { |
147 | GenericArgKind::Type(inner_ty) => SYNC_GUARD_PATHS.iter().any(|path| match_type(cx, inner_ty, path)), |
148 | GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false, |
149 | }); |
150 | if contains_sync_guard { |
151 | #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] |
152 | span_lint_and_then( |
153 | cx, |
154 | LET_UNDERSCORE_LOCK, |
155 | local.span, |
156 | "non-binding `let` on a synchronization lock", |
157 | |diag| { |
158 | diag.help( |
159 | "consider using an underscore-prefixed named \ |
160 | binding or dropping explicitly with `std::mem::drop`", |
161 | ); |
162 | }, |
163 | ); |
164 | } else if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() |
165 | && implements_trait(cx, cx.typeck_results().expr_ty(init), future_trait_def_id, &[]) |
166 | { |
167 | #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] |
168 | span_lint_and_then( |
169 | cx, |
170 | LET_UNDERSCORE_FUTURE, |
171 | local.span, |
172 | "non-binding `let` on a future", |
173 | |diag| { |
174 | diag.help("consider awaiting the future or dropping explicitly with `std::mem::drop`"); |
175 | }, |
176 | ); |
177 | } else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init)) { |
178 | #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] |
179 | span_lint_and_then( |
180 | cx, |
181 | LET_UNDERSCORE_MUST_USE, |
182 | local.span, |
183 | "non-binding `let` on an expression with `#[must_use]` type", |
184 | |diag| { |
185 | diag.help("consider explicitly using expression value"); |
186 | }, |
187 | ); |
188 | } else if is_must_use_func_call(cx, init) { |
189 | #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] |
190 | span_lint_and_then( |
191 | cx, |
192 | LET_UNDERSCORE_MUST_USE, |
193 | local.span, |
194 | "non-binding `let` on a result of a `#[must_use]` function", |
195 | |diag| { |
196 | diag.help("consider explicitly using function result"); |
197 | }, |
198 | ); |
199 | } |
200 | |
201 | if local.pat.default_binding_modes && local.ty.is_none() { |
202 | // When `default_binding_modes` is true, the `let` keyword is present. |
203 | |
204 | // Ignore unnameable types |
205 | if let Some(init) = local.init |
206 | && !cx.typeck_results().expr_ty(init).is_suggestable(cx.tcx, true) |
207 | { |
208 | return; |
209 | } |
210 | |
211 | // Ignore if it is from a procedural macro... |
212 | if is_from_proc_macro(cx, init) { |
213 | return; |
214 | } |
215 | |
216 | span_lint_and_then( |
217 | cx, |
218 | LET_UNDERSCORE_UNTYPED, |
219 | local.span, |
220 | "non-binding `let` without a type annotation", |
221 | |diag| { |
222 | diag.span_help( |
223 | Span::new( |
224 | local.pat.span.hi(), |
225 | local.pat.span.hi() + BytePos(1), |
226 | local.pat.span.ctxt(), |
227 | local.pat.span.parent(), |
228 | ), |
229 | "consider adding a type annotation", |
230 | ); |
231 | }, |
232 | ); |
233 | } |
234 | } |
235 | } |
236 | } |
237 |
Definitions
Learn Rust with the experts
Find out more