blob: 0c8c904e37454ffe263ffc2ee6f7c6160f733624 [file] [log] [blame]
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
use clippy_utils::source::snippet;
use clippy_utils::{is_from_proc_macro, path_to_local_id};
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Local, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Informs the user about a more concise way to create a vector with a known capacity.
///
/// ### Why is this bad?
/// The `Vec::with_capacity` constructor is less complex.
///
/// ### Example
/// ```rust
/// let mut v: Vec<usize> = vec![];
/// v.reserve(10);
/// ```
/// Use instead:
/// ```rust
/// let mut v: Vec<usize> = Vec::with_capacity(10);
/// ```
#[clippy::version = "1.73.0"]
pub RESERVE_AFTER_INITIALIZATION,
complexity,
"`reserve` called immediatly after `Vec` creation"
}
impl_lint_pass!(ReserveAfterInitialization => [RESERVE_AFTER_INITIALIZATION]);
#[derive(Default)]
pub struct ReserveAfterInitialization {
searcher: Option<VecReserveSearcher>,
}
struct VecReserveSearcher {
local_id: HirId,
err_span: Span,
init_part: String,
space_hint: String,
}
impl VecReserveSearcher {
fn display_err(&self, cx: &LateContext<'_>) {
if self.space_hint.is_empty() {
return;
}
let s = format!("{}Vec::with_capacity({});", self.init_part, self.space_hint);
span_lint_and_sugg(
cx,
RESERVE_AFTER_INITIALIZATION,
self.err_span,
"call to `reserve` immediately after creation",
"consider using `Vec::with_capacity(/* Space hint */)`",
s,
Applicability::HasPlaceholders,
);
}
}
impl<'tcx> LateLintPass<'tcx> for ReserveAfterInitialization {
fn check_block(&mut self, _: &LateContext<'tcx>, _: &'tcx Block<'tcx>) {
self.searcher = None;
}
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
if let Some(init_expr) = local.init
&& let PatKind::Binding(BindingAnnotation::MUT, id, _, None) = local.pat.kind
&& !in_external_macro(cx.sess(), local.span)
&& let Some(init) = get_vec_init_kind(cx, init_expr)
&& !matches!(init, VecInitKind::WithExprCapacity(_) | VecInitKind::WithConstCapacity(_))
{
self.searcher = Some(VecReserveSearcher {
local_id: id,
err_span: local.span,
init_part: snippet(cx, local.span.shrink_to_lo()
.to(init_expr.span.source_callsite().shrink_to_lo()), "..")
.into_owned(),
space_hint: String::new()
});
}
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if self.searcher.is_none()
&& let ExprKind::Assign(left, right, _) = expr.kind
&& let ExprKind::Path(QPath::Resolved(None, path)) = left.kind
&& let Res::Local(id) = path.res
&& !in_external_macro(cx.sess(), expr.span)
&& let Some(init) = get_vec_init_kind(cx, right)
&& !matches!(init, VecInitKind::WithExprCapacity(_) | VecInitKind::WithConstCapacity(_))
{
self.searcher = Some(VecReserveSearcher {
local_id: id,
err_span: expr.span,
init_part: snippet(cx, left.span.shrink_to_lo()
.to(right.span.source_callsite().shrink_to_lo()), "..")
.into_owned(), // see `assign_expression` test
space_hint: String::new()
});
}
}
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
if let Some(searcher) = self.searcher.take() {
if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind
&& let ExprKind::MethodCall(name, self_arg, [space_hint], _) = expr.kind
&& path_to_local_id(self_arg, searcher.local_id)
&& name.ident.as_str() == "reserve"
&& !is_from_proc_macro(cx, expr)
{
self.searcher = Some(VecReserveSearcher {
err_span: searcher.err_span.to(stmt.span),
space_hint: snippet(cx, space_hint.span, "..").into_owned(),
.. searcher
});
} else {
searcher.display_err(cx);
}
}
}
fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx Block<'tcx>) {
if let Some(searcher) = self.searcher.take() {
searcher.display_err(cx);
}
}
}