fix: implicit_saturating_sub suggests wrongly on untyped int literal

This commit is contained in:
Linshu Yang 2025-12-29 18:32:24 +00:00
parent 22c13893e6
commit 67a76f9fb5
6 changed files with 57 additions and 9 deletions

View file

@ -1,9 +1,13 @@
use std::borrow::Cow;
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::{Sugg, make_binop};
use clippy_utils::{
SpanlessEq, eq_expr_value, higher, is_in_const_context, is_integer_literal, peel_blocks, peel_blocks_with_stmt, sym,
SpanlessEq, eq_expr_value, higher, is_in_const_context, is_integer_literal, is_integer_literal_untyped,
peel_blocks, peel_blocks_with_stmt, sym,
};
use rustc_ast::ast::LitKind;
use rustc_data_structures::packed::Pu128;
@ -238,10 +242,21 @@ fn check_subtraction(
if eq_expr_value(cx, left, big_expr) && eq_expr_value(cx, right, little_expr) {
// This part of the condition is voluntarily split from the one before to ensure that
// if `snippet_opt` fails, it won't try the next conditions.
if (!is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST))
&& let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr).map(Sugg::maybe_paren)
&& let Some(little_expr_sugg) = Sugg::hir_opt(cx, little_expr)
{
if !is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST) {
let mut applicability = Applicability::MachineApplicable;
let big_expr_sugg = (if is_integer_literal_untyped(big_expr) {
let get_snippet = |span: Span| {
let snippet = snippet_with_applicability(cx, span, "..", &mut applicability);
let big_expr_ty = cx.typeck_results().expr_ty(big_expr);
Cow::Owned(format!("{snippet}_{big_expr_ty}"))
};
Sugg::hir_from_snippet(cx, big_expr, get_snippet)
} else {
Sugg::hir_with_applicability(cx, big_expr, "..", &mut applicability)
})
.maybe_paren();
let little_expr_sugg = Sugg::hir_with_applicability(cx, little_expr, "..", &mut applicability);
let sugg = format!(
"{}{big_expr_sugg}.saturating_sub({little_expr_sugg}){}",
if is_composited { "{ " } else { "" },
@ -254,7 +269,7 @@ fn check_subtraction(
"manual arithmetic check found",
"replace it with",
sugg,
Applicability::MachineApplicable,
applicability,
);
}
} else if eq_expr_value(cx, left, little_expr)

View file

@ -90,7 +90,7 @@ use std::sync::{Mutex, MutexGuard, OnceLock};
use itertools::Itertools;
use rustc_abi::Integer;
use rustc_ast::ast::{self, LitKind, RangeLimits};
use rustc_ast::join_path_syms;
use rustc_ast::{LitIntType, join_path_syms};
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::indexmap;
use rustc_data_structures::packed::Pu128;
@ -1385,6 +1385,17 @@ pub fn is_integer_literal(expr: &Expr<'_>, value: u128) -> bool {
false
}
/// Checks whether the given expression is an untyped integer literal.
pub fn is_integer_literal_untyped(expr: &Expr<'_>) -> bool {
if let ExprKind::Lit(spanned) = expr.kind
&& let LitKind::Int(_, suffix) = spanned.node
{
return suffix == LitIntType::Unsuffixed;
}
false
}
/// Checks whether the given expression is a constant literal of the given value.
pub fn is_float_literal(expr: &Expr<'_>, value: f64) -> bool {
if let ExprKind::Lit(spanned) = expr.kind

View file

@ -127,7 +127,7 @@ impl<'a> Sugg<'a> {
/// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*`
/// function variants of `Sugg`, since these use different snippet functions.
fn hir_from_snippet(
pub fn hir_from_snippet(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
mut get_snippet: impl FnMut(Span) -> Cow<'a, str>,

View file

@ -252,3 +252,11 @@ fn arbitrary_expression() {
0
};
}
fn issue16307() {
let x: u8 = 100;
let y = 100_u8.saturating_sub(x);
//~^ implicit_saturating_sub
println!("{y}");
}

View file

@ -326,3 +326,11 @@ fn arbitrary_expression() {
0
};
}
fn issue16307() {
let x: u8 = 100;
let y = if x >= 100 { 0 } else { 100 - x };
//~^ implicit_saturating_sub
println!("{y}");
}

View file

@ -238,5 +238,11 @@ error: manual arithmetic check found
LL | let _ = if a < b * 2 { 0 } else { a - b * 2 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `a.saturating_sub(b * 2)`
error: aborting due to 27 previous errors
error: manual arithmetic check found
--> tests/ui/implicit_saturating_sub.rs:332:13
|
LL | let y = if x >= 100 { 0 } else { 100 - x };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `100_u8.saturating_sub(x)`
error: aborting due to 28 previous errors