diff --git a/CHANGELOG.md b/CHANGELOG.md index a9448a57f7fd..962f9067a4e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1214,6 +1214,7 @@ Released 2018-09-13 [`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines [`toplevel_ref_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#toplevel_ref_arg [`transmute_bytes_to_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_bytes_to_str +[`transmute_float_to_int`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_float_to_int [`transmute_int_to_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_bool [`transmute_int_to_char`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_char [`transmute_int_to_float`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_float diff --git a/README.md b/README.md index 97a7c97b49a2..6133fa4c3a57 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. -[There are 339 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) +[There are 340 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you: diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index d14f946c8eb8..736ff30c81a7 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -735,6 +735,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf &trait_bounds::TYPE_REPETITION_IN_BOUNDS, &transmute::CROSSPOINTER_TRANSMUTE, &transmute::TRANSMUTE_BYTES_TO_STR, + &transmute::TRANSMUTE_FLOAT_TO_INT, &transmute::TRANSMUTE_INT_TO_BOOL, &transmute::TRANSMUTE_INT_TO_CHAR, &transmute::TRANSMUTE_INT_TO_FLOAT, @@ -1586,6 +1587,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf LintId::of(&mutex_atomic::MUTEX_INTEGER), LintId::of(&needless_borrow::NEEDLESS_BORROW), LintId::of(&path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE), + LintId::of(&transmute::TRANSMUTE_FLOAT_TO_INT), LintId::of(&use_self::USE_SELF), ]); } diff --git a/clippy_lints/src/transmute.rs b/clippy_lints/src/transmute.rs index 12de3023dada..3c63ef765fed 100644 --- a/clippy_lints/src/transmute.rs +++ b/clippy_lints/src/transmute.rs @@ -190,6 +190,28 @@ declare_clippy_lint! { "transmutes from an integer to a float" } +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from a float to an integer. + /// + /// **Why is this bad?** Transmutes are dangerous and error-prone, whereas `to_bits` is intuitive + /// and safe. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// unsafe { + /// let _: u32 = std::mem::transmute(1f32); + /// } + /// + /// // should be: + /// let _: u32 = 1f32.to_bits(); + /// ``` + pub TRANSMUTE_FLOAT_TO_INT, + nursery, + "transmutes from a float to an integer" +} + declare_clippy_lint! { /// **What it does:** Checks for transmutes from a pointer to a pointer, or /// from a reference to a reference. @@ -254,6 +276,7 @@ declare_lint_pass!(Transmute => [ TRANSMUTE_BYTES_TO_STR, TRANSMUTE_INT_TO_BOOL, TRANSMUTE_INT_TO_FLOAT, + TRANSMUTE_FLOAT_TO_INT, UNSOUND_COLLECTION_TRANSMUTE, ]); @@ -520,6 +543,50 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute { ); }, ), + (&ty::Float(float_ty), &ty::Int(_)) | (&ty::Float(float_ty), &ty::Uint(_)) => span_lint_and_then( + cx, + TRANSMUTE_FLOAT_TO_INT, + e.span, + &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), + |db| { + let mut expr = &args[0]; + let mut arg = sugg::Sugg::hir(cx, expr, ".."); + + if let ExprKind::Unary(UnOp::UnNeg, inner_expr) = &expr.kind { + expr = &inner_expr; + } + + if_chain! { + // if the expression is a float literal and it is unsuffixed then + // add a suffix so the suggestion is valid and unambiguous + let op = format!("{}{}", arg, float_ty.name_str()).into(); + if let ExprKind::Lit(lit) = &expr.kind; + if let ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) = lit.node; + then { + match arg { + sugg::Sugg::MaybeParen(_) => arg = sugg::Sugg::MaybeParen(op), + _ => arg = sugg::Sugg::NonParen(op) + } + } + } + + arg = sugg::Sugg::NonParen(format!("{}.to_bits()", arg.maybe_par()).into()); + + // cast the result of `to_bits` if `to_ty` is signed + arg = if let ty::Int(int_ty) = to_ty.kind { + arg.as_ty(int_ty.name_str().to_string()) + } else { + arg + }; + + db.span_suggestion( + e.span, + "consider using", + arg.to_string(), + Applicability::Unspecified, + ); + }, + ), (&ty::Adt(ref from_adt, ref from_substs), &ty::Adt(ref to_adt, ref to_substs)) => { if from_adt.did != to_adt.did || !COLLECTIONS.iter().any(|path| match_def_path(cx, to_adt.did, path)) { diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 1f1c24b2c303..f4ebf6cbd918 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -6,7 +6,7 @@ pub use lint::Lint; pub use lint::LINT_LEVELS; // begin lint list, do not remove this comment, it’s used in `update_lints` -pub const ALL_LINTS: [Lint; 339] = [ +pub const ALL_LINTS: [Lint; 340] = [ Lint { name: "absurd_extreme_comparisons", group: "correctness", @@ -1953,6 +1953,13 @@ pub const ALL_LINTS: [Lint; 339] = [ deprecation: None, module: "transmute", }, + Lint { + name: "transmute_float_to_int", + group: "nursery", + desc: "transmutes from a float to an integer", + deprecation: None, + module: "transmute", + }, Lint { name: "transmute_int_to_bool", group: "complexity", diff --git a/tests/ui/transmute_float_to_int.rs b/tests/ui/transmute_float_to_int.rs new file mode 100644 index 000000000000..ce942751ada8 --- /dev/null +++ b/tests/ui/transmute_float_to_int.rs @@ -0,0 +1,12 @@ +#[warn(clippy::transmute_float_to_int)] + +fn float_to_int() { + let _: u32 = unsafe { std::mem::transmute(1f32) }; + let _: i32 = unsafe { std::mem::transmute(1f32) }; + let _: u64 = unsafe { std::mem::transmute(1f64) }; + let _: i64 = unsafe { std::mem::transmute(1f64) }; + let _: u64 = unsafe { std::mem::transmute(1.0) }; + let _: u64 = unsafe { std::mem::transmute(-1.0) }; +} + +fn main() {} diff --git a/tests/ui/transmute_float_to_int.stderr b/tests/ui/transmute_float_to_int.stderr new file mode 100644 index 000000000000..eb786bb39f95 --- /dev/null +++ b/tests/ui/transmute_float_to_int.stderr @@ -0,0 +1,40 @@ +error: transmute from a `f32` to a `u32` + --> $DIR/transmute_float_to_int.rs:4:27 + | +LL | let _: u32 = unsafe { std::mem::transmute(1f32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1f32.to_bits()` + | + = note: `-D clippy::transmute-float-to-int` implied by `-D warnings` + +error: transmute from a `f32` to a `i32` + --> $DIR/transmute_float_to_int.rs:5:27 + | +LL | let _: i32 = unsafe { std::mem::transmute(1f32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1f32.to_bits() as i32` + +error: transmute from a `f64` to a `u64` + --> $DIR/transmute_float_to_int.rs:6:27 + | +LL | let _: u64 = unsafe { std::mem::transmute(1f64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1f64.to_bits()` + +error: transmute from a `f64` to a `i64` + --> $DIR/transmute_float_to_int.rs:7:27 + | +LL | let _: i64 = unsafe { std::mem::transmute(1f64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1f64.to_bits() as i64` + +error: transmute from a `f64` to a `u64` + --> $DIR/transmute_float_to_int.rs:8:27 + | +LL | let _: u64 = unsafe { std::mem::transmute(1.0) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1.0f64.to_bits()` + +error: transmute from a `f64` to a `u64` + --> $DIR/transmute_float_to_int.rs:9:27 + | +LL | let _: u64 = unsafe { std::mem::transmute(-1.0) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(-1.0f64).to_bits()` + +error: aborting due to 6 previous errors +