]> git.lizzy.rs Git - rust.git/blobdiff - src/visitor.rs
Merge pull request #2138 from topecongiro/comments-around-trait-bounds
[rust.git] / src / visitor.rs
index 642f75e58ab215e7a1f5696228c03ff73c9f54dd..b69edfda0c62fdf6cabfe410aced89daffdcb320 100644 (file)
 use expr::rewrite_literal;
 use spanned::Spanned;
 use codemap::{LineRangeUtils, SpanUtils};
-use comment::{contains_comment, recover_missing_comment_in_span, remove_trailing_white_spaces,
+use comment::{combine_strs_with_missing_comments, contains_comment, remove_trailing_white_spaces,
               CodeCharKind, CommentCodeSlices, FindUncommented};
 use comment::rewrite_comment;
 use config::{BraceStyle, Config};
-use items::{format_impl, format_struct, format_struct_struct, format_trait,
-            rewrite_associated_impl_type, rewrite_associated_type, rewrite_static,
-            rewrite_type_alias, FnSig};
+use items::{format_impl, format_trait, rewrite_associated_impl_type, rewrite_associated_type,
+            rewrite_type_alias, FnSig, StaticParts, StructParts};
 use lists::{itemize_list, write_list, DefinitiveListTactic, ListFormatting, SeparatorPlace,
             SeparatorTactic};
 use macros::{rewrite_macro, MacroPosition};
@@ -197,12 +196,11 @@ pub fn visit_block(&mut self, b: &ast::Block, inner_attrs: Option<&[ast::Attribu
             }
         }
 
-        let mut unindent_comment = self.is_if_else_block && !b.stmts.is_empty();
-        if unindent_comment {
+        let unindent_comment = (self.is_if_else_block && !b.stmts.is_empty()) && {
             let end_pos = source!(self, b.span).hi() - brace_compensation - remove_len;
             let snippet = self.snippet(mk_sp(self.last_pos, end_pos));
-            unindent_comment = snippet.contains("//") || snippet.contains("/*");
-        }
+            snippet.contains("//") || snippet.contains("/*")
+        };
         // FIXME: we should compress any newlines here to just one
         if unindent_comment {
             self.block_indent = self.block_indent.block_unindent(self.config);
@@ -243,24 +241,13 @@ fn visit_fn(
         generics: &ast::Generics,
         fd: &ast::FnDecl,
         s: Span,
-        _: ast::NodeId,
         defaultness: ast::Defaultness,
         inner_attrs: Option<&[ast::Attribute]>,
     ) {
         let indent = self.block_indent;
         let block;
         let rewrite = match fk {
-            visit::FnKind::ItemFn(ident, _, _, _, _, b) => {
-                block = b;
-                self.rewrite_fn(
-                    indent,
-                    ident,
-                    &FnSig::from_fn_kind(&fk, generics, fd, defaultness),
-                    mk_sp(s.lo(), b.span.lo()),
-                    b,
-                )
-            }
-            visit::FnKind::Method(ident, _, _, b) => {
+            visit::FnKind::ItemFn(ident, _, _, _, _, b) | visit::FnKind::Method(ident, _, _, b) => {
                 block = b;
                 self.rewrite_fn(
                     indent,
@@ -357,22 +344,8 @@ pub fn visit_item(&mut self, item: &ast::Item) {
                 let rw = rewrite_extern_crate(&self.get_context(), item);
                 self.push_rewrite(item.span, rw);
             }
-            ast::ItemKind::Struct(ref def, ref generics) => {
-                let rewrite = format_struct(
-                    &self.get_context(),
-                    "struct ",
-                    item.ident,
-                    &item.vis,
-                    def,
-                    Some(generics),
-                    item.span,
-                    self.block_indent,
-                    None,
-                ).map(|s| match *def {
-                    ast::VariantData::Tuple(..) => s + ";",
-                    _ => s,
-                });
-                self.push_rewrite(item.span, rewrite);
+            ast::ItemKind::Struct(..) | ast::ItemKind::Union(..) => {
+                self.visit_struct(&StructParts::from_item(item));
             }
             ast::ItemKind::Enum(ref def, ref generics) => {
                 self.format_missing_with_indent(source!(self, item.span).lo());
@@ -390,33 +363,8 @@ pub fn visit_item(&mut self, item: &ast::Item) {
                 self.format_missing_with_indent(source!(self, item.span).lo());
                 self.format_foreign_mod(foreign_mod, item.span);
             }
-            ast::ItemKind::Static(ref ty, mutability, ref expr) => {
-                let rewrite = rewrite_static(
-                    "static",
-                    &item.vis,
-                    item.ident,
-                    ty,
-                    mutability,
-                    Some(expr),
-                    self.block_indent,
-                    item.span,
-                    &self.get_context(),
-                );
-                self.push_rewrite(item.span, rewrite);
-            }
-            ast::ItemKind::Const(ref ty, ref expr) => {
-                let rewrite = rewrite_static(
-                    "const",
-                    &item.vis,
-                    item.ident,
-                    ty,
-                    ast::Mutability::Immutable,
-                    Some(expr),
-                    self.block_indent,
-                    item.span,
-                    &self.get_context(),
-                );
-                self.push_rewrite(item.span, rewrite);
+            ast::ItemKind::Static(..) | ast::ItemKind::Const(..) => {
+                self.visit_static(&StaticParts::from_item(item));
             }
             ast::ItemKind::AutoImpl(..) => {
                 // FIXME(#78): format impl definitions.
@@ -427,7 +375,6 @@ pub fn visit_item(&mut self, item: &ast::Item) {
                     generics,
                     decl,
                     item.span,
-                    item.id,
                     ast::Defaultness::Final,
                     Some(&item.attrs),
                 )
@@ -444,20 +391,6 @@ pub fn visit_item(&mut self, item: &ast::Item) {
                 );
                 self.push_rewrite(item.span, rewrite);
             }
-            ast::ItemKind::Union(ref def, ref generics) => {
-                let rewrite = format_struct_struct(
-                    &self.get_context(),
-                    "union ",
-                    item.ident,
-                    &item.vis,
-                    def.fields(),
-                    Some(generics),
-                    item.span,
-                    self.block_indent,
-                    None,
-                );
-                self.push_rewrite(item.span, rewrite);
-            }
             ast::ItemKind::GlobalAsm(..) => {
                 let snippet = Some(self.snippet(item.span));
                 self.push_rewrite(item.span, snippet);
@@ -479,20 +412,7 @@ pub fn visit_trait_item(&mut self, ti: &ast::TraitItem) {
         }
 
         match ti.node {
-            ast::TraitItemKind::Const(ref ty, ref expr_opt) => {
-                let rewrite = rewrite_static(
-                    "const",
-                    &ast::Visibility::Inherited,
-                    ti.ident,
-                    ty,
-                    ast::Mutability::Immutable,
-                    expr_opt.as_ref(),
-                    self.block_indent,
-                    ti.span,
-                    &self.get_context(),
-                );
-                self.push_rewrite(ti.span, rewrite);
-            }
+            ast::TraitItemKind::Const(..) => self.visit_static(&StaticParts::from_trait_item(ti)),
             ast::TraitItemKind::Method(ref sig, None) => {
                 let indent = self.block_indent;
                 let rewrite =
@@ -505,7 +425,6 @@ pub fn visit_trait_item(&mut self, ti: &ast::TraitItem) {
                     &ti.generics,
                     &sig.decl,
                     ti.span,
-                    ti.id,
                     ast::Defaultness::Final,
                     Some(&ti.attrs),
                 );
@@ -541,25 +460,11 @@ pub fn visit_impl_item(&mut self, ii: &ast::ImplItem) {
                     &ii.generics,
                     &sig.decl,
                     ii.span,
-                    ii.id,
                     ii.defaultness,
                     Some(&ii.attrs),
                 );
             }
-            ast::ImplItemKind::Const(ref ty, ref expr) => {
-                let rewrite = rewrite_static(
-                    "const",
-                    &ii.vis,
-                    ii.ident,
-                    ty,
-                    ast::Mutability::Immutable,
-                    Some(expr),
-                    self.block_indent,
-                    ii.span,
-                    &self.get_context(),
-                );
-                self.push_rewrite(ii.span, rewrite);
-            }
+            ast::ImplItemKind::Const(..) => self.visit_static(&StaticParts::from_impl_item(ii)),
             ast::ImplItemKind::Type(ref ty) => {
                 let rewrite = rewrite_associated_impl_type(
                     ii.ident,
@@ -605,6 +510,10 @@ pub fn from_codemap(parse_session: &'a ParseSess, config: &'a Config) -> FmtVisi
         }
     }
 
+    pub fn opt_snippet(&self, span: Span) -> Option<String> {
+        self.codemap.span_to_snippet(span).ok()
+    }
+
     pub fn snippet(&self, span: Span) -> String {
         match self.codemap.span_to_snippet(span) {
             Ok(s) => s,
@@ -790,6 +699,20 @@ pub fn format_separate_mod(&mut self, m: &ast::Mod, filemap: &codemap::FileMap)
         self.format_missing_with_indent(filemap.end_pos);
     }
 
+    pub fn skip_empty_lines(&mut self, end_pos: BytePos) {
+        while let Some(pos) = self.codemap
+            .opt_span_after(mk_sp(self.last_pos, end_pos), "\n")
+        {
+            if let Some(snippet) = self.opt_snippet(mk_sp(self.last_pos, pos)) {
+                if snippet.trim().is_empty() {
+                    self.last_pos = pos;
+                } else {
+                    return;
+                }
+            }
+        }
+    }
+
     pub fn get_context(&self) -> RewriteContext {
         RewriteContext {
             parse_session: self.parse_session,
@@ -886,106 +809,145 @@ fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
     }
 }
 
+/// Returns the first group of attributes that fills the given predicate.
+/// We consider two doc comments are in different group if they are separated by normal comments.
+fn take_while_with_pred<'a, P>(
+    context: &RewriteContext,
+    attrs: &'a [ast::Attribute],
+    pred: P,
+) -> &'a [ast::Attribute]
+where
+    P: Fn(&ast::Attribute) -> bool,
+{
+    let mut last_index = 0;
+    let mut iter = attrs.iter().enumerate().peekable();
+    while let Some((i, attr)) = iter.next() {
+        if !pred(attr) {
+            break;
+        }
+        if let Some(&(_, next_attr)) = iter.peek() {
+            // Extract comments between two attributes.
+            let span_between_attr = mk_sp(attr.span.hi(), next_attr.span.lo());
+            let snippet = context.snippet(span_between_attr);
+            if snippet.chars().filter(|c| *c == '\n').count() >= 2 || snippet.contains('/') {
+                break;
+            }
+        }
+        last_index = i;
+    }
+    if last_index == 0 {
+        &[]
+    } else {
+        &attrs[..last_index + 1]
+    }
+}
+
+fn rewrite_first_group_attrs(
+    context: &RewriteContext,
+    attrs: &[ast::Attribute],
+    shape: Shape,
+) -> Option<(usize, String)> {
+    if attrs.is_empty() {
+        return Some((0, String::new()));
+    }
+    // Rewrite doc comments
+    let sugared_docs = take_while_with_pred(context, attrs, |a| a.is_sugared_doc);
+    if !sugared_docs.is_empty() {
+        let snippet = sugared_docs
+            .iter()
+            .map(|a| context.snippet(a.span))
+            .collect::<Vec<_>>()
+            .join("\n");
+        return Some((
+            sugared_docs.len(),
+            rewrite_comment(&snippet, false, shape, context.config)?,
+        ));
+    }
+    // Rewrite `#[derive(..)]`s.
+    if context.config.merge_derives() {
+        let derives = take_while_with_pred(context, attrs, is_derive);
+        if !derives.is_empty() {
+            let mut derive_args = vec![];
+            for derive in derives {
+                derive_args.append(&mut get_derive_args(context, derive)?);
+            }
+            return Some((
+                derives.len(),
+                format_derive(context, &derive_args, shape)?,
+            ));
+        }
+    }
+    // Rewrite the first attribute.
+    Some((1, attrs[0].rewrite(context, shape)?))
+}
+
+fn has_newlines_before_after_comment(comment: &str) -> (&str, &str) {
+    // Look at before and after comment and see if there are any empty lines.
+    let comment_begin = comment.chars().position(|c| c == '/');
+    let len = comment_begin.unwrap_or_else(|| comment.len());
+    let mlb = comment.chars().take(len).filter(|c| *c == '\n').count() > 1;
+    let mla = if comment_begin.is_none() {
+        mlb
+    } else {
+        let comment_end = comment.chars().rev().position(|c| !c.is_whitespace());
+        let len = comment_end.unwrap();
+        comment
+            .chars()
+            .rev()
+            .take(len)
+            .filter(|c| *c == '\n')
+            .count() > 1
+    };
+    (if mlb { "\n" } else { "" }, if mla { "\n" } else { "" })
+}
+
 impl<'a> Rewrite for [ast::Attribute] {
     fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
         if self.is_empty() {
             return Some(String::new());
         }
-        let mut result = String::with_capacity(128);
-        let indent = shape.indent.to_string(context.config);
-
-        let mut derive_args = Vec::new();
-
-        let mut iter = self.iter().enumerate().peekable();
-        let mut insert_new_line = true;
-        let mut is_prev_sugared_doc = false;
-        while let Some((i, a)) = iter.next() {
-            let a_str = a.rewrite(context, shape)?;
-
-            // Write comments and blank lines between attributes.
-            if i > 0 {
-                let comment = context.snippet(mk_sp(self[i - 1].span.hi(), a.span.lo()));
-                // This particular horror show is to preserve line breaks in between doc
-                // comments. An alternative would be to force such line breaks to start
-                // with the usual doc comment token.
-                let (multi_line_before, multi_line_after) = if a.is_sugared_doc
-                    || is_prev_sugared_doc
-                {
-                    // Look at before and after comment and see if there are any empty lines.
-                    let comment_begin = comment.chars().position(|c| c == '/');
-                    let len = comment_begin.unwrap_or_else(|| comment.len());
-                    let mlb = comment.chars().take(len).filter(|c| *c == '\n').count() > 1;
-                    let mla = if comment_begin.is_none() {
-                        mlb
-                    } else {
-                        let comment_end = comment.chars().rev().position(|c| !c.is_whitespace());
-                        let len = comment_end.unwrap();
-                        comment
-                            .chars()
-                            .rev()
-                            .take(len)
-                            .filter(|c| *c == '\n')
-                            .count() > 1
-                    };
-                    (mlb, mla)
-                } else {
-                    (false, false)
-                };
-
-                let comment = recover_missing_comment_in_span(
-                    mk_sp(self[i - 1].span.hi(), a.span.lo()),
+        let (first_group_len, first_group_str) = rewrite_first_group_attrs(context, self, shape)?;
+        if self.len() == 1 || first_group_len == self.len() {
+            Some(first_group_str)
+        } else {
+            let rest_str = self[first_group_len..].rewrite(context, shape)?;
+            let missing_span = mk_sp(
+                self[first_group_len - 1].span.hi(),
+                self[first_group_len].span.lo(),
+            );
+            // Preserve an empty line before/after doc comments.
+            if self[0].is_sugared_doc || self[first_group_len].is_sugared_doc {
+                let snippet = context.snippet(missing_span);
+                let (mla, mlb) = has_newlines_before_after_comment(&snippet);
+                let comment = ::comment::recover_missing_comment_in_span(
+                    missing_span,
                     shape.with_max_width(context.config),
                     context,
                     0,
                 )?;
-
-                if !comment.is_empty() {
-                    if multi_line_before {
-                        result.push('\n');
-                    }
-                    result.push_str(&comment);
-                    result.push('\n');
-                    if multi_line_after {
-                        result.push('\n')
-                    }
-                } else if insert_new_line {
-                    result.push('\n');
-                    if multi_line_after {
-                        result.push('\n')
-                    }
-                }
-
-                if derive_args.is_empty() {
-                    result.push_str(&indent);
-                }
-
-                insert_new_line = true;
-            }
-
-            // Write the attribute itself.
-            if context.config.merge_derives() {
-                // If the attribute is `#[derive(...)]`, take the arguments.
-                if let Some(mut args) = get_derive_args(context, a) {
-                    derive_args.append(&mut args);
-                    match iter.peek() {
-                        // If the next attribute is `#[derive(...)]` as well, skip rewriting.
-                        Some(&(_, next_attr)) if is_derive(next_attr) => insert_new_line = false,
-                        // If not, rewrite the merged derives.
-                        _ => {
-                            result.push_str(&format_derive(context, &derive_args, shape)?);
-                            derive_args.clear();
-                        }
-                    }
+                let comment = if comment.is_empty() {
+                    format!("\n{}", mlb)
                 } else {
-                    result.push_str(&a_str);
-                }
+                    format!("{}{}\n{}", mla, comment, mlb)
+                };
+                Some(format!(
+                    "{}{}{}{}",
+                    first_group_str,
+                    comment,
+                    shape.indent.to_string(context.config),
+                    rest_str
+                ))
             } else {
-                result.push_str(&a_str);
+                combine_strs_with_missing_comments(
+                    context,
+                    &first_group_str,
+                    &rest_str,
+                    missing_span,
+                    shape,
+                    false,
+                )
             }
-
-            is_prev_sugared_doc = a.is_sugared_doc;
         }
-        Some(result)
     }
 }