New lint useless-nonzero-new_unchecked

This commit is contained in:
Samuel Tardieu 2025-01-13 11:26:22 +01:00
parent 8f257c71a3
commit 35dbaf8a61
8 changed files with 233 additions and 1 deletions

View file

@ -496,6 +496,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::methods::UNWRAP_OR_DEFAULT_INFO,
crate::methods::UNWRAP_USED_INFO,
crate::methods::USELESS_ASREF_INFO,
crate::methods::USELESS_NONZERO_NEW_UNCHECKED_INFO,
crate::methods::VEC_RESIZE_TO_ZERO_INFO,
crate::methods::VERBOSE_FILE_READS_INFO,
crate::methods::WAKER_CLONE_WAKE_INFO,

View file

@ -130,6 +130,7 @@ mod unnecessary_to_owned;
mod unused_enumerate_index;
mod unwrap_expect_used;
mod useless_asref;
mod useless_nonzero_new_unchecked;
mod utils;
mod vec_resize_to_zero;
mod verbose_file_reads;
@ -4311,6 +4312,33 @@ declare_clippy_lint! {
"using `Iterator::last` on a `DoubleEndedIterator`"
}
declare_clippy_lint! {
/// ### What it does
///
/// Checks for `NonZero*::new_unchecked()` being used in a `const` context.
///
/// ### Why is this bad?
///
/// Using `NonZero*::new_unchecked()` is an `unsafe` function and requires an `unsafe` context. When used in a
/// context evaluated at compilation time, `NonZero*::new().unwrap()` will provide the same result with identical
/// runtime performances while not requiring `unsafe`.
///
/// ### Example
/// ```no_run
/// use std::num::NonZeroUsize;
/// const PLAYERS: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(3) };
/// ```
/// Use instead:
/// ```no_run
/// use std::num::NonZeroUsize;
/// const PLAYERS: NonZeroUsize = NonZeroUsize::new(3).unwrap();
/// ```
#[clippy::version = "1.86.0"]
pub USELESS_NONZERO_NEW_UNCHECKED,
complexity,
"using `NonZero::new_unchecked()` in a `const` context"
}
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
@ -4477,6 +4505,7 @@ impl_lint_pass!(Methods => [
MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES,
UNNECESSARY_MAP_OR,
DOUBLE_ENDED_ITERATOR_LAST,
USELESS_NONZERO_NEW_UNCHECKED,
]);
/// Extracts a method call name, args, and `Span` of the method name.
@ -4505,6 +4534,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
from_iter_instead_of_collect::check(cx, expr, args, func);
unnecessary_fallible_conversions::check_function(cx, expr, func);
manual_c_str_literals::check(cx, expr, func, args, &self.msrv);
useless_nonzero_new_unchecked::check(cx, expr, func, args, &self.msrv);
},
ExprKind::MethodCall(method_call, receiver, args, _) => {
let method_span = method_call.ident.span;

View file

@ -0,0 +1,59 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::is_inside_always_const_context;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, Node, QPath, UnsafeSource};
use rustc_lint::LateContext;
use rustc_span::sym;
use super::USELESS_NONZERO_NEW_UNCHECKED;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, func: &Expr<'tcx>, args: &[Expr<'_>], msrv: &Msrv) {
if msrv.meets(msrvs::CONST_UNWRAP)
&& let ExprKind::Path(QPath::TypeRelative(ty, segment)) = func.kind
&& segment.ident.name == sym::new_unchecked
&& let [init_arg] = args
&& is_inside_always_const_context(cx.tcx, expr.hir_id)
&& is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::NonZero)
{
let mut app = Applicability::MachineApplicable;
let ty_str = snippet_with_applicability(cx, ty.span, "_", &mut app);
let msg = format!("`{ty_str}::new()` and `Option::unwrap()` can be safely used in a `const` context");
let sugg = format!(
"{ty_str}::new({}).unwrap()",
snippet_with_applicability(cx, init_arg.span, "_", &mut app)
);
if let Node::Block(Block {
stmts: [],
span: block_span,
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
..
}) = cx.tcx.parent_hir_node(expr.hir_id)
{
if !block_span.from_expansion() {
// The expression is the only component of an `unsafe` block. Propose
// to replace the block altogether.
span_lint_and_sugg(
cx,
USELESS_NONZERO_NEW_UNCHECKED,
*block_span,
msg,
"use instead",
sugg,
app,
);
}
} else {
// The expression is enclosed in a larger `unsafe` context. Indicate that
// this may no longer be needed for the fixed expression.
span_lint_and_then(cx, USELESS_NONZERO_NEW_UNCHECKED, expr.span, msg, |diagnostic| {
diagnostic
.span_suggestion(expr.span, "use instead", sugg, app)
.note("the fixed expression does not require an `unsafe` context");
});
}
}
}