-
Notifications
You must be signed in to change notification settings - Fork 12.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fb9913e
commit 46f8d36
Showing
5 changed files
with
163 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} |