blob: a8351690068df0d18642e5cd3edeabbec6b6dcc1 [file] [log] [blame] [edit]
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::VecArgs;
use clippy_utils::source::{snippet, snippet_indent};
use rustc_ast::LitKind;
use rustc_data_structures::packed::Pu128;
use rustc_errors::Applicability;
use rustc_hir::{ConstArgKind, ExprKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::IsSuggestable;
use rustc_session::declare_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Checks for array or vec initializations which contain an expression with side effects,
/// but which have a repeat count of zero.
///
/// ### Why is this bad?
/// Such an initialization, despite having a repeat length of 0, will still call the inner function.
/// This may not be obvious and as such there may be unintended side effects in code.
///
/// ### Example
/// ```no_run
/// fn side_effect() -> i32 {
/// println!("side effect");
/// 10
/// }
/// let a = [side_effect(); 0];
/// ```
/// Use instead:
/// ```no_run
/// fn side_effect() -> i32 {
/// println!("side effect");
/// 10
/// }
/// side_effect();
/// let a: [i32; 0] = [];
/// ```
#[clippy::version = "1.79.0"]
pub ZERO_REPEAT_SIDE_EFFECTS,
suspicious,
"usage of zero-sized initializations of arrays or vecs causing side effects"
}
declare_lint_pass!(ZeroRepeatSideEffects => [ZERO_REPEAT_SIDE_EFFECTS]);
impl LateLintPass<'_> for ZeroRepeatSideEffects {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &rustc_hir::Expr<'_>) {
if let Some(args) = VecArgs::hir(cx, expr)
&& let VecArgs::Repeat(inner_expr, len) = args
&& let ExprKind::Lit(l) = len.kind
&& let LitKind::Int(Pu128(0), _) = l.node
{
inner_check(cx, expr, inner_expr, true);
}
// Lint only if the length is a literal zero, and not a path to any constants.
// NOTE(@y21): When reading `[f(); LEN]`, I intuitively expect that the function is called and it
// doesn't seem as confusing as `[f(); 0]`. It would also have false positives when eg.
// the const item depends on `#[cfg]s` and has different values in different compilation
// sessions).
else if let ExprKind::Repeat(inner_expr, const_arg) = expr.kind
&& let ConstArgKind::Anon(anon_const) = const_arg.kind
&& let length_expr = cx.tcx.hir_body(anon_const.body).value
&& !length_expr.span.from_expansion()
&& let ExprKind::Lit(literal) = length_expr.kind
&& let LitKind::Int(Pu128(0), _) = literal.node
{
inner_check(cx, expr, inner_expr, false);
}
}
}
fn inner_check(cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>, inner_expr: &'_ rustc_hir::Expr<'_>, is_vec: bool) {
// check if expr is a call or has a call inside it
if inner_expr.can_have_side_effects() {
let parent_hir_node = cx.tcx.parent_hir_node(expr.hir_id);
let inner_expr_ty = cx.typeck_results().expr_ty(inner_expr);
let return_type = cx.typeck_results().expr_ty(expr);
let inner_expr = snippet(cx, inner_expr.span.source_callsite(), "..");
let indent = snippet_indent(cx, expr.span).unwrap_or_default();
let vec = if is_vec { "vec!" } else { "" };
let (span, sugg) = match parent_hir_node {
Node::LetStmt(l) => (
l.span,
format!(
"{inner_expr};\n{indent}let {var_name}: {return_type} = {vec}[];",
var_name = snippet(cx, l.pat.span.source_callsite(), "..")
),
),
Node::Expr(x) if let ExprKind::Assign(l, _, _) = x.kind => (
x.span,
format!(
"{inner_expr};\n{indent}{var_name} = {vec}[] as {return_type}",
var_name = snippet(cx, l.span.source_callsite(), "..")
),
),
// NOTE: don't use the stmt span to avoid touching the trailing semicolon
Node::Stmt(_) => (expr.span, format!("{inner_expr};\n{indent}{vec}[] as {return_type}")),
_ => (
expr.span,
format!(
"\
{{
{indent} {inner_expr};
{indent} {vec}[] as {return_type}
{indent}}}"
),
),
};
let span = span.source_callsite();
span_lint_and_then(
cx,
ZERO_REPEAT_SIDE_EFFECTS,
span,
"expression with side effects as the initial value in a zero-sized array initializer",
|diag| {
if (!inner_expr_ty.is_never() || cx.tcx.features().never_type())
&& return_type.is_suggestable(cx.tcx, true)
{
diag.span_suggestion_verbose(
span,
"consider performing the side effect separately",
sugg,
Applicability::Unspecified,
);
} else {
diag.help("consider performing the side effect separately");
}
},
);
}
}