]> git.lizzy.rs Git - rust.git/blobdiff - src/macros.rs
Cargo clippy
[rust.git] / src / macros.rs
index 3f4adf881200761bf6896a6bf115d10cd0e2bb32..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_inner, rewrite_array};
-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,6 +63,51 @@ fn opener(&self) -> &'static str {
     }
 }
 
+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>,
@@ -68,10 +115,11 @@ pub fn rewrite_macro(
     shape: Shape,
     position: MacroPosition,
 ) -> Option<String> {
-    let mut context = &mut context.clone();
+    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);
         }
     }
@@ -80,11 +128,13 @@ pub fn rewrite_macro(
 
     let macro_name = match extra_ident {
         None => format!("{}!", mac.node.path),
-        Some(ident) => if ident == symbol::keywords::Invalid.ident() {
-            format!("{}!", mac.node.path)
-        } else {
-            format!("{}! {}", mac.node.path, ident)
-        },
+        Some(ident) => {
+            if ident == symbol::keywords::Invalid.ident() {
+                format!("{}!", mac.node.path)
+            } else {
+                format!("{}! {}", mac.node.path, ident)
+            }
+        }
     };
 
     let style = if FORCED_BRACKET_MACROS.contains(&&macro_name[..]) {
@@ -93,8 +143,8 @@ pub fn rewrite_macro(
         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))
@@ -106,28 +156,16 @@ pub fn rewrite_macro(
     }
 
     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,25 +175,22 @@ pub fn rewrite_macro(
                     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();
@@ -171,25 +206,24 @@ pub fn rewrite_macro(
         MacroStyle::Parens => {
             // Format macro invocation as function call, forcing no trailing
             // comma because not all macros support them.
-            let rw = rewrite_call_inner(
+            rewrite_call_inner(
                 context,
                 &macro_name,
-                &expr_vec.iter().map(|e| &**e).collect::<Vec<_>>()[..],
+                &arg_vec.iter().map(|e| &*e).collect::<Vec<_>>()[..],
                 mac.span,
                 shape,
-                context.config.fn_call_width(),
+                context.config.width_heuristics().fn_call_width,
                 trailing_comma,
-            );
-            rw.ok().map(|rw| match position {
+            ).map(|rw| match position {
                 MacroPosition::Item => format!("{};", rw),
                 _ => rw,
             })
         }
         MacroStyle::Brackets => {
-            let mac_shape = try_opt!(shape.offset_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 {
                     ("[", "]")
@@ -197,10 +231,10 @@ pub fn rewrite_macro(
                 // 6 = `vec!` + `; `
                 let total_overhead = lbr.len() + rbr.len() + 6;
                 let nested_shape = mac_shape.block_indent(context.config.tab_spaces());
-                let lhs = try_opt!(expr_vec[0].rewrite(context, nested_shape));
-                let rhs = try_opt!(expr_vec[1].rewrite(context, nested_shape));
-                if !lhs.contains('\n') && !rhs.contains('\n') &&
-                    lhs.len() + rhs.len() + total_overhead <= shape.width
+                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 {
@@ -220,29 +254,26 @@ pub fn rewrite_macro(
                 // 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()) {
+                if FORCED_BRACKET_MACROS.contains(&macro_name.as_str()) {
                     context.inside_macro = false;
                     trailing_comma = false;
                 }
-                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,
-                    trailing_comma,
-                ));
+                // 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)
         }
     }
 }
@@ -257,7 +288,7 @@ pub fn convert_try_mac(mac: &ast::Mac, context: &RewriteContext) -> Option<ast::
 
         Some(ast::Expr {
             id: ast::NodeId::new(0), // dummy value
-            node: ast::ExprKind::Try(try_opt!(parser.parse_expr().ok())),
+            node: ast::ExprKind::Try(parser.parse_expr().ok()?),
             span: mac.span, // incorrect span, but shouldn't matter too much
             attrs: ThinVec::new(),
         })
@@ -280,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)
+}