-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uplift the let_underscore
lints from clippy into rustc.
#97739
Merged
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
821b32b
Add `let_underscore_drop` lint.
a2aaron ad7587f
Add `let_underscore_lock` lint.
a2aaron 758a9fd
Add `let_underscore_must_use` lint.
a2aaron 36b6309
Move let_underscore tests to their own subfolder.
a2aaron ae2ac3b
Allow `let_underscore_drop` and `let_underscore_must_use` by default.
a2aaron eba6c78
Show code suggestions in `let_undescore` lint messages.
a2aaron 6b179e3
Set `let_underscore_lock` to Deny by default.
a2aaron a7e2b3e
Move local functions to outer scope.
a2aaron 7e485bf
Move `let_underscore` tests into the `lint` subfolder.
a2aaron 1421cff
Add `let_underscore` lint group to `GROUP_DESCRIPTIONS`.
a2aaron 30e8adb
Use `has_attr` instead of `get_attrs` in `has_must_use_attr`
a2aaron e6b6678
Bail out early if the type does not has a trivial Drop implementation.
a2aaron 6342b58
Use diagnostic items instead of hard coded paths for `let_underscore_…
a2aaron 11663b1
Use `check-pass` instead of `run-pass`
a2aaron b5b5b54
Remove `let_underscore_must_use`
a2aaron 321a598
Add diagnostic items to MutexGuard and RwLock Guards
a2aaron 211feb1
Add `{{produces}}` tag to lint doc comments.
a2aaron cdf6606
Use `multipart_suggestion` to create an applicable suggestion.
a2aaron 7237e86
Reword suggestion messages.
a2aaron b040666
Have the drop code suggestion not include `let _ =`
a2aaron 8807c2d
Make `let_underscore_drop` Deny by default.
a2aaron a9095ff
Re-allow `let_underscore_drop` by default.
a2aaron a9f1b7b
Explain why let-underscoring a lock guard is incorrect.
a2aaron d355ec9
Fix imports.
a2aaron 76c90c3
Use `#![warn(let_underscore_drop)]` in tests.
a2aaron File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
use crate::{LateContext, LateLintPass, LintContext}; | ||
use rustc_errors::{Applicability, LintDiagnosticBuilder, MultiSpan}; | ||
use rustc_hir as hir; | ||
use rustc_middle::ty; | ||
use rustc_span::Symbol; | ||
|
||
declare_lint! { | ||
/// The `let_underscore_drop` lint checks for statements which don't bind | ||
/// an expression which has a non-trivial Drop implementation to anything, | ||
/// causing the expression to be dropped immediately instead of at end of | ||
/// scope. | ||
/// | ||
/// ### Example | ||
/// ``` | ||
/// struct SomeStruct; | ||
/// impl Drop for SomeStruct { | ||
/// fn drop(&mut self) { | ||
/// println!("Dropping SomeStruct"); | ||
/// } | ||
/// } | ||
/// | ||
/// fn main() { | ||
/// #[warn(let_underscore_drop)] | ||
/// // SomeStuct is dropped immediately instead of at end of scope, | ||
/// // so "Dropping SomeStruct" is printed before "end of main". | ||
/// // The order of prints would be reversed if SomeStruct was bound to | ||
/// // a name (such as "_foo"). | ||
/// let _ = SomeStruct; | ||
/// println!("end of main"); | ||
/// } | ||
/// ``` | ||
/// | ||
/// {{produces}} | ||
/// | ||
/// ### Explanation | ||
/// | ||
/// Statements which assign an expression to an underscore causes the | ||
/// expression to immediately drop instead of extending the expression's | ||
/// lifetime to the end of the scope. This is usually unintended, | ||
/// especially for types like `MutexGuard`, which are typically used to | ||
/// lock a mutex for the duration of an entire scope. | ||
/// | ||
/// If you want to extend the expression's lifetime to the end of the scope, | ||
/// assign an underscore-prefixed name (such as `_foo`) to the expression. | ||
/// If you do actually want to drop the expression immediately, then | ||
/// calling `std::mem::drop` on the expression is clearer and helps convey | ||
/// intent. | ||
pub LET_UNDERSCORE_DROP, | ||
Allow, | ||
"non-binding let on a type that implements `Drop`" | ||
} | ||
|
||
declare_lint! { | ||
/// The `let_underscore_lock` lint checks for statements which don't bind | ||
/// a mutex to anything, causing the lock to be released immediately instead | ||
/// of at end of scope, which is typically incorrect. | ||
/// | ||
/// ### Example | ||
/// ```compile_fail | ||
/// use std::sync::{Arc, Mutex}; | ||
/// use std::thread; | ||
/// let data = Arc::new(Mutex::new(0)); | ||
/// | ||
/// thread::spawn(move || { | ||
/// // The lock is immediately released instead of at the end of the | ||
/// // scope, which is probably not intended. | ||
/// let _ = data.lock().unwrap(); | ||
/// println!("doing some work"); | ||
/// let mut lock = data.lock().unwrap(); | ||
/// *lock += 1; | ||
/// }); | ||
/// ``` | ||
/// | ||
/// {{produces}} | ||
/// | ||
/// ### Explanation | ||
/// | ||
/// Statements which assign an expression to an underscore causes the | ||
/// expression to immediately drop instead of extending the expression's | ||
/// lifetime to the end of the scope. This is usually unintended, | ||
/// especially for types like `MutexGuard`, which are typically used to | ||
/// lock a mutex for the duration of an entire scope. | ||
/// | ||
/// If you want to extend the expression's lifetime to the end of the scope, | ||
/// assign an underscore-prefixed name (such as `_foo`) to the expression. | ||
/// If you do actually want to drop the expression immediately, then | ||
/// calling `std::mem::drop` on the expression is clearer and helps convey | ||
/// intent. | ||
pub LET_UNDERSCORE_LOCK, | ||
Deny, | ||
"non-binding let on a synchronization lock" | ||
} | ||
|
||
declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK]); | ||
|
||
const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [ | ||
rustc_span::sym::MutexGuard, | ||
rustc_span::sym::RwLockReadGuard, | ||
rustc_span::sym::RwLockWriteGuard, | ||
]; | ||
|
||
impl<'tcx> LateLintPass<'tcx> for LetUnderscore { | ||
fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) { | ||
if !matches!(local.pat.kind, hir::PatKind::Wild) { | ||
return; | ||
} | ||
if let Some(init) = local.init { | ||
let init_ty = cx.typeck_results().expr_ty(init); | ||
// If the type has a trivial Drop implementation, then it doesn't | ||
// matter that we drop the value immediately. | ||
if !init_ty.needs_drop(cx.tcx, cx.param_env) { | ||
return; | ||
} | ||
let is_sync_lock = match init_ty.kind() { | ||
ty::Adt(adt, _) => SYNC_GUARD_SYMBOLS | ||
.iter() | ||
.any(|guard_symbol| cx.tcx.is_diagnostic_item(*guard_symbol, adt.did())), | ||
Comment on lines
+115
to
+117
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we were ever to introduce 3rd party checks, it would be an extra arm here. |
||
_ => false, | ||
}; | ||
|
||
if is_sync_lock { | ||
let mut span = MultiSpan::from_spans(vec![local.pat.span, init.span]); | ||
span.push_span_label( | ||
local.pat.span, | ||
"this lock is not assigned to a binding and is immediately dropped".to_string(), | ||
); | ||
span.push_span_label( | ||
init.span, | ||
"this binding will immediately drop the value assigned to it".to_string(), | ||
); | ||
cx.struct_span_lint(LET_UNDERSCORE_LOCK, span, |lint| { | ||
build_and_emit_lint( | ||
lint, | ||
local, | ||
init.span, | ||
"non-binding let on a synchronization lock", | ||
) | ||
}) | ||
} else { | ||
cx.struct_span_lint(LET_UNDERSCORE_DROP, local.span, |lint| { | ||
build_and_emit_lint( | ||
lint, | ||
local, | ||
init.span, | ||
"non-binding let on a type that implements `Drop`", | ||
); | ||
}) | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn build_and_emit_lint( | ||
lint: LintDiagnosticBuilder<'_, ()>, | ||
local: &hir::Local<'_>, | ||
init_span: rustc_span::Span, | ||
msg: &str, | ||
) { | ||
lint.build(msg) | ||
.span_suggestion_verbose( | ||
local.pat.span, | ||
"consider binding to an unused variable to avoid immediately dropping the value", | ||
"_unused", | ||
Applicability::MachineApplicable, | ||
) | ||
.multipart_suggestion( | ||
"consider immediately dropping the value", | ||
vec![ | ||
(local.span.until(init_span), "drop(".to_string()), | ||
(init_span.shrink_to_hi(), ")".to_string()), | ||
], | ||
Applicability::MachineApplicable, | ||
) | ||
.emit(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// check-pass | ||
#![warn(let_underscore_drop)] | ||
|
||
struct NontrivialDrop; | ||
|
||
impl Drop for NontrivialDrop { | ||
fn drop(&mut self) { | ||
println!("Dropping!"); | ||
} | ||
} | ||
|
||
fn main() { | ||
let _ = NontrivialDrop; //~WARNING non-binding let on a type that implements `Drop` | ||
} |
22 changes: 22 additions & 0 deletions
22
src/test/ui/lint/let_underscore/let_underscore_drop.stderr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
warning: non-binding let on a type that implements `Drop` | ||
--> $DIR/let_underscore_drop.rs:13:5 | ||
| | ||
LL | let _ = NontrivialDrop; | ||
| ^^^^^^^^^^^^^^^^^^^^^^^ | ||
| | ||
note: the lint level is defined here | ||
--> $DIR/let_underscore_drop.rs:2:9 | ||
| | ||
LL | #![warn(let_underscore_drop)] | ||
| ^^^^^^^^^^^^^^^^^^^ | ||
help: consider binding to an unused variable to avoid immediately dropping the value | ||
| | ||
LL | let _unused = NontrivialDrop; | ||
| ~~~~~~~ | ||
help: consider immediately dropping the value | ||
| | ||
LL | drop(NontrivialDrop); | ||
| ~~~~~ + | ||
|
||
warning: 1 warning emitted | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// check-fail | ||
use std::sync::{Arc, Mutex}; | ||
|
||
fn main() { | ||
let data = Arc::new(Mutex::new(0)); | ||
let _ = data.lock().unwrap(); //~ERROR non-binding let on a synchronization lock | ||
} |
20 changes: 20 additions & 0 deletions
20
src/test/ui/lint/let_underscore/let_underscore_lock.stderr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
error: non-binding let on a synchronization lock | ||
--> $DIR/let_underscore_lock.rs:6:9 | ||
| | ||
LL | let _ = data.lock().unwrap(); | ||
| ^ ^^^^^^^^^^^^^^^^^^^^ this binding will immediately drop the value assigned to it | ||
| | | ||
| this lock is not assigned to a binding and is immediately dropped | ||
| | ||
= note: `#[deny(let_underscore_lock)]` on by default | ||
help: consider binding to an unused variable to avoid immediately dropping the value | ||
| | ||
LL | let _unused = data.lock().unwrap(); | ||
| ~~~~~~~ | ||
help: consider immediately dropping the value | ||
| | ||
LL | drop(data.lock().unwrap()); | ||
| ~~~~~ + | ||
|
||
error: aborting due to previous error | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! Can you push a new commit with this being
Deny
? That way we can do a crater run to see how bad this heuristic would actually be in the wild. Depending on the results of that run, we'd make this eitherWarn
orAllow
, as you have here.@joshtriplett this is necessary to catch all cases of the RAII pattern, but we need to verify what the S/N ratio is for the simplistic heuristic. Otherwise we'd need to extend
LET_UNDERSCORE_LOCK
to also check for the mutexes in parkinglot and tokio, at least.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! I will note that this ends up triggering on code in the compiler itself, which I'm guessing means it's going to be extremely noisy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@estebank I would hypothesize that
let _ = returns_type_implementing_drop()
is quite common, too much so to turn on by default.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Case in point:
:-/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@a2aaron this is a problem for what I wanted to do. Did you see how many cases are in the rustc repo? Because if
rustc
doesn't build, then we can't run crater to collect numbers on how many crates would suddenly start warning on this. I guess path inspecting against a denylist of common crates (or better, stabilizing an attribute that they can annotate with thatrustc
checks for) might be warranted in the end :-/There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@estebank There's a lot. Setting
let_underscore_drop
toWarn
and building with--warnings warn
turned on, I got 28 warnings. Most of these were instd
. It seems like 18 of them are caused by one macro (rtprintpanic!
).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's actually not as many as I thought there would be! 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I try manually fixing up all of those cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only if you have time, otherwise we can defer on that and focus on the specific denylist of std mutexes and leaving the "implicit
drop
" one as allow-by-default.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'll defer on it--the lint is too noisy and annoying to fix up right now, and every instance of it firing has been on types where it really doesn't matter that it gets dropped earlier (it's firing on a lot of
Box
s or variousResult
types). I've pushed a commit makinglet_underscore_drop
allow by default for now.