-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Add lint manual_option_folding
#11581
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
use clippy_utils::diagnostics::span_lint_and_then; | ||
use clippy_utils::ty::is_type_diagnostic_item; | ||
use rustc_hir as hir; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
use rustc_session::{declare_tool_lint, impl_lint_pass}; | ||
use rustc_span::{sym, Symbol}; | ||
|
||
declare_clippy_lint! { | ||
/// ### What it does | ||
/// Checks for cases where `unwrap_or_else` can be used in lieu of | ||
/// `get_or_insert_with` followed by `unwrap`/`unwrap_unchecked`/`expect`. | ||
/// | ||
/// ### Why is this bad? | ||
/// It is more concise to use `unwrap_or_else`, and using `unwrap_or_else` | ||
/// instead of `unwrap_unchecked` eliminates the need for an `unsafe` | ||
/// block. | ||
/// | ||
/// ### Example | ||
/// ```rust | ||
/// let mut opt: Option<i32> = None; | ||
/// opt.get_or_insert(42); | ||
/// let res = unsafe { opt.unwrap_unchecked() }; | ||
/// ``` | ||
/// Use instead: | ||
/// ```rust | ||
/// let opt: Option<i32> = None; | ||
/// let res: i32 = opt.unwrap_or(42); | ||
/// ``` | ||
#[clippy::version = "1.74.0"] | ||
pub MANUAL_OPTION_FOLDING, | ||
style, | ||
"manual implementation of `Option::unwrap_or_else`" | ||
} | ||
|
||
impl_lint_pass!(ManualOptionFolding<'_> => [MANUAL_OPTION_FOLDING]); | ||
|
||
pub struct ManualOptionFolding<'tcx> { | ||
get_call: Option<&'tcx hir::Expr<'tcx>>, | ||
recv: Option<&'tcx hir::Expr<'tcx>>, | ||
get_method_name: Option<Symbol>, | ||
} | ||
|
||
impl<'tcx> ManualOptionFolding<'tcx> { | ||
pub fn new() -> Self { | ||
Self { | ||
get_call: None, | ||
recv: None, | ||
get_method_name: None, | ||
} | ||
} | ||
} | ||
|
||
impl<'tcx> LateLintPass<'tcx> for ManualOptionFolding<'tcx> { | ||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { | ||
if !expr.span.from_expansion() | ||
&& let hir::ExprKind::MethodCall(path, recv, ..) = expr.kind | ||
&& let ty = cx.typeck_results().expr_ty(recv).peel_refs() | ||
&& is_type_diagnostic_item(cx, ty, sym::Option) | ||
{ | ||
if path.ident.name == sym!(get_or_insert) | ||
|| path.ident.name == sym!(get_or_insert_with) | ||
|| path.ident.name == sym!(get_or_insert_default) | ||
{ | ||
self.get_call = Some(expr); | ||
self.recv = Some(recv); | ||
self.get_method_name = Some(path.ident.name); | ||
} else if let Some(get_call) = self.get_call.take() | ||
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. This This means that any number of expressions could between this and the previous fn magic(condition: bool) -> u32 {
let mut option = None;
if condition {
option.insert(19);
}
option.unwrap()
} I think the best solution would be to check for block expressions and then iterate over the statements using a window of 2. That would only catch consequitve |
||
&& let Some(get_call_recv) = self.recv.take() | ||
&& let Some(get_method_name) = self.get_method_name.take() | ||
&& (path.ident.name == sym::unwrap | ||
|| path.ident.name == sym!(unwrap_unchecked) | ||
|| path.ident.name == sym::expect) | ||
&& let hir::ExprKind::Path(hir::QPath::Resolved(_, recv_path)) = recv.kind | ||
&& let hir::ExprKind::Path(hir::QPath::Resolved(_, get_call_recv_path)) = get_call_recv.kind | ||
&& recv_path.res == get_call_recv_path.res | ||
{ | ||
let sugg_method = if get_method_name == sym!(get_or_insert) { | ||
"unwrap_or".to_string() | ||
} else if get_method_name == sym!(get_or_insert_with) { | ||
"unwrap_or_else".to_string() | ||
} else { | ||
"unwrap_or_default".to_string() | ||
}; | ||
|
||
span_lint_and_then( | ||
cx, | ||
MANUAL_OPTION_FOLDING, | ||
expr.span, | ||
&format!("`{}` used after `{get_method_name}`", path.ident.name), | ||
|diag| { | ||
diag.span_note(get_call.span, format!("`{get_method_name}` used here")); | ||
diag.help(format!("try using `{sugg_method}` instead")); | ||
} | ||
); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
#![feature(option_get_or_insert_default)] | ||
#![allow(dead_code, clippy::unnecessary_lazy_evaluations, clippy::unnecessary_literal_unwrap)] | ||
#![warn(clippy::manual_option_folding)] | ||
|
||
fn main() { | ||
let mut opt: Option<i32> = Some(42); | ||
opt.get_or_insert_with(|| 21); | ||
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.
|
||
|
||
let opt: Option<i32> = Some(42); | ||
opt.unwrap(); | ||
|
||
let mut opt: Option<i32> = Some(42); | ||
opt.get_or_insert_with(|| 21); | ||
let _res: i32 = unsafe { opt.unwrap_unchecked() }; | ||
|
||
let mut opt: Option<i32> = Some(42); | ||
opt.get_or_insert_with(|| 21); | ||
let _res: i32 = opt.unwrap(); | ||
|
||
let mut opt: Option<i32> = Some(42); | ||
opt.get_or_insert_with(|| 21); | ||
let _res: i32 = opt.expect("msg"); | ||
|
||
let mut opt: Option<i32> = Some(42); | ||
opt.get_or_insert(21); | ||
let _res: i32 = opt.unwrap(); | ||
|
||
let mut opt: Option<i32> = Some(42); | ||
opt.get_or_insert_default(); | ||
let _res: i32 = opt.unwrap(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
error: `unwrap_unchecked` used after `get_or_insert_with` | ||
--> $DIR/manual_option_folding.rs:14:30 | ||
| | ||
LL | let _res: i32 = unsafe { opt.unwrap_unchecked() }; | ||
| ^^^^^^^^^^^^^^^^^^^^^^ | ||
| | ||
note: `get_or_insert_with` used here | ||
--> $DIR/manual_option_folding.rs:13:5 | ||
| | ||
LL | opt.get_or_insert_with(|| 21); | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
= help: try using `unwrap_or_else` instead | ||
= note: `-D clippy::manual-option-folding` implied by `-D warnings` | ||
= help: to override `-D warnings` add `#[allow(clippy::manual_option_folding)]` | ||
|
||
error: `unwrap` used after `get_or_insert_with` | ||
--> $DIR/manual_option_folding.rs:18:21 | ||
| | ||
LL | let _res: i32 = opt.unwrap(); | ||
| ^^^^^^^^^^^^ | ||
| | ||
note: `get_or_insert_with` used here | ||
--> $DIR/manual_option_folding.rs:17:5 | ||
| | ||
LL | opt.get_or_insert_with(|| 21); | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
= help: try using `unwrap_or_else` instead | ||
|
||
error: `expect` used after `get_or_insert_with` | ||
--> $DIR/manual_option_folding.rs:22:21 | ||
| | ||
LL | let _res: i32 = opt.expect("msg"); | ||
| ^^^^^^^^^^^^^^^^^ | ||
| | ||
note: `get_or_insert_with` used here | ||
--> $DIR/manual_option_folding.rs:21:5 | ||
| | ||
LL | opt.get_or_insert_with(|| 21); | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
= help: try using `unwrap_or_else` instead | ||
|
||
error: `unwrap` used after `get_or_insert` | ||
--> $DIR/manual_option_folding.rs:26:21 | ||
| | ||
LL | let _res: i32 = opt.unwrap(); | ||
| ^^^^^^^^^^^^ | ||
| | ||
note: `get_or_insert` used here | ||
--> $DIR/manual_option_folding.rs:25:5 | ||
| | ||
LL | opt.get_or_insert(21); | ||
| ^^^^^^^^^^^^^^^^^^^^^ | ||
= help: try using `unwrap_or` instead | ||
|
||
error: `unwrap` used after `get_or_insert_default` | ||
--> $DIR/manual_option_folding.rs:30:21 | ||
| | ||
LL | let _res: i32 = opt.unwrap(); | ||
| ^^^^^^^^^^^^ | ||
| | ||
note: `get_or_insert_default` used here | ||
--> $DIR/manual_option_folding.rs:29:5 | ||
| | ||
LL | opt.get_or_insert_default(); | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
= help: try using `unwrap_or_default` instead | ||
|
||
error: aborting due to 5 previous errors | ||
|
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.
This should also catch usages of
insert()
, which has been added in Rust 1.53. We have some docs for how you can check for the specified MSRV: https://doc.rust-lang.org/clippy/development/adding_lints.html#specifying-the-lints-minimum-supported-rust-version-msrv