-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
new lint
local_assigned_single_value
- Loading branch information
Showing
15 changed files
with
348 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
use clippy_utils::diagnostics::span_lint; | ||
use clippy_utils::fn_has_unsatisfiable_preds; | ||
use clippy_utils::source::snippet_opt; | ||
use itertools::Itertools; | ||
use rustc_const_eval::interpret::Scalar; | ||
use rustc_data_structures::fx::FxHashMap; | ||
use rustc_hir::def_id::LocalDefId; | ||
use rustc_hir::{intravisit::FnKind, Body, FnDecl}; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
use rustc_middle::mir::{ | ||
self, interpret::ConstValue, visit::Visitor, Constant, Location, Operand, Rvalue, Statement, StatementKind, | ||
}; | ||
use rustc_session::{declare_lint_pass, declare_tool_lint}; | ||
use rustc_span::Span; | ||
|
||
declare_clippy_lint! { | ||
/// ### What it does | ||
/// Checks for locals that are always assigned the same value. | ||
/// | ||
/// ### Why is this bad? | ||
/// It's almost always a typo. If not, it can be made immutable, or turned into a constant. | ||
/// | ||
/// ### Example | ||
/// ```rust | ||
/// let mut x = 1; | ||
/// x = 1; | ||
/// ``` | ||
/// Use instead: | ||
/// ```rust | ||
/// let x = 1; | ||
/// ``` | ||
#[clippy::version = "1.72.0"] | ||
pub LOCAL_ASSIGNED_SINGLE_VALUE, | ||
correctness, | ||
"disallows assigning locals many times with the same value" | ||
} | ||
declare_lint_pass!(LocalAssignedSingleValue => [LOCAL_ASSIGNED_SINGLE_VALUE]); | ||
|
||
impl LateLintPass<'_> for LocalAssignedSingleValue { | ||
fn check_fn( | ||
&mut self, | ||
cx: &LateContext<'_>, | ||
_: FnKind<'_>, | ||
_: &FnDecl<'_>, | ||
_: &Body<'_>, | ||
_: Span, | ||
def_id: LocalDefId, | ||
) { | ||
// Building MIR for `fn`s with unsatisfiable preds results in ICE. | ||
if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) { | ||
return; | ||
} | ||
|
||
let mir = cx.tcx.optimized_mir(def_id.to_def_id()); | ||
let mut v = V { | ||
body: mir, | ||
cx, | ||
local_usage: mir | ||
.local_decls | ||
.iter_enumerated() | ||
.map(|(local, _)| (local, LocalUsageValues::default())) | ||
.collect(), | ||
}; | ||
v.visit_body(mir); | ||
|
||
for (local, usage) in &v.local_usage { | ||
if should_lint(&v.local_usage, *local, usage) { | ||
let LocalUsageValues { | ||
usage, | ||
mut_ref_acquired: _, | ||
assigned_non_const: _, | ||
} = usage; | ||
|
||
if let Some(local_decl) = mir.local_decls.get(*local) | ||
&& let [dbg_info] = &*mir | ||
.var_debug_info | ||
.iter() | ||
.filter(|info| info.source_info.span == local_decl.source_info.span) | ||
.collect_vec() | ||
// Don't handle function arguments. | ||
&& dbg_info.argument_index.is_none() | ||
// Ignore anything from a procedural macro, or locals we cannot prove aren't | ||
// temporaries | ||
&& let Some(snippet) = snippet_opt(cx, dbg_info.source_info.span) | ||
&& snippet.ends_with(dbg_info.name.as_str()) | ||
{ | ||
span_lint( | ||
cx, | ||
LOCAL_ASSIGNED_SINGLE_VALUE, | ||
usage.iter().map(|(span, _)| *span).collect_vec(), | ||
"local only ever assigned single value", | ||
); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
type LocalUsage = FxHashMap<mir::Local, LocalUsageValues>; | ||
|
||
/// Holds the data we have for the usage of a local. | ||
#[derive(Default)] | ||
struct LocalUsageValues { | ||
/// Where and what this local is assigned. | ||
usage: Vec<(Span, Scalar)>, | ||
/// Whether it's mutably borrowed, ever. We should not lint this. | ||
mut_ref_acquired: bool, | ||
/// Whether it's assigned a value we cannot prove is constant, ever. We should not lint this. | ||
assigned_non_const: bool, | ||
} | ||
|
||
struct V<'a, 'tcx> { | ||
#[allow(dead_code)] | ||
body: &'a mir::Body<'tcx>, | ||
cx: &'a LateContext<'tcx>, | ||
local_usage: LocalUsage, | ||
} | ||
|
||
impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> { | ||
fn visit_statement(&mut self, stmt: &Statement<'tcx>, _: Location) { | ||
let Self { | ||
body: _, | ||
cx, | ||
local_usage, | ||
} = self; | ||
|
||
if stmt.source_info.span.from_expansion() { | ||
return; | ||
} | ||
|
||
if let StatementKind::Assign(assign) = &stmt.kind { | ||
let (place, rvalue) = &**assign; | ||
// Do not lint if there are any mutable borrows to a local | ||
if let Rvalue::Ref(_, mir::BorrowKind::Unique | mir::BorrowKind::Mut { .. }, place) = rvalue | ||
&& let Some(other) = local_usage.get_mut(&place.local) | ||
{ | ||
other.assigned_non_const = true; | ||
return; | ||
} | ||
let Some(usage) = local_usage.get_mut(&place.local) else { | ||
return; | ||
}; | ||
|
||
if let Rvalue::Use(Operand::Constant(constant)) = rvalue | ||
&& let Constant { literal, .. } = **constant | ||
&& let Some(ConstValue::Scalar(val)) = literal.try_to_value(cx.tcx) | ||
{ | ||
usage.usage.push((stmt.source_info.span, val)); | ||
} else if let Rvalue::Use(Operand::Copy(place)) = rvalue | ||
&& let [_base_proj, ..] = place.projection.as_slice() | ||
{ | ||
// While this could be `let [x, y] = [1, 1]` or `let (x, y) = (1, 1)`, which should | ||
// indeed be considered a constant, handling such a case would overcomplicate this | ||
// lint. | ||
// | ||
// TODO(Centri3): Let's do the above as a follow-up, in the future! In particular, | ||
// we need to handle `ProjectionElem::Field` and `ProjectionElem::Index`. | ||
usage.assigned_non_const = true; | ||
} else { | ||
// We can also probably handle stuff like `x += 1` here, maybe. But this would be | ||
// very very complex. Let's keep it simple enough. | ||
usage.assigned_non_const = true; | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn should_lint(_local_usage: &LocalUsage, _local: mir::Local, usage: &LocalUsageValues) -> bool { | ||
let LocalUsageValues { | ||
usage, | ||
mut_ref_acquired, | ||
assigned_non_const, | ||
} = usage; | ||
|
||
if usage.len() > 1 | ||
&& !mut_ref_acquired | ||
&& !assigned_non_const | ||
&& let [(_, head), tail @ ..] = &**usage | ||
&& tail.iter().all(|(_, i)| i == head) | ||
{ | ||
return true; | ||
} | ||
|
||
false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.