Skip to content
Draft
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
8 changes: 6 additions & 2 deletions clippy_lints/src/casts/unnecessary_cast.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::numeric_literal::NumericLiteral;
use clippy_utils::source::{SpanRangeExt, snippet_opt};
use clippy_utils::ty::expr_type_is_certain;
use clippy_utils::visitors::{Visitable, for_each_expr_without_closures};
use clippy_utils::{get_parent_expr, is_hir_ty_cfg_dependant, is_ty_alias, path_to_local};
use rustc_ast::{LitFloatType, LitIntType, LitKind};
Expand Down Expand Up @@ -143,7 +144,10 @@ pub(super) fn check<'tcx>(
}
}

if cast_from.kind() == cast_to.kind() && !expr.span.in_external_macro(cx.sess().source_map()) {
if cast_from.kind() == cast_to.kind()
&& !expr.span.in_external_macro(cx.sess().source_map())
&& expr_type_is_certain(cx, cast_expr)
{
enum MaybeParenOrBlock {
Paren,
Block,
Expand All @@ -167,7 +171,7 @@ pub(super) fn check<'tcx>(
sym::assert_ne_macro,
sym::debug_assert_ne_macro,
];
matches!(expr.span.ctxt().outer_expn_data().macro_def_id, Some(def_id) if
matches!(expr.span.ctxt().outer_expn_data().macro_def_id, Some(def_id) if
cx.tcx.get_diagnostic_name(def_id).is_some_and(|sym| ALLOWED_MACROS.contains(&sym)))
}

Expand Down
1 change: 1 addition & 0 deletions clippy_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#![feature(assert_matches)]
#![feature(unwrap_infallible)]
#![feature(array_windows)]
#![feature(try_trait_v2)]
#![recursion_limit = "512"]
#![allow(
clippy::missing_errors_doc,
Expand Down
62 changes: 51 additions & 11 deletions clippy_utils/src/ty/type_certainty/certainty.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
use rustc_hir::def_id::DefId;
use std::fmt::Debug;
use std::ops::{ControlFlow, FromResidual, Try};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TypeKind {
PrimTy,
AdtDef(DefId),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Certainty {
/// Determining the type requires contextual information.
Uncertain,

/// The type can be determined purely from subexpressions. If the argument is `Some(..)`, the
/// specific `DefId` is known. Such arguments are needed to handle path segments whose `res` is
/// `Res::Err`.
Certain(Option<DefId>),
/// specific primitive type or `DefId` is known. Such arguments are needed to handle path
/// segments whose `res` is `Res::Err`.
Certain(Option<TypeKind>),

/// The heuristic believes that more than one `DefId` applies to a type---this is a bug.
Contradiction,
Expand All @@ -23,7 +30,7 @@ pub trait TryJoin: Sized {
fn try_join(self, other: Self) -> Option<Self>;
}

impl Meet for Option<DefId> {
impl Meet for Option<TypeKind> {
fn meet(self, other: Self) -> Self {
match (self, other) {
(None, _) | (_, None) => None,
Expand All @@ -32,11 +39,11 @@ impl Meet for Option<DefId> {
}
}

impl TryJoin for Option<DefId> {
impl TryJoin for Option<TypeKind> {
fn try_join(self, other: Self) -> Option<Self> {
match (self, other) {
(Some(lhs), Some(rhs)) => (lhs == rhs).then_some(Some(lhs)),
(Some(def_id), _) | (_, Some(def_id)) => Some(Some(def_id)),
(Some(ty_kind), _) | (_, Some(ty_kind)) => Some(Some(ty_kind)),
(None, None) => Some(None),
}
}
Expand Down Expand Up @@ -79,29 +86,37 @@ impl Certainty {
/// Join two `Certainty`s after clearing their `DefId`s. This method should be used when `self`
/// or `other` do not necessarily refer to types, e.g., when they are aggregations of other
/// `Certainty`s.
pub fn join_clearing_def_ids(self, other: Self) -> Self {
self.clear_def_id().join(other.clear_def_id())
pub fn join_clearing_types(self, other: Self) -> Self {
self.clear_type().join(other.clear_type())
}

pub fn clear_def_id(self) -> Certainty {
pub fn clear_type(self) -> Certainty {
if matches!(self, Certainty::Certain(_)) {
Certainty::Certain(None)
} else {
self
}
}

pub fn with_prim_ty(self) -> Certainty {
if matches!(self, Certainty::Certain(_)) {
Certainty::Certain(Some(TypeKind::PrimTy))
} else {
self
}
}

pub fn with_def_id(self, def_id: DefId) -> Certainty {
if matches!(self, Certainty::Certain(_)) {
Certainty::Certain(Some(def_id))
Certainty::Certain(Some(TypeKind::AdtDef(def_id)))
} else {
self
}
}

pub fn to_def_id(self) -> Option<DefId> {
match self {
Certainty::Certain(inner) => inner,
Certainty::Certain(Some(TypeKind::AdtDef(def_id))) => Some(def_id),
_ => None,
}
}
Expand All @@ -120,3 +135,28 @@ pub fn meet(iter: impl Iterator<Item = Certainty>) -> Certainty {
pub fn join(iter: impl Iterator<Item = Certainty>) -> Certainty {
iter.fold(Certainty::Uncertain, Certainty::join)
}

pub struct NoCertainty(Certainty);

impl FromResidual<NoCertainty> for Certainty {
fn from_residual(residual: NoCertainty) -> Self {
residual.0
}
}

impl Try for Certainty {
type Output = Certainty;

type Residual = NoCertainty;

fn from_output(output: Self::Output) -> Self {
output
}

fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
match self {
Certainty::Certain(_) => ControlFlow::Continue(self),
_ => ControlFlow::Break(NoCertainty(self)),
}
}
}
44 changes: 26 additions & 18 deletions clippy_utils/src/ty/type_certainty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use rustc_middle::ty::{self, AdtDef, GenericArgKind, Ty};
use rustc_span::Span;

mod certainty;
use certainty::{Certainty, Meet, join, meet};
use certainty::{Certainty, Meet, TypeKind, join, meet};

pub fn expr_type_is_certain(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
expr_type_certainty(cx, expr, false).is_certain()
Expand All @@ -46,11 +46,12 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>, in_arg: bool) -> C
} else {
Certainty::Uncertain
};
lhs.join_clearing_def_ids(rhs)
lhs.join_clearing_types(rhs)
},

ExprKind::MethodCall(method, receiver, args, _) => {
let mut receiver_type_certainty = expr_type_certainty(cx, receiver, false);
let mut receiver_type_certainty = expr_type_certainty(cx, receiver, false)?;

// Even if `receiver_type_certainty` is `Certain(Some(..))`, the `Self` type in the method
// identified by `type_dependent_def_id(..)` can differ. This can happen as a result of a `deref`,
// for example. So update the `DefId` in `receiver_type_certainty` (if any).
Expand All @@ -66,7 +67,7 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>, in_arg: bool) -> C
.chain(args.iter().map(|arg| expr_type_certainty(cx, arg, true))),
)
} else {
Certainty::Uncertain
return Certainty::Uncertain;
};
lhs.join(rhs)
},
Expand Down Expand Up @@ -110,15 +111,20 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>, in_arg: bool) -> C

ExprKind::Struct(qpath, _, _) => qpath_certainty(cx, qpath, true),

ExprKind::Block(block, _) => block
.expr
.map_or(Certainty::Certain(None), |expr| expr_type_certainty(cx, expr, false)),

_ => Certainty::Uncertain,
};

let expr_ty = cx.typeck_results().expr_ty(expr);
if let Some(def_id) = adt_def_id(expr_ty) {
certainty.with_def_id(def_id)
} else {
certainty.clear_def_id()
}
let result = match cx.typeck_results().expr_ty(expr).kind() {
ty::Adt(adt_def, _) => certainty.with_def_id(adt_def.did()),
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) => certainty.with_prim_ty(),
_ => certainty.clear_type(),
};

result
}

struct CertaintyVisitor<'cx, 'tcx> {
Expand Down Expand Up @@ -205,7 +211,11 @@ fn qpath_certainty(cx: &LateContext<'_>, qpath: &QPath<'_>, resolves_to_type: bo
.map_or(Certainty::Uncertain, |def_id| {
let generics = cx.tcx.generics_of(def_id);
if generics.is_empty() {
Certainty::Certain(if resolves_to_type { Some(def_id) } else { None })
Certainty::Certain(if resolves_to_type {
Some(TypeKind::AdtDef(def_id))
} else {
None
})
} else {
Certainty::Uncertain
}
Expand All @@ -226,7 +236,7 @@ fn param_certainty(cx: &LateContext<'_>, param: &Param<'_>) -> Certainty {
let body_params = cx.tcx.hir_body_owned_by(owner_did).params;
std::iter::zip(body_params, inputs)
.find(|(p, _)| p.hir_id == param.hir_id)
.map_or(Certainty::Uncertain, |(_, ty)| type_certainty(cx, ty).clear_def_id())
.map_or(Certainty::Uncertain, |(_, ty)| type_certainty(cx, ty).clear_type())
}

fn path_segment_certainty(
Expand Down Expand Up @@ -259,7 +269,7 @@ fn path_segment_certainty(
.args
.map_or(Certainty::Uncertain, |args| generic_args_certainty(cx, args));
// See the comment preceding `qpath_certainty`. `def_id` could refer to a type or a value.
let certainty = lhs.join_clearing_def_ids(rhs);
let certainty = lhs.join_clearing_types(rhs);
if resolves_to_type {
if let DefKind::TyAlias = cx.tcx.def_kind(def_id) {
adt_def_id(cx.tcx.type_of(def_id).instantiate_identity())
Expand All @@ -275,9 +285,7 @@ fn path_segment_certainty(
}
},

Res::PrimTy(_) | Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } | Res::SelfCtor(_) => {
Certainty::Certain(None)
},
Res::PrimTy(_) => Certainty::Certain(Some(TypeKind::PrimTy)),

// `get_parent` because `hir_id` refers to a `Pat`, and we're interested in the node containing the `Pat`.
Res::Local(hir_id) => match cx.tcx.parent_hir_node(hir_id) {
Expand All @@ -295,13 +303,13 @@ fn path_segment_certainty(
if resolves_to_type {
certainty
} else {
certainty.clear_def_id()
certainty.clear_type()
}
},
_ => Certainty::Uncertain,
},

_ => Certainty::Uncertain,
_ => Certainty::Certain(None),
};
debug_assert!(resolves_to_type || certainty.to_def_id().is_none());
certainty
Expand Down
40 changes: 40 additions & 0 deletions tests/ui/unnecessary_cast.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
clippy::no_effect,
clippy::nonstandard_macro_braces,
clippy::unnecessary_operation,
clippy::double_parens,
nonstandard_style,
unused
)]
Expand Down Expand Up @@ -283,4 +284,43 @@ mod fixable {
let _ = 5i32 as i64;
//~^ unnecessary_cast
}

fn issue_14366(i: u32) {
// Do not remove the cast if it helps determining the type
let _ = ((1.0 / 8.0) as f64).powf(i as f64);

// But remove useless casts anyway
let _ = (((1.0 / 8.0) as f64)).powf(i as f64);
//~^ unnecessary_cast
}

fn ambiguity() {
pub trait T {}
impl T for u32 {}
impl T for String {}
fn f(_: impl T) {}

f((1 + 2) as u32);
f(((1 + 2u32)));
//~^ unnecessary_cast
}

fn with_blocks(a: i64, b: i64, c: u64) {
let threshold = if c < 10 { a } else { b };
let _ = threshold;
//~^ unnecessary_cast
}

fn with_prim_ty() {
let threshold = 20;
let threshold = if threshold == 0 {
i64::MAX
} else if threshold <= 60 {
10
} else {
0
};
let _ = threshold;
//~^ unnecessary_cast
}
}
40 changes: 40 additions & 0 deletions tests/ui/unnecessary_cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
clippy::no_effect,
clippy::nonstandard_macro_braces,
clippy::unnecessary_operation,
clippy::double_parens,
nonstandard_style,
unused
)]
Expand Down Expand Up @@ -283,4 +284,43 @@ mod fixable {
let _ = 5i32 as i64 as i64;
//~^ unnecessary_cast
}

fn issue_14366(i: u32) {
// Do not remove the cast if it helps determining the type
let _ = ((1.0 / 8.0) as f64).powf(i as f64);

// But remove useless casts anyway
let _ = (((1.0 / 8.0) as f64) as f64).powf(i as f64);
//~^ unnecessary_cast
}

fn ambiguity() {
pub trait T {}
impl T for u32 {}
impl T for String {}
fn f(_: impl T) {}

f((1 + 2) as u32);
f((1 + 2u32) as u32);
//~^ unnecessary_cast
}

fn with_blocks(a: i64, b: i64, c: u64) {
let threshold = if c < 10 { a } else { b };
let _ = threshold as i64;
//~^ unnecessary_cast
}

fn with_prim_ty() {
let threshold = 20;
let threshold = if threshold == 0 {
i64::MAX
} else if threshold <= 60 {
10
} else {
0
};
let _ = threshold as i64;
//~^ unnecessary_cast
}
}
Loading
Loading