From 3d2039aa93f740df2f28d7b94ad8224d90e3b518 Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Fri, 5 Jan 2024 15:45:35 +0800 Subject: [PATCH] new lint [`unconstrained_numeric_literal`] Signed-off-by: J-ZhengLi --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/guidelines/mod.rs | 31 +++ .../unconstrained_numeric_literal.rs | 102 +++++++++ .../unconstrained_numeric_literal.fixed | 82 +++++++ .../unconstrained_numeric_literal.rs | 82 +++++++ .../unconstrained_numeric_literal.stderr | 202 ++++++++++++++++++ 7 files changed, 501 insertions(+) create mode 100644 clippy_lints/src/guidelines/unconstrained_numeric_literal.rs create mode 100644 tests/ui/guidelines/unconstrained_numeric_literal.fixed create mode 100644 tests/ui/guidelines/unconstrained_numeric_literal.rs create mode 100644 tests/ui/guidelines/unconstrained_numeric_literal.stderr 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..d3f2f3ffcd71 --- /dev/null +++ b/clippy_lints/src/guidelines/unconstrained_numeric_literal.rs @@ -0,0 +1,102 @@ +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 + && !in_external_macro(cx.sess(), init.span) + && local_has_implicit_ty(local) + { + let mut visitor = LitVisitor::new(); + 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 happened 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 { + unconstrained_lit_spans: Vec, +} + +impl LitVisitor { + fn new() -> Self { + Self { + unconstrained_lit_spans: vec![], + } + } +} + +impl<'hir> Visitor<'hir> for LitVisitor { + fn visit_expr(&mut self, ex: &'hir Expr<'hir>) { + match &ex.kind { + // These are fine, because the numerics in them are always inferred. + ExprKind::Call(..) | ExprKind::MethodCall(..) => (), + ExprKind::Lit(lit) => { + if lit.node.is_numeric() && lit.node.is_unsuffixed() { + self.unconstrained_lit_spans.push(lit.span); + } + }, + ExprKind::Closure(_) => { + println!("span of closure: {:?}", ex.span); + walk_expr(self, ex); + }, + _ => 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..4e1e07fe6777 --- /dev/null +++ b/tests/ui/guidelines/unconstrained_numeric_literal.fixed @@ -0,0 +1,82 @@ +//@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; +} + +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..54890b922e10 --- /dev/null +++ b/tests/ui/guidelines/unconstrained_numeric_literal.rs @@ -0,0 +1,82 @@ +//@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; +} + +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..6f68a3768ac3 --- /dev/null +++ b/tests/ui/guidelines/unconstrained_numeric_literal.stderr @@ -0,0 +1,202 @@ +error: type of this numeric variable is unconstrained + --> $DIR/unconstrained_numeric_literal.rs:12:13 + | +LL | let x = 22; + | ^ + | +note: unconstrained numeric literals happened 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 happened 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 happened 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 happened 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 happened 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 happened 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 happened 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 happened 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 happened 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 happened 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 happened 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 happened 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: aborting due to 12 previous errors +