Skip to content

Commit 9877a96

Browse files
committed
new lint local_assigned_single_value
1 parent e11f36c commit 9877a96

15 files changed

+348
-37
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4900,6 +4900,7 @@ Released 2018-09-13
49004900
[`lines_filter_map_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok
49014901
[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist
49024902
[`little_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#little_endian_bytes
4903+
[`local_assigned_single_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#local_assigned_single_value
49034904
[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug
49044905
[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal
49054906
[`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
248248
crate::literal_representation::MISTYPED_LITERAL_SUFFIXES_INFO,
249249
crate::literal_representation::UNREADABLE_LITERAL_INFO,
250250
crate::literal_representation::UNUSUAL_BYTE_GROUPINGS_INFO,
251+
crate::local_assigned_single_value::LOCAL_ASSIGNED_SINGLE_VALUE_INFO,
251252
crate::loops::EMPTY_LOOP_INFO,
252253
crate::loops::EXPLICIT_COUNTER_LOOP_INFO,
253254
crate::loops::EXPLICIT_INTO_ITER_LOOP_INFO,

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
extern crate rustc_arena;
2525
extern crate rustc_ast;
2626
extern crate rustc_ast_pretty;
27+
extern crate rustc_const_eval;
2728
extern crate rustc_data_structures;
2829
extern crate rustc_driver;
2930
extern crate rustc_errors;
@@ -177,6 +178,7 @@ mod let_with_type_underscore;
177178
mod lifetimes;
178179
mod lines_filter_map_ok;
179180
mod literal_representation;
181+
mod local_assigned_single_value;
180182
mod loops;
181183
mod macro_use;
182184
mod main_recursion;
@@ -1050,6 +1052,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10501052
store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(stack_size_threshold)));
10511053
store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit));
10521054
store.register_late_pass(|_| Box::new(incorrect_impls::IncorrectImpls));
1055+
store.register_late_pass(|_| Box::new(local_assigned_single_value::LocalAssignedSingleValue));
10531056
// add lints here, do not remove this comment, it's used in `new_lint`
10541057
}
10551058

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
use clippy_utils::diagnostics::span_lint;
2+
use clippy_utils::fn_has_unsatisfiable_preds;
3+
use clippy_utils::source::snippet_opt;
4+
use itertools::Itertools;
5+
use rustc_const_eval::interpret::Scalar;
6+
use rustc_data_structures::fx::FxHashMap;
7+
use rustc_hir::def_id::LocalDefId;
8+
use rustc_hir::{intravisit::FnKind, Body, FnDecl};
9+
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_middle::mir::{
11+
self, interpret::ConstValue, visit::Visitor, Constant, Location, Operand, Rvalue, Statement, StatementKind,
12+
};
13+
use rustc_session::{declare_lint_pass, declare_tool_lint};
14+
use rustc_span::Span;
15+
16+
declare_clippy_lint! {
17+
/// ### What it does
18+
/// Checks for locals that are always assigned the same value.
19+
///
20+
/// ### Why is this bad?
21+
/// It's almost always a typo. If not, it can be made immutable, or turned into a constant.
22+
///
23+
/// ### Example
24+
/// ```rust
25+
/// let mut x = 1;
26+
/// x = 1;
27+
/// ```
28+
/// Use instead:
29+
/// ```rust
30+
/// let x = 1;
31+
/// ```
32+
#[clippy::version = "1.72.0"]
33+
pub LOCAL_ASSIGNED_SINGLE_VALUE,
34+
correctness,
35+
"disallows assigning locals many times with the same value"
36+
}
37+
declare_lint_pass!(LocalAssignedSingleValue => [LOCAL_ASSIGNED_SINGLE_VALUE]);
38+
39+
impl LateLintPass<'_> for LocalAssignedSingleValue {
40+
fn check_fn(
41+
&mut self,
42+
cx: &LateContext<'_>,
43+
_: FnKind<'_>,
44+
_: &FnDecl<'_>,
45+
_: &Body<'_>,
46+
_: Span,
47+
def_id: LocalDefId,
48+
) {
49+
// Building MIR for `fn`s with unsatisfiable preds results in ICE.
50+
if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) {
51+
return;
52+
}
53+
54+
let mir = cx.tcx.optimized_mir(def_id.to_def_id());
55+
let mut v = V {
56+
body: mir,
57+
cx,
58+
local_usage: mir
59+
.local_decls
60+
.iter_enumerated()
61+
.map(|(local, _)| (local, LocalUsageValues::default()))
62+
.collect(),
63+
};
64+
v.visit_body(mir);
65+
66+
for (local, usage) in &v.local_usage {
67+
if should_lint(&v.local_usage, *local, usage) {
68+
let LocalUsageValues {
69+
usage,
70+
mut_ref_acquired: _,
71+
assigned_non_const: _,
72+
} = usage;
73+
74+
if let Some(local_decl) = mir.local_decls.get(*local)
75+
&& let [dbg_info] = &*mir
76+
.var_debug_info
77+
.iter()
78+
.filter(|info| info.source_info.span == local_decl.source_info.span)
79+
.collect_vec()
80+
// Don't handle function arguments.
81+
&& dbg_info.argument_index.is_none()
82+
// Ignore anything from a procedural macro, or locals we cannot prove aren't
83+
// temporaries
84+
&& let Some(snippet) = snippet_opt(cx, dbg_info.source_info.span)
85+
&& snippet.ends_with(dbg_info.name.as_str())
86+
{
87+
span_lint(
88+
cx,
89+
LOCAL_ASSIGNED_SINGLE_VALUE,
90+
usage.iter().map(|(span, _)| *span).collect_vec(),
91+
"local only ever assigned single value",
92+
);
93+
}
94+
}
95+
}
96+
}
97+
}
98+
99+
type LocalUsage = FxHashMap<mir::Local, LocalUsageValues>;
100+
101+
/// Holds the data we have for the usage of a local.
102+
#[derive(Default)]
103+
struct LocalUsageValues {
104+
/// Where and what this local is assigned.
105+
usage: Vec<(Span, Scalar)>,
106+
/// Whether it's mutably borrowed, ever. We should not lint this.
107+
mut_ref_acquired: bool,
108+
/// Whether it's assigned a value we cannot prove is constant, ever. We should not lint this.
109+
assigned_non_const: bool,
110+
}
111+
112+
struct V<'a, 'tcx> {
113+
#[allow(dead_code)]
114+
body: &'a mir::Body<'tcx>,
115+
cx: &'a LateContext<'tcx>,
116+
local_usage: LocalUsage,
117+
}
118+
119+
impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
120+
fn visit_statement(&mut self, stmt: &Statement<'tcx>, _: Location) {
121+
let Self {
122+
body: _,
123+
cx,
124+
local_usage,
125+
} = self;
126+
127+
if stmt.source_info.span.from_expansion() {
128+
return;
129+
}
130+
131+
if let StatementKind::Assign(assign) = &stmt.kind {
132+
let (place, rvalue) = &**assign;
133+
// Do not lint if there are any mutable borrows to a local
134+
if let Rvalue::Ref(_, mir::BorrowKind::Unique | mir::BorrowKind::Mut { .. }, place) = rvalue
135+
&& let Some(other) = local_usage.get_mut(&place.local)
136+
{
137+
other.assigned_non_const = true;
138+
return;
139+
}
140+
let Some(usage) = local_usage.get_mut(&place.local) else {
141+
return;
142+
};
143+
144+
if let Rvalue::Use(Operand::Constant(constant)) = rvalue
145+
&& let Constant { literal, .. } = **constant
146+
&& let Some(ConstValue::Scalar(val)) = literal.try_to_value(cx.tcx)
147+
{
148+
usage.usage.push((stmt.source_info.span, val));
149+
} else if let Rvalue::Use(Operand::Copy(place)) = rvalue
150+
&& let [_base_proj, ..] = place.projection.as_slice()
151+
{
152+
// While this could be `let [x, y] = [1, 1]` or `let (x, y) = (1, 1)`, which should
153+
// indeed be considered a constant, handling such a case would overcomplicate this
154+
// lint.
155+
//
156+
// TODO(Centri3): Let's do the above as a follow-up, in the future! In particular,
157+
// we need to handle `ProjectionElem::Field` and `ProjectionElem::Index`.
158+
usage.assigned_non_const = true;
159+
} else {
160+
// We can also probably handle stuff like `x += 1` here, maybe. But this would be
161+
// very very complex. Let's keep it simple enough.
162+
usage.assigned_non_const = true;
163+
}
164+
}
165+
}
166+
}
167+
168+
fn should_lint(_local_usage: &LocalUsage, _local: mir::Local, usage: &LocalUsageValues) -> bool {
169+
let LocalUsageValues {
170+
usage,
171+
mut_ref_acquired,
172+
assigned_non_const,
173+
} = usage;
174+
175+
if usage.len() > 1
176+
&& !mut_ref_acquired
177+
&& !assigned_non_const
178+
&& let [(_, head), tail @ ..] = &**usage
179+
&& tail.iter().all(|(_, i)| i == head)
180+
{
181+
return true;
182+
}
183+
184+
false
185+
}

tests/ui/diverging_sub_expression.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![warn(clippy::diverging_sub_expression)]
22
#![allow(clippy::match_same_arms, clippy::overly_complex_bool_expr)]
3-
#![allow(clippy::nonminimal_bool)]
3+
#![allow(clippy::local_assigned_single_value, clippy::nonminimal_bool)]
44
#[allow(clippy::empty_loop)]
55
fn diverge() -> ! {
66
loop {}

tests/ui/explicit_counter_loop.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
#![warn(clippy::explicit_counter_loop)]
2-
#![allow(clippy::uninlined_format_args, clippy::useless_vec)]
2+
#![allow(
3+
clippy::local_assigned_single_value,
4+
clippy::uninlined_format_args,
5+
clippy::useless_vec
6+
)]
37

48
fn main() {
59
let mut vec = vec![1, 2, 3, 4];

tests/ui/explicit_counter_loop.stderr

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,55 @@
11
error: the variable `_index` is used as a loop counter
2-
--> $DIR/explicit_counter_loop.rs:7:5
2+
--> $DIR/explicit_counter_loop.rs:11:5
33
|
44
LL | for _v in &vec {
55
| ^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter().enumerate()`
66
|
77
= note: `-D clippy::explicit-counter-loop` implied by `-D warnings`
88

99
error: the variable `_index` is used as a loop counter
10-
--> $DIR/explicit_counter_loop.rs:13:5
10+
--> $DIR/explicit_counter_loop.rs:17:5
1111
|
1212
LL | for _v in &vec {
1313
| ^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter().enumerate()`
1414

1515
error: the variable `_index` is used as a loop counter
16-
--> $DIR/explicit_counter_loop.rs:18:5
16+
--> $DIR/explicit_counter_loop.rs:22:5
1717
|
1818
LL | for _v in &mut vec {
1919
| ^^^^^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter_mut().enumerate()`
2020

2121
error: the variable `_index` is used as a loop counter
22-
--> $DIR/explicit_counter_loop.rs:23:5
22+
--> $DIR/explicit_counter_loop.rs:27:5
2323
|
2424
LL | for _v in vec {
2525
| ^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.into_iter().enumerate()`
2626

2727
error: the variable `count` is used as a loop counter
28-
--> $DIR/explicit_counter_loop.rs:110:9
28+
--> $DIR/explicit_counter_loop.rs:114:9
2929
|
3030
LL | for ch in text.chars() {
3131
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `for (count, ch) in text.chars().enumerate()`
3232

3333
error: the variable `count` is used as a loop counter
34-
--> $DIR/explicit_counter_loop.rs:121:9
34+
--> $DIR/explicit_counter_loop.rs:125:9
3535
|
3636
LL | for ch in text.chars() {
3737
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `for (count, ch) in text.chars().enumerate()`
3838

3939
error: the variable `count` is used as a loop counter
40-
--> $DIR/explicit_counter_loop.rs:179:9
40+
--> $DIR/explicit_counter_loop.rs:183:9
4141
|
4242
LL | for _i in 3..10 {
4343
| ^^^^^^^^^^^^^^^ help: consider using: `for (count, _i) in (3..10).enumerate()`
4444

4545
error: the variable `idx_usize` is used as a loop counter
46-
--> $DIR/explicit_counter_loop.rs:219:9
46+
--> $DIR/explicit_counter_loop.rs:223:9
4747
|
4848
LL | for _item in slice {
4949
| ^^^^^^^^^^^^^^^^^^ help: consider using: `for (idx_usize, _item) in slice.iter().enumerate()`
5050

5151
error: the variable `idx_u32` is used as a loop counter
52-
--> $DIR/explicit_counter_loop.rs:231:9
52+
--> $DIR/explicit_counter_loop.rs:235:9
5353
|
5454
LL | for _item in slice {
5555
| ^^^^^^^^^^^^^^^^^^ help: consider using: `for (idx_u32, _item) in (0_u32..).zip(slice.iter())`

tests/ui/let_unit.fixed

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22

33
#![feature(lint_reasons)]
44
#![warn(clippy::let_unit_value)]
5-
#![allow(unused, clippy::no_effect, clippy::needless_late_init, path_statements)]
5+
#![allow(
6+
unused,
7+
clippy::local_assigned_single_value,
8+
clippy::no_effect,
9+
clippy::needless_late_init,
10+
path_statements
11+
)]
612

713
macro_rules! let_and_return {
814
($n:expr) => {{

tests/ui/let_unit.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22

33
#![feature(lint_reasons)]
44
#![warn(clippy::let_unit_value)]
5-
#![allow(unused, clippy::no_effect, clippy::needless_late_init, path_statements)]
5+
#![allow(
6+
unused,
7+
clippy::local_assigned_single_value,
8+
clippy::no_effect,
9+
clippy::needless_late_init,
10+
path_statements
11+
)]
612

713
macro_rules! let_and_return {
814
($n:expr) => {{

0 commit comments

Comments
 (0)