Skip to content

Commit 2b491d5

Browse files
authored
Merge pull request #2128 from lukasstevens/master
Add lint for opt.map_or(None, f)
2 parents b62b1b6 + 4438c41 commit 2b491d5

File tree

3 files changed

+208
-123
lines changed

3 files changed

+208
-123
lines changed

clippy_lints/src/methods.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,24 @@ declare_lint! {
193193
`map_or_else(g, f)`"
194194
}
195195

196+
/// **What it does:** Checks for usage of `_.map_or(None, _)`.
197+
///
198+
/// **Why is this bad?** Readability, this can be written more concisely as
199+
/// `_.and_then(_)`.
200+
///
201+
/// **Known problems:** None.
202+
///
203+
/// **Example:**
204+
/// ```rust
205+
/// opt.map_or(None, |a| a + 1)
206+
/// ```
207+
declare_lint! {
208+
pub OPTION_MAP_OR_NONE,
209+
Warn,
210+
"using `Option.map_or(None, f)`, which is more succinctly expressed as \
211+
`and_then(f)`"
212+
}
213+
196214
/// **What it does:** Checks for usage of `_.filter(_).next()`.
197215
///
198216
/// **Why is this bad?** Readability, this can be written more concisely as
@@ -574,6 +592,7 @@ impl LintPass for Pass {
574592
OK_EXPECT,
575593
OPTION_MAP_UNWRAP_OR,
576594
OPTION_MAP_UNWRAP_OR_ELSE,
595+
OPTION_MAP_OR_NONE,
577596
OR_FUN_CALL,
578597
CHARS_NEXT_CMP,
579598
CHARS_LAST_CMP,
@@ -620,6 +639,8 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
620639
lint_map_unwrap_or(cx, expr, arglists[0], arglists[1]);
621640
} else if let Some(arglists) = method_chain_args(expr, &["map", "unwrap_or_else"]) {
622641
lint_map_unwrap_or_else(cx, expr, arglists[0], arglists[1]);
642+
} else if let Some(arglists) = method_chain_args(expr, &["map_or"]) {
643+
lint_map_or_none(cx, expr, arglists[0]);
623644
} else if let Some(arglists) = method_chain_args(expr, &["filter", "next"]) {
624645
lint_filter_next(cx, expr, arglists[0]);
625646
} else if let Some(arglists) = method_chain_args(expr, &["filter", "map"]) {
@@ -1220,6 +1241,35 @@ fn lint_map_unwrap_or_else<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir
12201241
}
12211242
}
12221243

1244+
/// lint use of `_.map_or(None, _)` for `Option`s
1245+
fn lint_map_or_none<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr, map_or_args: &'tcx [hir::Expr]) {
1246+
1247+
if match_type(cx, cx.tables.expr_ty(&map_or_args[0]), &paths::OPTION) {
1248+
// check if the first non-self argument to map_or() is None
1249+
let map_or_arg_is_none = if let hir::Expr_::ExprPath(ref qpath) = map_or_args[1].node {
1250+
match_qpath(qpath, &paths::OPTION_NONE)
1251+
} else {
1252+
false
1253+
};
1254+
1255+
if map_or_arg_is_none {
1256+
// lint message
1257+
let msg = "called `map_or(None, f)` on an Option value. This can be done more directly by calling \
1258+
`and_then(f)` instead";
1259+
let map_or_self_snippet = snippet(cx, map_or_args[0].span, "..");
1260+
let map_or_func_snippet = snippet(cx, map_or_args[2].span, "..");
1261+
let hint = format!("{0}.and_then({1})", map_or_self_snippet, map_or_func_snippet);
1262+
span_lint_and_then(
1263+
cx,
1264+
OPTION_MAP_OR_NONE,
1265+
expr.span,
1266+
msg,
1267+
|db| { db.span_suggestion(expr.span, "try using and_then instead", hint); },
1268+
);
1269+
}
1270+
}
1271+
}
1272+
12231273
/// lint use of `filter().next()` for `Iterators`
12241274
fn lint_filter_next<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr, filter_args: &'tcx [hir::Expr]) {
12251275
// lint if caller of `.filter().next()` is an Iterator

tests/ui/methods.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ macro_rules! opt_map {
9191
/// Checks implementation of the following lints:
9292
/// * `OPTION_MAP_UNWRAP_OR`
9393
/// * `OPTION_MAP_UNWRAP_OR_ELSE`
94+
/// * `OPTION_MAP_OR_NONE`
9495
fn option_methods() {
9596
let opt = Some(1);
9697

@@ -137,6 +138,15 @@ fn option_methods() {
137138
);
138139
// macro case
139140
let _ = opt_map!(opt, |x| x + 1).unwrap_or_else(|| 0); // should not lint
141+
142+
// Check OPTION_MAP_OR_NONE
143+
// single line case
144+
let _ = opt.map_or(None, |x| Some(x + 1));
145+
// multi line case
146+
let _ = opt.map_or(None, |x| {
147+
Some(x + 1)
148+
}
149+
);
140150
}
141151

142152
/// Struct to generate false positives for things with .iter()

0 commit comments

Comments
 (0)