]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_lint/src/let_underscore.rs
Add `let_underscore_must_use` lint.
[rust.git] / compiler / rustc_lint / src / let_underscore.rs
1 use crate::{LateContext, LateLintPass, LintContext};
2 use rustc_hir as hir;
3 use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
4 use rustc_span::Symbol;
5
6 declare_lint! {
7     /// The `let_underscore_drop` lint checks for statements which don't bind
8     /// an expression which has a non-trivial Drop implementation to anything,
9     /// causing the expression to be dropped immediately instead of at end of
10     /// scope.
11     ///
12     /// ### Example
13     /// ```rust
14     /// struct SomeStruct;
15     /// impl Drop for SomeStruct {
16     ///     fn drop(&mut self) {
17     ///         println!("Dropping SomeStruct");
18     ///     }
19     /// }
20     ///
21     /// fn main() {
22     ///     // SomeStuct is dropped immediately instead of at end of scope,
23     ///     // so "Dropping SomeStruct" is printed before "end of main".
24     ///     // The order of prints would be reversed if SomeStruct was bound to
25     ///     // a name (such as "_foo").
26     ///     let _ = SomeStruct;
27     ///     println!("end of main");
28     /// }
29     /// ```
30     /// ### Explanation
31     ///
32     /// Statements which assign an expression to an underscore causes the
33     /// expression to immediately drop instead of extending the expression's
34     /// lifetime to the end of the scope. This is usually unintended,
35     /// especially for types like `MutexGuard`, which are typically used to
36     /// lock a mutex for the duration of an entire scope.
37     ///
38     /// If you want to extend the expression's lifetime to the end of the scope,
39     /// assign an underscore-prefixed name (such as `_foo`) to the expression.
40     /// If you do actually want to drop the expression immediately, then
41     /// calling `std::mem::drop` on the expression is clearer and helps convey
42     /// intent.
43     pub LET_UNDERSCORE_DROP,
44     Warn,
45     "non-binding let on a type that implements `Drop`"
46 }
47
48 declare_lint! {
49     /// The `let_underscore_lock` lint checks for statements which don't bind
50     /// a mutex to anything, causing the lock to be released immediately instead
51     /// of at end of scope, which is typically incorrect.
52     ///
53     /// ### Example
54     /// ```rust
55     /// use std::sync::{Arc, Mutex};
56     /// use std::thread;
57     /// let data = Arc::new(Mutex::new(0));
58     ///
59     /// thread::spawn(move || {
60     ///     // The lock is immediately released instead of at the end of the
61     ///     // scope, which is probably not intended.
62     ///     let _ = data.lock().unwrap();
63     ///     println!("doing some work");
64     ///     let mut lock = data.lock().unwrap();
65     ///     *lock += 1;
66     /// });
67     /// ```
68     /// ### Explanation
69     ///
70     /// Statements which assign an expression to an underscore causes the
71     /// expression to immediately drop instead of extending the expression's
72     /// lifetime to the end of the scope. This is usually unintended,
73     /// especially for types like `MutexGuard`, which are typically used to
74     /// lock a mutex for the duration of an entire scope.
75     ///
76     /// If you want to extend the expression's lifetime to the end of the scope,
77     /// assign an underscore-prefixed name (such as `_foo`) to the expression.
78     /// If you do actually want to drop the expression immediately, then
79     /// calling `std::mem::drop` on the expression is clearer and helps convey
80     /// intent.
81     pub LET_UNDERSCORE_LOCK,
82     Warn,
83     "non-binding let on a synchronization lock"
84 }
85
86 declare_lint! {
87     /// The `let_underscore_must_use` lint checks for statements which don't bind
88     /// a `must_use` expression to anything, causing the lock to be released
89     /// immediately instead of at end of scope, which is typically incorrect.
90     ///
91     /// ### Example
92     /// ```rust
93     /// #[must_use]
94     /// struct SomeStruct;
95     ///
96     /// fn main() {
97     ///     // SomeStuct is dropped immediately instead of at end of scope.
98     ///     let _ = SomeStruct;
99     /// }
100     /// ```
101     /// ### Explanation
102     ///
103     /// Statements which assign an expression to an underscore causes the
104     /// expression to immediately drop. Usually, it's better to explicitly handle
105     /// the `must_use` expression.
106     pub LET_UNDERSCORE_MUST_USE,
107     Warn,
108     "non-binding let on a expression marked `must_use`"
109 }
110
111 declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_MUST_USE]);
112
113 const SYNC_GUARD_PATHS: [&[&str]; 5] = [
114     &["std", "sync", "mutex", "MutexGuard"],
115     &["std", "sync", "rwlock", "RwLockReadGuard"],
116     &["std", "sync", "rwlock", "RwLockWriteGuard"],
117     &["parking_lot", "raw_mutex", "RawMutex"],
118     &["parking_lot", "raw_rwlock", "RawRwLock"],
119 ];
120
121 impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
122     fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) {
123         if !matches!(local.pat.kind, hir::PatKind::Wild) {
124             return;
125         }
126         if let Some(init) = local.init {
127             let init_ty = cx.typeck_results().expr_ty(init);
128             let needs_drop = init_ty.needs_drop(cx.tcx, cx.param_env);
129             let is_sync_lock = init_ty.walk().any(|inner| match inner.unpack() {
130                 GenericArgKind::Type(inner_ty) => {
131                     SYNC_GUARD_PATHS.iter().any(|guard_path| match inner_ty.kind() {
132                         ty::Adt(adt, _) => {
133                             let ty_path = cx.get_def_path(adt.did());
134                             guard_path.iter().map(|x| Symbol::intern(x)).eq(ty_path.iter().copied())
135                         }
136                         _ => false,
137                     })
138                 }
139
140                 GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
141             });
142             let is_must_use_ty = is_must_use_ty(cx, cx.typeck_results().expr_ty(init));
143             let is_must_use_func_call = is_must_use_func_call(cx, init);
144             if is_sync_lock {
145                 cx.struct_span_lint(LET_UNDERSCORE_LOCK, local.span, |lint| {
146                     lint.build("non-binding let on a synchronization lock")
147                         .help("consider binding to an unused variable")
148                         .help("consider explicitly droping with `std::mem::drop`")
149                         .emit();
150                 })
151             } else if is_must_use_ty || is_must_use_func_call {
152                 cx.struct_span_lint(LET_UNDERSCORE_MUST_USE, local.span, |lint| {
153                     lint.build("non-binding let on a expression marked `must_use`")
154                         .help("consider binding to an unused variable")
155                         .help("consider explicitly droping with `std::mem::drop`")
156                         .emit();
157                 })
158             } else if needs_drop {
159                 cx.struct_span_lint(LET_UNDERSCORE_DROP, local.span, |lint| {
160                     lint.build("non-binding let on a type that implements `Drop`")
161                         .help("consider binding to an unused variable")
162                         .help("consider explicitly droping with `std::mem::drop`")
163                         .emit();
164                 })
165             }
166         }
167
168         fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
169             match ty.kind() {
170                 ty::Adt(adt, _) => has_must_use_attr(cx, adt.did()),
171                 ty::Foreign(ref did) => has_must_use_attr(cx, *did),
172                 ty::Slice(ty)
173                 | ty::Array(ty, _)
174                 | ty::RawPtr(ty::TypeAndMut { ty, .. })
175                 | ty::Ref(_, ty, _) => {
176                     // for the Array case we don't need to care for the len == 0 case
177                     // because we don't want to lint functions returning empty arrays
178                     is_must_use_ty(cx, *ty)
179                 }
180                 ty::Tuple(substs) => substs.iter().any(|ty| is_must_use_ty(cx, ty)),
181                 ty::Opaque(ref def_id, _) => {
182                     for (predicate, _) in cx.tcx.explicit_item_bounds(*def_id) {
183                         if let ty::PredicateKind::Trait(trait_predicate) =
184                             predicate.kind().skip_binder()
185                         {
186                             if has_must_use_attr(cx, trait_predicate.trait_ref.def_id) {
187                                 return true;
188                             }
189                         }
190                     }
191                     false
192                 }
193                 ty::Dynamic(binder, _) => {
194                     for predicate in binder.iter() {
195                         if let ty::ExistentialPredicate::Trait(ref trait_ref) =
196                             predicate.skip_binder()
197                         {
198                             if has_must_use_attr(cx, trait_ref.def_id) {
199                                 return true;
200                             }
201                         }
202                     }
203                     false
204                 }
205                 _ => false,
206             }
207         }
208
209         // check if expr is calling method or function with #[must_use] attribute
210         fn is_must_use_func_call(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
211             let did = match expr.kind {
212                 hir::ExprKind::Call(path, _) if let hir::ExprKind::Path(ref qpath) = path.kind => {
213                     if let hir::def::Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id) {
214                         Some(did)
215                     } else {
216                         None
217                     }
218                 },
219                 hir::ExprKind::MethodCall(..) => {
220                     cx.typeck_results().type_dependent_def_id(expr.hir_id)
221                 }
222                 _ => None,
223             };
224
225             did.map_or(false, |did| has_must_use_attr(cx, did))
226         }
227
228         // returns true if DefId contains a `#[must_use]` attribute
229         fn has_must_use_attr(cx: &LateContext<'_>, did: hir::def_id::DefId) -> bool {
230             cx.tcx
231                 .get_attrs(did, rustc_span::sym::must_use)
232                 .find(|a| a.has_name(rustc_span::sym::must_use))
233                 .is_some()
234         }
235     }
236 }