]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
cdc7bbd5 XL |
2 | use clippy_utils::source::snippet; |
3 | use clippy_utils::ty::is_type_diagnostic_item; | |
5099ac24 | 4 | use clippy_utils::{is_lang_ctor, path_def_id}; |
f20569fa XL |
5 | use rustc_errors::Applicability; |
6 | use rustc_hir as hir; | |
cdc7bbd5 | 7 | use rustc_hir::LangItem::{OptionNone, OptionSome}; |
f20569fa | 8 | use rustc_lint::LateContext; |
5099ac24 | 9 | use rustc_middle::ty::DefIdTree; |
f20569fa XL |
10 | use rustc_span::symbol::sym; |
11 | ||
12 | use super::OPTION_MAP_OR_NONE; | |
13 | use super::RESULT_MAP_OR_INTO_OPTION; | |
14 | ||
a2a8927a XL |
15 | // The expression inside a closure may or may not have surrounding braces |
16 | // which causes problems when generating a suggestion. | |
5e7ed085 | 17 | fn reduce_unit_expression<'a>(expr: &'a hir::Expr<'_>) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> { |
a2a8927a XL |
18 | match expr.kind { |
19 | hir::ExprKind::Call(func, arg_char) => Some((func, arg_char)), | |
20 | hir::ExprKind::Block(block, _) => { | |
21 | match (block.stmts, block.expr) { | |
22 | (&[], Some(inner_expr)) => { | |
23 | // If block only contains an expression, | |
24 | // reduce `|x| { x + 1 }` to `|x| x + 1` | |
5e7ed085 | 25 | reduce_unit_expression(inner_expr) |
a2a8927a XL |
26 | }, |
27 | _ => None, | |
28 | } | |
29 | }, | |
30 | _ => None, | |
31 | } | |
32 | } | |
33 | ||
f20569fa | 34 | /// lint use of `_.map_or(None, _)` for `Option`s and `Result`s |
cdc7bbd5 XL |
35 | pub(super) fn check<'tcx>( |
36 | cx: &LateContext<'tcx>, | |
37 | expr: &'tcx hir::Expr<'_>, | |
38 | recv: &'tcx hir::Expr<'_>, | |
39 | def_arg: &'tcx hir::Expr<'_>, | |
40 | map_arg: &'tcx hir::Expr<'_>, | |
41 | ) { | |
c295e0f8 XL |
42 | let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option); |
43 | let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result); | |
f20569fa XL |
44 | |
45 | // There are two variants of this `map_or` lint: | |
46 | // (1) using `map_or` as an adapter from `Result<T,E>` to `Option<T>` | |
47 | // (2) using `map_or` as a combinator instead of `and_then` | |
48 | // | |
49 | // (For this lint) we don't care if any other type calls `map_or` | |
50 | if !is_option && !is_result { | |
51 | return; | |
52 | } | |
53 | ||
a2a8927a XL |
54 | let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = def_arg.kind { |
55 | is_lang_ctor(cx, qpath, OptionNone) | |
56 | } else { | |
57 | return; | |
58 | }; | |
f20569fa | 59 | |
a2a8927a XL |
60 | if !default_arg_is_none { |
61 | // nothing to lint! | |
62 | return; | |
63 | } | |
f20569fa | 64 | |
a2a8927a XL |
65 | let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_arg.kind { |
66 | is_lang_ctor(cx, qpath, OptionSome) | |
67 | } else { | |
68 | false | |
69 | }; | |
70 | ||
71 | if is_option { | |
72 | let self_snippet = snippet(cx, recv.span, ".."); | |
73 | if_chain! { | |
064997fb | 74 | if let hir::ExprKind::Closure(&hir::Closure { body, fn_decl_span, .. }) = map_arg.kind; |
923072b8 FG |
75 | let arg_snippet = snippet(cx, fn_decl_span, ".."); |
76 | let body = cx.tcx.hir().body(body); | |
f2b60f7d | 77 | if let Some((func, [arg_char])) = reduce_unit_expression(body.value); |
923072b8 FG |
78 | if let Some(id) = path_def_id(cx, func).map(|ctor_id| cx.tcx.parent(ctor_id)); |
79 | if Some(id) == cx.tcx.lang_items().option_some_variant(); | |
80 | then { | |
81 | let func_snippet = snippet(cx, arg_char.span, ".."); | |
82 | let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \ | |
83 | `map(..)` instead"; | |
84 | return span_lint_and_sugg( | |
85 | cx, | |
86 | OPTION_MAP_OR_NONE, | |
87 | expr.span, | |
88 | msg, | |
89 | "try using `map` instead", | |
90 | format!("{0}.map({1} {2})", self_snippet, arg_snippet,func_snippet), | |
91 | Applicability::MachineApplicable, | |
92 | ); | |
93 | } | |
f20569fa | 94 | } |
f20569fa | 95 | |
a2a8927a XL |
96 | let func_snippet = snippet(cx, map_arg.span, ".."); |
97 | let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \ | |
98 | `and_then(..)` instead"; | |
923072b8 | 99 | span_lint_and_sugg( |
a2a8927a XL |
100 | cx, |
101 | OPTION_MAP_OR_NONE, | |
102 | expr.span, | |
103 | msg, | |
104 | "try using `and_then` instead", | |
105 | format!("{0}.and_then({1})", self_snippet, func_snippet), | |
106 | Applicability::MachineApplicable, | |
107 | ); | |
108 | } else if f_arg_is_some { | |
109 | let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \ | |
110 | `ok()` instead"; | |
111 | let self_snippet = snippet(cx, recv.span, ".."); | |
923072b8 | 112 | span_lint_and_sugg( |
a2a8927a XL |
113 | cx, |
114 | RESULT_MAP_OR_INTO_OPTION, | |
115 | expr.span, | |
116 | msg, | |
117 | "try using `ok` instead", | |
118 | format!("{0}.ok()", self_snippet), | |
119 | Applicability::MachineApplicable, | |
120 | ); | |
121 | } | |
f20569fa | 122 | } |