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
::IfLetOrMatch
;
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, type_is_unsafe_function}
;
7 can_move_expr_to_closure
, in_constant
, is_else_clause
, is_lang_ctor
, is_lint_allowed
, path_to_local_id
,
8 peel_blocks
, peel_hir_expr_refs
, peel_hir_expr_while
, CaptureKind
,
10 use rustc_ast
::util
::parser
::PREC_POSTFIX
;
11 use rustc_errors
::Applicability
;
12 use rustc_hir
::LangItem
::{OptionNone, OptionSome}
;
14 def
::Res
, Arm
, BindingAnnotation
, Block
, BlockCheckMode
, Expr
, ExprKind
, HirId
, Mutability
, Pat
, PatKind
, Path
,
17 use rustc_lint
::{LateContext, LateLintPass, LintContext}
;
18 use rustc_middle
::lint
::in_external_macro
;
19 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
20 use rustc_span
::{sym, SyntaxContext}
;
22 declare_clippy_lint
! {
24 /// Checks for usages of `match` which could be implemented using `map`
26 /// ### Why is this bad?
27 /// Using the `map` method is clearer and more concise.
32 /// Some(x) => Some(x + 1),
38 /// Some(0).map(|x| x + 1);
40 #[clippy::version = "1.52.0"]
43 "reimplementation of `map`"
46 declare_lint_pass
!(ManualMap
=> [MANUAL_MAP
]);
48 impl<'tcx
> LateLintPass
<'tcx
> for ManualMap
{
49 #[allow(clippy::too_many_lines)]
50 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
51 let (scrutinee
, then_pat
, then_body
, else_pat
, else_body
) = match IfLetOrMatch
::parse(cx
, expr
) {
52 Some(IfLetOrMatch
::IfLet(scrutinee
, pat
, body
, Some(r
#else))) => (scrutinee, pat, body, None, r#else),
53 Some(IfLetOrMatch
::Match(
55 [arm1 @ Arm { guard: None, .. }
, arm2 @ Arm { guard: None, .. }
],
57 )) => (scrutinee
, arm1
.pat
, arm1
.body
, Some(arm2
.pat
), arm2
.body
),
60 if in_external_macro(cx
.sess(), expr
.span
) || in_constant(cx
, expr
.hir_id
) {
64 let (scrutinee_ty
, ty_ref_count
, ty_mutability
) =
65 peel_mid_ty_refs_is_mutable(cx
.typeck_results().expr_ty(scrutinee
));
66 if !(is_type_diagnostic_item(cx
, scrutinee_ty
, sym
::Option
)
67 && is_type_diagnostic_item(cx
, cx
.typeck_results().expr_ty(expr
), sym
::Option
))
72 let expr_ctxt
= expr
.span
.ctxt();
73 let (some_expr
, some_pat
, pat_ref_count
, is_wild_none
) = match (
74 try_parse_pattern(cx
, then_pat
, expr_ctxt
),
75 else_pat
.map_or(Some(OptionPat
::Wild
), |p
| try_parse_pattern(cx
, p
, expr_ctxt
)),
77 (Some(OptionPat
::Wild
), Some(OptionPat
::Some { pattern, ref_count }
)) if is_none_expr(cx
, then_body
) => {
78 (else_body
, pattern
, ref_count
, true)
80 (Some(OptionPat
::None
), Some(OptionPat
::Some { pattern, ref_count }
)) if is_none_expr(cx
, then_body
) => {
81 (else_body
, pattern
, ref_count
, false)
83 (Some(OptionPat
::Some { pattern, ref_count }
), Some(OptionPat
::Wild
)) if is_none_expr(cx
, else_body
) => {
84 (then_body
, pattern
, ref_count
, true)
86 (Some(OptionPat
::Some { pattern, ref_count }
), Some(OptionPat
::None
)) if is_none_expr(cx
, else_body
) => {
87 (then_body
, pattern
, ref_count
, false)
92 // Top level or patterns aren't allowed in closures.
93 if matches
!(some_pat
.kind
, PatKind
::Or(_
)) {
97 let some_expr
= match get_some_expr(cx
, some_expr
, false, expr_ctxt
) {
102 // These two lints will go back and forth with each other.
103 if cx
.typeck_results().expr_ty(some_expr
.expr
) == cx
.tcx
.types
.unit
104 && !is_lint_allowed(cx
, OPTION_MAP_UNIT_FN
, expr
.hir_id
)
109 // `map` won't perform any adjustments.
110 if !cx
.typeck_results().expr_adjustments(some_expr
.expr
).is_empty() {
114 // Determine which binding mode to use.
115 let explicit_ref
= some_pat
.contains_explicit_ref_binding();
116 let binding_ref
= explicit_ref
.or_else(|| (ty_ref_count
!= pat_ref_count
).then(|| ty_mutability
));
118 let as_ref_str
= match binding_ref
{
119 Some(Mutability
::Mut
) => ".as_mut()",
120 Some(Mutability
::Not
) => ".as_ref()",
124 match can_move_expr_to_closure(cx
, some_expr
.expr
) {
126 // Check if captures the closure will need conflict with borrows made in the scrutinee.
127 // TODO: check all the references made in the scrutinee expression. This will require interacting
128 // with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
129 if let Some(binding_ref_mutability
) = binding_ref
{
130 let e
= peel_hir_expr_while(scrutinee
, |e
| match e
.kind
{
131 ExprKind
::Field(e
, _
) | ExprKind
::AddrOf(_
, _
, e
) => Some(e
),
134 if let ExprKind
::Path(QPath
::Resolved(None
, Path { res: Res::Local(l), .. }
)) = e
.kind
{
135 match captures
.get(l
) {
136 Some(CaptureKind
::Value
| CaptureKind
::Ref(Mutability
::Mut
)) => return,
137 Some(CaptureKind
::Ref(Mutability
::Not
)) if binding_ref_mutability
== Mutability
::Mut
=> {
140 Some(CaptureKind
::Ref(Mutability
::Not
)) | None
=> (),
148 let mut app
= Applicability
::MachineApplicable
;
150 // Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
151 // it's being passed by value.
152 let scrutinee
= peel_hir_expr_refs(scrutinee
).0;
153 let (scrutinee_str
, _
) = snippet_with_context(cx
, scrutinee
.span
, expr_ctxt
, "..", &mut app
);
155 if scrutinee
.span
.ctxt() == expr
.span
.ctxt() && scrutinee
.precedence().order() < PREC_POSTFIX
{
156 format
!("({})", scrutinee_str
)
161 let body_str
= if let PatKind
::Binding(annotation
, id
, some_binding
, None
) = some_pat
.kind
{
163 if !some_expr
.needs_unsafe_block
;
164 if let Some(func
) = can_pass_as_func(cx
, id
, some_expr
.expr
);
165 if func
.span
.ctxt() == some_expr
.expr
.span
.ctxt();
167 snippet_with_applicability(cx
, func
.span
, "..", &mut app
).into_owned()
169 if path_to_local_id(some_expr
.expr
, id
)
170 && !is_lint_allowed(cx
, MATCH_AS_REF
, expr
.hir_id
)
171 && binding_ref
.is_some()
176 // `ref` and `ref mut` annotations were handled earlier.
177 let annotation
= if matches
!(annotation
, BindingAnnotation
::Mutable
) {
182 let expr_snip
= snippet_with_context(cx
, some_expr
.expr
.span
, expr_ctxt
, "..", &mut app
).0;
183 if some_expr
.needs_unsafe_block
{
184 format
!("|{}{}| unsafe {{ {} }}", annotation
, some_binding
, expr_snip
)
186 format
!("|{}{}| {}", annotation
, some_binding
, expr_snip
)
190 } else if !is_wild_none
&& explicit_ref
.is_none() {
191 // TODO: handle explicit reference annotations.
192 let pat_snip
= snippet_with_context(cx
, some_pat
.span
, expr_ctxt
, "..", &mut app
).0;
193 let expr_snip
= snippet_with_context(cx
, some_expr
.expr
.span
, expr_ctxt
, "..", &mut app
).0;
194 if some_expr
.needs_unsafe_block
{
195 format
!("|{}| unsafe {{ {} }}", pat_snip
, expr_snip
)
197 format
!("|{}| {}", pat_snip
, expr_snip
)
200 // Refutable bindings and mixed reference annotations can't be handled by `map`.
208 "manual implementation of `Option::map`",
210 if else_pat
.is_none() && is_else_clause(cx
.tcx
, expr
) {
211 format
!("{{ {}{}.map({}) }}", scrutinee_str
, as_ref_str
, body_str
)
213 format
!("{}{}.map({})", scrutinee_str
, as_ref_str
, body_str
)
220 // Checks whether the expression could be passed as a function, or whether a closure is needed.
221 // Returns the function to be passed to `map` if it exists.
222 fn can_pass_as_func
<'tcx
>(cx
: &LateContext
<'tcx
>, binding
: HirId
, expr
: &'tcx Expr
<'_
>) -> Option
<&'tcx Expr
<'tcx
>> {
224 ExprKind
::Call(func
, [arg
])
225 if path_to_local_id(arg
, binding
)
226 && cx
.typeck_results().expr_adjustments(arg
).is_empty()
227 && !type_is_unsafe_function(cx
, cx
.typeck_results().expr_ty(func
).peel_refs()) =>
239 // The pattern contained in the `Some` tuple.
240 pattern
: &'a Pat
<'a
>,
241 // The number of references before the `Some` tuple.
242 // e.g. `&&Some(_)` has a ref count of 2.
247 struct SomeExpr
<'tcx
> {
248 expr
: &'tcx Expr
<'tcx
>,
249 needs_unsafe_block
: bool
,
252 // Try to parse into a recognized `Option` pattern.
253 // i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
254 fn try_parse_pattern
<'tcx
>(cx
: &LateContext
<'tcx
>, pat
: &'tcx Pat
<'_
>, ctxt
: SyntaxContext
) -> Option
<OptionPat
<'tcx
>> {
256 cx
: &LateContext
<'tcx
>,
260 ) -> Option
<OptionPat
<'tcx
>> {
262 PatKind
::Wild
=> Some(OptionPat
::Wild
),
263 PatKind
::Ref(pat
, _
) => f(cx
, pat
, ref_count
+ 1, ctxt
),
264 PatKind
::Path(ref qpath
) if is_lang_ctor(cx
, qpath
, OptionNone
) => Some(OptionPat
::None
),
265 PatKind
::TupleStruct(ref qpath
, [pattern
], _
)
266 if is_lang_ctor(cx
, qpath
, OptionSome
) && pat
.span
.ctxt() == ctxt
=>
268 Some(OptionPat
::Some { pattern, ref_count }
)
276 // Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.
277 fn get_some_expr
<'tcx
>(
278 cx
: &LateContext
<'tcx
>,
279 expr
: &'tcx Expr
<'_
>,
280 needs_unsafe_block
: bool
,
282 ) -> Option
<SomeExpr
<'tcx
>> {
283 // TODO: Allow more complex expressions.
287 kind
: ExprKind
::Path(ref qpath
),
291 ) if ctxt
== expr
.span
.ctxt() && is_lang_ctor(cx
, qpath
, OptionSome
) => Some(SomeExpr
{
306 needs_unsafe_block
|| *rules
== BlockCheckMode
::UnsafeBlock(UnsafeSource
::UserProvided
),
313 // Checks for the `None` value.
314 fn is_none_expr(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) -> bool
{
315 matches
!(peel_blocks(expr
).kind
, ExprKind
::Path(ref qpath
) if is_lang_ctor(cx
, qpath
, OptionNone
))