Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support formatting macro calls with simple brace delimited arguments #6327

Closed
Closed
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
74 changes: 67 additions & 7 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ pub(crate) enum MacroArg {
Pat(ptr::P<ast::Pat>),
Item(ptr::P<ast::Item>),
Keyword(symbol::Ident, Span),
KeyValue(ptr::P<ast::Expr>, ptr::P<ast::Expr>),
}

impl MacroArg {
Expand Down Expand Up @@ -98,6 +99,11 @@ impl Rewrite for MacroArg {
MacroArg::Pat(ref pat) => pat.rewrite_result(context, shape),
MacroArg::Item(ref item) => item.rewrite_result(context, shape),
MacroArg::Keyword(ident, _) => Ok(ident.name.to_string()),
MacroArg::KeyValue(ref key, ref value) => {
let key_str = key.rewrite_result(context, shape)?;
let value_str = value.rewrite_result(context, shape)?;
Ok(format!("{} => {}", key_str, value_str))
}
}
}
}
Expand Down Expand Up @@ -264,7 +270,8 @@ fn rewrite_macro_inner(
args: arg_vec,
vec_with_semi,
trailing_comma,
} = match parse_macro_args(context, ts, style, is_forced_bracket) {
kv_brace_args,
} = match parse_macro_args(context, ts, style, is_forced_bracket, has_comment) {
Some(args) => args,
None => {
return return_macro_parse_failure_fallback(
Expand Down Expand Up @@ -354,18 +361,71 @@ fn rewrite_macro_inner(
}
Delimiter::Brace => {
// 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 != '{');
match trim_left_preserve_layout(snippet, shape.indent, context.config) {
Some(macro_body) => Ok(format!("{macro_name} {macro_body}")),
None => Ok(format!("{macro_name} {snippet}")),
// the `macro_name!` and `{ /* macro_body */ }` if the macro is
// a simple key-value map with only `x => y, ...` we ensure
// consistent indentation.

if !kv_brace_args {
let snippet = context.snippet(mac.span()).trim_start_matches(|c| c != '{');
return match trim_left_preserve_layout(snippet, shape.indent, context.config) {
Some(macro_body) => Ok(format!("{macro_name} {macro_body}")),
None => Ok(format!("{macro_name} {snippet}")),
};
}

return handle_brace_delimited_macro(context, &arg_vec, &macro_name, shape, position);
}
_ => unreachable!(),
}
}

fn handle_brace_delimited_macro(
context: &RewriteContext<'_>,
args: &[MacroArg],
macro_name: &str,
shape: Shape,
position: MacroPosition,
) -> RewriteResult {
let mut result = String::new();
let brace_open = "{";
let brace_close = "}";

result.push_str(&format!("{} {}", macro_name, brace_open));

let block_indent = shape.block_indent(context.config.tab_spaces());

let mut key_value_pairs = Vec::new();
for arg in args {
if let MacroArg::KeyValue(ref key_expr, ref value_expr) = arg {
let key_str = key_expr.rewrite(context, shape).unknown_error()?;
let value_str = value_expr.rewrite(context, shape).unknown_error()?;
key_value_pairs.push((key_str.clone(), value_str));
}
}

result.push('\n');

for (i, (key, value)) in key_value_pairs.iter().enumerate() {
result.push_str(&block_indent.indent.to_string(context.config));

result.push_str(&format!("{} => {}", key.trim(), value.trim()));

result.push(',');
if i < key_value_pairs.len() - 1 {
result.push('\n');
}
}

result.push_str(&shape.indent.to_string_with_newline(context.config));
result.push_str(brace_close);

if position == MacroPosition::Item {
result.push(';');
}

Ok(result)
}

fn handle_vec_semi(
context: &RewriteContext<'_>,
shape: Shape,
Expand Down
1 change: 1 addition & 0 deletions src/overflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ impl<'a> OverflowableItem<'a> {
MacroArg::Pat(..) => false,
MacroArg::Item(..) => len == 1,
MacroArg::Keyword(..) => false,
MacroArg::KeyValue(..) => false,
},
OverflowableItem::NestedMetaItem(nested_meta_item) if len == 1 => {
match nested_meta_item {
Expand Down
76 changes: 75 additions & 1 deletion src/parse/macros/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ fn parse_macro_arg<'a, 'b: 'a>(parser: &'a mut Parser<'b>) -> Option<MacroArg> {
pub(crate) struct ParsedMacroArgs {
pub(crate) vec_with_semi: bool,
pub(crate) trailing_comma: bool,
pub(crate) kv_brace_args: bool,
pub(crate) args: Vec<MacroArg>,
}

Expand All @@ -101,13 +102,85 @@ pub(crate) fn parse_macro_args(
tokens: TokenStream,
style: Delimiter,
forced_bracket: bool,
has_comment: 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;
let mut kv_brace_args = false;

if Delimiter::Brace != style {
if Delimiter::Brace == style {
// This parses "simple" brace-delimited macros like key value pairs to
// be formatted. For example: `params! { "a" => "b", "b" => "d" }`

if has_comment {
return Some(ParsedMacroArgs {
vec_with_semi,
trailing_comma,
kv_brace_args,
args,
});
}

kv_brace_args = true;

while parser.token != TokenKind::Eof
&& parser.token.kind != TokenKind::CloseDelim(Delimiter::Brace)
{
let key = if let Some(arg) = parse_macro_arg(&mut parser) {
arg
} else {
kv_brace_args = false;
break;
};

if parser.token.kind == TokenKind::FatArrow {
parser.bump();
} else {
kv_brace_args = false;
break;
}

let value = if let Some(arg) = parse_macro_arg(&mut parser) {
arg
} else {
kv_brace_args = false;
break;
};

args.push(MacroArg::KeyValue(
match key {
MacroArg::Expr(ref expr) => expr.clone(),
_ => {
kv_brace_args = false;
break;
}
},
match value {
MacroArg::Expr(ref expr) => expr.clone(),
_ => {
kv_brace_args = false;
break;
}
},
));

match parser.token.kind {
TokenKind::Comma => {
trailing_comma = true;
parser.bump();
}
TokenKind::CloseDelim(Delimiter::Brace) => {
break;
}
_ => {
kv_brace_args = false;
break;
}
}
}
} else {
loop {
if let Some(arg) = check_keyword(&mut parser) {
args.push(arg);
Expand Down Expand Up @@ -158,6 +231,7 @@ pub(crate) fn parse_macro_args(
Some(ParsedMacroArgs {
vec_with_semi,
trailing_comma,
kv_brace_args,
args,
})
}
Expand Down
1 change: 1 addition & 0 deletions src/spanned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ impl Spanned for MacroArg {
MacroArg::Pat(ref pat) => pat.span(),
MacroArg::Item(ref item) => item.span(),
MacroArg::Keyword(_, span) => span,
MacroArg::KeyValue(ref key, ref value) => key.span().to(value.span()),
}
}
}
Expand Down
19 changes: 19 additions & 0 deletions tests/source/macro-kv-braces-expr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
fn x() {
params! {
"hello" => my_func(
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
),
"goodbye" => "potato",
}
}
6 changes: 6 additions & 0 deletions tests/source/macro-kv-braces.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn x() {
params! {
"hello" => "world",
"goodbye" => "potato",
}
}
6 changes: 6 additions & 0 deletions tests/target/macro-kv-braces-expr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn x() {
params! {
"hello" => my_func(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,),
"goodbye" => "potato",
}
}
6 changes: 6 additions & 0 deletions tests/target/macro-kv-braces.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn x() {
params! {
"hello" => "world",
"goodbye" => "potato",
}
}
Loading