-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathzero_repeat_side_effects.rs
More file actions
175 lines (166 loc) · 6.67 KB
/
zero_repeat_side_effects.rs
File metadata and controls
175 lines (166 loc) · 6.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
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, Expr, ExprKind, LetStmt, LocalSource, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{IsSuggestable, Ty};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
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: &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: &'_ Expr<'_>, inner_expr: &'_ 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)
if matches!(l.source, LocalSource::AssignDesugar)
&& let mut parent_iter = cx.tcx.hir_parent_iter(l.hir_id)
&& let Some((_, Node::Stmt(_))) = parent_iter.next()
&& let Some((_, Node::Block(_))) = parent_iter.next()
&& let Some((_, Node::Expr(x))) = parent_iter.next() =>
{
(
x.span,
assign_expr_suggestion(cx, x, l.pat.span, &inner_expr, return_type, vec),
)
},
Node::LetStmt(l) => (l.span, let_stmt_suggestion(cx, l, &inner_expr, return_type, vec)),
Node::Expr(x) if let ExprKind::Assign(l, _, _) = x.kind => (
x.span,
assign_expr_suggestion(cx, x, l.span, &inner_expr, return_type, vec),
),
// 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");
}
},
);
}
}
fn let_stmt_suggestion(
cx: &LateContext<'_>,
let_stmt: &LetStmt<'_>,
inner_expr: &str,
return_type: Ty<'_>,
vec_str: &str,
) -> String {
let indent = snippet_indent(cx, let_stmt.span).unwrap_or_default();
format!(
"{inner_expr};\n{}let {var_name}: {return_type} = {vec_str}[];",
indent,
var_name = snippet(cx, let_stmt.pat.span.source_callsite(), "..")
)
}
fn assign_expr_suggestion(
cx: &LateContext<'_>,
outer_expr: &Expr<'_>,
assign_expr_span: Span,
inner_expr: &str,
return_type: Ty<'_>,
vec_str: &str,
) -> String {
let mut parent_hir_node = cx.tcx.parent_hir_node(outer_expr.hir_id);
if let Node::Stmt(stmt) = parent_hir_node {
parent_hir_node = cx.tcx.parent_hir_node(stmt.hir_id);
}
let needs_curly = !matches!(parent_hir_node, Node::Block(_));
let indent = snippet_indent(cx, outer_expr.span).unwrap_or_default();
let var_name = snippet(cx, assign_expr_span.source_callsite(), "..");
if needs_curly {
format!("{{\n {indent}{inner_expr};\n {indent}{var_name} = {vec_str}[] as {return_type}\n{indent}}}")
} else {
format!("{inner_expr};\n{indent}{var_name} = {vec_str}[] as {return_type}")
}
}