1 use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF}
;
2 use clippy_utils
::diagnostics
::span_lint_and_sugg
;
3 use clippy_utils
::source
::{snippet_with_applicability, snippet_with_context}
;
4 use clippy_utils
::ty
::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable}
;
6 can_move_expr_to_closure
, in_constant
, is_else_clause
, is_lang_ctor
, is_lint_allowed
, path_to_local_id
,
9 use rustc_ast
::util
::parser
::PREC_POSTFIX
;
10 use rustc_errors
::Applicability
;
11 use rustc_hir
::LangItem
::{OptionNone, OptionSome}
;
12 use rustc_hir
::{Arm, BindingAnnotation, Block, Expr, ExprKind, HirId, MatchSource, Mutability, Pat, PatKind}
;
13 use rustc_lint
::{LateContext, LateLintPass, LintContext}
;
14 use rustc_middle
::lint
::in_external_macro
;
15 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
16 use rustc_span
::{sym, SyntaxContext}
;
18 declare_clippy_lint
! {
19 /// **What it does:** Checks for usages of `match` which could be implemented using `map`
21 /// **Why is this bad?** Using the `map` method is clearer and more concise.
23 /// **Known problems:** None.
29 /// Some(x) => Some(x + 1),
35 /// Some(0).map(|x| x + 1);
39 "reimplementation of `map`"
42 declare_lint_pass
!(ManualMap
=> [MANUAL_MAP
]);
44 impl LateLintPass
<'_
> for ManualMap
{
45 #[allow(clippy::too_many_lines)]
46 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
47 if let ExprKind
::Match(
49 [arm1 @ Arm { guard: None, .. }
, arm2 @ Arm { guard: None, .. }
],
53 if in_external_macro(cx
.sess(), expr
.span
) || in_constant(cx
, expr
.hir_id
) {
57 let (scrutinee_ty
, ty_ref_count
, ty_mutability
) =
58 peel_mid_ty_refs_is_mutable(cx
.typeck_results().expr_ty(scrutinee
));
59 if !(is_type_diagnostic_item(cx
, scrutinee_ty
, sym
::option_type
)
60 && is_type_diagnostic_item(cx
, cx
.typeck_results().expr_ty(expr
), sym
::option_type
))
65 let expr_ctxt
= expr
.span
.ctxt();
66 let (some_expr
, some_pat
, pat_ref_count
, is_wild_none
) = match (
67 try_parse_pattern(cx
, arm1
.pat
, expr_ctxt
),
68 try_parse_pattern(cx
, arm2
.pat
, expr_ctxt
),
70 (Some(OptionPat
::Wild
), Some(OptionPat
::Some { pattern, ref_count }
))
71 if is_none_expr(cx
, arm1
.body
) =>
73 (arm2
.body
, pattern
, ref_count
, true)
75 (Some(OptionPat
::None
), Some(OptionPat
::Some { pattern, ref_count }
))
76 if is_none_expr(cx
, arm1
.body
) =>
78 (arm2
.body
, pattern
, ref_count
, false)
80 (Some(OptionPat
::Some { pattern, ref_count }
), Some(OptionPat
::Wild
))
81 if is_none_expr(cx
, arm2
.body
) =>
83 (arm1
.body
, pattern
, ref_count
, true)
85 (Some(OptionPat
::Some { pattern, ref_count }
), Some(OptionPat
::None
))
86 if is_none_expr(cx
, arm2
.body
) =>
88 (arm1
.body
, pattern
, ref_count
, false)
93 // Top level or patterns aren't allowed in closures.
94 if matches
!(some_pat
.kind
, PatKind
::Or(_
)) {
98 let some_expr
= match get_some_expr(cx
, some_expr
, expr_ctxt
) {
103 // These two lints will go back and forth with each other.
104 if cx
.typeck_results().expr_ty(some_expr
) == cx
.tcx
.types
.unit
105 && !is_lint_allowed(cx
, OPTION_MAP_UNIT_FN
, expr
.hir_id
)
110 // `map` won't perform any adjustments.
111 if !cx
.typeck_results().expr_adjustments(some_expr
).is_empty() {
115 if !can_move_expr_to_closure(cx
, some_expr
) {
119 // Determine which binding mode to use.
120 let explicit_ref
= some_pat
.contains_explicit_ref_binding();
121 let binding_ref
= explicit_ref
.or_else(|| (ty_ref_count
!= pat_ref_count
).then(|| ty_mutability
));
123 let as_ref_str
= match binding_ref
{
124 Some(Mutability
::Mut
) => ".as_mut()",
125 Some(Mutability
::Not
) => ".as_ref()",
129 let mut app
= Applicability
::MachineApplicable
;
131 // Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
132 // it's being passed by value.
133 let scrutinee
= peel_hir_expr_refs(scrutinee
).0;
134 let (scrutinee_str
, _
) = snippet_with_context(cx
, scrutinee
.span
, expr_ctxt
, "..", &mut app
);
136 if scrutinee
.span
.ctxt() == expr
.span
.ctxt() && scrutinee
.precedence().order() < PREC_POSTFIX
{
137 format
!("({})", scrutinee_str
)
142 let body_str
= if let PatKind
::Binding(annotation
, id
, some_binding
, None
) = some_pat
.kind
{
143 match can_pass_as_func(cx
, id
, some_expr
) {
144 Some(func
) if func
.span
.ctxt() == some_expr
.span
.ctxt() => {
145 snippet_with_applicability(cx
, func
.span
, "..", &mut app
).into_owned()
148 if path_to_local_id(some_expr
, id
)
149 && !is_lint_allowed(cx
, MATCH_AS_REF
, expr
.hir_id
)
150 && binding_ref
.is_some()
155 // `ref` and `ref mut` annotations were handled earlier.
156 let annotation
= if matches
!(annotation
, BindingAnnotation
::Mutable
) {
165 snippet_with_context(cx
, some_expr
.span
, expr_ctxt
, "..", &mut app
).0
169 } else if !is_wild_none
&& explicit_ref
.is_none() {
170 // TODO: handle explicit reference annotations.
173 snippet_with_context(cx
, some_pat
.span
, expr_ctxt
, "..", &mut app
).0,
174 snippet_with_context(cx
, some_expr
.span
, expr_ctxt
, "..", &mut app
).0
177 // Refutable bindings and mixed reference annotations can't be handled by `map`.
185 "manual implementation of `Option::map`",
187 if matches
!(match_kind
, MatchSource
::IfLetDesugar { .. }
) && is_else_clause(cx
.tcx
, expr
) {
188 format
!("{{ {}{}.map({}) }}", scrutinee_str
, as_ref_str
, body_str
)
190 format
!("{}{}.map({})", scrutinee_str
, as_ref_str
, body_str
)
198 // Checks whether the expression could be passed as a function, or whether a closure is needed.
199 // Returns the function to be passed to `map` if it exists.
200 fn can_pass_as_func(cx
: &LateContext
<'tcx
>, binding
: HirId
, expr
: &'tcx Expr
<'_
>) -> Option
<&'tcx Expr
<'tcx
>> {
202 ExprKind
::Call(func
, [arg
])
203 if path_to_local_id(arg
, binding
) && cx
.typeck_results().expr_adjustments(arg
).is_empty() =>
215 // The pattern contained in the `Some` tuple.
216 pattern
: &'a Pat
<'a
>,
217 // The number of references before the `Some` tuple.
218 // e.g. `&&Some(_)` has a ref count of 2.
223 // Try to parse into a recognized `Option` pattern.
224 // i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
225 fn try_parse_pattern(cx
: &LateContext
<'tcx
>, pat
: &'tcx Pat
<'_
>, ctxt
: SyntaxContext
) -> Option
<OptionPat
<'tcx
>> {
226 fn f(cx
: &LateContext
<'tcx
>, pat
: &'tcx Pat
<'_
>, ref_count
: usize, ctxt
: SyntaxContext
) -> Option
<OptionPat
<'tcx
>> {
228 PatKind
::Wild
=> Some(OptionPat
::Wild
),
229 PatKind
::Ref(pat
, _
) => f(cx
, pat
, ref_count
+ 1, ctxt
),
230 PatKind
::Path(ref qpath
) if is_lang_ctor(cx
, qpath
, OptionNone
) => Some(OptionPat
::None
),
231 PatKind
::TupleStruct(ref qpath
, [pattern
], _
)
232 if is_lang_ctor(cx
, qpath
, OptionSome
) && pat
.span
.ctxt() == ctxt
=>
234 Some(OptionPat
::Some { pattern, ref_count }
)
242 // Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.
243 fn get_some_expr(cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>, ctxt
: SyntaxContext
) -> Option
<&'tcx Expr
<'tcx
>> {
244 // TODO: Allow more complex expressions.
248 kind
: ExprKind
::Path(ref qpath
),
252 ) if ctxt
== expr
.span
.ctxt() && is_lang_ctor(cx
, qpath
, OptionSome
) => Some(arg
),
260 ) => get_some_expr(cx
, expr
, ctxt
),
265 // Checks for the `None` value.
266 fn is_none_expr(cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) -> bool
{
268 ExprKind
::Path(ref qpath
) => is_lang_ctor(cx
, qpath
, OptionNone
),
276 ) => is_none_expr(cx
, expr
),