Skip to content

Commit 478b5b4

Browse files
committed
Improve let_underscore_lock
- lint if the lock was in a nested pattern - lint if the lock is inside a `Result<Lock, _>`
1 parent 5cb2e7d commit 478b5b4

File tree

6 files changed

+137
-35
lines changed

6 files changed

+137
-35
lines changed

compiler/rustc_lint/messages.ftl

+3
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,9 @@ lint_multiple_supertrait_upcastable = `{$ident}` is object-safe and has multiple
345345
lint_node_source = `forbid` level set here
346346
.note = {$reason}
347347
348+
lint_non_binding_let_multi_drop_fn =
349+
consider immediately dropping the value using `drop(..)` after the `let` statement
350+
348351
lint_non_binding_let_multi_suggestion =
349352
consider immediately dropping the value
350353

compiler/rustc_lint/src/let_underscore.rs

+38-19
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
use rustc_errors::MultiSpan;
66
use rustc_hir as hir;
77
use rustc_middle::ty;
8-
use rustc_span::Symbol;
8+
use rustc_span::{sym, Symbol};
99

1010
declare_lint! {
1111
/// The `let_underscore_drop` lint checks for statements which don't bind
@@ -105,46 +105,65 @@ const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [
105105

106106
impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
107107
fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) {
108-
if !matches!(local.pat.kind, hir::PatKind::Wild) {
109-
return;
110-
}
111-
if let Some(init) = local.init {
112-
let init_ty = cx.typeck_results().expr_ty(init);
108+
let mut top_level = true;
109+
110+
// We recursively walk through all patterns, so that we can catch cases where the lock is nested in a pattern.
111+
// For the basic `let_underscore_drop` lint, we only look at the top level, since there are many legitimate reasons
112+
// to bind a sub-pattern to an `_`, if we're only interested in the rest.
113+
// But with locks, we prefer having the chance of "false positives" over missing cases, since the effects can be
114+
// quite catastrophic.
115+
local.pat.walk_always(|pat| {
116+
let is_top_level = top_level;
117+
top_level = false;
118+
119+
if !matches!(pat.kind, hir::PatKind::Wild) {
120+
return;
121+
}
122+
123+
let ty = cx.typeck_results().pat_ty(pat);
124+
113125
// If the type has a trivial Drop implementation, then it doesn't
114126
// matter that we drop the value immediately.
115-
if !init_ty.needs_drop(cx.tcx, cx.param_env) {
127+
if !ty.needs_drop(cx.tcx, cx.param_env) {
116128
return;
117129
}
118-
let is_sync_lock = match init_ty.kind() {
130+
// Lint for patterns like `mutex.lock()`, which returns `Result<MutexGuard, _>` as well.
131+
let potential_lock_type = match ty.kind() {
132+
ty::Adt(adt, args) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => {
133+
args.type_at(0)
134+
}
135+
_ => ty,
136+
};
137+
let is_sync_lock = match potential_lock_type.kind() {
119138
ty::Adt(adt, _) => SYNC_GUARD_SYMBOLS
120139
.iter()
121140
.any(|guard_symbol| cx.tcx.is_diagnostic_item(*guard_symbol, adt.did())),
122141
_ => false,
123142
};
124143

144+
let can_use_init = is_top_level.then_some(local.init).flatten();
145+
125146
let sub = NonBindingLetSub {
126-
suggestion: local.pat.span,
127-
multi_suggestion_start: local.span.until(init.span),
128-
multi_suggestion_end: init.span.shrink_to_hi(),
147+
suggestion: pat.span,
148+
// We can't suggest `drop()` when we're on the top level.
149+
drop_fn_start_end: can_use_init
150+
.map(|init| (local.span.until(init.span), init.span.shrink_to_hi())),
129151
};
130152
if is_sync_lock {
131-
let mut span = MultiSpan::from_spans(vec![local.pat.span, init.span]);
153+
let mut span = MultiSpan::from_span(pat.span);
132154
span.push_span_label(
133-
local.pat.span,
155+
pat.span,
134156
"this lock is not assigned to a binding and is immediately dropped".to_string(),
135157
);
136-
span.push_span_label(
137-
init.span,
138-
"this binding will immediately drop the value assigned to it".to_string(),
139-
);
140158
cx.emit_spanned_lint(LET_UNDERSCORE_LOCK, span, NonBindingLet::SyncLock { sub });
141-
} else {
159+
// Only emit let_underscore_drop for top-level `_` patterns.
160+
} else if can_use_init.is_some() {
142161
cx.emit_spanned_lint(
143162
LET_UNDERSCORE_DROP,
144163
local.span,
145164
NonBindingLet::DropType { sub },
146165
);
147166
}
148-
}
167+
});
149168
}
150169
}

compiler/rustc_lint/src/lints.rs

+13-10
Original file line numberDiff line numberDiff line change
@@ -930,8 +930,7 @@ pub enum NonBindingLet {
930930

931931
pub struct NonBindingLetSub {
932932
pub suggestion: Span,
933-
pub multi_suggestion_start: Span,
934-
pub multi_suggestion_end: Span,
933+
pub drop_fn_start_end: Option<(Span, Span)>,
935934
}
936935

937936
impl AddToDiagnostic for NonBindingLetSub {
@@ -945,14 +944,18 @@ impl AddToDiagnostic for NonBindingLetSub {
945944
"_unused",
946945
Applicability::MachineApplicable,
947946
);
948-
diag.multipart_suggestion(
949-
fluent::lint_non_binding_let_multi_suggestion,
950-
vec![
951-
(self.multi_suggestion_start, "drop(".to_string()),
952-
(self.multi_suggestion_end, ")".to_string()),
953-
],
954-
Applicability::MachineApplicable,
955-
);
947+
if let Some(drop_fn_start_end) = self.drop_fn_start_end {
948+
diag.multipart_suggestion(
949+
fluent::lint_non_binding_let_multi_suggestion,
950+
vec![
951+
(drop_fn_start_end.0, "drop(".to_string()),
952+
(drop_fn_start_end.1, ")".to_string()),
953+
],
954+
Applicability::MachineApplicable,
955+
);
956+
} else {
957+
diag.help(fluent::lint_non_binding_let_multi_drop_fn);
958+
}
956959
}
957960
}
958961

tests/ui/lint/let_underscore/let_underscore_drop.rs

+2
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ impl Drop for NontrivialDrop {
1111

1212
fn main() {
1313
let _ = NontrivialDrop; //~WARNING non-binding let on a type that implements `Drop`
14+
15+
let (_, _) = (NontrivialDrop, NontrivialDrop); // This should be ignored.
1416
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1-
// check-fail
21
use std::sync::{Arc, Mutex};
32

3+
struct Struct<T> {
4+
a: T,
5+
}
6+
47
fn main() {
58
let data = Arc::new(Mutex::new(0));
69
let _ = data.lock().unwrap(); //~ERROR non-binding let on a synchronization lock
10+
11+
let _ = data.lock(); //~ERROR non-binding let on a synchronization lock
12+
13+
let (_, _) = (data.lock(), 1); //~ERROR non-binding let on a synchronization lock
14+
15+
let (_a, Struct { a: _ }) = (0, Struct { a: data.lock() }); //~ERROR non-binding let on a synchronization lock
16+
17+
(_ , _) = (data.lock(), 1); //~ERROR non-binding let on a synchronization lock
18+
19+
let _b;
20+
(_b, Struct { a: _ }) = (0, Struct { a: data.lock() }); //~ERROR non-binding let on a synchronization lock
721
}
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
error: non-binding let on a synchronization lock
2-
--> $DIR/let_underscore_lock.rs:6:9
2+
--> $DIR/let_underscore_lock.rs:9:9
33
|
44
LL | let _ = data.lock().unwrap();
5-
| ^ ^^^^^^^^^^^^^^^^^^^^ this binding will immediately drop the value assigned to it
6-
| |
7-
| this lock is not assigned to a binding and is immediately dropped
5+
| ^ this lock is not assigned to a binding and is immediately dropped
86
|
97
= note: `#[deny(let_underscore_lock)]` on by default
108
help: consider binding to an unused variable to avoid immediately dropping the value
@@ -16,5 +14,68 @@ help: consider immediately dropping the value
1614
LL | drop(data.lock().unwrap());
1715
| ~~~~~ +
1816

19-
error: aborting due to 1 previous error
17+
error: non-binding let on a synchronization lock
18+
--> $DIR/let_underscore_lock.rs:11:9
19+
|
20+
LL | let _ = data.lock();
21+
| ^ this lock is not assigned to a binding and is immediately dropped
22+
|
23+
help: consider binding to an unused variable to avoid immediately dropping the value
24+
|
25+
LL | let _unused = data.lock();
26+
| ~~~~~~~
27+
help: consider immediately dropping the value
28+
|
29+
LL | drop(data.lock());
30+
| ~~~~~ +
31+
32+
error: non-binding let on a synchronization lock
33+
--> $DIR/let_underscore_lock.rs:13:10
34+
|
35+
LL | let (_, _) = (data.lock(), 1);
36+
| ^ this lock is not assigned to a binding and is immediately dropped
37+
|
38+
= help: consider immediately dropping the value using `drop(..)` after the `let` statement
39+
help: consider binding to an unused variable to avoid immediately dropping the value
40+
|
41+
LL | let (_unused, _) = (data.lock(), 1);
42+
| ~~~~~~~
43+
44+
error: non-binding let on a synchronization lock
45+
--> $DIR/let_underscore_lock.rs:15:26
46+
|
47+
LL | let (_a, Struct { a: _ }) = (0, Struct { a: data.lock() });
48+
| ^ this lock is not assigned to a binding and is immediately dropped
49+
|
50+
= help: consider immediately dropping the value using `drop(..)` after the `let` statement
51+
help: consider binding to an unused variable to avoid immediately dropping the value
52+
|
53+
LL | let (_a, Struct { a: _unused }) = (0, Struct { a: data.lock() });
54+
| ~~~~~~~
55+
56+
error: non-binding let on a synchronization lock
57+
--> $DIR/let_underscore_lock.rs:17:6
58+
|
59+
LL | (_ , _) = (data.lock(), 1);
60+
| ^ this lock is not assigned to a binding and is immediately dropped
61+
|
62+
= help: consider immediately dropping the value using `drop(..)` after the `let` statement
63+
help: consider binding to an unused variable to avoid immediately dropping the value
64+
|
65+
LL | (_unused , _) = (data.lock(), 1);
66+
| ~~~~~~~
67+
68+
error: non-binding let on a synchronization lock
69+
--> $DIR/let_underscore_lock.rs:20:22
70+
|
71+
LL | (_b, Struct { a: _ }) = (0, Struct { a: data.lock() });
72+
| ^ this lock is not assigned to a binding and is immediately dropped
73+
|
74+
= help: consider immediately dropping the value using `drop(..)` after the `let` statement
75+
help: consider binding to an unused variable to avoid immediately dropping the value
76+
|
77+
LL | (_b, Struct { a: _unused }) = (0, Struct { a: data.lock() });
78+
| ~~~~~~~
79+
80+
error: aborting due to 6 previous errors
2081

0 commit comments

Comments
 (0)