]> git.lizzy.rs Git - rust.git/blobdiff - src/macros.rs
Cargo clippy
[rust.git] / src / macros.rs
index eca89a4b609b56dfe897e962eebd218c5ceae40c..9c32e7f62befe3703e9498bbf261f5da808f9ad5 100644 (file)
 // foo!( x, y, z ). The token x may represent an identifier in the code, but we
 // interpreted as an expression.
 // Macro uses which are not-list like, such as bar!(key => val), will not be
-// reformated.
+// reformatted.
 // List-like invocations with parentheses will be formatted as function calls,
 // and those with brackets will be formatted as array literals.
 
 use syntax::ast;
 use syntax::codemap::BytePos;
-use syntax::parse::token::Token;
 use syntax::parse::new_parser_from_tts;
-use syntax::tokenstream::TokenStream;
+use syntax::parse::parser::Parser;
+use syntax::parse::token::Token;
 use syntax::symbol;
+use syntax::tokenstream::TokenStream;
 use syntax::util::ThinVec;
 
-use Shape;
 use codemap::SpanUtils;
+use comment::{contains_comment, FindUncommented};
+use expr::{rewrite_array, rewrite_call_inner};
 use rewrite::{Rewrite, RewriteContext};
-use expr::{rewrite_call, rewrite_array, rewrite_pair};
-use comment::{FindUncommented, contains_comment};
+use shape::{Indent, Shape};
 use utils::mk_sp;
 
-const FORCED_BRACKET_MACROS: &'static [&'static str] = &["vec!"];
+const FORCED_BRACKET_MACROS: &[&str] = &["vec!"];
 
 // FIXME: use the enum from libsyntax?
 #[derive(Clone, Copy, PartialEq, Eq)]
@@ -49,6 +50,7 @@ pub enum MacroPosition {
     Item,
     Statement,
     Expression,
+    Pat,
 }
 
 impl MacroStyle {
@@ -61,16 +63,63 @@ fn opener(&self) -> &'static str {
     }
 }
 
-pub fn rewrite_macro(mac: &ast::Mac,
-                     extra_ident: Option<ast::Ident>,
-                     context: &RewriteContext,
-                     shape: Shape,
-                     position: MacroPosition)
-                     -> Option<String> {
-    let mut context = &mut context.clone();
+pub enum MacroArg {
+    Expr(ast::Expr),
+    Ty(ast::Ty),
+    Pat(ast::Pat),
+}
+
+impl Rewrite for MacroArg {
+    fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
+        match *self {
+            MacroArg::Expr(ref expr) => expr.rewrite(context, shape),
+            MacroArg::Ty(ref ty) => ty.rewrite(context, shape),
+            MacroArg::Pat(ref pat) => pat.rewrite(context, shape),
+        }
+    }
+}
+
+fn parse_macro_arg(parser: &mut Parser) -> Option<MacroArg> {
+    macro_rules! parse_macro_arg {
+        ($target:tt, $macro_arg:ident, $parser:ident) => {
+            let mut cloned_parser = (*parser).clone();
+            match cloned_parser.$parser() {
+                Ok($target) => {
+                    if parser.sess.span_diagnostic.has_errors() {
+                        parser.sess.span_diagnostic.reset_err_count();
+                    } else {
+                        // Parsing succeeded.
+                        *parser = cloned_parser;
+                        return Some(MacroArg::$macro_arg((*$target).clone()));
+                    }
+                }
+                Err(mut e) => {
+                    e.cancel();
+                    parser.sess.span_diagnostic.reset_err_count();
+                }
+            }
+        }
+    }
+
+    parse_macro_arg!(expr, Expr, parse_expr);
+    parse_macro_arg!(ty, Ty, parse_ty);
+    parse_macro_arg!(pat, Pat, parse_pat);
+
+    None
+}
+
+pub fn rewrite_macro(
+    mac: &ast::Mac,
+    extra_ident: Option<ast::Ident>,
+    context: &RewriteContext,
+    shape: Shape,
+    position: MacroPosition,
+) -> Option<String> {
+    let context = &mut context.clone();
     context.inside_macro = true;
     if context.config.use_try_shorthand() {
         if let Some(expr) = convert_try_mac(mac, context) {
+            context.inside_macro = false;
             return expr.rewrite(context, shape);
         }
     }
@@ -94,8 +143,8 @@ pub fn rewrite_macro(mac: &ast::Mac,
         original_style
     };
 
-    let ts: TokenStream = mac.node.tts.clone().into();
-    if ts.is_empty() && !contains_comment(&context.snippet(mac.span)) {
+    let ts: TokenStream = mac.node.stream();
+    if ts.is_empty() && !contains_comment(context.snippet(mac.span)) {
         return match style {
             MacroStyle::Parens if position == MacroPosition::Item => {
                 Some(format!("{}();", macro_name))
@@ -107,27 +156,16 @@ pub fn rewrite_macro(mac: &ast::Mac,
     }
 
     let mut parser = new_parser_from_tts(context.parse_session, ts.trees().collect());
-    let mut expr_vec = Vec::new();
+    let mut arg_vec = Vec::new();
     let mut vec_with_semi = false;
+    let mut trailing_comma = false;
 
     if MacroStyle::Braces != style {
         loop {
-            let expr = match parser.parse_expr() {
-                Ok(expr) => {
-                    // Recovered errors.
-                    if context.parse_session.span_diagnostic.has_errors() {
-                        return Some(context.snippet(mac.span));
-                    }
-
-                    expr
-                }
-                Err(mut e) => {
-                    e.cancel();
-                    return Some(context.snippet(mac.span));
-                }
-            };
-
-            expr_vec.push(expr);
+            match parse_macro_arg(&mut parser) {
+                Some(arg) => arg_vec.push(arg),
+                None => return Some(context.snippet(mac.span).to_owned()),
+            }
 
             match parser.token {
                 Token::Eof => break,
@@ -137,36 +175,29 @@ pub fn rewrite_macro(mac: &ast::Mac,
                     if FORCED_BRACKET_MACROS.contains(&&macro_name[..]) {
                         parser.bump();
                         if parser.token != Token::Eof {
-                            match parser.parse_expr() {
-                                Ok(expr) => {
-                                    if context.parse_session.span_diagnostic.has_errors() {
-                                        return None;
-                                    }
-                                    expr_vec.push(expr);
+                            match parse_macro_arg(&mut parser) {
+                                Some(arg) => {
+                                    arg_vec.push(arg);
                                     parser.bump();
-                                    if parser.token == Token::Eof && expr_vec.len() == 2 {
+                                    if parser.token == Token::Eof && arg_vec.len() == 2 {
                                         vec_with_semi = true;
                                         break;
                                     }
                                 }
-                                Err(mut e) => e.cancel(),
+                                None => return Some(context.snippet(mac.span).to_owned()),
                             }
                         }
                     }
-                    return None;
+                    return Some(context.snippet(mac.span).to_owned());
                 }
-                _ => return None,
+                _ => return Some(context.snippet(mac.span).to_owned()),
             }
 
             parser.bump();
 
             if parser.token == Token::Eof {
-                // vec! is a special case of bracket macro which should be formated as an array.
-                if macro_name == "vec!" {
-                    break;
-                } else {
-                    return None;
-                }
+                trailing_comma = true;
+                break;
             }
         }
     }
@@ -175,47 +206,74 @@ pub fn rewrite_macro(mac: &ast::Mac,
         MacroStyle::Parens => {
             // Format macro invocation as function call, forcing no trailing
             // comma because not all macros support them.
-            rewrite_call(context, &macro_name, &expr_vec, mac.span, shape).map(
-                |rw| match position {
-                    MacroPosition::Item => format!("{};", rw),
-                    _ => rw,
-                },
-            )
+            rewrite_call_inner(
+                context,
+                &macro_name,
+                &arg_vec.iter().map(|e| &*e).collect::<Vec<_>>()[..],
+                mac.span,
+                shape,
+                context.config.width_heuristics().fn_call_width,
+                trailing_comma,
+            ).map(|rw| match position {
+                MacroPosition::Item => format!("{};", rw),
+                _ => rw,
+            })
         }
         MacroStyle::Brackets => {
-            let mac_shape = try_opt!(shape.shrink_left(macro_name.len()));
+            let mac_shape = shape.offset_left(macro_name.len())?;
             // Handle special case: `vec![expr; expr]`
             if vec_with_semi {
-                let (lbr, rbr) = if context.config.spaces_within_square_brackets() {
+                let (lbr, rbr) = if context.config.spaces_within_parens_and_brackets() {
                     ("[ ", " ]")
                 } else {
                     ("[", "]")
                 };
-                rewrite_pair(&*expr_vec[0],
-                             &*expr_vec[1],
-                             lbr,
-                             "; ",
-                             rbr,
-                             context,
-                             mac_shape)
-                    .map(|s| format!("{}{}", macro_name, s))
+                // 6 = `vec!` + `; `
+                let total_overhead = lbr.len() + rbr.len() + 6;
+                let nested_shape = mac_shape.block_indent(context.config.tab_spaces());
+                let lhs = arg_vec[0].rewrite(context, nested_shape)?;
+                let rhs = arg_vec[1].rewrite(context, nested_shape)?;
+                if !lhs.contains('\n') && !rhs.contains('\n')
+                    && lhs.len() + rhs.len() + total_overhead <= shape.width
+                {
+                    Some(format!("{}{}{}; {}{}", macro_name, lbr, lhs, rhs, rbr))
+                } else {
+                    Some(format!(
+                        "{}{}\n{}{};\n{}{}\n{}{}",
+                        macro_name,
+                        lbr,
+                        nested_shape.indent.to_string(context.config),
+                        lhs,
+                        nested_shape.indent.to_string(context.config),
+                        rhs,
+                        shape.indent.to_string(context.config),
+                        rbr
+                    ))
+                }
             } else {
-                // Format macro invocation as array literal.
-                let rewrite =
-                    try_opt!(rewrite_array(expr_vec.iter().map(|x| &**x),
-                                           mk_sp(context
-                                                     .codemap
-                                                     .span_after(mac.span, original_style.opener()),
-                                               mac.span.hi - BytePos(1)),
-                                           context,
-                                           mac_shape));
+                // If we are rewriting `vec!` macro or other special macros,
+                // then we can rewrite this as an usual array literal.
+                // Otherwise, we must preserve the original existence of trailing comma.
+                if FORCED_BRACKET_MACROS.contains(&macro_name.as_str()) {
+                    context.inside_macro = false;
+                    trailing_comma = false;
+                }
+                // Convert `MacroArg` into `ast::Expr`, as `rewrite_array` only accepts the latter.
+                let sp = mk_sp(
+                    context
+                        .codemap
+                        .span_after(mac.span, original_style.opener()),
+                    mac.span.hi() - BytePos(1),
+                );
+                let arg_vec = &arg_vec.iter().map(|e| &*e).collect::<Vec<_>>()[..];
+                let rewrite = rewrite_array(arg_vec, sp, context, mac_shape, trailing_comma)?;
 
                 Some(format!("{}{}", macro_name, rewrite))
             }
         }
         MacroStyle::Braces => {
             // Skip macro invocations with braces, for now.
-            None
+            indent_macro_snippet(context, context.snippet(mac.span), shape.indent)
         }
     }
 }
@@ -229,11 +287,11 @@ pub fn convert_try_mac(mac: &ast::Mac, context: &RewriteContext) -> Option<ast::
         let mut parser = new_parser_from_tts(context.parse_session, ts.trees().collect());
 
         Some(ast::Expr {
-                 id: ast::NodeId::new(0), // dummy value
-                 node: ast::ExprKind::Try(try_opt!(parser.parse_expr().ok())),
-                 span: mac.span, // incorrect span, but shouldn't matter too much
-                 attrs: ThinVec::new(),
-             })
+            id: ast::NodeId::new(0), // dummy value
+            node: ast::ExprKind::Try(parser.parse_expr().ok()?),
+            span: mac.span, // incorrect span, but shouldn't matter too much
+            attrs: ThinVec::new(),
+        })
     } else {
         None
     }
@@ -253,3 +311,85 @@ fn macro_style(mac: &ast::Mac, context: &RewriteContext) -> MacroStyle {
         MacroStyle::Braces
     }
 }
+
+/// Indent each line according to the specified `indent`.
+/// e.g.
+/// ```rust
+/// foo!{
+/// x,
+/// y,
+/// foo(
+///     a,
+///     b,
+///     c,
+/// ),
+/// }
+/// ```
+/// will become
+/// ```rust
+/// foo!{
+///     x,
+///     y,
+///     foo(
+///         a,
+///         b,
+///         c,
+//      ),
+/// }
+/// ```
+fn indent_macro_snippet(
+    context: &RewriteContext,
+    macro_str: &str,
+    indent: Indent,
+) -> Option<String> {
+    let mut lines = macro_str.lines();
+    let first_line = lines.next().map(|s| s.trim_right())?;
+    let mut trimmed_lines = Vec::with_capacity(16);
+
+    let min_prefix_space_width = lines
+        .filter_map(|line| {
+            let prefix_space_width = if is_empty_line(line) {
+                None
+            } else {
+                Some(get_prefix_space_width(context, line))
+            };
+            trimmed_lines.push((line.trim(), prefix_space_width));
+            prefix_space_width
+        })
+        .min()?;
+
+    Some(
+        String::from(first_line) + "\n"
+            + &trimmed_lines
+                .iter()
+                .map(|&(line, prefix_space_width)| match prefix_space_width {
+                    Some(original_indent_width) => {
+                        let new_indent_width = indent.width()
+                            + original_indent_width
+                                .checked_sub(min_prefix_space_width)
+                                .unwrap_or(0);
+                        let new_indent = Indent::from_width(context.config, new_indent_width);
+                        format!("{}{}", new_indent.to_string(context.config), line.trim())
+                    }
+                    None => String::new(),
+                })
+                .collect::<Vec<_>>()
+                .join("\n"),
+    )
+}
+
+fn get_prefix_space_width(context: &RewriteContext, s: &str) -> usize {
+    let mut width = 0;
+    for c in s.chars() {
+        match c {
+            ' ' => width += 1,
+            '\t' => width += context.config.tab_spaces(),
+            _ => return width,
+        }
+    }
+    width
+}
+
+fn is_empty_line(s: &str) -> bool {
+    s.is_empty() || s.chars().all(char::is_whitespace)
+}