]> git.lizzy.rs Git - rust.git/blob - src/macros.rs
Merge pull request #2258 from topecongiro/issue-819
[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 syntax::ast;
23 use syntax::codemap::BytePos;
24 use syntax::parse::new_parser_from_tts;
25 use syntax::parse::parser::Parser;
26 use syntax::parse::token::Token;
27 use syntax::symbol;
28 use syntax::tokenstream::TokenStream;
29 use syntax::util::ThinVec;
30
31 use codemap::SpanUtils;
32 use comment::{contains_comment, FindUncommented};
33 use expr::{rewrite_array, rewrite_call_inner};
34 use rewrite::{Rewrite, RewriteContext};
35 use shape::{Indent, Shape};
36 use utils::mk_sp;
37
38 const FORCED_BRACKET_MACROS: &[&str] = &["vec!"];
39
40 // FIXME: use the enum from libsyntax?
41 #[derive(Clone, Copy, PartialEq, Eq)]
42 enum MacroStyle {
43     Parens,
44     Brackets,
45     Braces,
46 }
47
48 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
49 pub enum MacroPosition {
50     Item,
51     Statement,
52     Expression,
53     Pat,
54 }
55
56 impl MacroStyle {
57     fn opener(&self) -> &'static str {
58         match *self {
59             MacroStyle::Parens => "(",
60             MacroStyle::Brackets => "[",
61             MacroStyle::Braces => "{",
62         }
63     }
64 }
65
66 pub enum MacroArg {
67     Expr(ast::Expr),
68     Ty(ast::Ty),
69     Pat(ast::Pat),
70 }
71
72 impl Rewrite for MacroArg {
73     fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
74         match *self {
75             MacroArg::Expr(ref expr) => expr.rewrite(context, shape),
76             MacroArg::Ty(ref ty) => ty.rewrite(context, shape),
77             MacroArg::Pat(ref pat) => pat.rewrite(context, shape),
78         }
79     }
80 }
81
82 fn parse_macro_arg(parser: &mut Parser) -> Option<MacroArg> {
83     macro_rules! parse_macro_arg {
84         ($target:tt, $macro_arg:ident, $parser:ident) => {
85             let mut cloned_parser = (*parser).clone();
86             match cloned_parser.$parser() {
87                 Ok($target) => {
88                     if parser.sess.span_diagnostic.has_errors() {
89                         parser.sess.span_diagnostic.reset_err_count();
90                     } else {
91                         // Parsing succeeded.
92                         *parser = cloned_parser;
93                         return Some(MacroArg::$macro_arg((*$target).clone()));
94                     }
95                 }
96                 Err(mut e) => {
97                     e.cancel();
98                     parser.sess.span_diagnostic.reset_err_count();
99                 }
100             }
101         }
102     }
103
104     parse_macro_arg!(expr, Expr, parse_expr);
105     parse_macro_arg!(ty, Ty, parse_ty);
106     parse_macro_arg!(pat, Pat, parse_pat);
107
108     None
109 }
110
111 pub fn rewrite_macro(
112     mac: &ast::Mac,
113     extra_ident: Option<ast::Ident>,
114     context: &RewriteContext,
115     shape: Shape,
116     position: MacroPosition,
117 ) -> Option<String> {
118     let context = &mut context.clone();
119     context.inside_macro = true;
120     if context.config.use_try_shorthand() {
121         if let Some(expr) = convert_try_mac(mac, context) {
122             return expr.rewrite(context, shape);
123         }
124     }
125
126     let original_style = macro_style(mac, context);
127
128     let macro_name = match extra_ident {
129         None => format!("{}!", mac.node.path),
130         Some(ident) => {
131             if ident == symbol::keywords::Invalid.ident() {
132                 format!("{}!", mac.node.path)
133             } else {
134                 format!("{}! {}", mac.node.path, ident)
135             }
136         }
137     };
138
139     let style = if FORCED_BRACKET_MACROS.contains(&&macro_name[..]) {
140         MacroStyle::Brackets
141     } else {
142         original_style
143     };
144
145     let ts: TokenStream = mac.node.stream();
146     if ts.is_empty() && !contains_comment(&context.snippet(mac.span)) {
147         return match style {
148             MacroStyle::Parens if position == MacroPosition::Item => {
149                 Some(format!("{}();", macro_name))
150             }
151             MacroStyle::Parens => Some(format!("{}()", macro_name)),
152             MacroStyle::Brackets => Some(format!("{}[]", macro_name)),
153             MacroStyle::Braces => Some(format!("{}{{}}", macro_name)),
154         };
155     }
156
157     let mut parser = new_parser_from_tts(context.parse_session, ts.trees().collect());
158     let mut arg_vec = Vec::new();
159     let mut vec_with_semi = false;
160     let mut trailing_comma = false;
161
162     if MacroStyle::Braces != style {
163         loop {
164             match parse_macro_arg(&mut parser) {
165                 Some(arg) => arg_vec.push(arg),
166                 None => return Some(context.snippet(mac.span).to_owned()),
167             }
168
169             match parser.token {
170                 Token::Eof => break,
171                 Token::Comma => (),
172                 Token::Semi => {
173                     // Try to parse `vec![expr; expr]`
174                     if FORCED_BRACKET_MACROS.contains(&&macro_name[..]) {
175                         parser.bump();
176                         if parser.token != Token::Eof {
177                             match parse_macro_arg(&mut parser) {
178                                 Some(arg) => {
179                                     arg_vec.push(arg);
180                                     parser.bump();
181                                     if parser.token == Token::Eof && arg_vec.len() == 2 {
182                                         vec_with_semi = true;
183                                         break;
184                                     }
185                                 }
186                                 None => return Some(context.snippet(mac.span).to_owned()),
187                             }
188                         }
189                     }
190                     return Some(context.snippet(mac.span).to_owned());
191                 }
192                 _ => return Some(context.snippet(mac.span).to_owned()),
193             }
194
195             parser.bump();
196
197             if parser.token == Token::Eof {
198                 trailing_comma = true;
199                 break;
200             }
201         }
202     }
203
204     match style {
205         MacroStyle::Parens => {
206             // Format macro invocation as function call, forcing no trailing
207             // comma because not all macros support them.
208             rewrite_call_inner(
209                 context,
210                 &macro_name,
211                 &arg_vec.iter().map(|e| &*e).collect::<Vec<_>>()[..],
212                 mac.span,
213                 shape,
214                 context.config.width_heuristics().fn_call_width,
215                 trailing_comma,
216             ).map(|rw| match position {
217                 MacroPosition::Item => format!("{};", rw),
218                 _ => rw,
219             })
220         }
221         MacroStyle::Brackets => {
222             let mac_shape = shape.offset_left(macro_name.len())?;
223             // Handle special case: `vec![expr; expr]`
224             if vec_with_semi {
225                 let (lbr, rbr) = if context.config.spaces_within_parens_and_brackets() {
226                     ("[ ", " ]")
227                 } else {
228                     ("[", "]")
229                 };
230                 // 6 = `vec!` + `; `
231                 let total_overhead = lbr.len() + rbr.len() + 6;
232                 let nested_shape = mac_shape.block_indent(context.config.tab_spaces());
233                 let lhs = arg_vec[0].rewrite(context, nested_shape)?;
234                 let rhs = arg_vec[1].rewrite(context, nested_shape)?;
235                 if !lhs.contains('\n') && !rhs.contains('\n')
236                     && lhs.len() + rhs.len() + total_overhead <= shape.width
237                 {
238                     Some(format!("{}{}{}; {}{}", macro_name, lbr, lhs, rhs, rbr))
239                 } else {
240                     Some(format!(
241                         "{}{}\n{}{};\n{}{}\n{}{}",
242                         macro_name,
243                         lbr,
244                         nested_shape.indent.to_string(context.config),
245                         lhs,
246                         nested_shape.indent.to_string(context.config),
247                         rhs,
248                         shape.indent.to_string(context.config),
249                         rbr
250                     ))
251                 }
252             } else {
253                 // If we are rewriting `vec!` macro or other special macros,
254                 // then we can rewrite this as an usual array literal.
255                 // Otherwise, we must preserve the original existence of trailing comma.
256                 if FORCED_BRACKET_MACROS.contains(&macro_name.as_str()) {
257                     context.inside_macro = false;
258                     trailing_comma = false;
259                 }
260                 // Convert `MacroArg` into `ast::Expr`, as `rewrite_array` only accepts the latter.
261                 let sp = mk_sp(
262                     context
263                         .codemap
264                         .span_after(mac.span, original_style.opener()),
265                     mac.span.hi() - BytePos(1),
266                 );
267                 let arg_vec = &arg_vec.iter().map(|e| &*e).collect::<Vec<_>>()[..];
268                 let rewrite = rewrite_array(arg_vec, sp, context, mac_shape, trailing_comma)?;
269
270                 Some(format!("{}{}", macro_name, rewrite))
271             }
272         }
273         MacroStyle::Braces => {
274             // Skip macro invocations with braces, for now.
275             indent_macro_snippet(context, context.snippet(mac.span), shape.indent)
276         }
277     }
278 }
279
280 /// Tries to convert a macro use into a short hand try expression. Returns None
281 /// when the macro is not an instance of try! (or parsing the inner expression
282 /// failed).
283 pub fn convert_try_mac(mac: &ast::Mac, context: &RewriteContext) -> Option<ast::Expr> {
284     if &format!("{}", mac.node.path)[..] == "try" {
285         let ts: TokenStream = mac.node.tts.clone().into();
286         let mut parser = new_parser_from_tts(context.parse_session, ts.trees().collect());
287
288         Some(ast::Expr {
289             id: ast::NodeId::new(0), // dummy value
290             node: ast::ExprKind::Try(parser.parse_expr().ok()?),
291             span: mac.span, // incorrect span, but shouldn't matter too much
292             attrs: ThinVec::new(),
293         })
294     } else {
295         None
296     }
297 }
298
299 fn macro_style(mac: &ast::Mac, context: &RewriteContext) -> MacroStyle {
300     let snippet = context.snippet(mac.span);
301     let paren_pos = snippet.find_uncommented("(").unwrap_or(usize::max_value());
302     let bracket_pos = snippet.find_uncommented("[").unwrap_or(usize::max_value());
303     let brace_pos = snippet.find_uncommented("{").unwrap_or(usize::max_value());
304
305     if paren_pos < bracket_pos && paren_pos < brace_pos {
306         MacroStyle::Parens
307     } else if bracket_pos < brace_pos {
308         MacroStyle::Brackets
309     } else {
310         MacroStyle::Braces
311     }
312 }
313
314 /// Indent each line according to the specified `indent`.
315 /// e.g.
316 /// ```rust
317 /// foo!{
318 /// x,
319 /// y,
320 /// foo(
321 ///     a,
322 ///     b,
323 ///     c,
324 /// ),
325 /// }
326 /// ```
327 /// will become
328 /// ```rust
329 /// foo!{
330 ///     x,
331 ///     y,
332 ///     foo(
333 ///         a,
334 ///         b,
335 ///         c,
336 //      ),
337 /// }
338 /// ```
339 fn indent_macro_snippet(
340     context: &RewriteContext,
341     macro_str: &str,
342     indent: Indent,
343 ) -> Option<String> {
344     let mut lines = macro_str.lines();
345     let first_line = lines.next().map(|s| s.trim_right())?;
346     let mut trimmed_lines = Vec::with_capacity(16);
347
348     let min_prefix_space_width = lines
349         .filter_map(|line| {
350             let prefix_space_width = if is_empty_line(line) {
351                 None
352             } else {
353                 Some(get_prefix_space_width(context, line))
354             };
355             trimmed_lines.push((line.trim(), prefix_space_width));
356             prefix_space_width
357         })
358         .min()?;
359
360     Some(
361         String::from(first_line) + "\n"
362             + &trimmed_lines
363                 .iter()
364                 .map(|&(line, prefix_space_width)| match prefix_space_width {
365                     Some(original_indent_width) => {
366                         let new_indent_width = indent.width()
367                             + original_indent_width
368                                 .checked_sub(min_prefix_space_width)
369                                 .unwrap_or(0);
370                         let new_indent = Indent::from_width(context.config, new_indent_width);
371                         format!("{}{}", new_indent.to_string(context.config), line.trim())
372                     }
373                     None => String::new(),
374                 })
375                 .collect::<Vec<_>>()
376                 .join("\n"),
377     )
378 }
379
380 fn get_prefix_space_width(context: &RewriteContext, s: &str) -> usize {
381     let mut width = 0;
382     for c in s.chars() {
383         match c {
384             ' ' => width += 1,
385             '\t' => width += context.config.tab_spaces(),
386             _ => return width,
387         }
388     }
389     width
390 }
391
392 fn is_empty_line(s: &str) -> bool {
393     s.is_empty() || s.chars().all(char::is_whitespace)
394 }