Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit ebc2a68

Browse files
Add new useless_concat lint (rust-lang#13829)
Fixes rust-lang#13793. Interestingly enough, to actually check that the macro call has at least two arguments, we need to use the rust lexer after getting the original source code snippet. changelog: Add new `useless_concat` lint
2 parents df33aaf + 0d25090 commit ebc2a68

File tree

8 files changed

+280
-0
lines changed

8 files changed

+280
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6440,6 +6440,7 @@ Released 2018-09-13
64406440
[`used_underscore_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_items
64416441
[`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref
64426442
[`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute
6443+
[`useless_concat`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_concat
64436444
[`useless_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
64446445
[`useless_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_format
64456446
[`useless_let_if_seq`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_let_if_seq

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
764764
crate::unwrap_in_result::UNWRAP_IN_RESULT_INFO,
765765
crate::upper_case_acronyms::UPPER_CASE_ACRONYMS_INFO,
766766
crate::use_self::USE_SELF_INFO,
767+
crate::useless_concat::USELESS_CONCAT_INFO,
767768
crate::useless_conversion::USELESS_CONVERSION_INFO,
768769
crate::vec::USELESS_VEC_INFO,
769770
crate::vec_init_then_push::VEC_INIT_THEN_PUSH_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,7 @@ mod unwrap;
393393
mod unwrap_in_result;
394394
mod upper_case_acronyms;
395395
mod use_self;
396+
mod useless_concat;
396397
mod useless_conversion;
397398
mod vec;
398399
mod vec_init_then_push;
@@ -937,6 +938,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
937938
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
938939
store.register_early_pass(|| Box::new(empty_line_after::EmptyLineAfter::new()));
939940
store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf)));
941+
store.register_late_pass(|_| Box::new(useless_concat::UselessConcat));
940942
store.register_late_pass(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern));
941943
store.register_late_pass(|_| Box::<unnecessary_semicolon::UnnecessarySemicolon>::default());
942944
store.register_late_pass(move |_| Box::new(non_std_lazy_statics::NonStdLazyStatic::new(conf)));

clippy_lints/src/useless_concat.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::macros::macro_backtrace;
3+
use clippy_utils::paths::CONCAT;
4+
use clippy_utils::source::snippet_opt;
5+
use clippy_utils::tokenize_with_text;
6+
use rustc_ast::LitKind;
7+
use rustc_errors::Applicability;
8+
use rustc_hir::{Expr, ExprKind};
9+
use rustc_lexer::TokenKind;
10+
use rustc_lint::{LateContext, LateLintPass};
11+
use rustc_session::declare_lint_pass;
12+
13+
declare_clippy_lint! {
14+
/// ### What it does
15+
/// Checks that the `concat!` macro has at least two arguments.
16+
///
17+
/// ### Why is this bad?
18+
/// If there are less than 2 arguments, then calling the macro is doing nothing.
19+
///
20+
/// ### Example
21+
/// ```no_run
22+
/// let x = concat!("a");
23+
/// ```
24+
/// Use instead:
25+
/// ```no_run
26+
/// let x = "a";
27+
/// ```
28+
#[clippy::version = "1.89.0"]
29+
pub USELESS_CONCAT,
30+
complexity,
31+
"checks that the `concat` macro has at least two arguments"
32+
}
33+
34+
declare_lint_pass!(UselessConcat => [USELESS_CONCAT]);
35+
36+
impl LateLintPass<'_> for UselessConcat {
37+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
38+
// Check that the expression is generated by a macro.
39+
if expr.span.from_expansion()
40+
// Check that it's a string literal.
41+
&& let ExprKind::Lit(lit) = expr.kind
42+
&& let LitKind::Str(lit_s, _) = lit.node
43+
// Get the direct parent of the expression.
44+
&& let Some(macro_call) = macro_backtrace(expr.span).next()
45+
// Check if the `concat` macro from the `core` library.
46+
&& CONCAT.matches(cx, macro_call.def_id)
47+
// We get the original code to parse it.
48+
&& let Some(original_code) = snippet_opt(cx, macro_call.span)
49+
// This check allows us to ensure that the code snippet:
50+
// 1. Doesn't come from proc-macro expansion.
51+
// 2. Doesn't come from foreign macro expansion.
52+
//
53+
// It works as follows: if the snippet we get doesn't contain `concat!(`, then it
54+
// means it's not code written in the current crate so we shouldn't lint.
55+
&& let mut parts = original_code.split('!')
56+
&& parts.next().is_some_and(|p| p.trim() == "concat")
57+
&& parts.next().is_some_and(|p| p.trim().starts_with('('))
58+
{
59+
let mut literal = None;
60+
let mut nb_commas = 0;
61+
let mut nb_idents = 0;
62+
for (token_kind, token_s, _) in tokenize_with_text(&original_code) {
63+
match token_kind {
64+
TokenKind::Eof => break,
65+
TokenKind::Literal { .. } => {
66+
if literal.is_some() {
67+
return;
68+
}
69+
literal = Some(token_s);
70+
},
71+
TokenKind::Ident => {
72+
if token_s == "true" || token_s == "false" {
73+
literal = Some(token_s);
74+
} else {
75+
nb_idents += 1;
76+
}
77+
},
78+
TokenKind::Comma => {
79+
nb_commas += 1;
80+
if nb_commas > 1 {
81+
return;
82+
}
83+
},
84+
// We're inside a macro definition and we are manipulating something we likely
85+
// shouldn't, so aborting.
86+
TokenKind::Dollar => return,
87+
_ => {},
88+
}
89+
}
90+
// There should always be the ident of the `concat` macro.
91+
if nb_idents == 1 {
92+
span_lint_and_sugg(
93+
cx,
94+
USELESS_CONCAT,
95+
macro_call.span,
96+
"unneeded use of `concat!` macro",
97+
"replace with",
98+
format!("{lit_s:?}"),
99+
Applicability::MachineApplicable,
100+
);
101+
}
102+
}
103+
}
104+
}

clippy_utils/src/paths.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ path_macros! {
129129
// Paths in `core`/`alloc`/`std`. This should be avoided and cleaned up by adding diagnostic items.
130130
pub static ALIGN_OF: PathLookup = value_path!(core::mem::align_of);
131131
pub static CHAR_TO_DIGIT: PathLookup = value_path!(char::to_digit);
132+
pub static CONCAT: PathLookup = macro_path!(core::concat);
132133
pub static IO_ERROR_NEW: PathLookup = value_path!(std::io::Error::new);
133134
pub static IO_ERRORKIND_OTHER_CTOR: PathLookup = value_path!(std::io::ErrorKind::Other);
134135
pub static ITER_STEP: PathLookup = type_path!(core::iter::Step);

tests/ui/useless_concat.fixed

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//@aux-build:proc_macros.rs
2+
3+
#![warn(clippy::useless_concat)]
4+
#![allow(clippy::print_literal)]
5+
6+
extern crate proc_macros;
7+
use proc_macros::{external, with_span};
8+
9+
macro_rules! my_concat {
10+
($fmt:literal $(, $e:expr)*) => {
11+
println!(concat!("ERROR: ", $fmt), $($e,)*);
12+
}
13+
}
14+
15+
fn main() {
16+
let x = ""; //~ useless_concat
17+
let x = "c"; //~ useless_concat
18+
let x = "\""; //~ useless_concat
19+
let x = "true"; //~ useless_concat
20+
let x = "1"; //~ useless_concat
21+
let x = "1.0000"; //~ useless_concat
22+
let x = "1"; //~ useless_concat
23+
let x = "1"; //~ useless_concat
24+
let x = "1.0000"; //~ useless_concat
25+
let x = "1.0000"; //~ useless_concat
26+
let x = "a😀\n"; //~ useless_concat
27+
let x = "a"; //~ useless_concat
28+
let x = "1"; //~ useless_concat
29+
println!("b: {}", "a"); //~ useless_concat
30+
// Should not lint.
31+
let x = concat!("a", "b");
32+
let local_i32 = 1;
33+
my_concat!("{}", local_i32);
34+
let x = concat!(file!(), "#L", line!());
35+
36+
external! { concat!(); }
37+
with_span! {
38+
span
39+
concat!();
40+
}
41+
}

tests/ui/useless_concat.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//@aux-build:proc_macros.rs
2+
3+
#![warn(clippy::useless_concat)]
4+
#![allow(clippy::print_literal)]
5+
6+
extern crate proc_macros;
7+
use proc_macros::{external, with_span};
8+
9+
macro_rules! my_concat {
10+
($fmt:literal $(, $e:expr)*) => {
11+
println!(concat!("ERROR: ", $fmt), $($e,)*);
12+
}
13+
}
14+
15+
fn main() {
16+
let x = concat!(); //~ useless_concat
17+
let x = concat!('c'); //~ useless_concat
18+
let x = concat!('"'); //~ useless_concat
19+
let x = concat!(true); //~ useless_concat
20+
let x = concat!(1f32); //~ useless_concat
21+
let x = concat!(1.0000f32); //~ useless_concat
22+
let x = concat!(1_f32); //~ useless_concat
23+
let x = concat!(1_); //~ useless_concat
24+
let x = concat!(1.0000_f32); //~ useless_concat
25+
let x = concat!(1.0000_); //~ useless_concat
26+
let x = concat!("a\u{1f600}\n"); //~ useless_concat
27+
let x = concat!(r##"a"##); //~ useless_concat
28+
let x = concat!(1); //~ useless_concat
29+
println!("b: {}", concat!("a")); //~ useless_concat
30+
// Should not lint.
31+
let x = concat!("a", "b");
32+
let local_i32 = 1;
33+
my_concat!("{}", local_i32);
34+
let x = concat!(file!(), "#L", line!());
35+
36+
external! { concat!(); }
37+
with_span! {
38+
span
39+
concat!();
40+
}
41+
}

tests/ui/useless_concat.stderr

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
error: unneeded use of `concat!` macro
2+
--> tests/ui/useless_concat.rs:16:13
3+
|
4+
LL | let x = concat!();
5+
| ^^^^^^^^^ help: replace with: `""`
6+
|
7+
= note: `-D clippy::useless-concat` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::useless_concat)]`
9+
10+
error: unneeded use of `concat!` macro
11+
--> tests/ui/useless_concat.rs:17:13
12+
|
13+
LL | let x = concat!('c');
14+
| ^^^^^^^^^^^^ help: replace with: `"c"`
15+
16+
error: unneeded use of `concat!` macro
17+
--> tests/ui/useless_concat.rs:18:13
18+
|
19+
LL | let x = concat!('"');
20+
| ^^^^^^^^^^^^ help: replace with: `"\""`
21+
22+
error: unneeded use of `concat!` macro
23+
--> tests/ui/useless_concat.rs:19:13
24+
|
25+
LL | let x = concat!(true);
26+
| ^^^^^^^^^^^^^ help: replace with: `"true"`
27+
28+
error: unneeded use of `concat!` macro
29+
--> tests/ui/useless_concat.rs:20:13
30+
|
31+
LL | let x = concat!(1f32);
32+
| ^^^^^^^^^^^^^ help: replace with: `"1"`
33+
34+
error: unneeded use of `concat!` macro
35+
--> tests/ui/useless_concat.rs:21:13
36+
|
37+
LL | let x = concat!(1.0000f32);
38+
| ^^^^^^^^^^^^^^^^^^ help: replace with: `"1.0000"`
39+
40+
error: unneeded use of `concat!` macro
41+
--> tests/ui/useless_concat.rs:22:13
42+
|
43+
LL | let x = concat!(1_f32);
44+
| ^^^^^^^^^^^^^^ help: replace with: `"1"`
45+
46+
error: unneeded use of `concat!` macro
47+
--> tests/ui/useless_concat.rs:23:13
48+
|
49+
LL | let x = concat!(1_);
50+
| ^^^^^^^^^^^ help: replace with: `"1"`
51+
52+
error: unneeded use of `concat!` macro
53+
--> tests/ui/useless_concat.rs:24:13
54+
|
55+
LL | let x = concat!(1.0000_f32);
56+
| ^^^^^^^^^^^^^^^^^^^ help: replace with: `"1.0000"`
57+
58+
error: unneeded use of `concat!` macro
59+
--> tests/ui/useless_concat.rs:25:13
60+
|
61+
LL | let x = concat!(1.0000_);
62+
| ^^^^^^^^^^^^^^^^ help: replace with: `"1.0000"`
63+
64+
error: unneeded use of `concat!` macro
65+
--> tests/ui/useless_concat.rs:26:13
66+
|
67+
LL | let x = concat!("a\u{1f600}\n");
68+
| ^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `"a😀\n"`
69+
70+
error: unneeded use of `concat!` macro
71+
--> tests/ui/useless_concat.rs:27:13
72+
|
73+
LL | let x = concat!(r##"a"##);
74+
| ^^^^^^^^^^^^^^^^^ help: replace with: `"a"`
75+
76+
error: unneeded use of `concat!` macro
77+
--> tests/ui/useless_concat.rs:28:13
78+
|
79+
LL | let x = concat!(1);
80+
| ^^^^^^^^^^ help: replace with: `"1"`
81+
82+
error: unneeded use of `concat!` macro
83+
--> tests/ui/useless_concat.rs:29:23
84+
|
85+
LL | println!("b: {}", concat!("a"));
86+
| ^^^^^^^^^^^^ help: replace with: `"a"`
87+
88+
error: aborting due to 14 previous errors
89+

0 commit comments

Comments
 (0)