Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6688,6 +6688,7 @@ Released 2018-09-13
[`needless_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
[`needless_return_with_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return_with_question_mark
[`needless_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_splitn
[`needless_type_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_type_cast
[`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update
[`neg_cmp_op_on_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_cmp_op_on_partial_ord
[`neg_multiply`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_multiply
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.

[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are over 800 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)

Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.
Expand Down
2 changes: 1 addition & 1 deletion book/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
A collection of lints to catch common mistakes and improve your
[Rust](https://github.com/rust-lang/rust) code.

[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are over 800 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)

Lints are divided into categories, each with a default [lint
level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how
Expand Down
32 changes: 32 additions & 0 deletions clippy_lints/src/casts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mod fn_to_numeric_cast;
mod fn_to_numeric_cast_any;
mod fn_to_numeric_cast_with_truncation;
mod manual_dangling_ptr;
mod needless_type_cast;
mod ptr_as_ptr;
mod ptr_cast_constness;
mod ref_as_ptr;
Expand Down Expand Up @@ -813,6 +814,32 @@ declare_clippy_lint! {
"casting a primitive method pointer to any integer type"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for bindings (constants, statics, or let bindings) that are defined
/// with one numeric type but are consistently cast to a different type in all usages.
///
/// ### Why is this bad?
/// If a binding is always cast to a different type when used, it would be clearer
/// and more efficient to define it with the target type from the start.
///
/// ### Example
/// ```no_run
/// const SIZE: u16 = 15;
/// let arr: [u8; SIZE as usize] = [0; SIZE as usize];
/// ```
///
/// Use instead:
/// ```no_run
/// const SIZE: usize = 15;
/// let arr: [u8; SIZE] = [0; SIZE];
/// ```
#[clippy::version = "1.93.0"]
pub NEEDLESS_TYPE_CAST,
pedantic,
"binding defined with one type but always cast to another"
}

pub struct Casts {
msrv: Msrv,
}
Expand Down Expand Up @@ -851,6 +878,7 @@ impl_lint_pass!(Casts => [
AS_POINTER_UNDERSCORE,
MANUAL_DANGLING_PTR,
CONFUSING_METHOD_TO_NUMERIC_CAST,
NEEDLESS_TYPE_CAST,
]);

impl<'tcx> LateLintPass<'tcx> for Casts {
Expand Down Expand Up @@ -920,4 +948,8 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
cast_slice_different_sizes::check(cx, expr, self.msrv);
ptr_cast_constness::check_null_ptr_cast_method(cx, expr);
}

fn check_body(&mut self, cx: &LateContext<'tcx>, body: &rustc_hir::Body<'tcx>) {
needless_type_cast::check(cx, body);
}
}
289 changes: 289 additions & 0 deletions clippy_lints/src/casts/needless_type_cast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::visitors::{Descend, for_each_expr, for_each_expr_without_closures};
use core::ops::ControlFlow;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{BlockCheckMode, Body, Expr, ExprKind, HirId, LetStmt, PatKind, StmtKind, UnsafeSource};
use rustc_lint::LateContext;
use rustc_middle::ty::{Ty, TypeVisitableExt};
use rustc_span::Span;

use super::NEEDLESS_TYPE_CAST;

struct BindingInfo<'a> {
source_ty: Ty<'a>,
ty_span: Span,
}

struct UsageInfo<'a> {
cast_to: Option<Ty<'a>>,
in_generic_context: bool,
}

pub(super) fn check<'a>(cx: &LateContext<'a>, body: &Body<'a>) {
let mut bindings: FxHashMap<HirId, BindingInfo<'a>> = FxHashMap::default();

for_each_expr_without_closures(body.value, |expr| {
match expr.kind {
ExprKind::Block(block, _) => {
for stmt in block.stmts {
if let StmtKind::Let(let_stmt) = stmt.kind {
collect_binding_from_local(cx, let_stmt, &mut bindings);
}
}
},
ExprKind::Let(let_expr) => {
collect_binding_from_let(cx, let_expr, &mut bindings);
},
_ => {},
}
ControlFlow::<()>::Continue(())
});

#[allow(rustc::potential_query_instability)]
let mut binding_vec: Vec<_> = bindings.into_iter().collect();
binding_vec.sort_by_key(|(_, info)| info.ty_span.lo());

for (hir_id, binding_info) in binding_vec {
check_binding_usages(cx, body, hir_id, &binding_info);
}
}

fn collect_binding_from_let<'a>(
cx: &LateContext<'a>,
let_expr: &rustc_hir::LetExpr<'a>,
bindings: &mut FxHashMap<HirId, BindingInfo<'a>>,
) {
if let_expr.ty.is_none()
|| let_expr.span.from_expansion()
|| has_generic_return_type(cx, let_expr.init)
|| contains_unsafe(let_expr.init)
{
return;
}

if let PatKind::Binding(_, hir_id, _, _) = let_expr.pat.kind
&& let Some(ty_hir) = let_expr.ty
{
let ty = cx.typeck_results().pat_ty(let_expr.pat);
if ty.is_numeric() {
bindings.insert(
hir_id,
BindingInfo {
source_ty: ty,
ty_span: ty_hir.span,
},
);
}
}
}

fn collect_binding_from_local<'a>(
cx: &LateContext<'a>,
let_stmt: &LetStmt<'a>,
bindings: &mut FxHashMap<HirId, BindingInfo<'a>>,
) {
if let_stmt.ty.is_none()
|| let_stmt.span.from_expansion()
|| let_stmt
.init
.is_some_and(|init| has_generic_return_type(cx, init) || contains_unsafe(init))
{
return;
}

if let PatKind::Binding(_, hir_id, _, _) = let_stmt.pat.kind
&& let Some(ty_hir) = let_stmt.ty
{
let ty = cx.typeck_results().pat_ty(let_stmt.pat);
if ty.is_numeric() {
bindings.insert(
hir_id,
BindingInfo {
source_ty: ty,
ty_span: ty_hir.span,
},
);
}
}
}

fn contains_unsafe(expr: &Expr<'_>) -> bool {
for_each_expr_without_closures(expr, |e| {
if let ExprKind::Block(block, _) = e.kind
&& let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules
{
return ControlFlow::Break(());
}
ControlFlow::Continue(())
})
.is_some()
}

fn has_generic_return_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
match &expr.kind {
ExprKind::Block(block, _) => {
if let Some(tail_expr) = block.expr {
return has_generic_return_type(cx, tail_expr);
}
false
},
ExprKind::If(_, then_block, else_expr) => {
has_generic_return_type(cx, then_block) || else_expr.is_some_and(|e| has_generic_return_type(cx, e))
},
ExprKind::Match(_, arms, _) => arms.iter().any(|arm| has_generic_return_type(cx, arm.body)),
ExprKind::Loop(block, label, ..) => for_each_expr_without_closures(*block, |e| {
match e.kind {
ExprKind::Loop(..) => {
// Unlabeled breaks inside nested loops target the inner loop, not ours
return ControlFlow::Continue(Descend::No);
},
ExprKind::Break(dest, Some(break_expr)) => {
let targets_this_loop =
dest.label.is_none() || dest.label.map(|l| l.ident) == label.map(|l| l.ident);
if targets_this_loop && has_generic_return_type(cx, break_expr) {
return ControlFlow::Break(());
}
},
_ => {},
}
ControlFlow::Continue(Descend::Yes)
})
.is_some(),
ExprKind::MethodCall(..) => {
if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
let sig = cx.tcx.fn_sig(def_id).instantiate_identity();
let ret_ty = sig.output().skip_binder();
return ret_ty.has_param();
}
false
},
ExprKind::Call(callee, _) => {
if let ExprKind::Path(qpath) = &callee.kind {
let res = cx.qpath_res(qpath, callee.hir_id);
if let Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) = res {
let sig = cx.tcx.fn_sig(def_id).instantiate_identity();
let ret_ty = sig.output().skip_binder();
return ret_ty.has_param();
}
}
false
},
_ => false,
}
}

fn is_generic_res(cx: &LateContext<'_>, res: Res) -> bool {
let has_type_params = |def_id| {
cx.tcx
.generics_of(def_id)
.own_params
.iter()
.any(|p| p.kind.is_ty_or_const())
};
match res {
Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => has_type_params(def_id),
// Ctor → Variant → ADT: constructor's parent is variant, variant's parent is the ADT
Res::Def(DefKind::Ctor(..), def_id) => has_type_params(cx.tcx.parent(cx.tcx.parent(def_id))),
_ => false,
}
}

fn is_cast_in_generic_context<'a>(cx: &LateContext<'a>, cast_expr: &Expr<'a>) -> bool {
let mut current_id = cast_expr.hir_id;

loop {
let parent_id = cx.tcx.parent_hir_id(current_id);
if parent_id == current_id {
return false;
}

let parent = cx.tcx.hir_node(parent_id);

match parent {
rustc_hir::Node::Expr(parent_expr) => {
match &parent_expr.kind {
ExprKind::Closure(_) => return false,
ExprKind::Call(callee, _) => {
if let ExprKind::Path(qpath) = &callee.kind {
let res = cx.qpath_res(qpath, callee.hir_id);
if is_generic_res(cx, res) {
return true;
}
}
},
ExprKind::MethodCall(..) => {
if let Some(def_id) = cx.typeck_results().type_dependent_def_id(parent_expr.hir_id)
&& cx
.tcx
.generics_of(def_id)
.own_params
.iter()
.any(|p| p.kind.is_ty_or_const())
{
return true;
}
},
_ => {},
}
current_id = parent_id;
},
_ => return false,
}
}
}

fn check_binding_usages<'a>(cx: &LateContext<'a>, body: &Body<'a>, hir_id: HirId, binding_info: &BindingInfo<'a>) {
let mut usages = Vec::new();

for_each_expr(cx, body.value, |expr| {
if let ExprKind::Path(ref qpath) = expr.kind
&& !expr.span.from_expansion()
&& let Res::Local(id) = cx.qpath_res(qpath, expr.hir_id)
&& id == hir_id
{
let parent_id = cx.tcx.parent_hir_id(expr.hir_id);
let parent = cx.tcx.hir_node(parent_id);

let usage = if let rustc_hir::Node::Expr(parent_expr) = parent
&& let ExprKind::Cast(..) = parent_expr.kind
&& !parent_expr.span.from_expansion()
{
UsageInfo {
cast_to: Some(cx.typeck_results().expr_ty(parent_expr)),
in_generic_context: is_cast_in_generic_context(cx, parent_expr),
}
} else {
UsageInfo {
cast_to: None,
in_generic_context: false,
}
};
usages.push(usage);
}
ControlFlow::<()>::Continue(())
});

let Some(first_target) = usages
.first()
.and_then(|u| u.cast_to)
.filter(|&t| t != binding_info.source_ty)
.filter(|&t| usages.iter().all(|u| u.cast_to == Some(t) && !u.in_generic_context))
else {
return;
};

span_lint_and_sugg(
cx,
NEEDLESS_TYPE_CAST,
binding_info.ty_span,
format!(
"this binding is defined as `{}` but is always cast to `{}`",
binding_info.source_ty, first_target
),
"consider defining it as",
first_target.to_string(),
Applicability::MaybeIncorrect,
);
}
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::casts::FN_TO_NUMERIC_CAST_ANY_INFO,
crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO,
crate::casts::MANUAL_DANGLING_PTR_INFO,
crate::casts::NEEDLESS_TYPE_CAST_INFO,
crate::casts::PTR_AS_PTR_INFO,
crate::casts::PTR_CAST_CONSTNESS_INFO,
crate::casts::REF_AS_PTR_INFO,
Expand Down
Loading