1 use crate::{LateContext, LateLintPass, LintContext};
3 use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
4 use rustc_span::Symbol;
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
14 /// struct SomeStruct;
15 /// impl Drop for SomeStruct {
16 /// fn drop(&mut self) {
17 /// println!("Dropping SomeStruct");
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");
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.
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
43 pub LET_UNDERSCORE_DROP,
45 "non-binding let on a type that implements `Drop`"
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.
55 /// use std::sync::{Arc, Mutex};
57 /// let data = Arc::new(Mutex::new(0));
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();
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.
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
81 pub LET_UNDERSCORE_LOCK,
83 "non-binding let on a synchronization lock"
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.
94 /// struct SomeStruct;
97 /// // SomeStuct is dropped immediately instead of at end of scope.
98 /// let _ = SomeStruct;
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,
108 "non-binding let on a expression marked `must_use`"
111 declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_MUST_USE]);
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"],
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) {
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() {
133 let ty_path = cx.get_def_path(adt.did());
134 guard_path.iter().map(|x| Symbol::intern(x)).eq(ty_path.iter().copied())
140 GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
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);
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`")
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`")
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`")
168 fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
170 ty::Adt(adt, _) => has_must_use_attr(cx, adt.did()),
171 ty::Foreign(ref did) => has_must_use_attr(cx, *did),
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)
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()
186 if has_must_use_attr(cx, trait_predicate.trait_ref.def_id) {
193 ty::Dynamic(binder, _) => {
194 for predicate in binder.iter() {
195 if let ty::ExistentialPredicate::Trait(ref trait_ref) =
196 predicate.skip_binder()
198 if has_must_use_attr(cx, trait_ref.def_id) {
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) {
219 hir::ExprKind::MethodCall(..) => {
220 cx.typeck_results().type_dependent_def_id(expr.hir_id)
225 did.map_or(false, |did| has_must_use_attr(cx, did))
228 // returns true if DefId contains a `#[must_use]` attribute
229 fn has_must_use_attr(cx: &LateContext<'_>, did: hir::def_id::DefId) -> bool {
231 .get_attrs(did, rustc_span::sym::must_use)
232 .find(|a| a.has_name(rustc_span::sym::must_use))