Various fixes for handling of macros (#16296)

Closes rust-lang/rust-clippy#16293

Also contains fixes of several other lints, on handling macros

changelog: [`checked_conversions`] fix wrongly unmangled macros
changelog: [`manual_ignore_case_cmp`] fix wrongly unmangled macros
changelog: [`manual_ilog2`] fix wrongly unmangled macros
changelog: [`needless_bool_assign`] fix wrongly unmangled macros
changelog: [`manual_is_multiple_of`] fix wrongly unmangled macros
This commit is contained in:
dswij 2025-12-28 15:07:10 +00:00 committed by GitHub
commit 7481abcc88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 274 additions and 37 deletions

View file

@ -1,7 +1,7 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::source::snippet_with_context;
use clippy_utils::{SpanlessEq, is_in_const_context, is_integer_literal, sym};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath, TyKind};
@ -80,7 +80,8 @@ impl LateLintPass<'_> for CheckedConversions {
&& self.msrv.meets(cx, msrvs::TRY_FROM)
{
let mut applicability = Applicability::MachineApplicable;
let snippet = snippet_with_applicability(cx, cv.expr_to_cast.span, "_", &mut applicability);
let (snippet, _) =
snippet_with_context(cx, cv.expr_to_cast.span, item.span.ctxt(), "_", &mut applicability);
span_lint_and_sugg(
cx,
CHECKED_CONVERSIONS,

View file

@ -1,7 +1,7 @@
use crate::manual_ignore_case_cmp::MatchType::{Literal, ToAscii};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::res::MaybeDef;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::source::snippet_with_context;
use clippy_utils::sym;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
@ -111,14 +111,12 @@ impl LateLintPass<'_> for ManualIgnoreCaseCmp {
"manual case-insensitive ASCII comparison",
|diag| {
let mut app = Applicability::MachineApplicable;
let (left_snip, _) = snippet_with_context(cx, left_span, expr.span.ctxt(), "..", &mut app);
let (right_snip, _) = snippet_with_context(cx, right_span, expr.span.ctxt(), "..", &mut app);
diag.span_suggestion_verbose(
expr.span,
"consider using `.eq_ignore_ascii_case()` instead",
format!(
"{neg}{}.eq_ignore_ascii_case({deref}{})",
snippet_with_applicability(cx, left_span, "_", &mut app),
snippet_with_applicability(cx, right_span, "_", &mut app)
),
format!("{neg}{left_snip}.eq_ignore_ascii_case({deref}{right_snip})"),
app,
);
},

View file

@ -1,7 +1,7 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::source::snippet_with_context;
use clippy_utils::{is_from_proc_macro, sym};
use rustc_ast::LitKind;
use rustc_data_structures::packed::Pu128;
@ -102,7 +102,7 @@ impl LateLintPass<'_> for ManualIlog2 {
fn emit(cx: &LateContext<'_>, recv: &Expr<'_>, full_expr: &Expr<'_>) {
let mut app = Applicability::MachineApplicable;
let recv = snippet_with_applicability(cx, recv.span, "_", &mut app);
let (recv, _) = snippet_with_context(cx, recv.span, full_expr.span.ctxt(), "_", &mut app);
span_lint_and_sugg(
cx,
MANUAL_ILOG2,

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg::Sugg;
use clippy_utils::{
SpanlessEq, get_parent_expr, higher, is_block_like, is_else_clause, is_parent_stmt, is_receiver_of_method_call,
@ -171,8 +171,8 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool {
&& SpanlessEq::new(cx).eq_expr(lhs_a, lhs_b)
{
let mut applicability = Applicability::MachineApplicable;
let cond = Sugg::hir_with_applicability(cx, cond, "..", &mut applicability);
let lhs = snippet_with_applicability(cx, lhs_a.span, "..", &mut applicability);
let cond = Sugg::hir_with_context(cx, cond, e.span.ctxt(), "..", &mut applicability);
let (lhs, _) = snippet_with_context(cx, lhs_a.span, e.span.ctxt(), "..", &mut applicability);
let mut sugg = if a == b {
format!("{cond}; {lhs} = {a:?};")
} else {

View file

@ -35,7 +35,7 @@ pub(super) fn check<'tcx>(
{
let mut app = Applicability::MachineApplicable;
let divisor = deref_sugg(
Sugg::hir_with_applicability(cx, operand_right, "_", &mut app),
Sugg::hir_with_context(cx, operand_right, expr.span.ctxt(), "_", &mut app),
cx.typeck_results().expr_ty_adjusted(operand_right),
);
span_lint_and_sugg(
@ -47,7 +47,7 @@ pub(super) fn check<'tcx>(
format!(
"{}{}.is_multiple_of({divisor})",
if op == BinOpKind::Eq { "" } else { "!" },
Sugg::hir_with_applicability(cx, operand_left, "_", &mut app).maybe_paren()
Sugg::hir_with_context(cx, operand_left, expr.span.ctxt(), "_", &mut app).maybe_paren()
),
app,
);

View file

@ -1,6 +1,7 @@
#![allow(
clippy::cast_lossless,
clippy::legacy_numeric_constants,
clippy::no_effect,
unused,
// Int::max_value will be deprecated in the future
deprecated,
@ -105,4 +106,19 @@ fn msrv_1_34() {
//~^ checked_conversions
}
fn issue16293() {
struct Outer {
inner: u32,
}
let outer = Outer { inner: 42 };
macro_rules! dot_inner {
($obj:expr) => {
$obj.inner
};
}
i32::try_from(dot_inner!(outer)).is_ok();
//~^ checked_conversions
}
fn main() {}

View file

@ -1,6 +1,7 @@
#![allow(
clippy::cast_lossless,
clippy::legacy_numeric_constants,
clippy::no_effect,
unused,
// Int::max_value will be deprecated in the future
deprecated,
@ -105,4 +106,19 @@ fn msrv_1_34() {
//~^ checked_conversions
}
fn issue16293() {
struct Outer {
inner: u32,
}
let outer = Outer { inner: 42 };
macro_rules! dot_inner {
($obj:expr) => {
$obj.inner
};
}
dot_inner!(outer) <= i32::MAX as u32;
//~^ checked_conversions
}
fn main() {}

View file

@ -1,5 +1,5 @@
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:15:13
--> tests/ui/checked_conversions.rs:16:13
|
LL | let _ = value <= (u32::max_value() as i64) && value >= 0;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()`
@ -8,100 +8,106 @@ LL | let _ = value <= (u32::max_value() as i64) && value >= 0;
= help: to override `-D warnings` add `#[allow(clippy::checked_conversions)]`
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:17:13
--> tests/ui/checked_conversions.rs:18:13
|
LL | let _ = value <= (u32::MAX as i64) && value >= 0;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()`
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:22:13
--> tests/ui/checked_conversions.rs:23:13
|
LL | let _ = value <= i64::from(u16::max_value()) && value >= 0;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()`
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:24:13
--> tests/ui/checked_conversions.rs:25:13
|
LL | let _ = value <= i64::from(u16::MAX) && value >= 0;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()`
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:29:13
--> tests/ui/checked_conversions.rs:30:13
|
LL | let _ = value <= (u8::max_value() as isize) && value >= 0;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u8::try_from(value).is_ok()`
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:31:13
--> tests/ui/checked_conversions.rs:32:13
|
LL | let _ = value <= (u8::MAX as isize) && value >= 0;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u8::try_from(value).is_ok()`
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:38:13
--> tests/ui/checked_conversions.rs:39:13
|
LL | let _ = value <= (i32::max_value() as i64) && value >= (i32::min_value() as i64);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()`
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:40:13
--> tests/ui/checked_conversions.rs:41:13
|
LL | let _ = value <= (i32::MAX as i64) && value >= (i32::MIN as i64);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()`
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:45:13
--> tests/ui/checked_conversions.rs:46:13
|
LL | let _ = value <= i64::from(i16::max_value()) && value >= i64::from(i16::min_value());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i16::try_from(value).is_ok()`
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:47:13
--> tests/ui/checked_conversions.rs:48:13
|
LL | let _ = value <= i64::from(i16::MAX) && value >= i64::from(i16::MIN);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i16::try_from(value).is_ok()`
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:54:13
--> tests/ui/checked_conversions.rs:55:13
|
LL | let _ = value <= i32::max_value() as u32;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()`
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:56:13
--> tests/ui/checked_conversions.rs:57:13
|
LL | let _ = value <= i32::MAX as u32;
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()`
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:61:13
--> tests/ui/checked_conversions.rs:62:13
|
LL | let _ = value <= isize::max_value() as usize && value as i32 == 5;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `isize::try_from(value).is_ok()`
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:63:13
--> tests/ui/checked_conversions.rs:64:13
|
LL | let _ = value <= isize::MAX as usize && value as i32 == 5;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `isize::try_from(value).is_ok()`
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:68:13
--> tests/ui/checked_conversions.rs:69:13
|
LL | let _ = value <= u16::max_value() as u32 && value as i32 == 5;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()`
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:70:13
--> tests/ui/checked_conversions.rs:71:13
|
LL | let _ = value <= u16::MAX as u32 && value as i32 == 5;
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()`
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:104:13
--> tests/ui/checked_conversions.rs:105:13
|
LL | let _ = value <= (u32::max_value() as i64) && value >= 0;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()`
error: aborting due to 17 previous errors
error: checked cast can be simplified
--> tests/ui/checked_conversions.rs:120:5
|
LL | dot_inner!(outer) <= i32::MAX as u32;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(dot_inner!(outer)).is_ok()`
error: aborting due to 18 previous errors

View file

@ -160,3 +160,23 @@ fn ref_osstring(a: OsString, b: &OsString) {
b.eq_ignore_ascii_case(a);
//~^ manual_ignore_case_cmp
}
fn wrongly_unmangled_macros(a: &str, b: &str) -> bool {
struct S<'a> {
inner: &'a str,
}
let a = S { inner: a };
let b = S { inner: b };
macro_rules! dot_inner {
($s:expr) => {
$s.inner
};
}
dot_inner!(a).eq_ignore_ascii_case(dot_inner!(b))
//~^ manual_ignore_case_cmp
|| dot_inner!(a).eq_ignore_ascii_case("abc")
//~^ manual_ignore_case_cmp
}

View file

@ -160,3 +160,23 @@ fn ref_osstring(a: OsString, b: &OsString) {
b.to_ascii_lowercase() == a.to_ascii_lowercase();
//~^ manual_ignore_case_cmp
}
fn wrongly_unmangled_macros(a: &str, b: &str) -> bool {
struct S<'a> {
inner: &'a str,
}
let a = S { inner: a };
let b = S { inner: b };
macro_rules! dot_inner {
($s:expr) => {
$s.inner
};
}
dot_inner!(a).to_ascii_lowercase() == dot_inner!(b).to_ascii_lowercase()
//~^ manual_ignore_case_cmp
|| dot_inner!(a).to_ascii_lowercase() == "abc"
//~^ manual_ignore_case_cmp
}

View file

@ -588,5 +588,29 @@ LL - b.to_ascii_lowercase() == a.to_ascii_lowercase();
LL + b.eq_ignore_ascii_case(a);
|
error: aborting due to 49 previous errors
error: manual case-insensitive ASCII comparison
--> tests/ui/manual_ignore_case_cmp.rs:178:5
|
LL | dot_inner!(a).to_ascii_lowercase() == dot_inner!(b).to_ascii_lowercase()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: consider using `.eq_ignore_ascii_case()` instead
|
LL - dot_inner!(a).to_ascii_lowercase() == dot_inner!(b).to_ascii_lowercase()
LL + dot_inner!(a).eq_ignore_ascii_case(dot_inner!(b))
|
error: manual case-insensitive ASCII comparison
--> tests/ui/manual_ignore_case_cmp.rs:180:12
|
LL | || dot_inner!(a).to_ascii_lowercase() == "abc"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: consider using `.eq_ignore_ascii_case()` instead
|
LL - || dot_inner!(a).to_ascii_lowercase() == "abc"
LL + || dot_inner!(a).eq_ignore_ascii_case("abc")
|
error: aborting due to 51 previous errors

View file

@ -30,3 +30,20 @@ fn foo(a: u32, b: u64) {
external!($a.ilog(2));
with_span!(span; a.ilog(2));
}
fn wrongly_unmangled_macros() {
struct S {
inner: u32,
}
let x = S { inner: 42 };
macro_rules! access {
($s:expr) => {
$s.inner
};
}
let log = access!(x).ilog2();
//~^ manual_ilog2
let log = access!(x).ilog2();
//~^ manual_ilog2
}

View file

@ -30,3 +30,20 @@ fn foo(a: u32, b: u64) {
external!($a.ilog(2));
with_span!(span; a.ilog(2));
}
fn wrongly_unmangled_macros() {
struct S {
inner: u32,
}
let x = S { inner: 42 };
macro_rules! access {
($s:expr) => {
$s.inner
};
}
let log = 31 - access!(x).leading_zeros();
//~^ manual_ilog2
let log = access!(x).ilog(2);
//~^ manual_ilog2
}

View file

@ -19,5 +19,17 @@ error: manually reimplementing `ilog2`
LL | 63 - b.leading_zeros();
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `b.ilog2()`
error: aborting due to 3 previous errors
error: manually reimplementing `ilog2`
--> tests/ui/manual_ilog2.rs:45:15
|
LL | let log = 31 - access!(x).leading_zeros();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `access!(x).ilog2()`
error: manually reimplementing `ilog2`
--> tests/ui/manual_ilog2.rs:47:15
|
LL | let log = access!(x).ilog(2);
| ^^^^^^^^^^^^^^^^^^ help: try: `access!(x).ilog2()`
error: aborting due to 5 previous errors

View file

@ -101,3 +101,19 @@ mod issue15103 {
(1..1_000).filter(|&i| i == d(d(i)) && i != d(i)).sum()
}
}
fn wrongly_unmangled_macros(a: u32, b: u32) {
struct Wrapper(u32);
let a = Wrapper(a);
let b = Wrapper(b);
macro_rules! dot_0 {
($x:expr) => {
$x.0
};
}
if dot_0!(a).is_multiple_of(dot_0!(b)) {
//~^ manual_is_multiple_of
todo!()
}
}

View file

@ -101,3 +101,19 @@ mod issue15103 {
(1..1_000).filter(|&i| i == d(d(i)) && i != d(i)).sum()
}
}
fn wrongly_unmangled_macros(a: u32, b: u32) {
struct Wrapper(u32);
let a = Wrapper(a);
let b = Wrapper(b);
macro_rules! dot_0 {
($x:expr) => {
$x.0
};
}
if dot_0!(a) % dot_0!(b) == 0 {
//~^ manual_is_multiple_of
todo!()
}
}

View file

@ -67,5 +67,11 @@ error: manual implementation of `.is_multiple_of()`
LL | let d = |n: u32| -> u32 { (1..=n / 2).filter(|i| n % i == 0).sum() };
| ^^^^^^^^^^ help: replace with: `n.is_multiple_of(*i)`
error: aborting due to 11 previous errors
error: manual implementation of `.is_multiple_of()`
--> tests/ui/manual_is_multiple_of.rs:115:8
|
LL | if dot_0!(a) % dot_0!(b) == 0 {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `dot_0!(a).is_multiple_of(dot_0!(b))`
error: aborting due to 12 previous errors

View file

@ -42,3 +42,22 @@ fn issue15063(x: bool, y: bool) {
} else { z = x || y; }
//~^^^^^ needless_bool_assign
}
fn wrongly_unmangled_macros(must_keep: fn(usize, usize) -> bool, x: usize, y: usize) {
struct Wrapper<T>(T);
let mut skip = Wrapper(false);
macro_rules! invoke {
($func:expr, $a:expr, $b:expr) => {
$func($a, $b)
};
}
macro_rules! dot_0 {
($w:expr) => {
$w.0
};
}
dot_0!(skip) = !invoke!(must_keep, x, y);
//~^^^^^ needless_bool_assign
}

View file

@ -58,3 +58,26 @@ fn issue15063(x: bool, y: bool) {
}
//~^^^^^ needless_bool_assign
}
fn wrongly_unmangled_macros(must_keep: fn(usize, usize) -> bool, x: usize, y: usize) {
struct Wrapper<T>(T);
let mut skip = Wrapper(false);
macro_rules! invoke {
($func:expr, $a:expr, $b:expr) => {
$func($a, $b)
};
}
macro_rules! dot_0 {
($w:expr) => {
$w.0
};
}
if invoke!(must_keep, x, y) {
dot_0!(skip) = false;
} else {
dot_0!(skip) = true;
}
//~^^^^^ needless_bool_assign
}

View file

@ -62,5 +62,15 @@ LL | | z = false;
LL | | }
| |_____^ help: you can reduce it to: `{ z = x || y; }`
error: aborting due to 5 previous errors
error: this if-then-else expression assigns a bool literal
--> tests/ui/needless_bool_assign.rs:77:5
|
LL | / if invoke!(must_keep, x, y) {
LL | | dot_0!(skip) = false;
LL | | } else {
LL | | dot_0!(skip) = true;
LL | | }
| |_____^ help: you can reduce it to: `dot_0!(skip) = !invoke!(must_keep, x, y);`
error: aborting due to 6 previous errors