diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fd01a2573b5..f0a946011a22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5434,6 +5434,7 @@ Released 2018-09-13 [`type_id_on_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_id_on_box [`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds [`unchecked_duration_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction +[`unconstrained_numeric_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#unconstrained_numeric_literal [`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks [`undropped_manually_drops`]: https://rust-lang.github.io/rust-clippy/master/index.html#undropped_manually_drops [`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index abdbd368d5ea..9a6f52e812da 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -213,6 +213,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::guidelines::PASSING_STRING_TO_C_FUNCTIONS_INFO, crate::guidelines::PTR_DOUBLE_FREE_INFO, crate::guidelines::RETURN_STACK_ADDRESS_INFO, + crate::guidelines::UNCONSTRAINED_NUMERIC_LITERAL_INFO, crate::guidelines::UNSAFE_BLOCK_IN_PROC_MACRO_INFO, crate::guidelines::UNTRUSTED_LIB_LOADING_INFO, crate::guidelines_early::IMPLICIT_ABI_INFO, diff --git a/clippy_lints/src/guidelines/mod.rs b/clippy_lints/src/guidelines/mod.rs index 8a02a5eeb144..363e73561d54 100644 --- a/clippy_lints/src/guidelines/mod.rs +++ b/clippy_lints/src/guidelines/mod.rs @@ -5,6 +5,7 @@ mod invalid_char_range; mod passing_string_to_c_functions; mod ptr; mod return_stack_address; +mod unconstrained_numeric_literal; mod unsafe_block_in_proc_macro; mod untrusted_lib_loading; @@ -354,6 +355,34 @@ declare_clippy_lint! { "converting to char from a out-of-range unsigned int" } +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of unconstrained numeric literals in variable initialization. + /// + /// This lint is differ from `default_numeric_fallback` in the following perspectives: + /// 1. It only checks numeric literals in a local binding. + /// 2. It lints all kinds of numeric literals rather than `i32` and `f64`. + /// + /// ### Why is this bad? + /// Initializing a numeric type without labeling its type could cause default numeric fallback. + /// + /// ### Example + /// ```rust + /// let i = 10; + /// let f = 1.23; + /// ``` + /// + /// Use instead: + /// ```rust + /// let i = 10i32; + /// let f = 1.23f64; + /// ``` + #[clippy::version = "1.74.0"] + pub UNCONSTRAINED_NUMERIC_LITERAL, + nursery, + "usage of unconstrained numeric literals in variable initialization" +} + /// Helper struct with user configured path-like functions, such as `std::fs::read`, /// and a set for `def_id`s which should be filled during checks. /// @@ -430,6 +459,7 @@ impl_lint_pass!(LintGroup => [ DANGLING_PTR_DEREFERENCE, RETURN_STACK_ADDRESS, INVALID_CHAR_RANGE, + UNCONSTRAINED_NUMERIC_LITERAL, ]); impl<'tcx> LateLintPass<'tcx> for LintGroup { @@ -500,6 +530,7 @@ impl<'tcx> LateLintPass<'tcx> for LintGroup { fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx hir::Local<'tcx>) { ptr::check_local(cx, local); + unconstrained_numeric_literal::check_local(cx, local); } fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'tcx>) { diff --git a/clippy_lints/src/guidelines/unconstrained_numeric_literal.rs b/clippy_lints/src/guidelines/unconstrained_numeric_literal.rs new file mode 100644 index 000000000000..e5f641aee7c5 --- /dev/null +++ b/clippy_lints/src/guidelines/unconstrained_numeric_literal.rs @@ -0,0 +1,96 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_lint_allowed; +use clippy_utils::source::snippet_opt; +use rustc_errors::{Applicability, MultiSpan}; +use rustc_hir::intravisit::{walk_expr, Visitor}; +use rustc_hir::{Expr, ExprKind, Local, TyKind}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_span::Span; + +use super::UNCONSTRAINED_NUMERIC_LITERAL; + +pub(super) fn check_local<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { + if !is_lint_allowed(cx, UNCONSTRAINED_NUMERIC_LITERAL, local.hir_id) + && let Some(init) = local.init + && local_has_implicit_ty(local) + { + let mut visitor = LitVisitor::new(cx); + visitor.visit_expr(init); + + // The type could be wildcard (`_`), therefore we need to include its span for suggestion. + let span = if let Some(ty) = local.ty { + local.pat.span.to(ty.span) + } else { + local.pat.span + }; + + if !visitor.unconstrained_lit_spans.is_empty() { + span_lint_and_then( + cx, + UNCONSTRAINED_NUMERIC_LITERAL, + span, + "type of this numeric variable is unconstrained", + |diag| { + let sugg = format!( + "{}: {}", + snippet_opt(cx, local.pat.span).unwrap_or("_".to_string()), + ty_suggestion(cx, init), + ); + diag.span_suggestion( + span, + "either add suffix to above numeric literal(s) or label the type explicitly", + sugg, + Applicability::MachineApplicable + ); + diag.span_note( + MultiSpan::from_spans(visitor.unconstrained_lit_spans), + "unconstrained numeric literals defined here", + ); + } + ); + } + } +} + +fn local_has_implicit_ty(local: &Local<'_>) -> bool { + match local.ty { + Some(ty) if matches!(ty.kind, TyKind::Infer) => true, + None => true, + _ => false, + } +} + +struct LitVisitor<'hir, 'tcx> { + cx: &'hir LateContext<'tcx>, + unconstrained_lit_spans: Vec, +} + +impl<'hir, 'tcx> LitVisitor<'hir, 'tcx> { + fn new(cx: &'hir LateContext<'tcx>) -> Self { + Self { + cx, + unconstrained_lit_spans: vec![], + } + } +} + +impl<'hir, 'tcx> Visitor<'hir> for LitVisitor<'hir, 'tcx> { + fn visit_expr(&mut self, ex: &'hir Expr<'hir>) { + if let ExprKind::Lit(lit) = ex.kind { + if lit.node.is_numeric() && lit.node.is_unsuffixed() && !in_external_macro(self.cx.sess(), lit.span) { + self.unconstrained_lit_spans.push(lit.span); + } + } else { + walk_expr(self, ex); + } + } + + // Don't visit local in this visitor, `Local`s are handled in `check_local` call. + fn visit_local(&mut self, _: &'hir Local<'hir>) {} +} + +fn ty_suggestion(cx: &LateContext<'_>, init: &Expr<'_>) -> String { + let ty = cx.typeck_results().expr_ty(init); + ty.to_string() +} diff --git a/tests/ui/guidelines/unconstrained_numeric_literal.fixed b/tests/ui/guidelines/unconstrained_numeric_literal.fixed new file mode 100644 index 000000000000..c3a0a3ec7eae --- /dev/null +++ b/tests/ui/guidelines/unconstrained_numeric_literal.fixed @@ -0,0 +1,95 @@ +//@aux-build:../auxiliary/proc_macros.rs + +#![feature(lint_reasons)] +#![warn(clippy::unconstrained_numeric_literal)] +#![allow(clippy::let_with_type_underscore, clippy::let_and_return)] + +extern crate proc_macros; +use proc_macros::{external, inline_macros}; + +mod basic_expr { + fn test() { + let x: i32 = 22; + //~^ ERROR: type of this numeric variable is unconstrained + //~| NOTE: `-D clippy::unconstrained-numeric-literal` implied by `-D warnings` + let x: f64 = 22.0; + //~^ ERROR: type of this numeric variable is unconstrained + let x: [i32; 3] = [1, 2, 3]; + //~^ ERROR: type of this numeric variable is unconstrained + let x: (i32, i32) = if true { (1, 2) } else { (3, 4) }; + //~^ ERROR: type of this numeric variable is unconstrained + let x: (f64, i32, f64) = if true { (1.0, 2, 3.0) } else { (3.0, 4, 5.0) }; + //~^ ERROR: type of this numeric variable is unconstrained + let x: i32 = match 1 { + //~^ ERROR: type of this numeric variable is unconstrained + 1 => 1, + _ => 2, + }; + // Has type annotation but it's a wildcard. + let x: i32 = 1; + //~^ ERROR: type of this numeric variable is unconstrained + + let x = 22_i32; + let x: [i32; 3] = [1, 2, 3]; + let x: (i32, i32) = if true { (1, 2) } else { (3, 4) }; + let x: u64 = 1; + const CONST_X: i8 = 1; + } +} + +mod nested_local { + fn test() { + let x: i32 = { + //~^ ERROR: type of this numeric variable is unconstrained + let y: i32 = 1; + //~^ ERROR: type of this numeric variable is unconstrained + 1 + }; + + let x: i32 = { + let y: i32 = 1; + //~^ ERROR: type of this numeric variable is unconstrained + 1 + }; + + const CONST_X: i32 = { + let y: i32 = 1; + //~^ ERROR: type of this numeric variable is unconstrained + 1 + }; + } +} + +mod in_macro { + use super::*; + + #[inline_macros] + fn internal() { + inline!(let x: i32 = 22;); + //~^ ERROR: type of this numeric variable is unconstrained + } + + fn external() { + external!(let x = 22;); + } +} + +fn check_expect_suppression() { + #[expect(clippy::unconstrained_numeric_literal)] + let x = 21; +} + +#[allow(clippy::useless_vec)] +fn check_vac_macro() { + let x: std::vec::Vec = vec![1, 2, 3]; + //~^ ERROR: type of this numeric variable is unconstrained + let x: std::vec::Vec = vec![1.0]; + //~^ ERROR: type of this numeric variable is unconstrained + + let y = vec![1_i32, 2_i32]; + let y = vec![0_u8, 1_u8]; + let y = vec![2.0_f64, 3.0_f64]; + let y: Vec = vec![1, 2]; +} + +fn main() {} diff --git a/tests/ui/guidelines/unconstrained_numeric_literal.rs b/tests/ui/guidelines/unconstrained_numeric_literal.rs new file mode 100644 index 000000000000..b7269dc77ff1 --- /dev/null +++ b/tests/ui/guidelines/unconstrained_numeric_literal.rs @@ -0,0 +1,95 @@ +//@aux-build:../auxiliary/proc_macros.rs + +#![feature(lint_reasons)] +#![warn(clippy::unconstrained_numeric_literal)] +#![allow(clippy::let_with_type_underscore, clippy::let_and_return)] + +extern crate proc_macros; +use proc_macros::{external, inline_macros}; + +mod basic_expr { + fn test() { + let x = 22; + //~^ ERROR: type of this numeric variable is unconstrained + //~| NOTE: `-D clippy::unconstrained-numeric-literal` implied by `-D warnings` + let x = 22.0; + //~^ ERROR: type of this numeric variable is unconstrained + let x = [1, 2, 3]; + //~^ ERROR: type of this numeric variable is unconstrained + let x = if true { (1, 2) } else { (3, 4) }; + //~^ ERROR: type of this numeric variable is unconstrained + let x = if true { (1.0, 2, 3.0) } else { (3.0, 4, 5.0) }; + //~^ ERROR: type of this numeric variable is unconstrained + let x = match 1 { + //~^ ERROR: type of this numeric variable is unconstrained + 1 => 1, + _ => 2, + }; + // Has type annotation but it's a wildcard. + let x: _ = 1; + //~^ ERROR: type of this numeric variable is unconstrained + + let x = 22_i32; + let x: [i32; 3] = [1, 2, 3]; + let x: (i32, i32) = if true { (1, 2) } else { (3, 4) }; + let x: u64 = 1; + const CONST_X: i8 = 1; + } +} + +mod nested_local { + fn test() { + let x = { + //~^ ERROR: type of this numeric variable is unconstrained + let y = 1; + //~^ ERROR: type of this numeric variable is unconstrained + 1 + }; + + let x: i32 = { + let y = 1; + //~^ ERROR: type of this numeric variable is unconstrained + 1 + }; + + const CONST_X: i32 = { + let y = 1; + //~^ ERROR: type of this numeric variable is unconstrained + 1 + }; + } +} + +mod in_macro { + use super::*; + + #[inline_macros] + fn internal() { + inline!(let x = 22;); + //~^ ERROR: type of this numeric variable is unconstrained + } + + fn external() { + external!(let x = 22;); + } +} + +fn check_expect_suppression() { + #[expect(clippy::unconstrained_numeric_literal)] + let x = 21; +} + +#[allow(clippy::useless_vec)] +fn check_vac_macro() { + let x = vec![1, 2, 3]; + //~^ ERROR: type of this numeric variable is unconstrained + let x = vec![1.0]; + //~^ ERROR: type of this numeric variable is unconstrained + + let y = vec![1_i32, 2_i32]; + let y = vec![0_u8, 1_u8]; + let y = vec![2.0_f64, 3.0_f64]; + let y: Vec = vec![1, 2]; +} + +fn main() {} diff --git a/tests/ui/guidelines/unconstrained_numeric_literal.stderr b/tests/ui/guidelines/unconstrained_numeric_literal.stderr new file mode 100644 index 000000000000..65372f1d15b5 --- /dev/null +++ b/tests/ui/guidelines/unconstrained_numeric_literal.stderr @@ -0,0 +1,234 @@ +error: type of this numeric variable is unconstrained + --> $DIR/unconstrained_numeric_literal.rs:12:13 + | +LL | let x = 22; + | ^ + | +note: unconstrained numeric literals defined here + --> $DIR/unconstrained_numeric_literal.rs:12:17 + | +LL | let x = 22; + | ^^ + = note: `-D clippy::unconstrained-numeric-literal` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unconstrained_numeric_literal)]` +help: either add suffix to above numeric literal(s) or label the type explicitly + | +LL | let x: i32 = 22; + | ~~~~~~ + +error: type of this numeric variable is unconstrained + --> $DIR/unconstrained_numeric_literal.rs:15:13 + | +LL | let x = 22.0; + | ^ + | +note: unconstrained numeric literals defined here + --> $DIR/unconstrained_numeric_literal.rs:15:17 + | +LL | let x = 22.0; + | ^^^^ +help: either add suffix to above numeric literal(s) or label the type explicitly + | +LL | let x: f64 = 22.0; + | ~~~~~~ + +error: type of this numeric variable is unconstrained + --> $DIR/unconstrained_numeric_literal.rs:17:13 + | +LL | let x = [1, 2, 3]; + | ^ + | +note: unconstrained numeric literals defined here + --> $DIR/unconstrained_numeric_literal.rs:17:18 + | +LL | let x = [1, 2, 3]; + | ^ ^ ^ +help: either add suffix to above numeric literal(s) or label the type explicitly + | +LL | let x: [i32; 3] = [1, 2, 3]; + | ~~~~~~~~~~~ + +error: type of this numeric variable is unconstrained + --> $DIR/unconstrained_numeric_literal.rs:19:13 + | +LL | let x = if true { (1, 2) } else { (3, 4) }; + | ^ + | +note: unconstrained numeric literals defined here + --> $DIR/unconstrained_numeric_literal.rs:19:28 + | +LL | let x = if true { (1, 2) } else { (3, 4) }; + | ^ ^ ^ ^ +help: either add suffix to above numeric literal(s) or label the type explicitly + | +LL | let x: (i32, i32) = if true { (1, 2) } else { (3, 4) }; + | ~~~~~~~~~~~~~ + +error: type of this numeric variable is unconstrained + --> $DIR/unconstrained_numeric_literal.rs:21:13 + | +LL | let x = if true { (1.0, 2, 3.0) } else { (3.0, 4, 5.0) }; + | ^ + | +note: unconstrained numeric literals defined here + --> $DIR/unconstrained_numeric_literal.rs:21:28 + | +LL | let x = if true { (1.0, 2, 3.0) } else { (3.0, 4, 5.0) }; + | ^^^ ^ ^^^ ^^^ ^ ^^^ +help: either add suffix to above numeric literal(s) or label the type explicitly + | +LL | let x: (f64, i32, f64) = if true { (1.0, 2, 3.0) } else { (3.0, 4, 5.0) }; + | ~~~~~~~~~~~~~~~~~~ + +error: type of this numeric variable is unconstrained + --> $DIR/unconstrained_numeric_literal.rs:23:13 + | +LL | let x = match 1 { + | ^ + | +note: unconstrained numeric literals defined here + --> $DIR/unconstrained_numeric_literal.rs:23:23 + | +LL | let x = match 1 { + | ^ +LL | +LL | 1 => 1, + | ^ ^ +LL | _ => 2, + | ^ +help: either add suffix to above numeric literal(s) or label the type explicitly + | +LL | let x: i32 = match 1 { + | ~~~~~~ + +error: type of this numeric variable is unconstrained + --> $DIR/unconstrained_numeric_literal.rs:29:13 + | +LL | let x: _ = 1; + | ^^^^ + | +note: unconstrained numeric literals defined here + --> $DIR/unconstrained_numeric_literal.rs:29:20 + | +LL | let x: _ = 1; + | ^ +help: either add suffix to above numeric literal(s) or label the type explicitly + | +LL | let x: i32 = 1; + | ~~~~~~ + +error: type of this numeric variable is unconstrained + --> $DIR/unconstrained_numeric_literal.rs:42:13 + | +LL | let x = { + | ^ + | +note: unconstrained numeric literals defined here + --> $DIR/unconstrained_numeric_literal.rs:46:13 + | +LL | 1 + | ^ +help: either add suffix to above numeric literal(s) or label the type explicitly + | +LL | let x: i32 = { + | ~~~~~~ + +error: type of this numeric variable is unconstrained + --> $DIR/unconstrained_numeric_literal.rs:44:17 + | +LL | let y = 1; + | ^ + | +note: unconstrained numeric literals defined here + --> $DIR/unconstrained_numeric_literal.rs:44:21 + | +LL | let y = 1; + | ^ +help: either add suffix to above numeric literal(s) or label the type explicitly + | +LL | let y: i32 = 1; + | ~~~~~~ + +error: type of this numeric variable is unconstrained + --> $DIR/unconstrained_numeric_literal.rs:50:17 + | +LL | let y = 1; + | ^ + | +note: unconstrained numeric literals defined here + --> $DIR/unconstrained_numeric_literal.rs:50:21 + | +LL | let y = 1; + | ^ +help: either add suffix to above numeric literal(s) or label the type explicitly + | +LL | let y: i32 = 1; + | ~~~~~~ + +error: type of this numeric variable is unconstrained + --> $DIR/unconstrained_numeric_literal.rs:56:17 + | +LL | let y = 1; + | ^ + | +note: unconstrained numeric literals defined here + --> $DIR/unconstrained_numeric_literal.rs:56:21 + | +LL | let y = 1; + | ^ +help: either add suffix to above numeric literal(s) or label the type explicitly + | +LL | let y: i32 = 1; + | ~~~~~~ + +error: type of this numeric variable is unconstrained + --> $DIR/unconstrained_numeric_literal.rs:68:21 + | +LL | inline!(let x = 22;); + | ^ + | +note: unconstrained numeric literals defined here + --> $DIR/unconstrained_numeric_literal.rs:68:25 + | +LL | inline!(let x = 22;); + | ^^ + = note: this error originates in the macro `__inline_mac_fn_internal` (in Nightly builds, run with -Z macro-backtrace for more info) +help: either add suffix to above numeric literal(s) or label the type explicitly + | +LL | inline!(let x: i32 = 22;); + | ~~~~~~ + +error: type of this numeric variable is unconstrained + --> $DIR/unconstrained_numeric_literal.rs:84:9 + | +LL | let x = vec![1, 2, 3]; + | ^ + | +note: unconstrained numeric literals defined here + --> $DIR/unconstrained_numeric_literal.rs:84:18 + | +LL | let x = vec![1, 2, 3]; + | ^ ^ ^ +help: either add suffix to above numeric literal(s) or label the type explicitly + | +LL | let x: std::vec::Vec = vec![1, 2, 3]; + | ~~~~~~~~~~~~~~~~~~~~~ + +error: type of this numeric variable is unconstrained + --> $DIR/unconstrained_numeric_literal.rs:86:9 + | +LL | let x = vec![1.0]; + | ^ + | +note: unconstrained numeric literals defined here + --> $DIR/unconstrained_numeric_literal.rs:86:18 + | +LL | let x = vec![1.0]; + | ^^^ +help: either add suffix to above numeric literal(s) or label the type explicitly + | +LL | let x: std::vec::Vec = vec![1.0]; + | ~~~~~~~~~~~~~~~~~~~~~ + +error: aborting due to 14 previous errors +