]> git.lizzy.rs Git - rust.git/blob - src/macros.rs
Merge pull request #2393 from RReverser/macro_rules
[rust.git] / src / macros.rs
1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 // Format list-like macro invocations. These are invocations whose token trees
12 // can be interpreted as expressions and separated by commas.
13 // Note that these token trees do not actually have to be interpreted as
14 // expressions by the compiler. An example of an invocation we would reformat is
15 // foo!( x, y, z ). The token x may represent an identifier in the code, but we
16 // interpreted as an expression.
17 // Macro uses which are not-list like, such as bar!(key => val), will not be
18 // reformatted.
19 // List-like invocations with parentheses will be formatted as function calls,
20 // and those with brackets will be formatted as array literals.
21
22 use std::collections::HashMap;
23 use syntax::ast;
24 use syntax::codemap::{BytePos, Span};
25 use syntax::parse::new_parser_from_tts;
26 use syntax::parse::parser::Parser;
27 use syntax::parse::token::{BinOpToken, DelimToken, Token};
28 use syntax::print::pprust;
29 use syntax::symbol;
30 use syntax::tokenstream::{Cursor, ThinTokenStream, TokenStream, TokenTree};
31 use syntax::util::ThinVec;
32
33 use codemap::SpanUtils;
34 use comment::{contains_comment, remove_trailing_white_spaces, FindUncommented};
35 use expr::{rewrite_array, rewrite_call_inner};
36 use lists::{itemize_list, write_list, DefinitiveListTactic, ListFormatting, SeparatorPlace,
37             SeparatorTactic};
38 use rewrite::{Rewrite, RewriteContext};
39 use shape::{Indent, Shape};
40 use utils::{format_visibility, mk_sp};
41
42 const FORCED_BRACKET_MACROS: &[&str] = &["vec!"];
43
44 // FIXME: use the enum from libsyntax?
45 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
46 enum MacroStyle {
47     Parens,
48     Brackets,
49     Braces,
50 }
51
52 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
53 pub enum MacroPosition {
54     Item,
55     Statement,
56     Expression,
57     Pat,
58 }
59
60 impl MacroStyle {
61     fn opener(&self) -> &'static str {
62         match *self {
63             MacroStyle::Parens => "(",
64             MacroStyle::Brackets => "[",
65             MacroStyle::Braces => "{",
66         }
67     }
68 }
69
70 #[derive(Debug)]
71 pub enum MacroArg {
72     Expr(ast::Expr),
73     Ty(ast::Ty),
74     Pat(ast::Pat),
75 }
76
77 impl Rewrite for MacroArg {
78     fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
79         match *self {
80             MacroArg::Expr(ref expr) => expr.rewrite(context, shape),
81             MacroArg::Ty(ref ty) => ty.rewrite(context, shape),
82             MacroArg::Pat(ref pat) => pat.rewrite(context, shape),
83         }
84     }
85 }
86
87 fn parse_macro_arg(parser: &mut Parser) -> Option<MacroArg> {
88     macro_rules! parse_macro_arg {
89         ($macro_arg: ident, $parser: ident) => {
90             let mut cloned_parser = (*parser).clone();
91             match cloned_parser.$parser() {
92                 Ok(x) => {
93                     if parser.sess.span_diagnostic.has_errors() {
94                         parser.sess.span_diagnostic.reset_err_count();
95                     } else {
96                         // Parsing succeeded.
97                         *parser = cloned_parser;
98                         return Some(MacroArg::$macro_arg((*x).clone()));
99                     }
100                 }
101                 Err(mut e) => {
102                     e.cancel();
103                     parser.sess.span_diagnostic.reset_err_count();
104                 }
105             }
106         };
107     }
108
109     parse_macro_arg!(Expr, parse_expr);
110     parse_macro_arg!(Ty, parse_ty);
111     parse_macro_arg!(Pat, parse_pat);
112
113     None
114 }
115
116 pub fn rewrite_macro(
117     mac: &ast::Mac,
118     extra_ident: Option<ast::Ident>,
119     context: &RewriteContext,
120     shape: Shape,
121     position: MacroPosition,
122 ) -> Option<String> {
123     let context = &mut context.clone();
124     context.inside_macro = true;
125     if context.config.use_try_shorthand() {
126         if let Some(expr) = convert_try_mac(mac, context) {
127             context.inside_macro = false;
128             return expr.rewrite(context, shape);
129         }
130     }
131
132     let original_style = macro_style(mac, context);
133
134     let macro_name = match extra_ident {
135         None => format!("{}!", mac.node.path),
136         Some(ident) => {
137             if ident == symbol::keywords::Invalid.ident() {
138                 format!("{}!", mac.node.path)
139             } else {
140                 format!("{}! {}", mac.node.path, ident)
141             }
142         }
143     };
144
145     let style = if FORCED_BRACKET_MACROS.contains(&&macro_name[..]) {
146         MacroStyle::Brackets
147     } else {
148         original_style
149     };
150
151     let ts: TokenStream = mac.node.stream();
152     if ts.is_empty() && !contains_comment(context.snippet(mac.span)) {
153         return match style {
154             MacroStyle::Parens if position == MacroPosition::Item => {
155                 Some(format!("{}();", macro_name))
156             }
157             MacroStyle::Parens => Some(format!("{}()", macro_name)),
158             MacroStyle::Brackets => Some(format!("{}[]", macro_name)),
159             MacroStyle::Braces => Some(format!("{}{{}}", macro_name)),
160         };
161     }
162
163     let mut parser = new_parser_from_tts(context.parse_session, ts.trees().collect());
164     let mut arg_vec = Vec::new();
165     let mut vec_with_semi = false;
166     let mut trailing_comma = false;
167
168     if MacroStyle::Braces != style {
169         loop {
170             match parse_macro_arg(&mut parser) {
171                 Some(arg) => arg_vec.push(arg),
172                 None => return Some(context.snippet(mac.span).to_owned()),
173             }
174
175             match parser.token {
176                 Token::Eof => break,
177                 Token::Comma => (),
178                 Token::Semi => {
179                     // Try to parse `vec![expr; expr]`
180                     if FORCED_BRACKET_MACROS.contains(&&macro_name[..]) {
181                         parser.bump();
182                         if parser.token != Token::Eof {
183                             match parse_macro_arg(&mut parser) {
184                                 Some(arg) => {
185                                     arg_vec.push(arg);
186                                     parser.bump();
187                                     if parser.token == Token::Eof && arg_vec.len() == 2 {
188                                         vec_with_semi = true;
189                                         break;
190                                     }
191                                 }
192                                 None => return Some(context.snippet(mac.span).to_owned()),
193                             }
194                         }
195                     }
196                     return Some(context.snippet(mac.span).to_owned());
197                 }
198                 _ => return Some(context.snippet(mac.span).to_owned()),
199             }
200
201             parser.bump();
202
203             if parser.token == Token::Eof {
204                 trailing_comma = true;
205                 break;
206             }
207         }
208     }
209
210     match style {
211         MacroStyle::Parens => {
212             // Format macro invocation as function call, forcing no trailing
213             // comma because not all macros support them.
214             rewrite_call_inner(
215                 context,
216                 &macro_name,
217                 &arg_vec.iter().map(|e| &*e).collect::<Vec<_>>()[..],
218                 mac.span,
219                 shape,
220                 context.config.width_heuristics().fn_call_width,
221                 trailing_comma,
222             ).map(|rw| match position {
223                 MacroPosition::Item => format!("{};", rw),
224                 _ => rw,
225             })
226         }
227         MacroStyle::Brackets => {
228             let mac_shape = shape.offset_left(macro_name.len())?;
229             // Handle special case: `vec![expr; expr]`
230             if vec_with_semi {
231                 let (lbr, rbr) = if context.config.spaces_within_parens_and_brackets() {
232                     ("[ ", " ]")
233                 } else {
234                     ("[", "]")
235                 };
236                 // 6 = `vec!` + `; `
237                 let total_overhead = lbr.len() + rbr.len() + 6;
238                 let nested_shape = mac_shape.block_indent(context.config.tab_spaces());
239                 let lhs = arg_vec[0].rewrite(context, nested_shape)?;
240                 let rhs = arg_vec[1].rewrite(context, nested_shape)?;
241                 if !lhs.contains('\n') && !rhs.contains('\n')
242                     && lhs.len() + rhs.len() + total_overhead <= shape.width
243                 {
244                     Some(format!("{}{}{}; {}{}", macro_name, lbr, lhs, rhs, rbr))
245                 } else {
246                     Some(format!(
247                         "{}{}\n{}{};\n{}{}\n{}{}",
248                         macro_name,
249                         lbr,
250                         nested_shape.indent.to_string(context.config),
251                         lhs,
252                         nested_shape.indent.to_string(context.config),
253                         rhs,
254                         shape.indent.to_string(context.config),
255                         rbr
256                     ))
257                 }
258             } else {
259                 // If we are rewriting `vec!` macro or other special macros,
260                 // then we can rewrite this as an usual array literal.
261                 // Otherwise, we must preserve the original existence of trailing comma.
262                 if FORCED_BRACKET_MACROS.contains(&macro_name.as_str()) {
263                     context.inside_macro = false;
264                     trailing_comma = false;
265                 }
266                 // Convert `MacroArg` into `ast::Expr`, as `rewrite_array` only accepts the latter.
267                 let sp = mk_sp(
268                     context
269                         .codemap
270                         .span_after(mac.span, original_style.opener()),
271                     mac.span.hi() - BytePos(1),
272                 );
273                 let arg_vec = &arg_vec.iter().map(|e| &*e).collect::<Vec<_>>()[..];
274                 let rewrite = rewrite_array(arg_vec, sp, context, mac_shape, trailing_comma)?;
275
276                 Some(format!("{}{}", macro_name, rewrite))
277             }
278         }
279         MacroStyle::Braces => {
280             // Skip macro invocations with braces, for now.
281             indent_macro_snippet(context, context.snippet(mac.span), shape.indent)
282         }
283     }
284 }
285
286 pub fn rewrite_macro_def(
287     context: &RewriteContext,
288     shape: Shape,
289     indent: Indent,
290     def: &ast::MacroDef,
291     ident: ast::Ident,
292     vis: &ast::Visibility,
293     span: Span,
294 ) -> Option<String> {
295     let snippet = Some(remove_trailing_white_spaces(context.snippet(span)));
296
297     let mut parser = MacroParser::new(def.stream().into_trees());
298     let parsed_def = match parser.parse() {
299         Some(def) => def,
300         None => return snippet,
301     };
302
303     let mut result = if def.legacy {
304         String::from("macro_rules!")
305     } else {
306         format!("{}macro", format_visibility(vis))
307     };
308
309     result += " ";
310     result += &ident.name.as_str();
311
312     let multi_branch_style = def.legacy || parsed_def.branches.len() != 1;
313
314     let arm_shape = if multi_branch_style {
315         shape
316             .block_indent(context.config.tab_spaces())
317             .with_max_width(context.config)
318     } else {
319         shape
320     };
321
322     let branch_items = itemize_list(
323         context.codemap,
324         parsed_def.branches.iter(),
325         "}",
326         ";",
327         |branch| branch.span.lo(),
328         |branch| branch.span.hi(),
329         |branch| branch.rewrite(context, arm_shape, multi_branch_style),
330         context.codemap.span_after(span, "{"),
331         span.hi(),
332         false,
333     ).collect::<Vec<_>>();
334
335     let fmt = ListFormatting {
336         tactic: DefinitiveListTactic::Vertical,
337         separator: if def.legacy { ";" } else { "" },
338         trailing_separator: SeparatorTactic::Always,
339         separator_place: SeparatorPlace::Back,
340         shape: arm_shape,
341         ends_with_newline: true,
342         preserve_newline: true,
343         config: context.config,
344     };
345
346     if multi_branch_style {
347         result += " {\n";
348         result += &arm_shape.indent.to_string(context.config);
349     }
350
351     result += write_list(&branch_items, &fmt)?.as_str();
352
353     if multi_branch_style {
354         result += "\n";
355         result += &indent.to_string(context.config);
356         result += "}";
357     }
358
359     Some(result)
360 }
361
362 // Replaces `$foo` with `zfoo`. We must check for name overlap to ensure we
363 // aren't causing problems.
364 // This should also work for escaped `$` variables, where we leave earlier `$`s.
365 fn replace_names(input: &str) -> Option<(String, HashMap<String, String>)> {
366     // Each substitution will require five or six extra bytes.
367     let mut result = String::with_capacity(input.len() + 64);
368     let mut substs = HashMap::new();
369     let mut dollar_count = 0;
370     let mut cur_name = String::new();
371
372     for c in input.chars() {
373         if c == '$' {
374             dollar_count += 1;
375         } else if dollar_count == 0 {
376             result.push(c);
377         } else if !c.is_alphanumeric() && !cur_name.is_empty() {
378             // Terminates a name following one or more dollars.
379             let mut new_name = String::new();
380             let mut old_name = String::new();
381             old_name.push('$');
382             for _ in 0..(dollar_count - 1) {
383                 new_name.push('$');
384                 old_name.push('$');
385             }
386             new_name.push('z');
387             new_name.push_str(&cur_name);
388             old_name.push_str(&cur_name);
389
390             result.push_str(&new_name);
391             substs.insert(old_name, new_name);
392
393             result.push(c);
394
395             dollar_count = 0;
396             cur_name = String::new();
397         } else if c == '(' && cur_name.is_empty() {
398             // FIXME: Support macro def with repeat.
399             return None;
400         } else if c.is_alphanumeric() {
401             cur_name.push(c);
402         }
403     }
404
405     // FIXME: duplicate code
406     if !cur_name.is_empty() {
407         let mut new_name = String::new();
408         let mut old_name = String::new();
409         old_name.push('$');
410         for _ in 0..(dollar_count - 1) {
411             new_name.push('$');
412             old_name.push('$');
413         }
414         new_name.push('z');
415         new_name.push_str(&cur_name);
416         old_name.push_str(&cur_name);
417
418         result.push_str(&new_name);
419         substs.insert(old_name, new_name);
420     }
421
422     debug!("replace_names `{}` {:?}", result, substs);
423
424     Some((result, substs))
425 }
426
427 // This is a bit sketchy. The token rules probably need tweaking, but it works
428 // for some common cases. I hope the basic logic is sufficient. Note that the
429 // meaning of some tokens is a bit different here from usual Rust, e.g., `*`
430 // and `(`/`)` have special meaning.
431 //
432 // We always try and format on one line.
433 fn format_macro_args(toks: ThinTokenStream) -> Option<String> {
434     let mut result = String::with_capacity(128);
435     let mut insert_space = SpaceState::Never;
436
437     for tok in (toks.into(): TokenStream).trees() {
438         match tok {
439             TokenTree::Token(_, t) => {
440                 if !result.is_empty() && force_space_before(&t) {
441                     insert_space = SpaceState::Always;
442                 }
443                 if force_no_space_before(&t) {
444                     insert_space = SpaceState::Never;
445                 }
446                 match (insert_space, ident_like(&t)) {
447                     (SpaceState::Always, _)
448                     | (SpaceState::Punctuation, false)
449                     | (SpaceState::Ident, true) => {
450                         result.push(' ');
451                     }
452                     _ => {}
453                 }
454                 result.push_str(&pprust::token_to_string(&t));
455                 insert_space = next_space(&t);
456             }
457             TokenTree::Delimited(_, d) => {
458                 if let SpaceState::Always = insert_space {
459                     result.push(' ');
460                 }
461                 let formatted = format_macro_args(d.tts)?;
462                 match d.delim {
463                     DelimToken::Paren => {
464                         result.push_str(&format!("({})", formatted));
465                         insert_space = SpaceState::Always;
466                     }
467                     DelimToken::Bracket => {
468                         result.push_str(&format!("[{}]", formatted));
469                         insert_space = SpaceState::Always;
470                     }
471                     DelimToken::Brace => {
472                         result.push_str(&format!(" {{ {} }}", formatted));
473                         insert_space = SpaceState::Always;
474                     }
475                     DelimToken::NoDelim => {
476                         result.push_str(&format!("{}", formatted));
477                         insert_space = SpaceState::Always;
478                     }
479                 }
480             }
481         }
482     }
483
484     Some(result)
485 }
486
487 // We should insert a space if the next token is a:
488 #[derive(Copy, Clone)]
489 enum SpaceState {
490     Never,
491     Punctuation,
492     Ident, // Or ident/literal-like thing.
493     Always,
494 }
495
496 fn force_space_before(tok: &Token) -> bool {
497     match *tok {
498         Token::Eq
499         | Token::Lt
500         | Token::Le
501         | Token::EqEq
502         | Token::Ne
503         | Token::Ge
504         | Token::Gt
505         | Token::AndAnd
506         | Token::OrOr
507         | Token::Not
508         | Token::Tilde
509         | Token::BinOpEq(_)
510         | Token::At
511         | Token::RArrow
512         | Token::LArrow
513         | Token::FatArrow
514         | Token::Pound
515         | Token::Dollar => true,
516         Token::BinOp(bot) => bot != BinOpToken::Star,
517         _ => false,
518     }
519 }
520
521 fn force_no_space_before(tok: &Token) -> bool {
522     match *tok {
523         Token::Semi | Token::Comma | Token::Dot => true,
524         Token::BinOp(bot) => bot == BinOpToken::Star,
525         _ => false,
526     }
527 }
528 fn ident_like(tok: &Token) -> bool {
529     match *tok {
530         Token::Ident(_) | Token::Literal(..) | Token::Lifetime(_) => true,
531         _ => false,
532     }
533 }
534
535 fn next_space(tok: &Token) -> SpaceState {
536     match *tok {
537         Token::Not
538         | Token::Tilde
539         | Token::At
540         | Token::Comma
541         | Token::Dot
542         | Token::DotDot
543         | Token::DotDotDot
544         | Token::DotDotEq
545         | Token::DotEq
546         | Token::Question
547         | Token::Underscore
548         | Token::BinOp(_) => SpaceState::Punctuation,
549
550         Token::ModSep
551         | Token::Pound
552         | Token::Dollar
553         | Token::OpenDelim(_)
554         | Token::CloseDelim(_)
555         | Token::Whitespace => SpaceState::Never,
556
557         Token::Literal(..) | Token::Ident(_) | Token::Lifetime(_) => SpaceState::Ident,
558
559         _ => SpaceState::Always,
560     }
561 }
562
563 /// Tries to convert a macro use into a short hand try expression. Returns None
564 /// when the macro is not an instance of try! (or parsing the inner expression
565 /// failed).
566 pub fn convert_try_mac(mac: &ast::Mac, context: &RewriteContext) -> Option<ast::Expr> {
567     if &format!("{}", mac.node.path)[..] == "try" {
568         let ts: TokenStream = mac.node.tts.clone().into();
569         let mut parser = new_parser_from_tts(context.parse_session, ts.trees().collect());
570
571         Some(ast::Expr {
572             id: ast::NodeId::new(0), // dummy value
573             node: ast::ExprKind::Try(parser.parse_expr().ok()?),
574             span: mac.span, // incorrect span, but shouldn't matter too much
575             attrs: ThinVec::new(),
576         })
577     } else {
578         None
579     }
580 }
581
582 fn macro_style(mac: &ast::Mac, context: &RewriteContext) -> MacroStyle {
583     let snippet = context.snippet(mac.span);
584     let paren_pos = snippet.find_uncommented("(").unwrap_or(usize::max_value());
585     let bracket_pos = snippet.find_uncommented("[").unwrap_or(usize::max_value());
586     let brace_pos = snippet.find_uncommented("{").unwrap_or(usize::max_value());
587
588     if paren_pos < bracket_pos && paren_pos < brace_pos {
589         MacroStyle::Parens
590     } else if bracket_pos < brace_pos {
591         MacroStyle::Brackets
592     } else {
593         MacroStyle::Braces
594     }
595 }
596
597 /// Indent each line according to the specified `indent`.
598 /// e.g.
599 /// ```rust
600 /// foo!{
601 /// x,
602 /// y,
603 /// foo(
604 ///     a,
605 ///     b,
606 ///     c,
607 /// ),
608 /// }
609 /// ```
610 /// will become
611 /// ```rust
612 /// foo!{
613 ///     x,
614 ///     y,
615 ///     foo(
616 ///         a,
617 ///         b,
618 ///         c,
619 //      ),
620 /// }
621 /// ```
622 fn indent_macro_snippet(
623     context: &RewriteContext,
624     macro_str: &str,
625     indent: Indent,
626 ) -> Option<String> {
627     let mut lines = macro_str.lines();
628     let first_line = lines.next().map(|s| s.trim_right())?;
629     let mut trimmed_lines = Vec::with_capacity(16);
630
631     let min_prefix_space_width = lines
632         .filter_map(|line| {
633             let prefix_space_width = if is_empty_line(line) {
634                 None
635             } else {
636                 Some(get_prefix_space_width(context, line))
637             };
638             trimmed_lines.push((line.trim(), prefix_space_width));
639             prefix_space_width
640         })
641         .min()?;
642
643     Some(
644         String::from(first_line) + "\n"
645             + &trimmed_lines
646                 .iter()
647                 .map(|&(line, prefix_space_width)| match prefix_space_width {
648                     Some(original_indent_width) => {
649                         let new_indent_width = indent.width()
650                             + original_indent_width
651                                 .checked_sub(min_prefix_space_width)
652                                 .unwrap_or(0);
653                         let new_indent = Indent::from_width(context.config, new_indent_width);
654                         format!("{}{}", new_indent.to_string(context.config), line.trim())
655                     }
656                     None => String::new(),
657                 })
658                 .collect::<Vec<_>>()
659                 .join("\n"),
660     )
661 }
662
663 fn get_prefix_space_width(context: &RewriteContext, s: &str) -> usize {
664     let mut width = 0;
665     for c in s.chars() {
666         match c {
667             ' ' => width += 1,
668             '\t' => width += context.config.tab_spaces(),
669             _ => return width,
670         }
671     }
672     width
673 }
674
675 fn is_empty_line(s: &str) -> bool {
676     s.is_empty() || s.chars().all(char::is_whitespace)
677 }
678
679 // A very simple parser that just parses a macros 2.0 definition into its branches.
680 // Currently we do not attempt to parse any further than that.
681 #[derive(new)]
682 struct MacroParser {
683     toks: Cursor,
684 }
685
686 impl MacroParser {
687     // (`(` ... `)` `=>` `{` ... `}`)*
688     fn parse(&mut self) -> Option<Macro> {
689         let mut branches = vec![];
690         while self.toks.look_ahead(1).is_some() {
691             branches.push(self.parse_branch()?);
692         }
693
694         Some(Macro { branches })
695     }
696
697     // `(` ... `)` `=>` `{` ... `}`
698     fn parse_branch(&mut self) -> Option<MacroBranch> {
699         let tok = self.toks.next()?;
700         let (lo, args_paren_kind) = match tok {
701             TokenTree::Token(..) => return None,
702             TokenTree::Delimited(sp, ref d) => (sp.lo(), d.delim),
703         };
704         let args = tok.joint().into();
705         match self.toks.next()? {
706             TokenTree::Token(_, Token::FatArrow) => {}
707             _ => return None,
708         }
709         let (mut hi, body) = match self.toks.next()? {
710             TokenTree::Token(..) => return None,
711             TokenTree::Delimited(sp, _) => {
712                 let data = sp.data();
713                 (
714                     data.hi,
715                     Span::new(data.lo + BytePos(1), data.hi - BytePos(1), data.ctxt),
716                 )
717             }
718         };
719         if let Some(TokenTree::Token(sp, Token::Semi)) = self.toks.look_ahead(0) {
720             self.toks.next();
721             hi = sp.hi();
722         }
723         Some(MacroBranch {
724             span: mk_sp(lo, hi),
725             args_paren_kind,
726             args,
727             body,
728         })
729     }
730 }
731
732 // A parsed macros 2.0 macro definition.
733 struct Macro {
734     branches: Vec<MacroBranch>,
735 }
736
737 // FIXME: it would be more efficient to use references to the token streams
738 // rather than clone them, if we can make the borrowing work out.
739 struct MacroBranch {
740     span: Span,
741     args_paren_kind: DelimToken,
742     args: ThinTokenStream,
743     body: Span,
744 }
745
746 impl MacroBranch {
747     fn rewrite(
748         &self,
749         context: &RewriteContext,
750         shape: Shape,
751         multi_branch_style: bool,
752     ) -> Option<String> {
753         // Only attempt to format function-like macros.
754         if self.args_paren_kind != DelimToken::Paren {
755             // FIXME(#1539): implement for non-sugared macros.
756             return None;
757         }
758
759         let mut result = format_macro_args(self.args.clone())?;
760
761         if multi_branch_style {
762             result += " =>";
763         }
764
765         // The macro body is the most interesting part. It might end up as various
766         // AST nodes, but also has special variables (e.g, `$foo`) which can't be
767         // parsed as regular Rust code (and note that these can be escaped using
768         // `$$`). We'll try and format like an AST node, but we'll substitute
769         // variables for new names with the same length first.
770
771         let old_body = context.snippet(self.body).trim();
772         let (body_str, substs) = replace_names(old_body)?;
773
774         let mut config = context.config.clone();
775         config.set().hide_parse_errors(true);
776
777         result += " {";
778
779         let has_block_body = old_body.starts_with('{');
780
781         let body_indent = if has_block_body {
782             shape.indent
783         } else {
784             // We'll hack the indent below, take this into account when formatting,
785             let body_indent = shape.indent.block_indent(&config);
786             let new_width = config.max_width() - body_indent.width();
787             config.set().max_width(new_width);
788             body_indent
789         };
790
791         // First try to format as items, then as statements.
792         let new_body = match ::format_snippet(&body_str, &config) {
793             Some(new_body) => new_body,
794             None => match ::format_code_block(&body_str, &config) {
795                 Some(new_body) => new_body,
796                 None => return None,
797             },
798         };
799
800         // Indent the body since it is in a block.
801         let indent_str = body_indent.to_string(&config);
802         let mut new_body = new_body
803             .trim_right()
804             .lines()
805             .fold(String::new(), |mut s, l| {
806                 if !l.is_empty() {
807                     s += &indent_str;
808                 }
809                 s + l + "\n"
810             });
811
812         // Undo our replacement of macro variables.
813         // FIXME: this could be *much* more efficient.
814         for (old, new) in &substs {
815             if old_body.find(new).is_some() {
816                 debug!("rewrite_macro_def: bailing matching variable: `{}`", new);
817                 return None;
818             }
819             new_body = new_body.replace(new, old);
820         }
821
822         if has_block_body {
823             result += new_body.trim();
824         } else if !new_body.is_empty() {
825             result += "\n";
826             result += &new_body;
827             result += &shape.indent.to_string(&config);
828         }
829
830         result += "}";
831
832         Some(result)
833     }
834 }
835
836 #[cfg(test)]
837 mod test {
838     use super::*;
839     use syntax::parse::{parse_stream_from_source_str, ParseSess};
840     use syntax::codemap::{FileName, FilePathMapping};
841
842     fn format_macro_args_str(s: &str) -> String {
843         let input = parse_stream_from_source_str(
844             FileName::Custom("stdin".to_owned()),
845             s.to_owned(),
846             &ParseSess::new(FilePathMapping::empty()),
847             None,
848         );
849         format_macro_args(input.into()).unwrap()
850     }
851
852     #[test]
853     fn test_format_macro_args() {
854         assert_eq!(format_macro_args_str(""), "".to_owned());
855         assert_eq!(format_macro_args_str("$ x : ident"), "$x: ident".to_owned());
856         assert_eq!(
857             format_macro_args_str("$ m1 : ident , $ m2 : ident , $ x : ident"),
858             "$m1: ident, $m2: ident, $x: ident".to_owned()
859         );
860         assert_eq!(
861             format_macro_args_str("$($beginning:ident),*;$middle:ident;$($end:ident),*"),
862             "$($beginning: ident),*; $middle: ident; $($end: ident),*".to_owned()
863         );
864         assert_eq!(
865             format_macro_args_str(
866                 "$ name : ident ( $ ( $ dol : tt $ var : ident ) * ) $ ( $ body : tt ) *"
867             ),
868             "$name: ident($($dol: tt $var: ident)*) $($body: tt)*".to_owned()
869         );
870     }
871 }