use clippy_utils::diagnostics::span_lint_and_then; use rustc_errors::Applicability; use rustc_hir::{BindingAnnotation, Mutability, Node, Pat, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { /// ### What it does /// Checks for bindings that needlessly destructure a reference and borrow the inner /// value with `&ref`. /// /// ### Why is this bad? /// This pattern has no effect in almost all cases. /// /// ### Example /// ```rust /// let mut v = Vec::::new(); /// v.iter_mut().filter(|&ref a| a.is_empty()); /// /// if let &[ref first, ref second] = v.as_slice() {} /// ``` /// /// Use instead: /// ```rust /// let mut v = Vec::::new(); /// v.iter_mut().filter(|a| a.is_empty()); /// /// if let [first, second] = v.as_slice() {} /// ``` #[clippy::version = "pre 1.29.0"] pub NEEDLESS_BORROWED_REFERENCE, complexity, "destructuring a reference and borrowing the inner value" } declare_lint_pass!(NeedlessBorrowedRef => [NEEDLESS_BORROWED_REFERENCE]); impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowedRef { fn check_pat(&mut self, cx: &LateContext<'tcx>, ref_pat: &'tcx Pat<'_>) { if ref_pat.span.from_expansion() { // OK, simple enough, lints doesn't check in macro. return; } // Do not lint patterns that are part of an OR `|` pattern, the binding mode must match in all arms for (_, node) in cx.tcx.hir().parent_iter(ref_pat.hir_id) { let Node::Pat(pat) = node else { break }; if matches!(pat.kind, PatKind::Or(_)) { return; } } // Only lint immutable refs, because `&mut ref T` may be useful. let PatKind::Ref(pat, Mutability::Not) = ref_pat.kind else { return }; match pat.kind { // Check sub_pat got a `ref` keyword (excluding `ref mut`). PatKind::Binding(BindingAnnotation::REF, _, ident, None) => { span_lint_and_then( cx, NEEDLESS_BORROWED_REFERENCE, ref_pat.span, "this pattern takes a reference on something that is being dereferenced", |diag| { // `&ref ident` // ^^^^^ let span = ref_pat.span.until(ident.span); diag.span_suggestion_verbose( span, "try removing the `&ref` part", String::new(), Applicability::MachineApplicable, ); }, ); }, // Slices where each element is `ref`: `&[ref a, ref b, ..., ref z]` PatKind::Slice( before, None | Some(Pat { kind: PatKind::Wild, .. }), after, ) => { check_subpatterns( cx, "dereferencing a slice pattern where every element takes a reference", ref_pat, pat, itertools::chain(before, after), ); }, PatKind::Tuple(subpatterns, _) | PatKind::TupleStruct(_, subpatterns, _) => { check_subpatterns( cx, "dereferencing a tuple pattern where every element takes a reference", ref_pat, pat, subpatterns, ); }, PatKind::Struct(_, fields, _) => { check_subpatterns( cx, "dereferencing a struct pattern where every field's pattern takes a reference", ref_pat, pat, fields.iter().map(|field| field.pat), ); }, _ => {}, } } } fn check_subpatterns<'tcx>( cx: &LateContext<'tcx>, message: &str, ref_pat: &Pat<'_>, pat: &Pat<'_>, subpatterns: impl IntoIterator>, ) { let mut suggestions = Vec::new(); for subpattern in subpatterns { match subpattern.kind { PatKind::Binding(BindingAnnotation::REF, _, ident, None) => { // `ref ident` // ^^^^ let span = subpattern.span.until(ident.span); suggestions.push((span, String::new())); }, PatKind::Wild => {}, _ => return, } } if !suggestions.is_empty() { span_lint_and_then(cx, NEEDLESS_BORROWED_REFERENCE, ref_pat.span, message, |diag| { // `&pat` // ^ let span = ref_pat.span.until(pat.span); suggestions.push((span, String::new())); diag.multipart_suggestion( "try removing the `&` and `ref` parts", suggestions, Applicability::MachineApplicable, ); }); } }