| use std::collections::BTreeMap; |
| use std::collections::btree_map::Entry; |
| use std::mem; |
| use std::ops::ControlFlow; |
| |
| use clippy_config::Conf; |
| use clippy_utils::consts::{ConstEvalCtxt, Constant}; |
| use clippy_utils::diagnostics::span_lint_hir_and_then; |
| use clippy_utils::msrvs::{self, Msrv}; |
| use clippy_utils::source::SpanRangeExt; |
| use clippy_utils::ty::is_copy; |
| use clippy_utils::visitors::for_each_local_use_after_expr; |
| use clippy_utils::{VEC_METHODS_SHADOWING_SLICE_METHODS, get_parent_expr, higher, is_in_test, span_contains_comment}; |
| use rustc_errors::Applicability; |
| use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, LetStmt, Mutability, Node, Pat, PatKind}; |
| use rustc_lint::{LateContext, LateLintPass}; |
| use rustc_middle::ty; |
| use rustc_middle::ty::layout::LayoutOf; |
| use rustc_session::impl_lint_pass; |
| use rustc_span::{DesugaringKind, Span}; |
| |
| pub struct UselessVec { |
| too_large_for_stack: u64, |
| msrv: Msrv, |
| /// Maps from a `vec![]` source callsite invocation span to the "state" (i.e., whether we can |
| /// emit a warning there or not). |
| /// |
| /// The purpose of this is to buffer lints up until `check_crate_post` so that we can cancel a |
| /// lint while visiting, because a `vec![]` invocation span can appear multiple times when |
| /// it is passed as a macro argument, once in a context that doesn't require a `Vec<_>` and |
| /// another time that does. Consider: |
| /// ``` |
| /// macro_rules! m { |
| /// ($v:expr) => { |
| /// let a = $v; |
| /// $v.push(3); |
| /// } |
| /// } |
| /// m!(vec![1, 2]); |
| /// ``` |
| /// The macro invocation expands to two `vec![1, 2]` invocations. If we eagerly suggest changing |
| /// the first `vec![1, 2]` (which is shared with the other expn) to an array which indeed would |
| /// work, we get a false positive warning on the `$v.push(3)` which really requires `$v` to |
| /// be a vector. |
| span_to_state: BTreeMap<Span, VecState>, |
| allow_in_test: bool, |
| } |
| |
| impl UselessVec { |
| pub fn new(conf: &'static Conf) -> Self { |
| Self { |
| too_large_for_stack: conf.too_large_for_stack, |
| msrv: conf.msrv, |
| span_to_state: BTreeMap::new(), |
| allow_in_test: conf.allow_useless_vec_in_tests, |
| } |
| } |
| } |
| |
| declare_clippy_lint! { |
| /// ### What it does |
| /// Checks for usage of `vec![..]` when using `[..]` would |
| /// be possible. |
| /// |
| /// ### Why is this bad? |
| /// This is less efficient. |
| /// |
| /// ### Example |
| /// ```no_run |
| /// fn foo(_x: &[u8]) {} |
| /// |
| /// foo(&vec![1, 2]); |
| /// ``` |
| /// |
| /// Use instead: |
| /// ```no_run |
| /// # fn foo(_x: &[u8]) {} |
| /// foo(&[1, 2]); |
| /// ``` |
| #[clippy::version = "pre 1.29.0"] |
| pub USELESS_VEC, |
| perf, |
| "useless `vec!`" |
| } |
| |
| impl_lint_pass!(UselessVec => [USELESS_VEC]); |
| |
| /// The "state" of a `vec![]` invocation, indicating whether it can or cannot be changed. |
| enum VecState { |
| Change { |
| suggest_ty: SuggestedType, |
| vec_snippet: String, |
| expr_hir_id: HirId, |
| }, |
| NoChange, |
| } |
| |
| enum VecToArray { |
| /// Expression does not need to be a `Vec<_>` and its type can be changed to an array (or |
| /// slice). |
| Possible, |
| /// Expression must be a `Vec<_>`. Type cannot change. |
| Impossible, |
| } |
| |
| impl UselessVec { |
| /// Checks if the surrounding environment requires this expression to actually be of type |
| /// `Vec<_>`, or if it can be changed to `&[]`/`[]` without causing type errors. |
| fn expr_usage_requires_vec(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) -> VecToArray { |
| match cx.tcx.parent_hir_node(expr.hir_id) { |
| // 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) |
| Node::LetStmt(LetStmt { |
| ty: None, |
| pat: |
| Pat { |
| kind: PatKind::Binding(_, id, ..), |
| .. |
| }, |
| .. |
| }) => { |
| 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) |
| || match parent.kind { |
| ExprKind::Index(..) => true, |
| ExprKind::MethodCall(path, _, [], _) => { |
| // If the given expression is a method call to a `Vec` method that also exists on |
| // slices, it means that this expression does not actually require a `Vec` and could |
| // just work with an array. |
| VEC_METHODS_SHADOWING_SLICE_METHODS.contains(&path.ident.name) |
| }, |
| _ => false, |
| }) |
| { |
| ControlFlow::Continue(()) |
| } else { |
| ControlFlow::Break(()) |
| } |
| }) |
| .is_continue(); |
| |
| if only_slice_uses { |
| VecToArray::Possible |
| } else { |
| VecToArray::Impossible |
| } |
| }, |
| // if the local pattern has a specified type, do not lint. |
| Node::LetStmt(LetStmt { ty: Some(_), .. }) if higher::VecArgs::hir(cx, expr).is_some() => { |
| VecToArray::Impossible |
| }, |
| // search for `for _ in vec![...]` |
| Node::Expr(expr) |
| if expr.span.is_desugaring(DesugaringKind::ForLoop) |
| && self.msrv.meets(cx, msrvs::ARRAY_INTO_ITERATOR) => |
| { |
| VecToArray::Possible |
| }, |
| // search for `&vec![_]` or `vec![_]` expressions where the adjusted type is `&[_]` |
| _ => { |
| if adjusts_to_slice(cx, expr) { |
| VecToArray::Possible |
| } else { |
| VecToArray::Impossible |
| } |
| }, |
| } |
| } |
| } |
| |
| impl<'tcx> LateLintPass<'tcx> for UselessVec { |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { |
| if let Some(vec_args) = higher::VecArgs::hir(cx, expr.peel_borrows()) |
| // The `vec![]` or `&vec![]` invocation span. |
| && let vec_span = expr.span.parent_callsite().unwrap_or(expr.span) |
| && !vec_span.from_expansion() |
| { |
| if self.allow_in_test && is_in_test(cx.tcx, expr.hir_id) { |
| return; |
| } |
| |
| match self.expr_usage_requires_vec(cx, expr) { |
| VecToArray::Possible => { |
| let suggest_ty = suggest_type(expr); |
| |
| // Size and `Copy` checks don't depend on the enclosing usage of the expression |
| // and don't need to be inserted into the state map. |
| let vec_snippet = match vec_args { |
| higher::VecArgs::Repeat(expr, len) => { |
| if is_copy(cx, cx.typeck_results().expr_ty(expr)) |
| && let Some(Constant::Int(length)) = ConstEvalCtxt::new(cx).eval(len) |
| && let Ok(length) = u64::try_from(length) |
| && size_of(cx, expr) |
| .checked_mul(length) |
| .is_some_and(|size| size <= self.too_large_for_stack) |
| { |
| suggest_ty.snippet( |
| cx, |
| Some(expr.span.source_callsite()), |
| Some(len.span.source_callsite()), |
| ) |
| } else { |
| return; |
| } |
| }, |
| higher::VecArgs::Vec(args) => { |
| if let Ok(length) = u64::try_from(args.len()) |
| && size_of(cx, expr) |
| .checked_mul(length) |
| .is_some_and(|size| size <= self.too_large_for_stack) |
| { |
| suggest_ty.snippet( |
| cx, |
| args.first().zip(args.last()).map(|(first, last)| { |
| first.span.source_callsite().to(last.span.source_callsite()) |
| }), |
| None, |
| ) |
| } else { |
| return; |
| } |
| }, |
| }; |
| |
| if let Entry::Vacant(entry) = self.span_to_state.entry(vec_span) { |
| entry.insert(VecState::Change { |
| suggest_ty, |
| vec_snippet, |
| expr_hir_id: expr.hir_id, |
| }); |
| } |
| }, |
| VecToArray::Impossible => { |
| self.span_to_state.insert(vec_span, VecState::NoChange); |
| }, |
| } |
| } |
| } |
| |
| fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { |
| for (span, state) in mem::take(&mut self.span_to_state) { |
| if let VecState::Change { |
| suggest_ty, |
| vec_snippet, |
| expr_hir_id, |
| } = state |
| { |
| span_lint_hir_and_then(cx, USELESS_VEC, expr_hir_id, span, "useless use of `vec!`", |diag| { |
| let help_msg = format!("you can use {} directly", suggest_ty.desc()); |
| // If the `vec!` macro contains comment, better not make the suggestion machine applicable as it |
| // would remove them. |
| let applicability = if span_contains_comment(cx.tcx.sess.source_map(), span) { |
| Applicability::Unspecified |
| } else { |
| Applicability::MachineApplicable |
| }; |
| diag.span_suggestion(span, help_msg, vec_snippet, applicability); |
| }); |
| } |
| } |
| } |
| } |
| |
| #[derive(Copy, Clone)] |
| pub(crate) enum SuggestedType { |
| /// Suggest using a slice `&[..]` / `&mut [..]` |
| SliceRef(Mutability), |
| /// Suggest using an array: `[..]` |
| Array, |
| } |
| |
| impl SuggestedType { |
| fn desc(self) -> &'static str { |
| match self { |
| Self::SliceRef(_) => "a slice", |
| Self::Array => "an array", |
| } |
| } |
| |
| fn snippet(self, cx: &LateContext<'_>, args_span: Option<Span>, len_span: Option<Span>) -> String { |
| // Invariant of the lint as implemented: all spans are from the root context (and as a result, |
| // always trivially crate-local). |
| assert!(args_span.is_none_or(|s| !s.from_expansion())); |
| assert!(len_span.is_none_or(|s| !s.from_expansion())); |
| |
| let maybe_args = args_span.map(|sp| sp.get_source_text(cx).expect("spans are always crate-local")); |
| let maybe_args = maybe_args.as_deref().unwrap_or_default(); |
| let maybe_len = len_span |
| .map(|sp| sp.get_source_text(cx).expect("spans are always crate-local")) |
| .map(|st| format!("; {st}")) |
| .unwrap_or_default(); |
| |
| match self { |
| Self::SliceRef(Mutability::Mut) => format!("&mut [{maybe_args}{maybe_len}]"), |
| Self::SliceRef(Mutability::Not) => format!("&[{maybe_args}{maybe_len}]"), |
| Self::Array => format!("[{maybe_args}{maybe_len}]"), |
| } |
| } |
| } |
| |
| fn size_of(cx: &LateContext<'_>, expr: &Expr<'_>) -> u64 { |
| let ty = cx.typeck_results().expr_ty_adjusted(expr); |
| cx.layout_of(ty).map_or(0, |l| l.size.bytes()) |
| } |
| |
| fn adjusts_to_slice(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { |
| matches!(cx.typeck_results().expr_ty_adjusted(e).kind(), ty::Ref(_, ty, _) if ty.is_slice()) |
| } |
| |
| fn suggest_type(expr: &Expr<'_>) -> SuggestedType { |
| if let ExprKind::AddrOf(BorrowKind::Ref, mutability, _) = expr.kind { |
| // `expr` is `&vec![_]`, so suggest `&[_]` (or `&mut[_]` resp.) |
| SuggestedType::SliceRef(mutability) |
| } else { |
| // `expr` is the `vec![_]` expansion, so suggest `[_]` |
| SuggestedType::Array |
| } |
| } |