-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
293 additions
and
3 deletions.
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
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,175 @@ | ||
use crate::utils::{is_type_diagnostic_item, match_def_path, paths, snippet_with_applicability, span_lint_and_sugg}; | ||
use core::fmt; | ||
use if_chain::if_chain; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, PatKind, QPath}; | ||
use rustc_lint::{LateContext, LateLintPass, LintContext}; | ||
use rustc_middle::lint::in_external_macro; | ||
use rustc_session::{declare_lint_pass, declare_tool_lint}; | ||
use rustc_span::symbol::{sym, Ident}; | ||
|
||
declare_clippy_lint! { | ||
/// **What it does:** Checks for usages of `match` which could be implemented using `Option::map` | ||
/// | ||
/// **Why is this bad?** Using the `map` method is clearer and more concise. | ||
/// | ||
/// **Known problems:** None. | ||
/// | ||
/// **Example:** | ||
/// | ||
/// ```rust | ||
/// match Some(0) { | ||
/// Some(x) => Some(x + 1), | ||
/// None => None, | ||
/// }; | ||
/// ``` | ||
/// Use instead: | ||
/// ```rust | ||
/// Some(0).map(|x| x + 1); | ||
/// ``` | ||
pub OPTION_MANUAL_MAP, | ||
style, | ||
"reimplementation of `Option::map`" | ||
} | ||
|
||
declare_lint_pass!(OptionManualMap => [OPTION_MANUAL_MAP]); | ||
|
||
impl LateLintPass<'_> for OptionManualMap { | ||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | ||
if_chain! { | ||
if !in_external_macro(cx.sess(), expr.span); | ||
if let ExprKind::Match(scrutinee, [arm1, arm2], _) = expr.kind; | ||
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::option_type); | ||
if let Some((some_binding, some_expr, none_expr)) = if is_none_arm(cx, arm1) { | ||
get_some_binding(cx, arm2).map(|binding| (binding, arm2.body, arm1.body)) | ||
} else if is_none_arm(cx, arm2) { | ||
get_some_binding(cx, arm1).map(|binding| (binding, arm1.body, arm2.body)) | ||
} else { | ||
None | ||
}; | ||
|
||
if is_none_expr(cx, none_expr); | ||
if let Some(some_expr) = get_some_expr(cx, some_expr); | ||
|
||
then { | ||
let mut app = Applicability::MachineApplicable; | ||
|
||
let body = if_chain! { | ||
if let SomeBinding::Name(some_name) = some_binding; | ||
if let ExprKind::Call(func, [arg]) = some_expr.kind; | ||
if let ExprKind::Path(QPath::Resolved(_, arg_path)) = arg.kind; | ||
if let [arg_name] = arg_path.segments; | ||
if arg_name.ident.name == some_name.name; | ||
then { | ||
snippet_with_applicability(cx, func.span, "_", &mut app).into_owned() | ||
} else { | ||
format!("|{}| {}", some_binding, snippet_with_applicability(cx, some_expr.span, "..", &mut app)) | ||
} | ||
}; | ||
|
||
let scrutinee = snippet_with_applicability(cx, scrutinee.span, "_", &mut app); | ||
span_lint_and_sugg( | ||
cx, | ||
OPTION_MANUAL_MAP, | ||
expr.span, | ||
"manual implementation of `Option::map`", | ||
"use map instead", | ||
format!("{}.map({})", scrutinee, body), | ||
app | ||
); | ||
} | ||
} | ||
} | ||
} | ||
|
||
enum SomeBinding { | ||
Wild, | ||
Name(Ident), | ||
} | ||
impl fmt::Display for SomeBinding { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
match self { | ||
Self::Wild => f.write_str("_"), | ||
Self::Name(name) => name.fmt(f), | ||
} | ||
} | ||
} | ||
|
||
fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { | ||
match expr.kind { | ||
ExprKind::Call( | ||
Expr { | ||
kind: ExprKind::Path(QPath::Resolved(None, path)), | ||
.. | ||
}, | ||
[arg], | ||
) => { | ||
if match_def_path(cx, path.res.opt_def_id()?, &paths::OPTION_SOME) { | ||
Some(arg) | ||
} else { | ||
None | ||
} | ||
}, | ||
ExprKind::Block( | ||
Block { | ||
stmts: [], | ||
expr: Some(expr), | ||
.. | ||
}, | ||
_, | ||
) => get_some_expr(cx, expr), | ||
_ => None, | ||
} | ||
} | ||
|
||
fn is_none_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { | ||
match expr.kind { | ||
ExprKind::Path(QPath::Resolved(None, path)) => path | ||
.res | ||
.opt_def_id() | ||
.map_or(false, |id| match_def_path(cx, id, &paths::OPTION_NONE)), | ||
ExprKind::Block( | ||
Block { | ||
stmts: [], | ||
expr: Some(expr), | ||
.. | ||
}, | ||
_, | ||
) => is_none_expr(cx, expr), | ||
_ => false, | ||
} | ||
} | ||
|
||
fn is_none_arm(cx: &LateContext<'tcx>, arm: &'tcx Arm<'_>) -> bool { | ||
if arm.guard.is_some() { | ||
return false; | ||
} | ||
match &arm.pat.kind { | ||
PatKind::Wild => true, | ||
PatKind::Path(QPath::Resolved(None, path)) => path | ||
.res | ||
.opt_def_id() | ||
.map_or(false, |id| match_def_path(cx, id, &paths::OPTION_NONE)), | ||
_ => false, | ||
} | ||
} | ||
|
||
fn get_some_binding(cx: &LateContext<'tcx>, arm: &'tcx Arm<'_>) -> Option<SomeBinding> { | ||
if_chain! { | ||
if arm.guard.is_none(); | ||
if let PatKind::TupleStruct(QPath::Resolved(None, path), [some_pat], _) = arm.pat.kind; | ||
if let Some(id) = path.res.opt_def_id(); | ||
if match_def_path(cx, id, &paths::OPTION_SOME); | ||
then { | ||
match some_pat.kind { | ||
PatKind::Binding(BindingAnnotation::Unannotated, _, bind_name, None) => { | ||
Some(SomeBinding::Name(bind_name)) | ||
} | ||
PatKind::Wild => Some(SomeBinding::Wild), | ||
_ => None, | ||
} | ||
} else { | ||
None | ||
} | ||
} | ||
} |
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 @@ | ||
// run-rustfix | ||
|
||
#![warn(clippy::option_manual_map)] | ||
#![allow(clippy::map_identity)] | ||
|
||
fn main() { | ||
Some(0).map(|_| 2); | ||
|
||
Some(0).map(|x| x + 1); | ||
|
||
Some("").map(|x| x.is_empty()); | ||
|
||
Some(0).map(|x| !x); | ||
|
||
#[rustfmt::skip] | ||
Some(0).map(std::convert::identity); | ||
|
||
match Some(0) { | ||
Some(x) if false => Some(x + 1), | ||
_ => None, | ||
}; | ||
} |
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,38 @@ | ||
// run-rustfix | ||
|
||
#![warn(clippy::option_manual_map)] | ||
#![allow(clippy::map_identity)] | ||
|
||
fn main() { | ||
match Some(0) { | ||
Some(_) => Some(2), | ||
None::<u32> => None, | ||
}; | ||
|
||
match Some(0) { | ||
Some(x) => Some(x + 1), | ||
_ => None, | ||
}; | ||
|
||
match Some("") { | ||
Some(x) => Some(x.is_empty()), | ||
None => None, | ||
}; | ||
|
||
if let Some(x) = Some(0) { | ||
Some(!x) | ||
} else { | ||
None | ||
}; | ||
|
||
#[rustfmt::skip] | ||
match Some(0) { | ||
Some(x) => { Some(std::convert::identity(x)) } | ||
None => { None } | ||
}; | ||
|
||
match Some(0) { | ||
Some(x) if false => Some(x + 1), | ||
_ => None, | ||
}; | ||
} |
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,50 @@ | ||
error: manual implementation of `Option::map` | ||
--> $DIR/option_manual_map.rs:7:5 | ||
| | ||
LL | / match Some(0) { | ||
LL | | Some(_) => Some(2), | ||
LL | | None::<u32> => None, | ||
LL | | }; | ||
| |_____^ help: use map instead: `Some(0).map(|_| 2)` | ||
| | ||
= note: `-D clippy::option-manual-map` implied by `-D warnings` | ||
|
||
error: manual implementation of `Option::map` | ||
--> $DIR/option_manual_map.rs:12:5 | ||
| | ||
LL | / match Some(0) { | ||
LL | | Some(x) => Some(x + 1), | ||
LL | | _ => None, | ||
LL | | }; | ||
| |_____^ help: use map instead: `Some(0).map(|x| x + 1)` | ||
|
||
error: manual implementation of `Option::map` | ||
--> $DIR/option_manual_map.rs:17:5 | ||
| | ||
LL | / match Some("") { | ||
LL | | Some(x) => Some(x.is_empty()), | ||
LL | | None => None, | ||
LL | | }; | ||
| |_____^ help: use map instead: `Some("").map(|x| x.is_empty())` | ||
|
||
error: manual implementation of `Option::map` | ||
--> $DIR/option_manual_map.rs:22:5 | ||
| | ||
LL | / if let Some(x) = Some(0) { | ||
LL | | Some(!x) | ||
LL | | } else { | ||
LL | | None | ||
LL | | }; | ||
| |_____^ help: use map instead: `Some(0).map(|x| !x)` | ||
|
||
error: manual implementation of `Option::map` | ||
--> $DIR/option_manual_map.rs:29:5 | ||
| | ||
LL | / match Some(0) { | ||
LL | | Some(x) => { Some(std::convert::identity(x)) } | ||
LL | | None => { None } | ||
LL | | }; | ||
| |_____^ help: use map instead: `Some(0).map(std::convert::identity)` | ||
|
||
error: aborting due to 5 previous errors | ||
|