Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 23 additions & 20 deletions clippy_lints/src/nonstandard_macro_braces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ declare_clippy_lint! {
/// Checks that common macros are used with consistent bracing.
///
/// ### Why is this bad?
/// This is mostly a consistency lint although using () or []
/// doesn't give you a semicolon in item position, which can be unexpected.
/// This is mostly a consistency lint.
///
/// ### Example
/// ```no_run
Expand All @@ -33,8 +32,8 @@ declare_clippy_lint! {
"check consistent use of braces in macro"
}

/// The (callsite span, (open brace, close brace), source snippet)
type MacroInfo = (Span, (char, char), SourceText);
/// The (callsite span, old open brace, (open brace, close brace), source snippet)
type MacroInfo = (Span, char, (char, char), SourceText);

pub struct MacroBraces {
macro_braces: FxHashMap<String, (char, char)>,
Expand All @@ -54,29 +53,33 @@ impl_lint_pass!(MacroBraces => [NONSTANDARD_MACRO_BRACES]);

impl EarlyLintPass for MacroBraces {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
if let Some((span, braces, snip)) = is_offending_macro(cx, item.span, self) {
emit_help(cx, &snip, braces, span);
if let Some((span, _, braces, snip)) = is_offending_macro(cx, item.span, self) {
emit_help(cx, &snip, braces, span, false);
self.done.insert(span);
}
}

fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) {
if let Some((span, braces, snip)) = is_offending_macro(cx, stmt.span, self) {
emit_help(cx, &snip, braces, span);
if let Some((span, old_open_brace, braces, snip)) = is_offending_macro(cx, stmt.span, self) {
// if we turn `macro!{}` into `macro!()`/`macro![]`, we'll no longer get the implicit
// trailing semicolon, see #9913
// NOTE: `stmt.kind != StmtKind::MacCall` because `EarlyLintPass` happens after macro expansion
let add_semi = matches!(stmt.kind, ast::StmtKind::Expr(..)) && old_open_brace == '{';
emit_help(cx, &snip, braces, span, add_semi);
self.done.insert(span);
}
}

fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
if let Some((span, braces, snip)) = is_offending_macro(cx, expr.span, self) {
emit_help(cx, &snip, braces, span);
if let Some((span, _, braces, snip)) = is_offending_macro(cx, expr.span, self) {
emit_help(cx, &snip, braces, span, false);
self.done.insert(span);
}
}

fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
if let Some((span, braces, snip)) = is_offending_macro(cx, ty.span, self) {
emit_help(cx, &snip, braces, span);
if let Some((span, _, braces, snip)) = is_offending_macro(cx, ty.span, self) {
emit_help(cx, &snip, braces, span, false);
self.done.insert(span);
}
}
Expand All @@ -97,32 +100,32 @@ fn is_offending_macro(cx: &EarlyContext<'_>, span: Span, mac_braces: &MacroBrace
&& let Some(snip) = span_call_site.get_source_text(cx)
// we must check only invocation sites
// https://github.com/rust-lang/rust-clippy/issues/7422
&& snip.starts_with(&format!("{name}!"))
&& let Some(macro_args_str) = snip.strip_prefix(name).and_then(|snip| snip.strip_prefix('!'))
&& let Some(old_open_brace @ ('{' | '(' | '[')) = macro_args_str.trim_start().chars().next()
&& old_open_brace != braces.0
&& unnested_or_local()
// make formatting consistent
&& let c = snip.replace(' ', "")
&& !c.starts_with(&format!("{name}!{}", braces.0))
&& !mac_braces.done.contains(&span_call_site)
{
Some((span_call_site, braces, snip))
Some((span_call_site, old_open_brace, braces, snip))
} else {
None
}
}

fn emit_help(cx: &EarlyContext<'_>, snip: &str, (open, close): (char, char), span: Span) {
fn emit_help(cx: &EarlyContext<'_>, snip: &str, (open, close): (char, char), span: Span, add_semi: bool) {
let semi = if add_semi { ";" } else { "" };
if let Some((macro_name, macro_args_str)) = snip.split_once('!') {
let mut macro_args = macro_args_str.trim().to_string();
// now remove the wrong braces
macro_args.remove(0);
macro_args.pop();
macro_args.remove(0);
span_lint_and_sugg(
cx,
NONSTANDARD_MACRO_BRACES,
span,
format!("use of irregular braces for `{macro_name}!` macro"),
"consider writing",
format!("{macro_name}!{open}{macro_args}{close}"),
format!("{macro_name}!{open}{macro_args}{close}{semi}"),
Applicability::MachineApplicable,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,11 @@ fn main() {

printlnfoo!["test if printlnfoo is triggered by println"];
}

#[rustfmt::skip]
#[expect(clippy::no_effect)]
fn issue9913() {
println!("hello world");
[0]; // separate statement, not indexing into the result of println.
//~^^ nonstandard_macro_braces
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,11 @@ fn main() {

printlnfoo!["test if printlnfoo is triggered by println"];
}

#[rustfmt::skip]
#[expect(clippy::no_effect)]
fn issue9913() {
println! {"hello world"}
[0]; // separate statement, not indexing into the result of println.
//~^^ nonstandard_macro_braces
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,11 @@ error: use of irregular braces for `eprint!` macro
LL | eprint!("test if user config overrides defaults");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `eprint!["test if user config overrides defaults"]`

error: aborting due to 8 previous errors
error: use of irregular braces for `println!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:74:5
|
LL | println! {"hello world"}
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `println!("hello world");`

error: aborting due to 9 previous errors