]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/matches/manual_utils.rs
Auto merge of #105252 - bjorn3:codegen_less_pair_values, r=nagisa
[rust.git] / src / tools / clippy / clippy_lints / src / matches / manual_utils.rs
1 use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF};
2 use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
3 use clippy_utils::ty::{is_copy, is_type_diagnostic_item, peel_mid_ty_refs_is_mutable, type_is_unsafe_function};
4 use clippy_utils::{
5     can_move_expr_to_closure, is_else_clause, is_lint_allowed, is_res_lang_ctor, path_res, path_to_local_id,
6     peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, sugg::Sugg, CaptureKind,
7 };
8 use rustc_ast::util::parser::PREC_POSTFIX;
9 use rustc_errors::Applicability;
10 use rustc_hir::LangItem::{OptionNone, OptionSome};
11 use rustc_hir::{def::Res, BindingAnnotation, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath};
12 use rustc_lint::LateContext;
13 use rustc_span::{sym, SyntaxContext};
14
15 #[expect(clippy::too_many_arguments)]
16 #[expect(clippy::too_many_lines)]
17 pub(super) fn check_with<'tcx, F>(
18     cx: &LateContext<'tcx>,
19     expr: &'tcx Expr<'_>,
20     scrutinee: &'tcx Expr<'_>,
21     then_pat: &'tcx Pat<'_>,
22     then_body: &'tcx Expr<'_>,
23     else_pat: Option<&'tcx Pat<'_>>,
24     else_body: &'tcx Expr<'_>,
25     get_some_expr_fn: F,
26 ) -> Option<SuggInfo<'tcx>>
27 where
28     F: Fn(&LateContext<'tcx>, &'tcx Pat<'_>, &'tcx Expr<'_>, SyntaxContext) -> Option<SomeExpr<'tcx>>,
29 {
30     let (scrutinee_ty, ty_ref_count, ty_mutability) =
31         peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee));
32     if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::Option)
33         && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Option))
34     {
35         return None;
36     }
37
38     let expr_ctxt = expr.span.ctxt();
39     let (some_expr, some_pat, pat_ref_count, is_wild_none) = match (
40         try_parse_pattern(cx, then_pat, expr_ctxt),
41         else_pat.map_or(Some(OptionPat::Wild), |p| try_parse_pattern(cx, p, expr_ctxt)),
42     ) {
43         (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
44             (else_body, pattern, ref_count, true)
45         },
46         (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
47             (else_body, pattern, ref_count, false)
48         },
49         (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_body) => {
50             (then_body, pattern, ref_count, true)
51         },
52         (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_body) => {
53             (then_body, pattern, ref_count, false)
54         },
55         _ => return None,
56     };
57
58     // Top level or patterns aren't allowed in closures.
59     if matches!(some_pat.kind, PatKind::Or(_)) {
60         return None;
61     }
62
63     let Some(some_expr) = get_some_expr_fn(cx, some_pat, some_expr, expr_ctxt) else {
64         return None;
65     };
66
67     // These two lints will go back and forth with each other.
68     if cx.typeck_results().expr_ty(some_expr.expr) == cx.tcx.types.unit
69         && !is_lint_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id)
70     {
71         return None;
72     }
73
74     // `map` won't perform any adjustments.
75     if !cx.typeck_results().expr_adjustments(some_expr.expr).is_empty() {
76         return None;
77     }
78
79     // Determine which binding mode to use.
80     let explicit_ref = some_pat.contains_explicit_ref_binding();
81     let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then_some(ty_mutability));
82
83     let as_ref_str = match binding_ref {
84         Some(Mutability::Mut) => ".as_mut()",
85         Some(Mutability::Not) => ".as_ref()",
86         None => "",
87     };
88
89     match can_move_expr_to_closure(cx, some_expr.expr) {
90         Some(captures) => {
91             // Check if captures the closure will need conflict with borrows made in the scrutinee.
92             // TODO: check all the references made in the scrutinee expression. This will require interacting
93             // with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
94             if let Some(binding_ref_mutability) = binding_ref {
95                 let e = peel_hir_expr_while(scrutinee, |e| match e.kind {
96                     ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
97                     _ => None,
98                 });
99                 if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(l), .. })) = e.kind {
100                     match captures.get(l) {
101                         Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return None,
102                         Some(CaptureKind::Ref(Mutability::Not)) if binding_ref_mutability == Mutability::Mut => {
103                             return None;
104                         },
105                         Some(CaptureKind::Ref(Mutability::Not)) | None => (),
106                     }
107                 }
108             }
109         },
110         None => return None,
111     };
112
113     let mut app = Applicability::MachineApplicable;
114
115     // Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
116     // it's being passed by value.
117     let scrutinee = peel_hir_expr_refs(scrutinee).0;
118     let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app);
119     let scrutinee_str = if scrutinee.span.ctxt() == expr.span.ctxt() && scrutinee.precedence().order() < PREC_POSTFIX {
120         format!("({scrutinee_str})")
121     } else {
122         scrutinee_str.into()
123     };
124
125     let closure_expr_snip = some_expr.to_snippet_with_context(cx, expr_ctxt, &mut app);
126     let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
127         if_chain! {
128             if !some_expr.needs_unsafe_block;
129             if let Some(func) = can_pass_as_func(cx, id, some_expr.expr);
130             if func.span.ctxt() == some_expr.expr.span.ctxt();
131             then {
132                 snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
133             } else {
134                 if path_to_local_id(some_expr.expr, id)
135                     && !is_lint_allowed(cx, MATCH_AS_REF, expr.hir_id)
136                     && binding_ref.is_some()
137                 {
138                     return None;
139                 }
140
141                 // `ref` and `ref mut` annotations were handled earlier.
142                 let annotation = if matches!(annotation, BindingAnnotation::MUT) {
143                     "mut "
144                 } else {
145                     ""
146                 };
147
148                 if some_expr.needs_unsafe_block {
149                     format!("|{annotation}{some_binding}| unsafe {{ {closure_expr_snip} }}")
150                 } else {
151                     format!("|{annotation}{some_binding}| {closure_expr_snip}")
152                 }
153             }
154         }
155     } else if !is_wild_none && explicit_ref.is_none() {
156         // TODO: handle explicit reference annotations.
157         let pat_snip = snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0;
158         if some_expr.needs_unsafe_block {
159             format!("|{pat_snip}| unsafe {{ {closure_expr_snip} }}")
160         } else {
161             format!("|{pat_snip}| {closure_expr_snip}")
162         }
163     } else {
164         // Refutable bindings and mixed reference annotations can't be handled by `map`.
165         return None;
166     };
167
168     // relies on the fact that Option<T>: Copy where T: copy
169     let scrutinee_impl_copy = is_copy(cx, scrutinee_ty);
170
171     Some(SuggInfo {
172         needs_brackets: else_pat.is_none() && is_else_clause(cx.tcx, expr),
173         scrutinee_impl_copy,
174         scrutinee_str,
175         as_ref_str,
176         body_str,
177         app,
178     })
179 }
180
181 pub struct SuggInfo<'a> {
182     pub needs_brackets: bool,
183     pub scrutinee_impl_copy: bool,
184     pub scrutinee_str: String,
185     pub as_ref_str: &'a str,
186     pub body_str: String,
187     pub app: Applicability,
188 }
189
190 // Checks whether the expression could be passed as a function, or whether a closure is needed.
191 // Returns the function to be passed to `map` if it exists.
192 fn can_pass_as_func<'tcx>(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
193     match expr.kind {
194         ExprKind::Call(func, [arg])
195             if path_to_local_id(arg, binding)
196                 && cx.typeck_results().expr_adjustments(arg).is_empty()
197                 && !type_is_unsafe_function(cx, cx.typeck_results().expr_ty(func).peel_refs()) =>
198         {
199             Some(func)
200         },
201         _ => None,
202     }
203 }
204
205 #[derive(Debug)]
206 pub(super) enum OptionPat<'a> {
207     Wild,
208     None,
209     Some {
210         // The pattern contained in the `Some` tuple.
211         pattern: &'a Pat<'a>,
212         // The number of references before the `Some` tuple.
213         // e.g. `&&Some(_)` has a ref count of 2.
214         ref_count: usize,
215     },
216 }
217
218 pub(super) struct SomeExpr<'tcx> {
219     pub expr: &'tcx Expr<'tcx>,
220     pub needs_unsafe_block: bool,
221     pub needs_negated: bool, // for `manual_filter` lint
222 }
223
224 impl<'tcx> SomeExpr<'tcx> {
225     pub fn new_no_negated(expr: &'tcx Expr<'tcx>, needs_unsafe_block: bool) -> Self {
226         Self {
227             expr,
228             needs_unsafe_block,
229             needs_negated: false,
230         }
231     }
232
233     pub fn to_snippet_with_context(
234         &self,
235         cx: &LateContext<'tcx>,
236         ctxt: SyntaxContext,
237         app: &mut Applicability,
238     ) -> Sugg<'tcx> {
239         let sugg = Sugg::hir_with_context(cx, self.expr, ctxt, "..", app);
240         if self.needs_negated { !sugg } else { sugg }
241     }
242 }
243
244 // Try to parse into a recognized `Option` pattern.
245 // i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
246 pub(super) fn try_parse_pattern<'tcx>(
247     cx: &LateContext<'tcx>,
248     pat: &'tcx Pat<'_>,
249     ctxt: SyntaxContext,
250 ) -> Option<OptionPat<'tcx>> {
251     fn f<'tcx>(
252         cx: &LateContext<'tcx>,
253         pat: &'tcx Pat<'_>,
254         ref_count: usize,
255         ctxt: SyntaxContext,
256     ) -> Option<OptionPat<'tcx>> {
257         match pat.kind {
258             PatKind::Wild => Some(OptionPat::Wild),
259             PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt),
260             PatKind::Path(ref qpath) if is_res_lang_ctor(cx, cx.qpath_res(qpath, pat.hir_id), OptionNone) => {
261                 Some(OptionPat::None)
262             },
263             PatKind::TupleStruct(ref qpath, [pattern], _)
264                 if is_res_lang_ctor(cx, cx.qpath_res(qpath, pat.hir_id), OptionSome) && pat.span.ctxt() == ctxt =>
265             {
266                 Some(OptionPat::Some { pattern, ref_count })
267             },
268             _ => None,
269         }
270     }
271     f(cx, pat, 0, ctxt)
272 }
273
274 // Checks for the `None` value.
275 fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
276     is_res_lang_ctor(cx, path_res(cx, peel_blocks(expr)), OptionNone)
277 }