From a8679816dc33567d811f743d52586b0aa29754a2 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 14 Dec 2024 23:40:38 +0100 Subject: Add new `useless_concat` lint --- clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/useless_concat.rs | 108 +++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 clippy_lints/src/useless_concat.rs (limited to 'clippy_lints') diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index e046ab0b42b..69ef3d69bb8 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -762,6 +762,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::unwrap_in_result::UNWRAP_IN_RESULT_INFO, crate::upper_case_acronyms::UPPER_CASE_ACRONYMS_INFO, crate::use_self::USE_SELF_INFO, + crate::useless_concat::USELESS_CONCAT_INFO, crate::useless_conversion::USELESS_CONVERSION_INFO, crate::vec::USELESS_VEC_INFO, crate::vec_init_then_push::VEC_INIT_THEN_PUSH_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 64fbc0252e2..a51e45bff7a 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -392,6 +392,7 @@ mod unwrap; mod unwrap_in_result; mod upper_case_acronyms; mod use_self; +mod useless_concat; mod useless_conversion; mod vec; mod vec_init_then_push; @@ -936,6 +937,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound)); store.register_early_pass(|| Box::new(empty_line_after::EmptyLineAfter::new())); store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf))); + store.register_late_pass(|_| Box::new(useless_concat::UselessConcat)); store.register_late_pass(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern)); store.register_late_pass(|_| Box::::default()); store.register_late_pass(move |_| Box::new(non_std_lazy_statics::NonStdLazyStatic::new(conf))); diff --git a/clippy_lints/src/useless_concat.rs b/clippy_lints/src/useless_concat.rs new file mode 100644 index 00000000000..3f68b93d4f5 --- /dev/null +++ b/clippy_lints/src/useless_concat.rs @@ -0,0 +1,108 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::macros::macro_backtrace; +use clippy_utils::source::snippet_opt; +use clippy_utils::{match_def_path, tokenize_with_text}; +use rustc_ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lexer::TokenKind; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// Checks that the `concat!` macro has at least two arguments. + /// + /// ### Why is this bad? + /// If there are less than 2 arguments, then calling the macro is doing nothing. + /// + /// ### Example + /// ```no_run + /// let x = concat!("a"); + /// ``` + /// Use instead: + /// ```no_run + /// let x = "a"; + /// ``` + #[clippy::version = "1.85.0"] + pub USELESS_CONCAT, + complexity, + "checks that the `concat` macro has at least two arguments" +} + +declare_lint_pass!(UselessConcat => [USELESS_CONCAT]); + +impl LateLintPass<'_> for UselessConcat { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + // Check that the expression is generated by a macro. + if expr.span.from_expansion() + // Check that it's a string literal. + && let ExprKind::Lit(lit) = expr.kind + && let LitKind::Str(_, _) = lit.node + // Get the direct parent of the expression. + && let Some(macro_call) = macro_backtrace(expr.span).next() + // Check if the `concat` macro from the `core` library. + && match_def_path(cx, macro_call.def_id, &["core", "macros", "builtin", "concat"]) + // We get the original code to parse it. + && let Some(original_code) = snippet_opt(cx, macro_call.span) + // This check allows us to ensure that the code snippet: + // 1. Doesn't come from proc-macro expansion. + // 2. Doesn't come from foreign macro expansion. + // + // It works as follows: if the snippet we get doesn't contain `concat!(`, then it + // means it's not code written in the current crate so we shouldn't lint. + && let mut parts = original_code.split('!') + && parts.next().is_some_and(|p| p.trim() == "concat") + && parts.next().is_some_and(|p| p.trim().starts_with('(')) + { + let mut literal = None; + let mut nb_commas = 0; + let mut nb_idents = 0; + for (token_kind, token_s, _) in tokenize_with_text(&original_code) { + match token_kind { + TokenKind::Eof => break, + TokenKind::Literal { .. } => { + if literal.is_some() { + return; + } + literal = Some(token_s); + }, + TokenKind::Ident => nb_idents += 1, + TokenKind::Comma => { + nb_commas += 1; + if nb_commas > 1 { + return; + } + }, + // We're inside a macro definition and we are manipulating something we likely + // shouldn't, so aborting. + TokenKind::Dollar => return, + _ => {}, + } + } + let literal = match literal { + Some(lit) => { + // Literals can also be number, so we need to check this case too. + if lit.starts_with('"') { + lit.to_string() + } else { + format!("\"{lit}\"") + } + }, + None => "\"\"".to_string(), + }; + // There should always be the ident of the `concat` macro. + if nb_idents == 1 { + span_lint_and_sugg( + cx, + USELESS_CONCAT, + macro_call.span, + "unneeded use of `concat!` macro", + "replace with", + literal, + Applicability::MachineApplicable, + ); + } + } + } +} -- cgit 1.4.1-3-g733a5 From 7b06d2b776c401f66190509bd61804956426f9b0 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 29 Dec 2024 20:10:51 +0100 Subject: Move `concat` macro path into `clippy_utils::paths` --- clippy_lints/src/useless_concat.rs | 3 ++- clippy_utils/src/paths.rs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'clippy_lints') diff --git a/clippy_lints/src/useless_concat.rs b/clippy_lints/src/useless_concat.rs index 3f68b93d4f5..4a818532f17 100644 --- a/clippy_lints/src/useless_concat.rs +++ b/clippy_lints/src/useless_concat.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::macro_backtrace; +use clippy_utils::paths::CONCAT; use clippy_utils::source::snippet_opt; use clippy_utils::{match_def_path, tokenize_with_text}; use rustc_ast::LitKind; @@ -42,7 +43,7 @@ impl LateLintPass<'_> for UselessConcat { // Get the direct parent of the expression. && let Some(macro_call) = macro_backtrace(expr.span).next() // Check if the `concat` macro from the `core` library. - && match_def_path(cx, macro_call.def_id, &["core", "macros", "builtin", "concat"]) + && match_def_path(cx, macro_call.def_id, &CONCAT) // We get the original code to parse it. && let Some(original_code) = snippet_opt(cx, macro_call.span) // This check allows us to ensure that the code snippet: diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index e5179e479cc..9c909033447 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -129,6 +129,7 @@ path_macros! { // Paths in `core`/`alloc`/`std`. This should be avoided and cleaned up by adding diagnostic items. pub static ALIGN_OF: PathLookup = value_path!(core::mem::align_of); pub static CHAR_TO_DIGIT: PathLookup = value_path!(char::to_digit); +pub static CONCAT: PathLookup = value_path!(core::macros::builtin::concat); pub static IO_ERROR_NEW: PathLookup = value_path!(std::io::Error::new); pub static IO_ERRORKIND_OTHER_CTOR: PathLookup = value_path!(std::io::ErrorKind::Other); pub static ITER_STEP: PathLookup = type_path!(core::iter::Step); -- cgit 1.4.1-3-g733a5 From 0d25090d262cd506d4c4809f3a534d4d8b318870 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 2 May 2025 17:32:48 +0200 Subject: Improve code and apply suggestions --- clippy_lints/src/useless_concat.rs | 29 +++++++-------- clippy_utils/src/paths.rs | 2 +- tests/ui/useless_concat.fixed | 21 +++++++++++ tests/ui/useless_concat.rs | 23 +++++++++++- tests/ui/useless_concat.stderr | 74 ++++++++++++++++++++++++++++++++++---- 5 files changed, 123 insertions(+), 26 deletions(-) (limited to 'clippy_lints') diff --git a/clippy_lints/src/useless_concat.rs b/clippy_lints/src/useless_concat.rs index 4a818532f17..1ed1fbb3b9c 100644 --- a/clippy_lints/src/useless_concat.rs +++ b/clippy_lints/src/useless_concat.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::macro_backtrace; use clippy_utils::paths::CONCAT; use clippy_utils::source::snippet_opt; -use clippy_utils::{match_def_path, tokenize_with_text}; +use clippy_utils::tokenize_with_text; use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; @@ -25,7 +25,7 @@ declare_clippy_lint! { /// ```no_run /// let x = "a"; /// ``` - #[clippy::version = "1.85.0"] + #[clippy::version = "1.89.0"] pub USELESS_CONCAT, complexity, "checks that the `concat` macro has at least two arguments" @@ -39,11 +39,11 @@ impl LateLintPass<'_> for UselessConcat { if expr.span.from_expansion() // Check that it's a string literal. && let ExprKind::Lit(lit) = expr.kind - && let LitKind::Str(_, _) = lit.node + && let LitKind::Str(lit_s, _) = lit.node // Get the direct parent of the expression. && let Some(macro_call) = macro_backtrace(expr.span).next() // Check if the `concat` macro from the `core` library. - && match_def_path(cx, macro_call.def_id, &CONCAT) + && CONCAT.matches(cx, macro_call.def_id) // We get the original code to parse it. && let Some(original_code) = snippet_opt(cx, macro_call.span) // This check allows us to ensure that the code snippet: @@ -68,7 +68,13 @@ impl LateLintPass<'_> for UselessConcat { } literal = Some(token_s); }, - TokenKind::Ident => nb_idents += 1, + TokenKind::Ident => { + if token_s == "true" || token_s == "false" { + literal = Some(token_s); + } else { + nb_idents += 1; + } + }, TokenKind::Comma => { nb_commas += 1; if nb_commas > 1 { @@ -81,17 +87,6 @@ impl LateLintPass<'_> for UselessConcat { _ => {}, } } - let literal = match literal { - Some(lit) => { - // Literals can also be number, so we need to check this case too. - if lit.starts_with('"') { - lit.to_string() - } else { - format!("\"{lit}\"") - } - }, - None => "\"\"".to_string(), - }; // There should always be the ident of the `concat` macro. if nb_idents == 1 { span_lint_and_sugg( @@ -100,7 +95,7 @@ impl LateLintPass<'_> for UselessConcat { macro_call.span, "unneeded use of `concat!` macro", "replace with", - literal, + format!("{lit_s:?}"), Applicability::MachineApplicable, ); } diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 9c909033447..9d7f3086b05 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -129,7 +129,7 @@ path_macros! { // Paths in `core`/`alloc`/`std`. This should be avoided and cleaned up by adding diagnostic items. pub static ALIGN_OF: PathLookup = value_path!(core::mem::align_of); pub static CHAR_TO_DIGIT: PathLookup = value_path!(char::to_digit); -pub static CONCAT: PathLookup = value_path!(core::macros::builtin::concat); +pub static CONCAT: PathLookup = macro_path!(core::concat); pub static IO_ERROR_NEW: PathLookup = value_path!(std::io::Error::new); pub static IO_ERRORKIND_OTHER_CTOR: PathLookup = value_path!(std::io::ErrorKind::Other); pub static ITER_STEP: PathLookup = type_path!(core::iter::Step); diff --git a/tests/ui/useless_concat.fixed b/tests/ui/useless_concat.fixed index 0400b1fcd6d..360b6f6ce82 100644 --- a/tests/ui/useless_concat.fixed +++ b/tests/ui/useless_concat.fixed @@ -1,6 +1,11 @@ +//@aux-build:proc_macros.rs + #![warn(clippy::useless_concat)] #![allow(clippy::print_literal)] +extern crate proc_macros; +use proc_macros::{external, with_span}; + macro_rules! my_concat { ($fmt:literal $(, $e:expr)*) => { println!(concat!("ERROR: ", $fmt), $($e,)*); @@ -9,6 +14,16 @@ macro_rules! my_concat { fn main() { let x = ""; //~ useless_concat + let x = "c"; //~ useless_concat + let x = "\""; //~ useless_concat + let x = "true"; //~ useless_concat + let x = "1"; //~ useless_concat + let x = "1.0000"; //~ useless_concat + let x = "1"; //~ useless_concat + let x = "1"; //~ useless_concat + let x = "1.0000"; //~ useless_concat + let x = "1.0000"; //~ useless_concat + let x = "a😀\n"; //~ useless_concat let x = "a"; //~ useless_concat let x = "1"; //~ useless_concat println!("b: {}", "a"); //~ useless_concat @@ -17,4 +32,10 @@ fn main() { let local_i32 = 1; my_concat!("{}", local_i32); let x = concat!(file!(), "#L", line!()); + + external! { concat!(); } + with_span! { + span + concat!(); + } } diff --git a/tests/ui/useless_concat.rs b/tests/ui/useless_concat.rs index 02e68568c10..338d20a48ae 100644 --- a/tests/ui/useless_concat.rs +++ b/tests/ui/useless_concat.rs @@ -1,6 +1,11 @@ +//@aux-build:proc_macros.rs + #![warn(clippy::useless_concat)] #![allow(clippy::print_literal)] +extern crate proc_macros; +use proc_macros::{external, with_span}; + macro_rules! my_concat { ($fmt:literal $(, $e:expr)*) => { println!(concat!("ERROR: ", $fmt), $($e,)*); @@ -9,7 +14,17 @@ macro_rules! my_concat { fn main() { let x = concat!(); //~ useless_concat - let x = concat!("a"); //~ useless_concat + let x = concat!('c'); //~ useless_concat + let x = concat!('"'); //~ useless_concat + let x = concat!(true); //~ useless_concat + let x = concat!(1f32); //~ useless_concat + let x = concat!(1.0000f32); //~ useless_concat + let x = concat!(1_f32); //~ useless_concat + let x = concat!(1_); //~ useless_concat + let x = concat!(1.0000_f32); //~ useless_concat + let x = concat!(1.0000_); //~ useless_concat + let x = concat!("a\u{1f600}\n"); //~ useless_concat + let x = concat!(r##"a"##); //~ useless_concat let x = concat!(1); //~ useless_concat println!("b: {}", concat!("a")); //~ useless_concat // Should not lint. @@ -17,4 +32,10 @@ fn main() { let local_i32 = 1; my_concat!("{}", local_i32); let x = concat!(file!(), "#L", line!()); + + external! { concat!(); } + with_span! { + span + concat!(); + } } diff --git a/tests/ui/useless_concat.stderr b/tests/ui/useless_concat.stderr index 63038b6660b..43d6d9ff579 100644 --- a/tests/ui/useless_concat.stderr +++ b/tests/ui/useless_concat.stderr @@ -1,5 +1,5 @@ error: unneeded use of `concat!` macro - --> tests/ui/useless_concat.rs:11:13 + --> tests/ui/useless_concat.rs:16:13 | LL | let x = concat!(); | ^^^^^^^^^ help: replace with: `""` @@ -8,22 +8,82 @@ LL | let x = concat!(); = help: to override `-D warnings` add `#[allow(clippy::useless_concat)]` error: unneeded use of `concat!` macro - --> tests/ui/useless_concat.rs:12:13 + --> tests/ui/useless_concat.rs:17:13 | -LL | let x = concat!("a"); - | ^^^^^^^^^^^^ help: replace with: `"a"` +LL | let x = concat!('c'); + | ^^^^^^^^^^^^ help: replace with: `"c"` error: unneeded use of `concat!` macro - --> tests/ui/useless_concat.rs:13:13 + --> tests/ui/useless_concat.rs:18:13 + | +LL | let x = concat!('"'); + | ^^^^^^^^^^^^ help: replace with: `"\""` + +error: unneeded use of `concat!` macro + --> tests/ui/useless_concat.rs:19:13 + | +LL | let x = concat!(true); + | ^^^^^^^^^^^^^ help: replace with: `"true"` + +error: unneeded use of `concat!` macro + --> tests/ui/useless_concat.rs:20:13 + | +LL | let x = concat!(1f32); + | ^^^^^^^^^^^^^ help: replace with: `"1"` + +error: unneeded use of `concat!` macro + --> tests/ui/useless_concat.rs:21:13 + | +LL | let x = concat!(1.0000f32); + | ^^^^^^^^^^^^^^^^^^ help: replace with: `"1.0000"` + +error: unneeded use of `concat!` macro + --> tests/ui/useless_concat.rs:22:13 + | +LL | let x = concat!(1_f32); + | ^^^^^^^^^^^^^^ help: replace with: `"1"` + +error: unneeded use of `concat!` macro + --> tests/ui/useless_concat.rs:23:13 + | +LL | let x = concat!(1_); + | ^^^^^^^^^^^ help: replace with: `"1"` + +error: unneeded use of `concat!` macro + --> tests/ui/useless_concat.rs:24:13 + | +LL | let x = concat!(1.0000_f32); + | ^^^^^^^^^^^^^^^^^^^ help: replace with: `"1.0000"` + +error: unneeded use of `concat!` macro + --> tests/ui/useless_concat.rs:25:13 + | +LL | let x = concat!(1.0000_); + | ^^^^^^^^^^^^^^^^ help: replace with: `"1.0000"` + +error: unneeded use of `concat!` macro + --> tests/ui/useless_concat.rs:26:13 + | +LL | let x = concat!("a\u{1f600}\n"); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `"a😀\n"` + +error: unneeded use of `concat!` macro + --> tests/ui/useless_concat.rs:27:13 + | +LL | let x = concat!(r##"a"##); + | ^^^^^^^^^^^^^^^^^ help: replace with: `"a"` + +error: unneeded use of `concat!` macro + --> tests/ui/useless_concat.rs:28:13 | LL | let x = concat!(1); | ^^^^^^^^^^ help: replace with: `"1"` error: unneeded use of `concat!` macro - --> tests/ui/useless_concat.rs:14:23 + --> tests/ui/useless_concat.rs:29:23 | LL | println!("b: {}", concat!("a")); | ^^^^^^^^^^^^ help: replace with: `"a"` -error: aborting due to 4 previous errors +error: aborting due to 14 previous errors -- cgit 1.4.1-3-g733a5