about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-12-12 22:57:24 +0000
committerbors <bors@rust-lang.org>2023-12-12 22:57:24 +0000
commitc19508b356c67eb188504fc41692220cc760508e (patch)
tree0b8f51ea28ea64edbb500bea282f7d6e07091df3
parent2e96c74dcea20816a34a73d2d3e336a4ebd35eef (diff)
parent884bec3d8558aef4c57bae1244fca76ab668e5b6 (diff)
downloadrust-c19508b356c67eb188504fc41692220cc760508e.tar.gz
rust-c19508b356c67eb188504fc41692220cc760508e.zip
Auto merge of #11895 - ericwu2003:useless_vec-FP, r=blyxyas
Useless vec false positive

changelog: [`useless_vec`]: fix false positive in macros.

fixes #11861

We delay the emission of `useless_vec` lints to the check_crate_post stage, which allows us to effectively undo lints if we find that a `vec![]` expression is being used multiple times after macro expansion.
-rw-r--r--clippy_lints/src/lib.rs3
-rw-r--r--clippy_lints/src/vec.rs157
-rw-r--r--tests/ui/vec.fixed34
-rw-r--r--tests/ui/vec.rs34
-rw-r--r--tests/ui/vec.stderr8
5 files changed, 164 insertions, 72 deletions
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index e4ff7ebb0f7..7758d6a58e6 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -50,6 +50,8 @@ extern crate clippy_utils;
 #[macro_use]
 extern crate declare_clippy_lint;
 
+use std::collections::BTreeMap;
+
 use rustc_data_structures::fx::FxHashSet;
 use rustc_lint::{Lint, LintId};
 
@@ -725,6 +727,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
         Box::new(vec::UselessVec {
             too_large_for_stack,
             msrv: msrv(),
+            span_to_lint_map: BTreeMap::new(),
         })
     });
     store.register_late_pass(|_| Box::new(panic_unimplemented::PanicUnimplemented));
diff --git a/clippy_lints/src/vec.rs b/clippy_lints/src/vec.rs
index ba958c5b392..5e13c73f035 100644
--- a/clippy_lints/src/vec.rs
+++ b/clippy_lints/src/vec.rs
@@ -1,25 +1,27 @@
+use std::collections::BTreeMap;
 use std::ops::ControlFlow;
 
 use clippy_config::msrvs::{self, Msrv};
 use clippy_utils::consts::{constant, Constant};
-use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::diagnostics::span_lint_hir_and_then;
 use clippy_utils::source::snippet_with_applicability;
 use clippy_utils::ty::is_copy;
 use clippy_utils::visitors::for_each_local_use_after_expr;
 use clippy_utils::{get_parent_expr, higher, is_trait_method};
 use rustc_errors::Applicability;
-use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Node, PatKind};
+use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Node, PatKind};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_middle::ty::layout::LayoutOf;
 use rustc_middle::ty::{self, Ty};
 use rustc_session::impl_lint_pass;
-use rustc_span::{sym, Span};
+use rustc_span::{sym, DesugaringKind, Span};
 
 #[expect(clippy::module_name_repetitions)]
 #[derive(Clone)]
 pub struct UselessVec {
     pub too_large_for_stack: u64,
     pub msrv: Msrv,
+    pub span_to_lint_map: BTreeMap<Span, Option<(HirId, SuggestedType, String, Applicability)>>,
 }
 
 declare_clippy_lint! {
@@ -69,64 +71,88 @@ pub fn is_allowed_vec_method(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
 
 impl<'tcx> LateLintPass<'tcx> for UselessVec {
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
-        // search for `&vec![_]` or `vec![_]` expressions where the adjusted type is `&[_]`
-        if adjusts_to_slice(cx, expr)
-            && let Some(vec_args) = higher::VecArgs::hir(cx, expr.peel_borrows())
-        {
-            let (suggest_slice, span) = if let ExprKind::AddrOf(BorrowKind::Ref, mutability, _) = expr.kind {
-                // `expr` is `&vec![_]`, so suggest `&[_]` (or `&mut[_]` resp.)
-                (SuggestedType::SliceRef(mutability), expr.span)
-            } else {
-                // `expr` is the `vec![_]` expansion, so suggest `[_]`
-                // and also use the span of the actual `vec![_]` expression
-                (SuggestedType::Array, expr.span.ctxt().outer_expn_data().call_site)
-            };
-
-            self.check_vec_macro(cx, &vec_args, span, suggest_slice);
-        }
+        if let Some(vec_args) = higher::VecArgs::hir(cx, expr.peel_borrows()) {
+            // search for `let foo = vec![_]` expressions where all uses of `foo`
+            // adjust to slices or call a method that exist on slices (e.g. len)
+            if let Node::Local(local) = cx.tcx.hir().get_parent(expr.hir_id)
+                // for now ignore locals with type annotations.
+                // this is to avoid compile errors when doing the suggestion here: let _: Vec<_> = vec![..];
+                && local.ty.is_none()
+                && let PatKind::Binding(_, id, ..) = local.pat.kind
+                && is_copy(cx, vec_type(cx.typeck_results().expr_ty_adjusted(expr.peel_borrows())))
+            {
+                let only_slice_uses = for_each_local_use_after_expr(cx, id, expr.hir_id, |expr| {
+                    // allow indexing into a vec and some set of allowed method calls that exist on slices, too
+                    if let Some(parent) = get_parent_expr(cx, expr)
+                        && (adjusts_to_slice(cx, expr)
+                            || matches!(parent.kind, ExprKind::Index(..))
+                            || is_allowed_vec_method(cx, parent))
+                    {
+                        ControlFlow::Continue(())
+                    } else {
+                        ControlFlow::Break(())
+                    }
+                })
+                .is_continue();
 
-        // search for `let foo = vec![_]` expressions where all uses of `foo`
-        // adjust to slices or call a method that exist on slices (e.g. len)
-        if let Some(vec_args) = higher::VecArgs::hir(cx, expr)
-            && let Node::Local(local) = cx.tcx.hir().get_parent(expr.hir_id)
-            // for now ignore locals with type annotations.
-            // this is to avoid compile errors when doing the suggestion here: let _: Vec<_> = vec![..];
-            && local.ty.is_none()
-            && let PatKind::Binding(_, id, ..) = local.pat.kind
-            && is_copy(cx, vec_type(cx.typeck_results().expr_ty_adjusted(expr)))
-        {
-            let only_slice_uses = for_each_local_use_after_expr(cx, id, expr.hir_id, |expr| {
-                // allow indexing into a vec and some set of allowed method calls that exist on slices, too
-                if let Some(parent) = get_parent_expr(cx, expr)
-                    && (adjusts_to_slice(cx, expr)
-                        || matches!(parent.kind, ExprKind::Index(..))
-                        || is_allowed_vec_method(cx, parent))
-                {
-                    ControlFlow::Continue(())
+                let span = expr.span.ctxt().outer_expn_data().call_site;
+                if only_slice_uses {
+                    self.check_vec_macro(cx, &vec_args, span, expr.hir_id, SuggestedType::Array);
                 } else {
-                    ControlFlow::Break(())
+                    self.span_to_lint_map.insert(span, None);
+                }
+            }
+            // if the local pattern has a specified type, do not lint.
+            else if let Some(_) = higher::VecArgs::hir(cx, expr)
+                && let Node::Local(local) = cx.tcx.hir().get_parent(expr.hir_id)
+                && local.ty.is_some()
+            {
+                let span = expr.span.ctxt().outer_expn_data().call_site;
+                self.span_to_lint_map.insert(span, None);
+            }
+            // search for `for _ in vec![...]`
+            else if let Some(parent) = get_parent_expr(cx, expr)
+                && parent.span.is_desugaring(DesugaringKind::ForLoop)
+                && self.msrv.meets(msrvs::ARRAY_INTO_ITERATOR)
+            {
+                // report the error around the `vec!` not inside `<std macros>:`
+                let span = expr.span.ctxt().outer_expn_data().call_site;
+                self.check_vec_macro(cx, &vec_args, span, expr.hir_id, SuggestedType::Array);
+            }
+            // search for `&vec![_]` or `vec![_]` expressions where the adjusted type is `&[_]`
+            else {
+                let (suggest_slice, span) = if let ExprKind::AddrOf(BorrowKind::Ref, mutability, _) = expr.kind {
+                    // `expr` is `&vec![_]`, so suggest `&[_]` (or `&mut[_]` resp.)
+                    (SuggestedType::SliceRef(mutability), expr.span)
+                } else {
+                    // `expr` is the `vec![_]` expansion, so suggest `[_]`
+                    // and also use the span of the actual `vec![_]` expression
+                    (SuggestedType::Array, expr.span.ctxt().outer_expn_data().call_site)
+                };
+
+                if adjusts_to_slice(cx, expr) {
+                    self.check_vec_macro(cx, &vec_args, span, expr.hir_id, suggest_slice);
+                } else {
+                    self.span_to_lint_map.insert(span, None);
                 }
-            })
-            .is_continue();
-
-            if only_slice_uses {
-                self.check_vec_macro(
-                    cx,
-                    &vec_args,
-                    expr.span.ctxt().outer_expn_data().call_site,
-                    SuggestedType::Array,
-                );
             }
         }
+    }
 
-        // search for `for _ in vec![…]`
-        if let Some(higher::ForLoop { arg, .. }) = higher::ForLoop::hir(expr)
-            && let Some(vec_args) = higher::VecArgs::hir(cx, arg)
-            && self.msrv.meets(msrvs::ARRAY_INTO_ITERATOR)
-        {
-            // report the error around the `vec!` not inside `<std macros>:`
-            let span = arg.span.ctxt().outer_expn_data().call_site;
-            self.check_vec_macro(cx, &vec_args, span, SuggestedType::Array);
+    fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
+        for (span, lint_opt) in &self.span_to_lint_map {
+            if let Some((hir_id, suggest_slice, snippet, applicability)) = lint_opt {
+                let help_msg = format!(
+                    "you can use {} directly",
+                    match suggest_slice {
+                        SuggestedType::SliceRef(_) => "a slice",
+                        SuggestedType::Array => "an array",
+                    }
+                );
+                span_lint_hir_and_then(cx, USELESS_VEC, *hir_id, *span, "useless use of `vec!`", |diag| {
+                    diag.span_suggestion(*span, help_msg, snippet, *applicability);
+                });
+            }
         }
     }
 
@@ -134,7 +160,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec {
 }
 
 #[derive(Copy, Clone)]
-enum SuggestedType {
+pub(crate) enum SuggestedType {
     /// Suggest using a slice `&[..]` / `&mut [..]`
     SliceRef(Mutability),
     /// Suggest using an array: `[..]`
@@ -147,6 +173,7 @@ impl UselessVec {
         cx: &LateContext<'tcx>,
         vec_args: &higher::VecArgs<'tcx>,
         span: Span,
+        hir_id: HirId,
         suggest_slice: SuggestedType,
     ) {
         if span.from_expansion() {
@@ -204,21 +231,9 @@ impl UselessVec {
             },
         };
 
-        span_lint_and_sugg(
-            cx,
-            USELESS_VEC,
-            span,
-            "useless use of `vec!`",
-            &format!(
-                "you can use {} directly",
-                match suggest_slice {
-                    SuggestedType::SliceRef(_) => "a slice",
-                    SuggestedType::Array => "an array",
-                }
-            ),
-            snippet,
-            applicability,
-        );
+        self.span_to_lint_map
+            .entry(span)
+            .or_insert(Some((hir_id, suggest_slice, snippet, applicability)));
     }
 }
 
diff --git a/tests/ui/vec.fixed b/tests/ui/vec.fixed
index bcbca971a78..81b8bd7da77 100644
--- a/tests/ui/vec.fixed
+++ b/tests/ui/vec.fixed
@@ -176,3 +176,37 @@ fn below() {
         let _: String = a;
     }
 }
+
+fn func_needing_vec(_bar: usize, _baz: Vec<usize>) {}
+fn func_not_needing_vec(_bar: usize, _baz: usize) {}
+
+fn issue11861() {
+    macro_rules! this_macro_needs_vec {
+        ($x:expr) => {{
+            func_needing_vec($x.iter().sum(), $x);
+            for _ in $x {}
+        }};
+    }
+    macro_rules! this_macro_doesnt_need_vec {
+        ($x:expr) => {{ func_not_needing_vec($x.iter().sum(), $x.iter().sum()) }};
+    }
+
+    // Do not lint the next line
+    this_macro_needs_vec!(vec![1]);
+    this_macro_doesnt_need_vec!([1]); //~ ERROR: useless use of `vec!`
+
+    macro_rules! m {
+        ($x:expr) => {
+            fn f2() {
+                let _x: Vec<i32> = $x;
+            }
+            fn f() {
+                let _x = $x;
+                $x.starts_with(&[]);
+            }
+        };
+    }
+
+    // should not lint
+    m!(vec![1]);
+}
diff --git a/tests/ui/vec.rs b/tests/ui/vec.rs
index 087425585de..5aca9b2925c 100644
--- a/tests/ui/vec.rs
+++ b/tests/ui/vec.rs
@@ -176,3 +176,37 @@ fn below() {
         let _: String = a;
     }
 }
+
+fn func_needing_vec(_bar: usize, _baz: Vec<usize>) {}
+fn func_not_needing_vec(_bar: usize, _baz: usize) {}
+
+fn issue11861() {
+    macro_rules! this_macro_needs_vec {
+        ($x:expr) => {{
+            func_needing_vec($x.iter().sum(), $x);
+            for _ in $x {}
+        }};
+    }
+    macro_rules! this_macro_doesnt_need_vec {
+        ($x:expr) => {{ func_not_needing_vec($x.iter().sum(), $x.iter().sum()) }};
+    }
+
+    // Do not lint the next line
+    this_macro_needs_vec!(vec![1]);
+    this_macro_doesnt_need_vec!(vec![1]); //~ ERROR: useless use of `vec!`
+
+    macro_rules! m {
+        ($x:expr) => {
+            fn f2() {
+                let _x: Vec<i32> = $x;
+            }
+            fn f() {
+                let _x = $x;
+                $x.starts_with(&[]);
+            }
+        };
+    }
+
+    // should not lint
+    m!(vec![1]);
+}
diff --git a/tests/ui/vec.stderr b/tests/ui/vec.stderr
index fc261838fe3..c9018f94f9d 100644
--- a/tests/ui/vec.stderr
+++ b/tests/ui/vec.stderr
@@ -115,5 +115,11 @@ error: useless use of `vec!`
 LL |     for a in vec![String::new(), String::new()] {
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can use an array directly: `[String::new(), String::new()]`
 
-error: aborting due to 19 previous errors
+error: useless use of `vec!`
+  --> $DIR/vec.rs:196:33
+   |
+LL |     this_macro_doesnt_need_vec!(vec![1]);
+   |                                 ^^^^^^^ help: you can use an array directly: `[1]`
+
+error: aborting due to 20 previous errors