Skip to content
Closed
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
60 changes: 37 additions & 23 deletions clippy_lints/src/casts/cast_lossless.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use clippy_utils::ty::is_isize_or_usize;
use rustc_errors::Applicability;
use rustc_hir::{Expr, QPath, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, FloatTy, Ty};
use rustc_middle::ty::{self, Ty};
use rustc_span::hygiene;

use super::{CAST_LOSSLESS, utils};
Expand Down Expand Up @@ -76,28 +76,42 @@ fn should_lint(cx: &LateContext<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: M
return false;
}

match (
utils::int_ty_to_nbits(cx.tcx, cast_from),
utils::int_ty_to_nbits(cx.tcx, cast_to),
) {
(Some(from_nbits), Some(to_nbits)) => {
let cast_signed_to_unsigned = cast_from.is_signed() && !cast_to.is_signed();
!is_isize_or_usize(cast_from)
&& !is_isize_or_usize(cast_to)
&& from_nbits < to_nbits
&& !cast_signed_to_unsigned
},
if matches!(cast_from.kind(), ty::Bool) && cast_to.is_integral() {
return msrv.meets(cx, msrvs::FROM_BOOL);
}

(Some(from_nbits), None) => {
// FIXME: handle `f16` and `f128`
let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind() {
32
} else {
64
};
!is_isize_or_usize(cast_from) && from_nbits < to_nbits
},
(None, Some(_)) if cast_from.is_bool() && msrv.meets(cx, msrvs::FROM_BOOL) => true,
_ => matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64)),
if cast_from.is_numeric() {
return match (cast_from.is_integral(), cast_to.is_integral()) {
(true, true) => {
let cast_signed_to_unsigned = cast_from.is_signed() && !cast_to.is_signed();
let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx);
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
!is_isize_or_usize(cast_from)
&& !is_isize_or_usize(cast_to)
&& from_nbits < to_nbits
&& !cast_signed_to_unsigned
},
(true, false) => {
let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx);
let to_nbits = utils::float_ty_to_nbits(cast_to);
if from_nbits == 64 && to_nbits == 128 {
// FIXME(f16_f128): https://github.com/rust-lang/rust/blob/91fad92585b2dafc52a074e502b2a6c1f093ca35/library/core/src/convert/num.rs#L171
return false;
}
!is_isize_or_usize(cast_from) && from_nbits < to_nbits
},
(false, true) => false,
(false, false) => {
let from_nbits = utils::float_ty_to_nbits(cast_from);
let to_nbits = utils::float_ty_to_nbits(cast_to);
if from_nbits == 16 && to_nbits == 32 {
// FIXME(f16_f128): https://github.com/rust-lang/rust/issues/123831
return false;
}
from_nbits < to_nbits
},
};
}

false
}
24 changes: 16 additions & 8 deletions clippy_lints/src/casts/cast_possible_truncation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use rustc_errors::{Applicability, Diag};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, FloatTy, Ty};
use rustc_middle::ty::{self, Ty};
use rustc_span::Span;

use super::{CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION, utils};
Expand Down Expand Up @@ -91,14 +91,15 @@ pub(super) fn check(
cast_to: Ty<'_>,
cast_to_span: Span,
) {
let msg = match (cast_from.kind(), utils::int_ty_to_nbits(cx.tcx, cast_to)) {
(ty::Int(_) | ty::Uint(_), Some(to_nbits)) => {
let msg = match (cast_from.kind(), cast_to.is_integral()) {
(ty::Int(_) | ty::Uint(_), true) => {
let from_nbits = apply_reductions(
cx,
utils::int_ty_to_nbits(cx.tcx, cast_from).unwrap(),
utils::int_ty_to_nbits(cast_from, cx.tcx),
cast_expr,
cast_from.is_signed(),
);
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);

let (should_lint, suffix) = match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) {
(true, true) | (false, false) => (to_nbits < from_nbits, ""),
Expand All @@ -120,7 +121,7 @@ pub(super) fn check(
format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}",)
},

(ty::Adt(def, _), Some(to_nbits)) if def.is_enum() => {
(ty::Adt(def, _), true) if def.is_enum() => {
let (from_nbits, variant) = if let ExprKind::Path(p) = &cast_expr.kind
&& let Res::Def(DefKind::Ctor(..), id) = cx.qpath_res(p, cast_expr.hir_id)
{
Expand All @@ -131,6 +132,7 @@ pub(super) fn check(
} else {
(utils::enum_ty_to_nbits(*def, cx.tcx), None)
};
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);

let cast_from_ptr_size = def.repr().int.is_none_or(|ty| matches!(ty, IntegerType::Pointer(_),));
let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) {
Expand All @@ -155,12 +157,18 @@ pub(super) fn check(
format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}")
},

(ty::Float(_), Some(_)) => {
(ty::Float(_), true) => {
format!("casting `{cast_from}` to `{cast_to}` may truncate the value")
},

(ty::Float(FloatTy::F64), None) if matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) => {
"casting `f64` to `f32` may truncate the value".to_string()
(ty::Float(_), false) => {
let from_nbits = utils::float_ty_to_nbits(cast_from);
let to_nbits = utils::float_ty_to_nbits(cast_to);
if from_nbits > to_nbits {
format!("casting `f{from_nbits}` to `f{to_nbits}` may truncate the value")
} else {
return;
}
},

_ => return,
Expand Down
10 changes: 5 additions & 5 deletions clippy_lints/src/casts/cast_possible_wrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,9 @@ enum EmitState {
}

pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
let (Some(from_nbits), Some(to_nbits)) = (
utils::int_ty_to_nbits(cx.tcx, cast_from),
utils::int_ty_to_nbits(cx.tcx, cast_to),
) else {
if !(cast_from.is_integral() && cast_to.is_integral()) {
return;
};
}

// emit a lint if a cast is:
// 1. unsigned to signed
Expand All @@ -38,6 +35,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca
return;
}

let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx);
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);

let should_lint = match (cast_from.is_ptr_sized_integral(), cast_to.is_ptr_sized_integral()) {
(true, true) => {
// casts between two ptr sized integers are trivially always the same size
Expand Down
21 changes: 9 additions & 12 deletions clippy_lints/src/casts/cast_precision_loss.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,25 @@ use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::is_isize_or_usize;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, FloatTy, Ty};
use rustc_middle::ty::Ty;

use super::{CAST_PRECISION_LOSS, utils};

pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
let Some(from_nbits) = utils::int_ty_to_nbits(cx.tcx, cast_from) else {
if !cast_from.is_integral() || cast_to.is_integral() {
return;
};
}

// FIXME: handle `f16` and `f128`
let to_nbits = match cast_to.kind() {
ty::Float(f @ (FloatTy::F32 | FloatTy::F64)) => f.bit_width(),
_ => return,
};
let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx);
let to_nbits = utils::float_ty_to_nbits(cast_to);

if !(is_isize_or_usize(cast_from) || from_nbits >= to_nbits) {
return;
}

let cast_to_f64 = to_nbits == 64;
let mantissa_nbits = if cast_to_f64 { 52 } else { 23 };
let arch_dependent = is_isize_or_usize(cast_from) && cast_to_f64;
let mantissa_nbits = utils::float_ty_to_mantissa_nbits(cast_to);

let arch_dependent = is_isize_or_usize(cast_from) && to_nbits == 64;
let arch_dependent_str = "on targets with 64-bit wide pointers ";
let from_nbits_str = if arch_dependent {
"64".to_owned()
Expand All @@ -42,7 +39,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca
"casting `{0}` to `{1}` causes a loss of precision {2}(`{0}` is {3} bits wide, \
but `{1}`'s mantissa is only {4} bits wide)",
cast_from,
if cast_to_f64 { "f64" } else { "f32" },
format!("f{to_nbits}"),
if arch_dependent { arch_dependent_str } else { "" },
from_nbits_str,
mantissa_nbits
Expand Down
12 changes: 7 additions & 5 deletions clippy_lints/src/casts/fn_to_numeric_cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,18 @@ use super::{FN_TO_NUMERIC_CAST, utils};

pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
// We only want to check casts to `ty::Uint` or `ty::Int`
let Some(to_nbits) = utils::int_ty_to_nbits(cx.tcx, cast_to) else {
return;
};
match cast_to.kind() {
ty::Uint(_) | ty::Int(..) => { /* continue on */ },
_ => return,
}

match cast_from.kind() {
ty::FnDef(..) | ty::FnPtr(..) => {
let mut applicability = Applicability::MaybeIncorrect;
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability);
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);

if to_nbits >= cx.tcx.data_layout.pointer_size.bits() && !cast_to.is_usize() {
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability);
if (to_nbits >= cx.tcx.data_layout.pointer_size.bits()) && !cast_to.is_usize() {
span_lint_and_sugg(
cx,
FN_TO_NUMERIC_CAST,
Expand Down
9 changes: 6 additions & 3 deletions clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ use super::{FN_TO_NUMERIC_CAST_WITH_TRUNCATION, utils};

pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
// We only want to check casts to `ty::Uint` or `ty::Int`
let Some(to_nbits) = utils::int_ty_to_nbits(cx.tcx, cast_to) else {
return;
};
match cast_to.kind() {
ty::Uint(_) | ty::Int(..) => { /* continue on */ },
_ => return,
}

match cast_from.kind() {
ty::FnDef(..) | ty::FnPtr(..) => {
let mut applicability = Applicability::MaybeIncorrect;
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability);
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);

if to_nbits < cx.tcx.data_layout.pointer_size.bits() {
span_lint_and_sugg(
Expand Down
17 changes: 4 additions & 13 deletions clippy_lints/src/casts/unnecessary_cast.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::casts::utils;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::numeric_literal::NumericLiteral;
use clippy_utils::source::{SpanRangeExt, snippet_opt};
Expand All @@ -9,7 +10,7 @@ use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, ExprKind, Lit, Node, Path, QPath, TyKind, UnOp};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::ty::adjustment::Adjust;
use rustc_middle::ty::{self, FloatTy, InferTy, Ty};
use rustc_middle::ty::{self, Ty};
use rustc_span::{Symbol, sym};
use std::ops::ControlFlow;

Expand Down Expand Up @@ -108,8 +109,8 @@ pub(super) fn check<'tcx>(
&& let Some(src) = cast_expr.span.get_source_text(cx)
&& cast_to.is_floating_point()
&& let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node)
&& let from_nbits = 128 - n.get().leading_zeros()
&& let to_nbits = fp_ty_mantissa_nbits(cast_to)
&& let from_nbits = 128 - u64::from(n.get().leading_zeros())
&& let to_nbits = utils::float_ty_to_mantissa_nbits(cast_to)
&& from_nbits != 0
&& to_nbits != 0
&& from_nbits <= to_nbits
Expand Down Expand Up @@ -257,16 +258,6 @@ fn get_numeric_literal<'e>(expr: &'e Expr<'e>) -> Option<&'e Lit> {
}
}

/// Returns the mantissa bits wide of a fp type.
/// Will return 0 if the type is not a fp
fn fp_ty_mantissa_nbits(typ: Ty<'_>) -> u32 {
match typ.kind() {
ty::Float(FloatTy::F32) => 23,
ty::Float(FloatTy::F64) | ty::Infer(InferTy::FloatVar(_)) => 52,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ty::Infer(InferTy::FloatVar(_)) => 52 should be unnecessary, is that right?

_ => 0,
}
}

/// Finds whether an `Expr` returns a type alias.
///
/// TODO: Maybe we should move this to `clippy_utils` so others won't need to go down this dark,
Expand Down
59 changes: 50 additions & 9 deletions clippy_lints/src/casts/utils.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,55 @@
use clippy_utils::ty::{EnumValue, read_explicit_enum_value};
use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, UintTy, VariantDiscr};
use rustc_middle::ty::{self, AdtDef, FloatTy, IntTy, Ty, TyCtxt, UintTy, VariantDiscr};

/// Returns the size in bits of an integral type, or `None` if `ty` is not an
/// integral type.
pub(super) fn int_ty_to_nbits(tcx: TyCtxt<'_>, ty: Ty<'_>) -> Option<u64> {
match ty.kind() {
ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => Some(tcx.data_layout.pointer_size.bits()),
ty::Int(i) => i.bit_width(),
ty::Uint(i) => i.bit_width(),
_ => None,
/// Returns the size in bits of an integral type.
/// Panics if the type is not an int or uint variant
pub(super) fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 {
match typ.kind() {
ty::Int(i) => match i {
IntTy::Isize => tcx.data_layout.pointer_size.bits(),
IntTy::I8 => 8,
IntTy::I16 => 16,
IntTy::I32 => 32,
IntTy::I64 => 64,
IntTy::I128 => 128,
},
ty::Uint(i) => match i {
UintTy::Usize => tcx.data_layout.pointer_size.bits(),
UintTy::U8 => 8,
UintTy::U16 => 16,
UintTy::U32 => 32,
UintTy::U64 => 64,
UintTy::U128 => 128,
},
_ => unreachable!(),
}
}
Comment on lines +6 to +26
Copy link
Member

@samueltardieu samueltardieu Jun 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are reverting (here and at other places) changes that were committed yesterday. Please don't do that. And please explain what your PR is doing, what it fixes, why it is necessary, and when you change existing things why your solution is better than the one in place. For example, why would panicking (through unreachable!()) be best than returning None here? The Option version lets us check if this is an integral type at the same time, which is almost always necessary from the caller point of view anyway. Also, it is shorter by using i.bit_width() rather than hard-coding every case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to your approach, how can we make use of int_ty_to_nbits, float_ty_to_nbits, and float_ty_to_mantissa_nbits at the same time? Should all three return Option, and then handle the None cases externally?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what you're trying to achieve, so this is a difficult question to answer.


/// Returns the size in bits of an floating type.
/// Panics if the type is not an float variant
pub(super) fn float_ty_to_nbits(typ: Ty<'_>) -> u64 {
match typ.kind() {
ty::Float(i) => match i {
FloatTy::F16 => 16,
FloatTy::F32 => 32,
FloatTy::F64 => 64,
FloatTy::F128 => 128,
},
_ => unreachable!(),
}
}

/// Returns the mantissa size in bits of an floating type.
/// Panics if the type is not an float variant
pub(super) fn float_ty_to_mantissa_nbits(typ: Ty<'_>) -> u64 {
match typ.kind() {
ty::Float(i) => match i {
FloatTy::F16 => 10,
FloatTy::F32 => 23,
FloatTy::F64 => 52,
FloatTy::F128 => 112,
},
_ => unreachable!(),
}
}

Expand Down