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
::higher
;
4 use clippy_utils
::source
::{snippet_with_applicability, snippet_with_context}
;
5 use clippy_utils
::ty
::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable}
;
7 can_move_expr_to_closure
, in_constant
, is_else_clause
, is_lang_ctor
, is_lint_allowed
, path_to_local_id
,
10 use rustc_ast
::util
::parser
::PREC_POSTFIX
;
11 use rustc_errors
::Applicability
;
12 use rustc_hir
::LangItem
::{OptionNone, OptionSome}
;
13 use rustc_hir
::{Arm, BindingAnnotation, Block, Expr, ExprKind, HirId, Mutability, Pat, PatKind}
;
14 use rustc_lint
::{LateContext, LateLintPass, LintContext}
;
15 use rustc_middle
::lint
::in_external_macro
;
16 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
17 use rustc_span
::{sym, SyntaxContext}
;
19 declare_clippy_lint
! {
21 /// Checks for usages of `match` which could be implemented using `map`
23 /// ### Why is this bad?
24 /// Using the `map` method is clearer and more concise.
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 Some(higher
::IfLet
{
51 if_else
: Some(if_else
),
52 }) = higher
::IfLet
::hir(cx
, expr
)
54 manage_lint(cx
, expr
, (&let_pat
.kind
, if_then
), (&PatKind
::Wild
, if_else
), let_expr
);
57 if let ExprKind
::Match(scrutinee
, [then @ Arm { guard: None, .. }
, r
#else @ Arm { guard: None, .. }], _) =
63 (&then
.pat
.kind
, then
.body
),
64 (&r
#else.pat.kind, r#else.body),
72 cx
: &LateContext
<'tcx
>,
74 then
: (&'tcx PatKind
<'_
>, &'tcx Expr
<'_
>),
75 r
#else: (&'tcx PatKind<'_>, &'tcx Expr<'_>),
76 scrut
: &'tcx Expr
<'_
>,
78 if in_external_macro(cx
.sess(), expr
.span
) || in_constant(cx
, expr
.hir_id
) {
82 let (scrutinee_ty
, ty_ref_count
, ty_mutability
) = peel_mid_ty_refs_is_mutable(cx
.typeck_results().expr_ty(scrut
));
83 if !(is_type_diagnostic_item(cx
, scrutinee_ty
, sym
::option_type
)
84 && is_type_diagnostic_item(cx
, cx
.typeck_results().expr_ty(expr
), sym
::option_type
))
89 let (then_pat
, then_expr
) = then
;
90 let (else_pat
, else_expr
) = r
#else;
92 let expr_ctxt
= expr
.span
.ctxt();
93 let (some_expr
, some_pat
, pat_ref_count
, is_wild_none
) = match (
94 try_parse_pattern(cx
, then_pat
, expr_ctxt
),
95 try_parse_pattern(cx
, else_pat
, expr_ctxt
),
97 (Some(OptionPat
::Wild
), Some(OptionPat
::Some { pattern, ref_count }
)) if is_none_expr(cx
, then_expr
) => {
98 (else_expr
, pattern
, ref_count
, true)
100 (Some(OptionPat
::None
), Some(OptionPat
::Some { pattern, ref_count }
)) if is_none_expr(cx
, then_expr
) => {
101 (else_expr
, pattern
, ref_count
, false)
103 (Some(OptionPat
::Some { pattern, ref_count }
), Some(OptionPat
::Wild
)) if is_none_expr(cx
, else_expr
) => {
104 (then_expr
, pattern
, ref_count
, true)
106 (Some(OptionPat
::Some { pattern, ref_count }
), Some(OptionPat
::None
)) if is_none_expr(cx
, else_expr
) => {
107 (then_expr
, pattern
, ref_count
, false)
112 // Top level or patterns aren't allowed in closures.
113 if matches
!(some_pat
.kind
, PatKind
::Or(_
)) {
117 let some_expr
= match get_some_expr(cx
, some_expr
, expr_ctxt
) {
122 if cx
.typeck_results().expr_ty(some_expr
) == cx
.tcx
.types
.unit
&& !is_lint_allowed(cx
, OPTION_MAP_UNIT_FN
, expr
.hir_id
) {
126 // `map` won't perform any adjustments.
127 if !cx
.typeck_results().expr_adjustments(some_expr
).is_empty() {
131 if !can_move_expr_to_closure(cx
, some_expr
) {
135 // Determine which binding mode to use.
136 let explicit_ref
= some_pat
.contains_explicit_ref_binding();
137 let binding_ref
= explicit_ref
.or_else(|| (ty_ref_count
!= pat_ref_count
).then(|| ty_mutability
));
139 let as_ref_str
= match binding_ref
{
140 Some(Mutability
::Mut
) => ".as_mut()",
141 Some(Mutability
::Not
) => ".as_ref()",
145 let mut app
= Applicability
::MachineApplicable
;
147 // Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
148 // it's being passed by value.
149 let scrutinee
= peel_hir_expr_refs(scrut
).0;
150 let (scrutinee_str
, _
) = snippet_with_context(cx
, scrutinee
.span
, expr_ctxt
, "..", &mut app
);
151 let scrutinee_str
= if scrutinee
.span
.ctxt() == expr
.span
.ctxt() && scrutinee
.precedence().order() < PREC_POSTFIX
{
152 format
!("({})", scrutinee_str
)
157 let body_str
= if let PatKind
::Binding(annotation
, id
, some_binding
, None
) = some_pat
.kind
{
158 match can_pass_as_func(cx
, id
, some_expr
) {
159 Some(func
) if func
.span
.ctxt() == some_expr
.span
.ctxt() => {
160 snippet_with_applicability(cx
, func
.span
, "..", &mut app
).into_owned()
163 if path_to_local_id(some_expr
, id
)
164 && !is_lint_allowed(cx
, MATCH_AS_REF
, expr
.hir_id
)
165 && binding_ref
.is_some()
170 // `ref` and `ref mut` annotations were handled earlier.
171 let annotation
= if matches
!(annotation
, BindingAnnotation
::Mutable
) {
180 snippet_with_context(cx
, some_expr
.span
, expr_ctxt
, "..", &mut app
).0
184 } else if !is_wild_none
&& explicit_ref
.is_none() {
185 // TODO: handle explicit reference annotations.
188 snippet_with_context(cx
, some_pat
.span
, expr_ctxt
, "..", &mut app
).0,
189 snippet_with_context(cx
, some_expr
.span
, expr_ctxt
, "..", &mut app
).0
192 // Refutable bindings and mixed reference annotations can't be handled by `map`.
200 "manual implementation of `Option::map`",
202 if is_else_clause(cx
.tcx
, expr
) {
203 format
!("{{ {}{}.map({}) }}", scrutinee_str
, as_ref_str
, body_str
)
205 format
!("{}{}.map({})", scrutinee_str
, as_ref_str
, body_str
)
211 // Checks whether the expression could be passed as a function, or whether a closure is needed.
212 // Returns the function to be passed to `map` if it exists.
213 fn can_pass_as_func(cx
: &LateContext
<'tcx
>, binding
: HirId
, expr
: &'tcx Expr
<'_
>) -> Option
<&'tcx Expr
<'tcx
>> {
215 ExprKind
::Call(func
, [arg
])
216 if path_to_local_id (arg
, binding
) && cx
.typeck_results().expr_adjustments(arg
).is_empty() =>
228 // The pattern contained in the `Some` tuple.
229 pattern
: &'a Pat
<'a
>,
230 // The number of references before the `Some` tuple.
231 // e.g. `&&Some(_)` has a ref count of 2.
236 // Try to parse into a recognized `Option` pattern.
237 // i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
238 fn try_parse_pattern(
239 cx
: &LateContext
<'tcx
>,
240 pat_kind
: &'tcx PatKind
<'_
>,
242 ) -> Option
<OptionPat
<'tcx
>> {
244 cx
: &LateContext
<'tcx
>,
245 pat_kind
: &'tcx PatKind
<'_
>,
248 ) -> Option
<OptionPat
<'tcx
>> {
250 PatKind
::Wild
=> Some(OptionPat
::Wild
),
251 PatKind
::Ref(ref_pat
, _
) => f(cx
, &ref_pat
.kind
, ref_count
+ 1, ctxt
),
252 PatKind
::Path(ref qpath
) if is_lang_ctor(cx
, qpath
, OptionNone
) => Some(OptionPat
::None
),
253 PatKind
::TupleStruct(ref qpath
, [pattern
], _
) if is_lang_ctor(cx
, qpath
, OptionSome
) => {
254 Some(OptionPat
::Some { pattern, ref_count }
)
259 f(cx
, pat_kind
, 0, ctxt
)
262 // Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.
263 fn get_some_expr(cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>, ctxt
: SyntaxContext
) -> Option
<&'tcx Expr
<'tcx
>> {
264 // TODO: Allow more complex expressions.
268 kind
: ExprKind
::Path(ref qpath
),
272 ) if ctxt
== expr
.span
.ctxt() && is_lang_ctor(cx
, qpath
, OptionSome
) => Some(arg
),
280 ) => get_some_expr(cx
, expr
, ctxt
),
285 // Checks for the `None` value.
286 fn is_none_expr(cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) -> bool
{
288 ExprKind
::Path(ref qpath
) => is_lang_ctor(cx
, qpath
, OptionNone
),
296 ) => is_none_expr(cx
, expr
),