diff --git a/src/macros.rs b/src/macros.rs index 06495e2f8e9..27471949e5c 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -61,6 +61,7 @@ pub(crate) enum MacroArg { Pat(ptr::P), Item(ptr::P), Keyword(symbol::Ident, Span), + KeyValue(ptr::P, ptr::P), } impl MacroArg { @@ -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)) + } } } } @@ -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( @@ -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, ¯o_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, diff --git a/src/overflow.rs b/src/overflow.rs index fdc8e23e72b..bdd1afe03f6 100644 --- a/src/overflow.rs +++ b/src/overflow.rs @@ -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 { diff --git a/src/parse/macros/mod.rs b/src/parse/macros/mod.rs index 7271e73db8d..a7214f7458b 100644 --- a/src/parse/macros/mod.rs +++ b/src/parse/macros/mod.rs @@ -78,6 +78,7 @@ fn parse_macro_arg<'a, 'b: 'a>(parser: &'a mut Parser<'b>) -> Option { 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, } @@ -101,13 +102,85 @@ pub(crate) fn parse_macro_args( tokens: TokenStream, style: Delimiter, forced_bracket: bool, + has_comment: bool, ) -> Option { 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); @@ -158,6 +231,7 @@ pub(crate) fn parse_macro_args( Some(ParsedMacroArgs { vec_with_semi, trailing_comma, + kv_brace_args, args, }) } diff --git a/src/spanned.rs b/src/spanned.rs index b4424e476ee..5fb4c288528 100644 --- a/src/spanned.rs +++ b/src/spanned.rs @@ -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()), } } } diff --git a/tests/source/macro-kv-braces-expr.rs b/tests/source/macro-kv-braces-expr.rs new file mode 100644 index 00000000000..ffa200017bc --- /dev/null +++ b/tests/source/macro-kv-braces-expr.rs @@ -0,0 +1,19 @@ +fn x() { + params! { + "hello" => my_func( + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + ), + "goodbye" => "potato", + } +} diff --git a/tests/source/macro-kv-braces.rs b/tests/source/macro-kv-braces.rs new file mode 100644 index 00000000000..14732db3123 --- /dev/null +++ b/tests/source/macro-kv-braces.rs @@ -0,0 +1,6 @@ +fn x() { + params! { + "hello" => "world", + "goodbye" => "potato", + } +} diff --git a/tests/target/macro-kv-braces-expr.rs b/tests/target/macro-kv-braces-expr.rs new file mode 100644 index 00000000000..6c23ed41058 --- /dev/null +++ b/tests/target/macro-kv-braces-expr.rs @@ -0,0 +1,6 @@ +fn x() { + params! { + "hello" => my_func(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,), + "goodbye" => "potato", + } +} diff --git a/tests/target/macro-kv-braces.rs b/tests/target/macro-kv-braces.rs new file mode 100644 index 00000000000..ac7bdca9dac --- /dev/null +++ b/tests/target/macro-kv-braces.rs @@ -0,0 +1,6 @@ +fn x() { + params! { + "hello" => "world", + "goodbye" => "potato", + } +}