Skip to content

Commit 6d28e1a

Browse files
committed
Lint casts to u128 in cast_lossless
1 parent 1807580 commit 6d28e1a

14 files changed

+973
-327
lines changed
+47-64
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
use clippy_config::msrvs::{self, Msrv};
2-
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::diagnostics::span_lint_and_then;
33
use clippy_utils::in_constant;
4-
use clippy_utils::source::{snippet_opt, snippet_with_applicability};
4+
use clippy_utils::source::snippet_opt;
5+
use clippy_utils::sugg::Sugg;
56
use clippy_utils::ty::is_isize_or_usize;
67
use rustc_errors::Applicability;
7-
use rustc_hir::{Expr, ExprKind, QPath, TyKind};
8+
use rustc_hir::{Expr, QPath, TyKind};
89
use rustc_lint::LateContext;
9-
use rustc_middle::ty::{self, FloatTy, Ty, UintTy};
10+
use rustc_middle::ty::{self, FloatTy, Ty};
11+
use rustc_span::hygiene;
1012

1113
use super::{utils, CAST_LOSSLESS};
1214

1315
pub(super) fn check(
1416
cx: &LateContext<'_>,
1517
expr: &Expr<'_>,
16-
cast_op: &Expr<'_>,
18+
cast_from_expr: &Expr<'_>,
1719
cast_from: Ty<'_>,
1820
cast_to: Ty<'_>,
1921
cast_to_hir: &rustc_hir::Ty<'_>,
@@ -23,64 +25,54 @@ pub(super) fn check(
2325
return;
2426
}
2527

26-
// The suggestion is to use a function call, so if the original expression
27-
// has parens on the outside, they are no longer needed.
28-
let mut app = Applicability::MachineApplicable;
29-
let opt = snippet_opt(cx, cast_op.span.source_callsite());
30-
let sugg = opt.as_ref().map_or_else(
31-
|| {
32-
app = Applicability::HasPlaceholders;
33-
".."
34-
},
35-
|snip| {
36-
if should_strip_parens(cast_op, snip) {
37-
&snip[1..snip.len() - 1]
38-
} else {
39-
snip.as_str()
40-
}
41-
},
42-
);
43-
44-
// Display the type alias instead of the aliased type. Fixes #11285
45-
//
46-
// FIXME: Once `lazy_type_alias` is stabilized(?) we should use `rustc_middle` types instead,
47-
// this will allow us to display the right type with `cast_from` as well.
48-
let cast_to_fmt = if let TyKind::Path(QPath::Resolved(None, path)) = cast_to_hir.kind
49-
// It's a bit annoying but the turbofish is optional for types. A type in an `as` cast
50-
// shouldn't have these if they're primitives, which are the only things we deal with.
51-
//
52-
// This could be removed for performance if this check is determined to have a pretty major
53-
// effect.
54-
&& path.segments.iter().all(|segment| segment.args.is_none())
55-
{
56-
snippet_with_applicability(cx, cast_to_hir.span, "..", &mut app)
57-
} else {
58-
cast_to.to_string().into()
59-
};
60-
61-
let message = if cast_from.is_bool() {
62-
format!("casting `{cast_from}` to `{cast_to_fmt}` is more cleanly stated with `{cast_to_fmt}::from(_)`")
63-
} else {
64-
format!("casting `{cast_from}` to `{cast_to_fmt}` may become silently lossy if you later change the type")
65-
};
66-
67-
span_lint_and_sugg(
28+
span_lint_and_then(
6829
cx,
6930
CAST_LOSSLESS,
7031
expr.span,
71-
message,
72-
"try",
73-
format!("{cast_to_fmt}::from({sugg})"),
74-
app,
32+
format!("casts from `{cast_from}` to `{cast_to}` can be expressed infallibly using `From`"),
33+
|diag| {
34+
diag.help("an `as` cast can become silently lossy if the types change in the future");
35+
let mut applicability = Applicability::MachineApplicable;
36+
let from_sugg = Sugg::hir_with_context(cx, cast_from_expr, expr.span.ctxt(), "<from>", &mut applicability);
37+
let Some(ty) = snippet_opt(cx, hygiene::walk_chain(cast_to_hir.span, expr.span.ctxt())) else {
38+
return;
39+
};
40+
match cast_to_hir.kind {
41+
TyKind::Infer => {
42+
diag.span_suggestion_verbose(
43+
expr.span,
44+
"use `Into::into` instead",
45+
format!("{}.into()", from_sugg.maybe_par()),
46+
applicability,
47+
);
48+
},
49+
// Don't suggest `A<_>::B::From(x)` or `macro!()::from(x)`
50+
kind if matches!(kind, TyKind::Path(QPath::Resolved(_, path)) if path.segments.iter().any(|s| s.args.is_some()))
51+
|| !cast_to_hir.span.eq_ctxt(expr.span) =>
52+
{
53+
diag.span_suggestion_verbose(
54+
expr.span,
55+
format!("use `<{ty}>::from` instead"),
56+
format!("<{ty}>::from({from_sugg})"),
57+
applicability,
58+
);
59+
},
60+
_ => {
61+
diag.span_suggestion_verbose(
62+
expr.span,
63+
format!("use `{ty}::from` instead"),
64+
format!("{ty}::from({from_sugg})"),
65+
applicability,
66+
);
67+
},
68+
}
69+
},
7570
);
7671
}
7772

7873
fn should_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: &Msrv) -> bool {
7974
// Do not suggest using From in consts/statics until it is valid to do so (see #2267).
80-
//
81-
// If destination is u128, do not lint because source type cannot be larger
82-
// If source is bool, still lint due to the lint message differing (refers to style)
83-
if in_constant(cx, expr.hir_id) || (!cast_from.is_bool() && matches!(cast_to.kind(), ty::Uint(UintTy::U128))) {
75+
if in_constant(cx, expr.hir_id) {
8476
return false;
8577
}
8678

@@ -110,12 +102,3 @@ fn should_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to
110102
},
111103
}
112104
}
113-
114-
fn should_strip_parens(cast_expr: &Expr<'_>, snip: &str) -> bool {
115-
if let ExprKind::Binary(_, _, _) = cast_expr.kind {
116-
if snip.starts_with('(') && snip.ends_with(')') {
117-
return true;
118-
}
119-
}
120-
false
121-
}

clippy_lints/src/casts/mod.rs

+18-18
Original file line numberDiff line numberDiff line change
@@ -763,45 +763,45 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
763763
return;
764764
}
765765

766-
if let ExprKind::Cast(cast_expr, cast_to_hir) = expr.kind {
766+
if let ExprKind::Cast(cast_from_expr, cast_to_hir) = expr.kind {
767767
if is_hir_ty_cfg_dependant(cx, cast_to_hir) {
768768
return;
769769
}
770770
let (cast_from, cast_to) = (
771-
cx.typeck_results().expr_ty(cast_expr),
771+
cx.typeck_results().expr_ty(cast_from_expr),
772772
cx.typeck_results().expr_ty(expr),
773773
);
774774

775-
if !expr.span.from_expansion() && unnecessary_cast::check(cx, expr, cast_expr, cast_from, cast_to) {
775+
if !expr.span.from_expansion() && unnecessary_cast::check(cx, expr, cast_from_expr, cast_from, cast_to) {
776776
return;
777777
}
778-
cast_slice_from_raw_parts::check(cx, expr, cast_expr, cast_to, &self.msrv);
779-
ptr_cast_constness::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
780-
as_ptr_cast_mut::check(cx, expr, cast_expr, cast_to);
781-
fn_to_numeric_cast_any::check(cx, expr, cast_expr, cast_from, cast_to);
782-
fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to);
783-
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
784-
zero_ptr::check(cx, expr, cast_expr, cast_to_hir);
778+
cast_slice_from_raw_parts::check(cx, expr, cast_from_expr, cast_to, &self.msrv);
779+
ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, &self.msrv);
780+
as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to);
781+
fn_to_numeric_cast_any::check(cx, expr, cast_from_expr, cast_from, cast_to);
782+
fn_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to);
783+
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to);
784+
zero_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
785785

786786
if cast_to.is_numeric() {
787-
cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to, cast_to_hir.span);
787+
cast_possible_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir.span);
788788
if cast_from.is_numeric() {
789789
cast_possible_wrap::check(cx, expr, cast_from, cast_to);
790790
cast_precision_loss::check(cx, expr, cast_from, cast_to);
791-
cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
792-
cast_abs_to_unsigned::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
793-
cast_nan_to_int::check(cx, expr, cast_expr, cast_from, cast_to);
791+
cast_sign_loss::check(cx, expr, cast_from_expr, cast_from, cast_to);
792+
cast_abs_to_unsigned::check(cx, expr, cast_from_expr, cast_from, cast_to, &self.msrv);
793+
cast_nan_to_int::check(cx, expr, cast_from_expr, cast_from, cast_to);
794794
}
795-
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, cast_to_hir, &self.msrv);
796-
cast_enum_constructor::check(cx, expr, cast_expr, cast_from);
795+
cast_lossless::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir, &self.msrv);
796+
cast_enum_constructor::check(cx, expr, cast_from_expr, cast_from);
797797
}
798798

799799
as_underscore::check(cx, expr, cast_to_hir);
800800

801801
if self.msrv.meets(msrvs::PTR_FROM_REF) {
802-
ref_as_ptr::check(cx, expr, cast_expr, cast_to_hir);
802+
ref_as_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
803803
} else if self.msrv.meets(msrvs::BORROW_AS_PTR) {
804-
borrow_as_ptr::check(cx, expr, cast_expr, cast_to_hir);
804+
borrow_as_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
805805
}
806806
}
807807

clippy_utils/src/sugg.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ impl Display for Sugg<'_> {
5252
impl<'a> Sugg<'a> {
5353
/// Prepare a suggestion from an expression.
5454
pub fn hir_opt(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Self> {
55-
let get_snippet = |span| snippet(cx, span, "");
55+
let ctxt = expr.span.ctxt();
56+
let get_snippet = |span| snippet_with_context(cx, span, ctxt, "", &mut Applicability::Unspecified).0;
5657
snippet_opt(cx, expr.span).map(|_| Self::hir_from_snippet(expr, get_snippet))
5758
}
5859

@@ -100,7 +101,9 @@ impl<'a> Sugg<'a> {
100101
applicability: &mut Applicability,
101102
) -> Self {
102103
if expr.span.ctxt() == ctxt {
103-
Self::hir_from_snippet(expr, |span| snippet(cx, span, default))
104+
Self::hir_from_snippet(expr, |span| {
105+
snippet_with_context(cx, span, ctxt, default, applicability).0
106+
})
104107
} else {
105108
let (snip, _) = snippet_with_context(cx, expr.span, ctxt, default, applicability);
106109
Sugg::NonParen(snip)
@@ -109,7 +112,7 @@ impl<'a> Sugg<'a> {
109112

110113
/// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*`
111114
/// function variants of `Sugg`, since these use different snippet functions.
112-
fn hir_from_snippet(expr: &hir::Expr<'_>, get_snippet: impl Fn(Span) -> Cow<'a, str>) -> Self {
115+
fn hir_from_snippet(expr: &hir::Expr<'_>, mut get_snippet: impl FnMut(Span) -> Cow<'a, str>) -> Self {
113116
if let Some(range) = higher::Range::hir(expr) {
114117
let op = match range.limits {
115118
ast::RangeLimits::HalfOpen => AssocOp::DotDot,

0 commit comments

Comments
 (0)