From 46f8d360de99afe38dacc1dd6e29133d856e9fd3 Mon Sep 17 00:00:00 2001 From: Samarth1696 Date: Fri, 26 Jul 2024 00:51:30 +0530 Subject: [PATCH] Lint ready --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/non_zero_suggestions.rs | 107 +++++++++++++++++++++++ tests/ui/non_zero_suggestions.rs | 52 +++++++++++ 5 files changed, 163 insertions(+) create mode 100644 clippy_lints/src/non_zero_suggestions.rs create mode 100644 tests/ui/non_zero_suggestions.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f1de51c93..0e5d1688e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5773,6 +5773,7 @@ Released 2018-09-13 [`non_minimal_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_minimal_cfg [`non_octal_unix_permissions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_octal_unix_permissions [`non_send_fields_in_send_ty`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_send_fields_in_send_ty +[`non_zero_suggestions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_zero_suggestions [`nonminimal_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonminimal_bool [`nonsensical_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonsensical_open_options [`nonstandard_macro_braces`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonstandard_macro_braces diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 6f468f01b..16c64830e 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -557,6 +557,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::non_expressive_names::SIMILAR_NAMES_INFO, crate::non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS_INFO, crate::non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY_INFO, + crate::non_zero_suggestions::NON_ZERO_SUGGESTIONS_INFO, crate::nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES_INFO, crate::octal_escapes::OCTAL_ESCAPES_INFO, crate::only_used_in_recursion::ONLY_USED_IN_RECURSION_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index bc16a3b0c..3604090b6 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -273,6 +273,7 @@ mod non_copy_const; mod non_expressive_names; mod non_octal_unix_permissions; mod non_send_fields_in_send_ty; +mod non_zero_suggestions; mod nonstandard_macro_braces; mod octal_escapes; mod only_used_in_recursion; @@ -940,5 +941,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(pointers_in_nomem_asm_block::PointersInNomemAsmBlock)); store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf))); store.register_late_pass(|_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo)); + store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/non_zero_suggestions.rs b/clippy_lints/src/non_zero_suggestions.rs new file mode 100644 index 000000000..b9e7053db --- /dev/null +++ b/clippy_lints/src/non_zero_suggestions.rs @@ -0,0 +1,107 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, Ty}; +use rustc_session::declare_lint_pass; +use rustc_span::symbol::sym; + +declare_clippy_lint! { + /// ### What it does + /// + /// ### Why is this bad? + /// + /// ### Example + /// ```no_run + /// // example code where clippy issues a warning + /// ``` + /// Use instead: + /// ```no_run + /// // example code which does not raise clippy warning + /// ``` + #[clippy::version = "1.81.0"] + pub NON_ZERO_SUGGESTIONS, + restriction, + "suggests using `NonZero#` from `u#` or `i#` for more efficient and type-safe conversions" +} + +declare_lint_pass!(NonZeroSuggestions => [NON_ZERO_SUGGESTIONS]); + +impl<'tcx> LateLintPass<'tcx> for NonZeroSuggestions { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if let ExprKind::Call(func, [arg]) = expr.kind { + if let ExprKind::Path(qpath) = &func.kind { + if let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id() { + let fn_name = cx.tcx.item_name(def_id); + let target_ty = cx.typeck_results().expr_ty(expr); + + if let ExprKind::MethodCall(rcv_path, receiver, _, _) = &arg.kind { + let receiver_ty = cx.typeck_results().expr_ty(receiver); + if let ty::Adt(adt_def, _) = receiver_ty.kind() { + if adt_def.is_struct() && cx.tcx.get_diagnostic_name(adt_def.did()) == Some(sym::NonZero) { + if let Some(target_non_zero_type) = get_target_non_zero_type(target_ty) { + let arg_snippet = get_arg_snippet(cx, arg, rcv_path); + suggest_non_zero_conversion(cx, expr, fn_name, target_non_zero_type, &arg_snippet); + } + } + } + } + } + } + } + } +} + +fn get_arg_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, rcv_path: &rustc_hir::PathSegment<'_>) -> String { + let arg_snippet = snippet(cx, arg.span, ".."); + if let Some(index) = arg_snippet.rfind(&format!(".{}", rcv_path.ident.name)) { + arg_snippet[..index].trim().to_string() + } else { + arg_snippet.to_string() + } +} + +fn suggest_non_zero_conversion( + cx: &LateContext<'_>, + expr: &Expr<'_>, + fn_name: rustc_span::Symbol, + target_non_zero_type: &str, + arg_snippet: &str, +) { + let suggestion = format!("{}::{}({})", target_non_zero_type, fn_name, arg_snippet); + span_lint_and_sugg( + cx, + NON_ZERO_SUGGESTIONS, + expr.span, + format!( + "Consider using `{}::{}()` for more efficient and type-safe conversion", + target_non_zero_type, fn_name + ), + "Replace with", + suggestion, + Applicability::MachineApplicable, + ); +} + +fn get_target_non_zero_type(ty: Ty<'_>) -> Option<&'static str> { + match ty.kind() { + ty::Uint(uint_ty) => Some(match uint_ty { + ty::UintTy::U8 => "NonZeroU8", + ty::UintTy::U16 => "NonZeroU16", + ty::UintTy::U32 => "NonZeroU32", + ty::UintTy::U64 => "NonZeroU64", + ty::UintTy::U128 => "NonZeroU128", + ty::UintTy::Usize => "NonZeroUsize", + }), + ty::Int(int_ty) => Some(match int_ty { + ty::IntTy::I8 => "NonZeroI8", + ty::IntTy::I16 => "NonZeroI16", + ty::IntTy::I32 => "NonZeroI32", + ty::IntTy::I64 => "NonZeroI64", + ty::IntTy::I128 => "NonZeroI128", + ty::IntTy::Isize => "NonZeroIsize", + }), + _ => None, + } +} diff --git a/tests/ui/non_zero_suggestions.rs b/tests/ui/non_zero_suggestions.rs new file mode 100644 index 000000000..1a5ee40dc --- /dev/null +++ b/tests/ui/non_zero_suggestions.rs @@ -0,0 +1,52 @@ +#![warn(clippy::non_zero_suggestions)] + +use std::num::{ + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128, NonZeroU16, NonZeroU32, + NonZeroU64, NonZeroU8, NonZeroUsize, +}; + +fn main() { + // Basic cases + let _ = u8::try_from(NonZeroU8::new(5).unwrap().get()); + + let _ = u16::from(NonZeroU16::new(10).unwrap().get()); + + // Different integer types + let _ = u32::from(NonZeroU32::new(15).unwrap().get()); + + let _ = u64::from(NonZeroU64::new(20).unwrap().get()); + + let _ = u128::from(NonZeroU128::new(25).unwrap().get()); + + let _ = usize::from(NonZeroUsize::new(30).unwrap().get()); + + // Signed integer types + let _ = i8::try_from(NonZeroI8::new(-5).unwrap().get()); + + let _ = i16::from(NonZeroI16::new(-10).unwrap().get()); + + let _ = i32::from(NonZeroI32::new(-15).unwrap().get()); + + // Edge cases + + // Complex expression + let _ = u8::from(NonZeroU8::new(5).unwrap().get() + 1); + + // Function call + fn get_non_zero() -> NonZeroU8 { + NonZeroU8::new(42).unwrap() + } + let _ = u8::from(get_non_zero().get()); + + // Method chaining + let _ = u16::from(NonZeroU16::new(100).unwrap().get().checked_add(1).unwrap()); + // This should not trigger the lint + + // Different conversion methods + let _ = u32::try_from(NonZeroU32::new(200).unwrap().get()).unwrap(); + + // Cases that should not trigger the lint + let _ = u8::from(5); + let _ = u16::from(10u8); + let _ = i32::try_from(40u32).unwrap(); +}