1use clippy_utils::diagnostics::span_lint_and_then;
2use clippy_utils::ty::{implements_trait, is_must_use_ty, match_type};
3use clippy_utils::{is_from_proc_macro, is_must_use_func_call, paths};
4use rustc_hir::{LetStmt, LocalSource, PatKind};
5use rustc_lint::{LateContext, LateLintPass};
6use rustc_middle::ty::{GenericArgKind, IsSuggestable};
7use rustc_session::declare_lint_pass;
8use rustc_span::{BytePos, Span};
9
10declare_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
33declare_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
61declare_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
92declare_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
130declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_FUTURE, LET_UNDERSCORE_UNTYPED]);
131
132const 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
138impl<'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

Provided by KDAB

Privacy Policy
Learn Rust with the experts
Find out more