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, type_is_unsafe_function}
;
6 can_move_expr_to_closure
, is_else_clause
, is_lang_ctor
, is_lint_allowed
, path_to_local_id
, peel_blocks
,
7 peel_hir_expr_refs
, peel_hir_expr_while
, CaptureKind
,
9 use rustc_ast
::util
::parser
::PREC_POSTFIX
;
10 use rustc_errors
::Applicability
;
11 use rustc_hir
::LangItem
::{OptionNone, OptionSome}
;
13 def
::Res
, Arm
, BindingAnnotation
, Block
, BlockCheckMode
, Expr
, ExprKind
, HirId
, Mutability
, Pat
, PatKind
, Path
,
16 use rustc_lint
::LateContext
;
17 use rustc_span
::{sym, SyntaxContext}
;
19 use super::MANUAL_MAP
;
21 pub(super) fn check_match
<'tcx
>(
22 cx
: &LateContext
<'tcx
>,
24 scrutinee
: &'tcx Expr
<'_
>,
25 arms
: &'tcx
[Arm
<'_
>],
27 if let [arm1
, arm2
] = arms
28 && arm1
.guard
.is_none()
29 && arm2
.guard
.is_none()
31 check(cx
, expr
, scrutinee
, arm1
.pat
, arm1
.body
, Some(arm2
.pat
), arm2
.body
);
35 pub(super) fn check_if_let
<'tcx
>(
36 cx
: &LateContext
<'tcx
>,
38 let_pat
: &'tcx Pat
<'_
>,
39 let_expr
: &'tcx Expr
<'_
>,
40 then_expr
: &'tcx Expr
<'_
>,
41 else_expr
: &'tcx Expr
<'_
>,
43 check(cx
, expr
, let_expr
, let_pat
, then_expr
, None
, else_expr
);
46 #[expect(clippy::too_many_lines)]
48 cx
: &LateContext
<'tcx
>,
50 scrutinee
: &'tcx Expr
<'_
>,
51 then_pat
: &'tcx Pat
<'_
>,
52 then_body
: &'tcx Expr
<'_
>,
53 else_pat
: Option
<&'tcx Pat
<'_
>>,
54 else_body
: &'tcx Expr
<'_
>,
56 let (scrutinee_ty
, ty_ref_count
, ty_mutability
) =
57 peel_mid_ty_refs_is_mutable(cx
.typeck_results().expr_ty(scrutinee
));
58 if !(is_type_diagnostic_item(cx
, scrutinee_ty
, sym
::Option
)
59 && is_type_diagnostic_item(cx
, cx
.typeck_results().expr_ty(expr
), sym
::Option
))
64 let expr_ctxt
= expr
.span
.ctxt();
65 let (some_expr
, some_pat
, pat_ref_count
, is_wild_none
) = match (
66 try_parse_pattern(cx
, then_pat
, expr_ctxt
),
67 else_pat
.map_or(Some(OptionPat
::Wild
), |p
| try_parse_pattern(cx
, p
, expr_ctxt
)),
69 (Some(OptionPat
::Wild
), Some(OptionPat
::Some { pattern, ref_count }
)) if is_none_expr(cx
, then_body
) => {
70 (else_body
, pattern
, ref_count
, true)
72 (Some(OptionPat
::None
), Some(OptionPat
::Some { pattern, ref_count }
)) if is_none_expr(cx
, then_body
) => {
73 (else_body
, pattern
, ref_count
, false)
75 (Some(OptionPat
::Some { pattern, ref_count }
), Some(OptionPat
::Wild
)) if is_none_expr(cx
, else_body
) => {
76 (then_body
, pattern
, ref_count
, true)
78 (Some(OptionPat
::Some { pattern, ref_count }
), Some(OptionPat
::None
)) if is_none_expr(cx
, else_body
) => {
79 (then_body
, pattern
, ref_count
, false)
84 // Top level or patterns aren't allowed in closures.
85 if matches
!(some_pat
.kind
, PatKind
::Or(_
)) {
89 let some_expr
= match get_some_expr(cx
, some_expr
, false, expr_ctxt
) {
94 // These two lints will go back and forth with each other.
95 if cx
.typeck_results().expr_ty(some_expr
.expr
) == cx
.tcx
.types
.unit
96 && !is_lint_allowed(cx
, OPTION_MAP_UNIT_FN
, expr
.hir_id
)
101 // `map` won't perform any adjustments.
102 if !cx
.typeck_results().expr_adjustments(some_expr
.expr
).is_empty() {
106 // Determine which binding mode to use.
107 let explicit_ref
= some_pat
.contains_explicit_ref_binding();
108 let binding_ref
= explicit_ref
.or_else(|| (ty_ref_count
!= pat_ref_count
).then(|| ty_mutability
));
110 let as_ref_str
= match binding_ref
{
111 Some(Mutability
::Mut
) => ".as_mut()",
112 Some(Mutability
::Not
) => ".as_ref()",
116 match can_move_expr_to_closure(cx
, some_expr
.expr
) {
118 // Check if captures the closure will need conflict with borrows made in the scrutinee.
119 // TODO: check all the references made in the scrutinee expression. This will require interacting
120 // with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
121 if let Some(binding_ref_mutability
) = binding_ref
{
122 let e
= peel_hir_expr_while(scrutinee
, |e
| match e
.kind
{
123 ExprKind
::Field(e
, _
) | ExprKind
::AddrOf(_
, _
, e
) => Some(e
),
126 if let ExprKind
::Path(QPath
::Resolved(None
, Path { res: Res::Local(l), .. }
)) = e
.kind
{
127 match captures
.get(l
) {
128 Some(CaptureKind
::Value
| CaptureKind
::Ref(Mutability
::Mut
)) => return,
129 Some(CaptureKind
::Ref(Mutability
::Not
)) if binding_ref_mutability
== Mutability
::Mut
=> {
132 Some(CaptureKind
::Ref(Mutability
::Not
)) | None
=> (),
140 let mut app
= Applicability
::MachineApplicable
;
142 // Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
143 // it's being passed by value.
144 let scrutinee
= peel_hir_expr_refs(scrutinee
).0;
145 let (scrutinee_str
, _
) = snippet_with_context(cx
, scrutinee
.span
, expr_ctxt
, "..", &mut app
);
146 let scrutinee_str
= if scrutinee
.span
.ctxt() == expr
.span
.ctxt() && scrutinee
.precedence().order() < PREC_POSTFIX
{
147 format
!("({})", scrutinee_str
)
152 let body_str
= if let PatKind
::Binding(annotation
, id
, some_binding
, None
) = some_pat
.kind
{
154 if !some_expr
.needs_unsafe_block
;
155 if let Some(func
) = can_pass_as_func(cx
, id
, some_expr
.expr
);
156 if func
.span
.ctxt() == some_expr
.expr
.span
.ctxt();
158 snippet_with_applicability(cx
, func
.span
, "..", &mut app
).into_owned()
160 if path_to_local_id(some_expr
.expr
, id
)
161 && !is_lint_allowed(cx
, MATCH_AS_REF
, expr
.hir_id
)
162 && binding_ref
.is_some()
167 // `ref` and `ref mut` annotations were handled earlier.
168 let annotation
= if matches
!(annotation
, BindingAnnotation
::Mutable
) {
173 let expr_snip
= snippet_with_context(cx
, some_expr
.expr
.span
, expr_ctxt
, "..", &mut app
).0;
174 if some_expr
.needs_unsafe_block
{
175 format
!("|{}{}| unsafe {{ {} }}", annotation
, some_binding
, expr_snip
)
177 format
!("|{}{}| {}", annotation
, some_binding
, expr_snip
)
181 } else if !is_wild_none
&& explicit_ref
.is_none() {
182 // TODO: handle explicit reference annotations.
183 let pat_snip
= snippet_with_context(cx
, some_pat
.span
, expr_ctxt
, "..", &mut app
).0;
184 let expr_snip
= snippet_with_context(cx
, some_expr
.expr
.span
, expr_ctxt
, "..", &mut app
).0;
185 if some_expr
.needs_unsafe_block
{
186 format
!("|{}| unsafe {{ {} }}", pat_snip
, expr_snip
)
188 format
!("|{}| {}", pat_snip
, expr_snip
)
191 // Refutable bindings and mixed reference annotations can't be handled by `map`.
199 "manual implementation of `Option::map`",
201 if else_pat
.is_none() && is_else_clause(cx
.tcx
, expr
) {
202 format
!("{{ {}{}.map({}) }}", scrutinee_str
, as_ref_str
, body_str
)
204 format
!("{}{}.map({})", scrutinee_str
, as_ref_str
, body_str
)
210 // Checks whether the expression could be passed as a function, or whether a closure is needed.
211 // Returns the function to be passed to `map` if it exists.
212 fn can_pass_as_func
<'tcx
>(cx
: &LateContext
<'tcx
>, binding
: HirId
, expr
: &'tcx Expr
<'_
>) -> Option
<&'tcx Expr
<'tcx
>> {
214 ExprKind
::Call(func
, [arg
])
215 if path_to_local_id(arg
, binding
)
216 && cx
.typeck_results().expr_adjustments(arg
).is_empty()
217 && !type_is_unsafe_function(cx
, cx
.typeck_results().expr_ty(func
).peel_refs()) =>
229 // The pattern contained in the `Some` tuple.
230 pattern
: &'a Pat
<'a
>,
231 // The number of references before the `Some` tuple.
232 // e.g. `&&Some(_)` has a ref count of 2.
237 struct SomeExpr
<'tcx
> {
238 expr
: &'tcx Expr
<'tcx
>,
239 needs_unsafe_block
: bool
,
242 // Try to parse into a recognized `Option` pattern.
243 // i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
244 fn try_parse_pattern
<'tcx
>(cx
: &LateContext
<'tcx
>, pat
: &'tcx Pat
<'_
>, ctxt
: SyntaxContext
) -> Option
<OptionPat
<'tcx
>> {
246 cx
: &LateContext
<'tcx
>,
250 ) -> Option
<OptionPat
<'tcx
>> {
252 PatKind
::Wild
=> Some(OptionPat
::Wild
),
253 PatKind
::Ref(pat
, _
) => f(cx
, pat
, ref_count
+ 1, ctxt
),
254 PatKind
::Path(ref qpath
) if is_lang_ctor(cx
, qpath
, OptionNone
) => Some(OptionPat
::None
),
255 PatKind
::TupleStruct(ref qpath
, [pattern
], _
)
256 if is_lang_ctor(cx
, qpath
, OptionSome
) && pat
.span
.ctxt() == ctxt
=>
258 Some(OptionPat
::Some { pattern, ref_count }
)
266 // Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.
267 fn get_some_expr
<'tcx
>(
268 cx
: &LateContext
<'tcx
>,
269 expr
: &'tcx Expr
<'_
>,
270 needs_unsafe_block
: bool
,
272 ) -> Option
<SomeExpr
<'tcx
>> {
273 // TODO: Allow more complex expressions.
277 kind
: ExprKind
::Path(ref qpath
),
281 ) if ctxt
== expr
.span
.ctxt() && is_lang_ctor(cx
, qpath
, OptionSome
) => Some(SomeExpr
{
296 needs_unsafe_block
|| *rules
== BlockCheckMode
::UnsafeBlock(UnsafeSource
::UserProvided
),
303 // Checks for the `None` value.
304 fn is_none_expr(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) -> bool
{
305 matches
!(peel_blocks(expr
).kind
, ExprKind
::Path(ref qpath
) if is_lang_ctor(cx
, qpath
, OptionNone
))