Skip to content

Commit

Permalink
Add real suggestion to option_map_unwrap_or
Browse files Browse the repository at this point in the history
  • Loading branch information
m-ober committed Oct 14, 2019
1 parent a865d0a commit 2f500a5
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 27 deletions.
2 changes: 1 addition & 1 deletion clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1070,7 +1070,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
["unwrap", "get_mut"] => lint_get_unwrap(cx, expr, arg_lists[1], true),
["unwrap", ..] => lint_unwrap(cx, expr, arg_lists[0]),
["expect", "ok"] => lint_ok_expect(cx, expr, arg_lists[1]),
["unwrap_or", "map"] => option_map_unwrap_or::lint(cx, expr, arg_lists[1], arg_lists[0]),
["unwrap_or", "map"] => option_map_unwrap_or::lint(cx, expr, arg_lists[1], arg_lists[0], method_spans[1]),
["unwrap_or_else", "map"] => lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0]),
["map_or", ..] => lint_map_or_none(cx, expr, arg_lists[0]),
["and_then", ..] => lint_option_and_then_some(cx, expr, arg_lists[0]),
Expand Down
47 changes: 25 additions & 22 deletions clippy_lints/src/methods/option_map_unwrap_or.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::utils::paths;
use crate::utils::{is_copy, match_type, snippet, span_lint, span_note_and_lint};
use crate::utils::{differing_macro_contexts, paths, snippet_with_applicability, span_lint_and_then};
use crate::utils::{is_copy, match_type, snippet};
use rustc::hir::intravisit::{walk_path, NestedVisitorMap, Visitor};
use rustc::hir::{self, *};
use rustc::lint::LateContext;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use syntax::source_map::Span;
use syntax_pos::symbol::Symbol;

use super::OPTION_MAP_UNWRAP_OR;
Expand All @@ -14,6 +16,7 @@ pub(super) fn lint<'a, 'tcx>(
expr: &hir::Expr,
map_args: &'tcx [hir::Expr],
unwrap_args: &'tcx [hir::Expr],
map_span: Span,
) {
// lint if the caller of `map()` is an `Option`
if match_type(cx, cx.tables.expr_ty(&map_args[0]), &paths::OPTION) {
Expand All @@ -39,9 +42,13 @@ pub(super) fn lint<'a, 'tcx>(
}
}

// get snippets for args to map() and unwrap_or()
let map_snippet = snippet(cx, map_args[1].span, "..");
let unwrap_snippet = snippet(cx, unwrap_args[1].span, "..");
if differing_macro_contexts(unwrap_args[1].span, map_span) {
return;
}

let mut applicability = Applicability::MachineApplicable;
// get snippet for unwrap_or()
let unwrap_snippet = snippet_with_applicability(cx, unwrap_args[1].span, "..", &mut applicability);
// lint message
// comparing the snippet from source to raw text ("None") below is safe
// because we already have checked the type.
Expand All @@ -56,24 +63,20 @@ pub(super) fn lint<'a, 'tcx>(
This can be done more directly by calling `{}` instead",
arg, suggest
);
// lint, with note if neither arg is > 1 line and both map() and
// unwrap_or() have the same span
let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1;
let same_span = map_args[1].span.ctxt() == unwrap_args[1].span.ctxt();
if same_span && !multiline {
let suggest = if unwrap_snippet == "None" {
format!("and_then({})", map_snippet)
} else {
format!("map_or({}, {})", unwrap_snippet, map_snippet)
};
let note = format!(
"replace `map({}).unwrap_or({})` with `{}`",
map_snippet, unwrap_snippet, suggest

span_lint_and_then(cx, OPTION_MAP_UNWRAP_OR, expr.span, msg, |db| {
let map_arg_span = map_args[1].span;

db.multipart_suggestion(
"use `map_or` instead",
vec![
(map_span, String::from("map_or")),
(expr.span.with_lo(unwrap_args[0].span.hi()), String::from("")),
(map_arg_span.with_hi(map_arg_span.lo()), format!("{}, ", unwrap_snippet)),
],
applicability,
);
span_note_and_lint(cx, OPTION_MAP_UNWRAP_OR, expr.span, msg, expr.span, &note);
} else if same_span && multiline {
span_lint(cx, OPTION_MAP_UNWRAP_OR, expr.span, msg);
};
});
}
}

Expand Down
37 changes: 33 additions & 4 deletions tests/ui/methods.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ LL | | .unwrap_or(0);
| |____________________________^
|
= note: `-D clippy::option-map-unwrap-or` implied by `-D warnings`
= note: replace `map(|x| x + 1).unwrap_or(0)` with `map_or(0, |x| x + 1)`
help: use `map_or` instead
|
LL | let _ = opt.map_or(0, |x| x + 1);
| ^^^^^^ ^^ --

error: called `map(f).unwrap_or(a)` on an Option value. This can be done more directly by calling `map_or(a, f)` instead
--> $DIR/methods.rs:178:13
Expand All @@ -39,6 +42,13 @@ LL | | x + 1
LL | | }
LL | | ).unwrap_or(0);
| |____________________________^
help: use `map_or` instead
|
LL | let _ = opt.map_or(0, |x| {
LL | x + 1
LL | }
LL | );
|

error: called `map(f).unwrap_or(a)` on an Option value. This can be done more directly by calling `map_or(a, f)` instead
--> $DIR/methods.rs:182:13
Expand All @@ -49,14 +59,22 @@ LL | | .unwrap_or({
LL | | 0
LL | | });
| |__________________^
help: use `map_or` instead
|
LL | let _ = opt.map_or({
LL | 0
LL | }, |x| x + 1);
|

error: called `map(f).unwrap_or(None)` on an Option value. This can be done more directly by calling `and_then(f)` instead
--> $DIR/methods.rs:187:13
|
LL | let _ = opt.map(|x| Some(x + 1)).unwrap_or(None);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: use `map_or` instead
|
= note: replace `map(|x| Some(x + 1)).unwrap_or(None)` with `and_then(|x| Some(x + 1))`
LL | let _ = opt.map_or(None, |x| Some(x + 1));
| ^^^^^^ ^^^^^ --

error: called `map(f).unwrap_or(None)` on an Option value. This can be done more directly by calling `and_then(f)` instead
--> $DIR/methods.rs:189:13
Expand All @@ -67,6 +85,13 @@ LL | | Some(x + 1)
LL | | }
LL | | ).unwrap_or(None);
| |_____________________^
help: use `map_or` instead
|
LL | let _ = opt.map_or(None, |x| {
LL | Some(x + 1)
LL | }
LL | );
|

error: called `map(f).unwrap_or(None)` on an Option value. This can be done more directly by calling `and_then(f)` instead
--> $DIR/methods.rs:193:13
Expand All @@ -76,16 +101,20 @@ LL | let _ = opt
LL | | .map(|x| Some(x + 1))
LL | | .unwrap_or(None);
| |________________________^
help: use `map_or` instead
|
= note: replace `map(|x| Some(x + 1)).unwrap_or(None)` with `and_then(|x| Some(x + 1))`
LL | .map_or(None, |x| Some(x + 1));
| ^^^^^^ ^^^^^ --

error: called `map(f).unwrap_or(a)` on an Option value. This can be done more directly by calling `map_or(a, f)` instead
--> $DIR/methods.rs:204:13
|
LL | let _ = Some("prefix").map(|p| format!("{}.", p)).unwrap_or(id);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: use `map_or` instead
|
= note: replace `map(|p| format!("{}.", p)).unwrap_or(id)` with `map_or(id, |p| format!("{}.", p))`
LL | let _ = Some("prefix").map_or(id, |p| format!("{}.", p));
| ^^^^^^ ^^^ --

error: called `map(f).unwrap_or_else(g)` on an Option value. This can be done more directly by calling `map_or_else(g, f)` instead
--> $DIR/methods.rs:208:13
Expand Down

0 comments on commit 2f500a5

Please sign in to comment.