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};
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,
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};
15 #[expect(clippy::too_many_arguments)]
16 #[expect(clippy::too_many_lines)]
17 pub(super) fn check_with<'tcx, F>(
18 cx: &LateContext<'tcx>,
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<'_>,
26 ) -> Option<SuggInfo<'tcx>>
28 F: Fn(&LateContext<'tcx>, &'tcx Pat<'_>, &'tcx Expr<'_>, SyntaxContext) -> Option<SomeExpr<'tcx>>,
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))
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)),
43 (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
44 (else_body, pattern, ref_count, true)
46 (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
47 (else_body, pattern, ref_count, false)
49 (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_body) => {
50 (then_body, pattern, ref_count, true)
52 (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_body) => {
53 (then_body, pattern, ref_count, false)
58 // Top level or patterns aren't allowed in closures.
59 if matches!(some_pat.kind, PatKind::Or(_)) {
63 let Some(some_expr) = get_some_expr_fn(cx, some_pat, some_expr, expr_ctxt) else {
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)
74 // `map` won't perform any adjustments.
75 if !cx.typeck_results().expr_adjustments(some_expr.expr).is_empty() {
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));
83 let as_ref_str = match binding_ref {
84 Some(Mutability::Mut) => ".as_mut()",
85 Some(Mutability::Not) => ".as_ref()",
89 match can_move_expr_to_closure(cx, some_expr.expr) {
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),
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 => {
105 Some(CaptureKind::Ref(Mutability::Not)) | None => (),
113 let mut app = Applicability::MachineApplicable;
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})")
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 {
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();
132 snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
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()
141 // `ref` and `ref mut` annotations were handled earlier.
142 let annotation = if matches!(annotation, BindingAnnotation::MUT) {
148 if some_expr.needs_unsafe_block {
149 format!("|{annotation}{some_binding}| unsafe {{ {closure_expr_snip} }}")
151 format!("|{annotation}{some_binding}| {closure_expr_snip}")
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} }}")
161 format!("|{pat_snip}| {closure_expr_snip}")
164 // Refutable bindings and mixed reference annotations can't be handled by `map`.
168 // relies on the fact that Option<T>: Copy where T: copy
169 let scrutinee_impl_copy = is_copy(cx, scrutinee_ty);
172 needs_brackets: else_pat.is_none() && is_else_clause(cx.tcx, expr),
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,
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>> {
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()) =>
206 pub(super) enum OptionPat<'a> {
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.
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
224 impl<'tcx> SomeExpr<'tcx> {
225 pub fn new_no_negated(expr: &'tcx Expr<'tcx>, needs_unsafe_block: bool) -> Self {
229 needs_negated: false,
233 pub fn to_snippet_with_context(
235 cx: &LateContext<'tcx>,
237 app: &mut Applicability,
239 let sugg = Sugg::hir_with_context(cx, self.expr, ctxt, "..", app);
240 if self.needs_negated { !sugg } else { sugg }
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>,
250 ) -> Option<OptionPat<'tcx>> {
252 cx: &LateContext<'tcx>,
256 ) -> Option<OptionPat<'tcx>> {
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)
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 =>
266 Some(OptionPat::Some { pattern, ref_count })
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)