Skip to content

Commit a1e49f9

Browse files
authored
Rollup merge of rust-lang#5415 - nickrtorres:master, r=flip1995
Add new lint for `Result<T, E>.map_or(None, Some(T))` Fixes rust-lang#5414 PR Checklist --- - [x] Followed lint naming conventions (the name is a bit awkward, but it seems to conform) - [x] Added passing UI tests (including committed .stderr file) - [x] cargo test passes locally - [x] Executed cargo dev update_lints - [x] Added lint documentation - [x] Run cargo dev fmt `Result<T, E>` has an [`ok()`](https://doc.rust-lang.org/std/result/enum.Result.html#method.ok) method that adapts a `Result<T,E>` into an `Option<T>`. It's possible to get around this adapter by writing `Result<T,E>.map_or(None, Some)`. This lint is implemented as a new variant of the existing [`option_map_none` lint](rust-lang/rust-clippy#2128)
2 parents 5ea4771 + 5d54fbb commit a1e49f9

File tree

7 files changed

+142
-16
lines changed

7 files changed

+142
-16
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1448,6 +1448,7 @@ Released 2018-09-13
14481448
[`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts
14491449
[`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs
14501450
[`result_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_expect_used
1451+
[`result_map_or_into_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_or_into_option
14511452
[`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn
14521453
[`result_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unwrap_or_else
14531454
[`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used

clippy_lints/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
666666
&methods::OPTION_UNWRAP_USED,
667667
&methods::OR_FUN_CALL,
668668
&methods::RESULT_EXPECT_USED,
669+
&methods::RESULT_MAP_OR_INTO_OPTION,
669670
&methods::RESULT_MAP_UNWRAP_OR_ELSE,
670671
&methods::RESULT_UNWRAP_USED,
671672
&methods::SEARCH_IS_SOME,
@@ -1281,6 +1282,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
12811282
LintId::of(&methods::OPTION_AS_REF_DEREF),
12821283
LintId::of(&methods::OPTION_MAP_OR_NONE),
12831284
LintId::of(&methods::OR_FUN_CALL),
1285+
LintId::of(&methods::RESULT_MAP_OR_INTO_OPTION),
12841286
LintId::of(&methods::SEARCH_IS_SOME),
12851287
LintId::of(&methods::SHOULD_IMPLEMENT_TRAIT),
12861288
LintId::of(&methods::SINGLE_CHAR_PATTERN),
@@ -1459,6 +1461,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14591461
LintId::of(&methods::NEW_RET_NO_SELF),
14601462
LintId::of(&methods::OK_EXPECT),
14611463
LintId::of(&methods::OPTION_MAP_OR_NONE),
1464+
LintId::of(&methods::RESULT_MAP_OR_INTO_OPTION),
14621465
LintId::of(&methods::SHOULD_IMPLEMENT_TRAIT),
14631466
LintId::of(&methods::STRING_EXTEND_CHARS),
14641467
LintId::of(&methods::UNNECESSARY_FOLD),

clippy_lints/src/methods/mod.rs

+83-16
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,32 @@ declare_clippy_lint! {
331331
"using `Option.map_or(None, f)`, which is more succinctly expressed as `and_then(f)`"
332332
}
333333

334+
declare_clippy_lint! {
335+
/// **What it does:** Checks for usage of `_.map_or(None, Some)`.
336+
///
337+
/// **Why is this bad?** Readability, this can be written more concisely as
338+
/// `_.ok()`.
339+
///
340+
/// **Known problems:** None.
341+
///
342+
/// **Example:**
343+
///
344+
/// Bad:
345+
/// ```rust
346+
/// # let r: Result<u32, &str> = Ok(1);
347+
/// assert_eq!(Some(1), r.map_or(None, Some));
348+
/// ```
349+
///
350+
/// Good:
351+
/// ```rust
352+
/// # let r: Result<u32, &str> = Ok(1);
353+
/// assert_eq!(Some(1), r.ok());
354+
/// ```
355+
pub RESULT_MAP_OR_INTO_OPTION,
356+
style,
357+
"using `Result.map_or(None, Some)`, which is more succinctly expressed as `ok()`"
358+
}
359+
334360
declare_clippy_lint! {
335361
/// **What it does:** Checks for usage of `_.and_then(|x| Some(y))`.
336362
///
@@ -1249,6 +1275,7 @@ declare_lint_pass!(Methods => [
12491275
OPTION_MAP_UNWRAP_OR,
12501276
OPTION_MAP_UNWRAP_OR_ELSE,
12511277
RESULT_MAP_UNWRAP_OR_ELSE,
1278+
RESULT_MAP_OR_INTO_OPTION,
12521279
OPTION_MAP_OR_NONE,
12531280
OPTION_AND_THEN_SOME,
12541281
OR_FUN_CALL,
@@ -2524,38 +2551,78 @@ fn lint_map_unwrap_or_else<'a, 'tcx>(
25242551
}
25252552
}
25262553

2527-
/// lint use of `_.map_or(None, _)` for `Option`s
2554+
/// lint use of `_.map_or(None, _)` for `Option`s and `Result`s
25282555
fn lint_map_or_none<'a, 'tcx>(
25292556
cx: &LateContext<'a, 'tcx>,
25302557
expr: &'tcx hir::Expr<'_>,
25312558
map_or_args: &'tcx [hir::Expr<'_>],
25322559
) {
2533-
if match_type(cx, cx.tables.expr_ty(&map_or_args[0]), &paths::OPTION) {
2534-
// check if the first non-self argument to map_or() is None
2535-
let map_or_arg_is_none = if let hir::ExprKind::Path(ref qpath) = map_or_args[1].kind {
2560+
let is_option = match_type(cx, cx.tables.expr_ty(&map_or_args[0]), &paths::OPTION);
2561+
let is_result = match_type(cx, cx.tables.expr_ty(&map_or_args[0]), &paths::RESULT);
2562+
2563+
// There are two variants of this `map_or` lint:
2564+
// (1) using `map_or` as an adapter from `Result<T,E>` to `Option<T>`
2565+
// (2) using `map_or` as a combinator instead of `and_then`
2566+
//
2567+
// (For this lint) we don't care if any other type calls `map_or`
2568+
if !is_option && !is_result {
2569+
return;
2570+
}
2571+
2572+
let (lint_name, msg, instead, hint) = {
2573+
let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = map_or_args[1].kind {
25362574
match_qpath(qpath, &paths::OPTION_NONE)
2575+
} else {
2576+
return;
2577+
};
2578+
2579+
if !default_arg_is_none {
2580+
// nothing to lint!
2581+
return;
2582+
}
2583+
2584+
let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_or_args[2].kind {
2585+
match_qpath(qpath, &paths::OPTION_SOME)
25372586
} else {
25382587
false
25392588
};
25402589

2541-
if map_or_arg_is_none {
2542-
// lint message
2590+
if is_option {
2591+
let self_snippet = snippet(cx, map_or_args[0].span, "..");
2592+
let func_snippet = snippet(cx, map_or_args[2].span, "..");
25432593
let msg = "called `map_or(None, f)` on an `Option` value. This can be done more directly by calling \
25442594
`and_then(f)` instead";
2545-
let map_or_self_snippet = snippet(cx, map_or_args[0].span, "..");
2546-
let map_or_func_snippet = snippet(cx, map_or_args[2].span, "..");
2547-
let hint = format!("{0}.and_then({1})", map_or_self_snippet, map_or_func_snippet);
2548-
span_lint_and_sugg(
2549-
cx,
2595+
(
25502596
OPTION_MAP_OR_NONE,
2551-
expr.span,
25522597
msg,
25532598
"try using `and_then` instead",
2554-
hint,
2555-
Applicability::MachineApplicable,
2556-
);
2599+
format!("{0}.and_then({1})", self_snippet, func_snippet),
2600+
)
2601+
} else if f_arg_is_some {
2602+
let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \
2603+
`ok()` instead";
2604+
let self_snippet = snippet(cx, map_or_args[0].span, "..");
2605+
(
2606+
RESULT_MAP_OR_INTO_OPTION,
2607+
msg,
2608+
"try using `ok` instead",
2609+
format!("{0}.ok()", self_snippet),
2610+
)
2611+
} else {
2612+
// nothing to lint!
2613+
return;
25572614
}
2558-
}
2615+
};
2616+
2617+
span_lint_and_sugg(
2618+
cx,
2619+
lint_name,
2620+
expr.span,
2621+
msg,
2622+
instead,
2623+
hint,
2624+
Applicability::MachineApplicable,
2625+
);
25592626
}
25602627

25612628
/// Lint use of `_.and_then(|x| Some(y))` for `Option`s

src/lintlist/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -1823,6 +1823,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
18231823
deprecation: None,
18241824
module: "methods",
18251825
},
1826+
Lint {
1827+
name: "result_map_or_into_option",
1828+
group: "style",
1829+
desc: "using `Result.map_or(None, Some)`, which is more succinctly expressed as `ok()`",
1830+
deprecation: None,
1831+
module: "methods",
1832+
},
18261833
Lint {
18271834
name: "result_map_unit_fn",
18281835
group: "complexity",
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::result_map_or_into_option)]
4+
5+
fn main() {
6+
let opt: Result<u32, &str> = Ok(1);
7+
let _ = opt.ok();
8+
9+
let rewrap = |s: u32| -> Option<u32> { Some(s) };
10+
11+
// A non-Some `f` arg should not emit the lint
12+
let opt: Result<u32, &str> = Ok(1);
13+
let _ = opt.map_or(None, rewrap);
14+
15+
// A non-Some `f` closure where the argument is not used as the
16+
// return should not emit the lint
17+
let opt: Result<u32, &str> = Ok(1);
18+
opt.map_or(None, |_x| Some(1));
19+
}

tests/ui/result_map_or_into_option.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::result_map_or_into_option)]
4+
5+
fn main() {
6+
let opt: Result<u32, &str> = Ok(1);
7+
let _ = opt.map_or(None, Some);
8+
9+
let rewrap = |s: u32| -> Option<u32> { Some(s) };
10+
11+
// A non-Some `f` arg should not emit the lint
12+
let opt: Result<u32, &str> = Ok(1);
13+
let _ = opt.map_or(None, rewrap);
14+
15+
// A non-Some `f` closure where the argument is not used as the
16+
// return should not emit the lint
17+
let opt: Result<u32, &str> = Ok(1);
18+
opt.map_or(None, |_x| Some(1));
19+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling `ok()` instead
2+
--> $DIR/result_map_or_into_option.rs:7:13
3+
|
4+
LL | let _ = opt.map_or(None, Some);
5+
| ^^^^^^^^^^^^^^^^^^^^^^ help: try using `ok` instead: `opt.ok()`
6+
|
7+
= note: `-D clippy::result-map-or-into-option` implied by `-D warnings`
8+
9+
error: aborting due to previous error
10+

0 commit comments

Comments
 (0)