Skip to content
20 changes: 20 additions & 0 deletions Configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,26 @@ macro_rules! foo {

See also [`format_macro_matchers`](#format_macro_matchers).

## `format_brace_macros`

Format the contents of fn-like macro invocations that use brace delimiters.

- **Default value**: `false`
- **Possible values**: `true`, `false`
- **Stable**: No

#### `false` (default):

```rust
foo! {"bar"}
```

#### `true`:

```rust
foo! { "bar" }
```

## `skip_macro_invocations`

Skip formatting the bodies of macro invocations with the following names.
Expand Down
3 changes: 3 additions & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ create_config! {
format_macro_matchers: bool, false, false,
"Format the metavariable matching patterns in macros";
format_macro_bodies: bool, true, false, "Format the bodies of declarative macro definitions";
format_brace_macros: bool, false, false,
"Format the contents of fn-like macro invocations that use brace delimiters";
skip_macro_invocations: MacroSelectors, MacroSelectors::default(), false,
"Skip formatting the bodies of macros invoked with the following names.";
hex_literal_case: HexLiteralCase, HexLiteralCase::Preserve, false,
Expand Down Expand Up @@ -643,6 +645,7 @@ normalize_doc_attributes = false
format_strings = false
format_macro_matchers = false
format_macro_bodies = true
format_brace_macros = false
skip_macro_invocations = []
hex_literal_case = "Preserve"
empty_item_single_line = true
Expand Down
76 changes: 61 additions & 15 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use std::collections::HashMap;
use std::panic::{catch_unwind, AssertUnwindSafe};

use rustc_ast::token::{BinOpToken, Delimiter, Token, TokenKind};
use rustc_ast::tokenstream::{RefTokenTreeCursor, TokenStream, TokenTree};
use rustc_ast::tokenstream::{DelimSpacing, RefTokenTreeCursor, Spacing, TokenStream, TokenTree};
use rustc_ast::{ast, ptr};
use rustc_ast_pretty::pprust;
use rustc_span::{
Expand Down Expand Up @@ -209,7 +209,7 @@ fn rewrite_macro_inner(
original_style
};

let ts = mac.args.tokens.clone();
let mut ts = mac.args.tokens.clone();
let has_comment = contains_comment(context.snippet(mac.span()));
if ts.is_empty() && !has_comment {
return match style {
Expand All @@ -232,23 +232,40 @@ fn rewrite_macro_inner(
}
}

if Delimiter::Brace == style && context.config.format_brace_macros() {
ts = TokenStream::new(vec![TokenTree::Delimited(
mac.args.dspan,
DelimSpacing::new(Spacing::Alone, Spacing::Alone),
Delimiter::Brace,
ts,
)]);
}

let ParsedMacroArgs {
args: arg_vec,
vec_with_semi,
trailing_comma,
} = match parse_macro_args(context, ts, style, is_forced_bracket) {
Some(args) => args,
None => {
return return_macro_parse_failure_fallback(
context,
shape.indent,
position,
mac.span(),
);
} = if Delimiter::Brace == style && !context.config.format_brace_macros() {
ParsedMacroArgs::default()
} else {
match parse_macro_args(context, ts.clone(), is_forced_bracket) {
Some(args) => args,
None => {
if Delimiter::Brace == style {
ParsedMacroArgs::default()
} else {
return return_macro_parse_failure_fallback(
context,
shape.indent,
position,
mac.span(),
);
}
}
}
};

if !arg_vec.is_empty() && arg_vec.iter().all(MacroArg::is_item) {
if !arg_vec.is_empty() && arg_vec.iter().all(MacroArg::is_item) && Delimiter::Brace != style {
return rewrite_macro_with_items(
context,
&arg_vec,
Expand Down Expand Up @@ -325,10 +342,39 @@ fn rewrite_macro_inner(
}
}
Delimiter::Brace => {
let snippet = if arg_vec.is_empty() || !context.config.format_brace_macros() {
None
} else {
overflow::rewrite_undelimited(
context,
&macro_name,
arg_vec.iter(),
shape,
mac.span(),
context.config.fn_call_width(),
None,
)
}
.unwrap_or(context.snippet(mac.span()).into());

let mut snippet = snippet.trim_start_matches(|c| c != '{');

// Only format if the rewritten TokenStream is the same as the original TokenStream.
if context.config.format_brace_macros() {
let rewritten_ts = rustc_parse::parse_stream_from_source_str(
rustc_span::FileName::anon_source_code(""),
snippet.to_string(),
&rustc_session::parse::ParseSess::with_silent_emitter(None),
None,
);

if !ts.eq_unspanned(&rewritten_ts) {
snippet = context.snippet(mac.span()).trim_start_matches(|c| c != '{');
}
}

// For macro invocations with braces, always put a space between
// the `macro_name!` and `{ /* macro_body */ }` but skip modifying
// anything in between the braces (for now).
let snippet = context.snippet(mac.span()).trim_start_matches(|c| c != '{');
// the `macro_name!` and `{ /* macro_body */ }`.
match trim_left_preserve_layout(snippet, shape.indent, context.config) {
Some(macro_body) => Some(format!("{macro_name} {macro_body}")),
None => Some(format!("{macro_name} {snippet}")),
Expand Down
24 changes: 24 additions & 0 deletions src/overflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,30 @@ pub(crate) fn rewrite_with_parens<'a, T: 'a + IntoOverflowableItem<'a>>(
.rewrite(shape)
}

pub(crate) fn rewrite_undelimited<'a, T: 'a + IntoOverflowableItem<'a>>(
context: &'a RewriteContext<'_>,
ident: &'a str,
items: impl Iterator<Item = &'a T>,
shape: Shape,
span: Span,
item_max_width: usize,
force_separator_tactic: Option<SeparatorTactic>,
) -> Option<String> {
Context::new(
context,
items,
ident,
shape,
span,
"",
"",
item_max_width,
force_separator_tactic,
None,
)
.rewrite(shape)
}

pub(crate) fn rewrite_with_angle_brackets<'a, T: 'a + IntoOverflowableItem<'a>>(
context: &'a RewriteContext<'_>,
ident: &'a str,
Expand Down
74 changes: 36 additions & 38 deletions src/parse/macros/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use rustc_ast::token::{Delimiter, TokenKind};
use rustc_ast::token::TokenKind;
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::{ast, ptr};
use rustc_parse::parser::{ForceCollect, Parser};
Expand Down Expand Up @@ -69,6 +69,7 @@ fn parse_macro_arg<'a, 'b: 'a>(parser: &'a mut Parser<'b>) -> Option<MacroArg> {
None
}

#[derive(Default)]
pub(crate) struct ParsedMacroArgs {
pub(crate) vec_with_semi: bool,
pub(crate) trailing_comma: bool,
Expand All @@ -95,59 +96,56 @@ fn check_keyword<'a, 'b: 'a>(parser: &'a mut Parser<'b>) -> Option<MacroArg> {
pub(crate) fn parse_macro_args(
context: &RewriteContext<'_>,
tokens: TokenStream,
style: Delimiter,
forced_bracket: bool,
) -> Option<ParsedMacroArgs> {
let mut parser = build_parser(context, tokens);
let mut args = Vec::new();
let mut vec_with_semi = false;
let mut trailing_comma = false;

if Delimiter::Brace != style {
loop {
if let Some(arg) = check_keyword(&mut parser) {
args.push(arg);
} else if let Some(arg) = parse_macro_arg(&mut parser) {
args.push(arg);
} else {
return None;
}
loop {
if let Some(arg) = check_keyword(&mut parser) {
args.push(arg);
} else if let Some(arg) = parse_macro_arg(&mut parser) {
args.push(arg);
} else {
return None;
}

match parser.token.kind {
TokenKind::Eof => break,
TokenKind::Comma => (),
TokenKind::Semi => {
// Try to parse `vec![expr; expr]`
if forced_bracket {
parser.bump();
if parser.token.kind != TokenKind::Eof {
match parse_macro_arg(&mut parser) {
Some(arg) => {
args.push(arg);
parser.bump();
if parser.token.kind == TokenKind::Eof && args.len() == 2 {
vec_with_semi = true;
break;
}
}
None => {
return None;
match parser.token.kind {
TokenKind::Eof => break,
TokenKind::Comma => (),
TokenKind::Semi => {
// Try to parse `vec![expr; expr]`
if forced_bracket {
parser.bump();
if parser.token.kind != TokenKind::Eof {
match parse_macro_arg(&mut parser) {
Some(arg) => {
args.push(arg);
parser.bump();
if parser.token.kind == TokenKind::Eof && args.len() == 2 {
vec_with_semi = true;
break;
}
}
None => {
return None;
}
}
}
return None;
}
_ if args.last().map_or(false, MacroArg::is_item) => continue,
_ => return None,
return None;
}
_ if args.last().map_or(false, MacroArg::is_item) => continue,
_ => return None,
}

parser.bump();
parser.bump();

if parser.token.kind == TokenKind::Eof {
trailing_comma = true;
break;
}
if parser.token.kind == TokenKind::Eof {
trailing_comma = true;
break;
}
}

Expand Down
Loading