| 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, 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 | |
| } | |
| } |