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.
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.
11 // Formatting top-level items - functions, structs, enums, traits, impls.
13 use {ReturnIndent, BraceStyle};
14 use utils::{format_visibility, make_indent};
15 use lists::{write_list, ListFormatting, SeparatorTactic, ListTactic};
16 use visitor::FmtVisitor;
17 use syntax::{ast, abi};
18 use syntax::codemap::{self, Span, BytePos};
19 use syntax::print::pprust;
20 use syntax::parse::token;
22 impl<'a> FmtVisitor<'a> {
23 pub fn rewrite_fn(&mut self,
27 explicit_self: Option<&ast::ExplicitSelf>,
28 generics: &ast::Generics,
29 unsafety: &ast::Unsafety,
30 constness: &ast::Constness,
36 let newline_brace = self.newline_for_brace(&generics.where_clause);
38 let mut result = self.rewrite_fn_base(indent,
50 // Prepare for the function body by possibly adding a newline and indent.
51 // FIXME we'll miss anything between the end of the signature and the start
52 // of the body, but we need more spans from the compiler to solve this.
55 result.push_str(&make_indent(indent));
63 pub fn rewrite_required_fn(&mut self,
70 // Drop semicolon or it will be interpreted as comment
71 let span_end = span.hi - BytePos(1);
73 let mut result = self.rewrite_fn_base(indent,
76 Some(&sig.explicit_self),
81 ast::Visibility::Inherited,
85 // Re-attach semicolon
91 fn rewrite_fn_base(&mut self,
95 explicit_self: Option<&ast::ExplicitSelf>,
96 generics: &ast::Generics,
97 unsafety: &ast::Unsafety,
98 constness: &ast::Constness,
100 vis: ast::Visibility,
105 // FIXME we'll lose any comments in between parts of the function decl, but anyone
106 // who comments there probably deserves what they get.
108 let where_clause = &generics.where_clause;
110 let mut result = String::with_capacity(1024);
112 result.push_str(format_visibility(vis));
114 if let &ast::Unsafety::Unsafe = unsafety {
115 result.push_str("unsafe ");
117 if let &ast::Constness::Const = constness {
118 result.push_str("const ");
120 if *abi != abi::Rust {
121 result.push_str("extern ");
122 result.push_str(&abi.to_string());
127 result.push_str("fn ");
128 result.push_str(&token::get_ident(ident));
131 let generics_indent = indent + result.len();
132 result.push_str(&self.rewrite_generics(generics,
134 span_for_return(&fd.output).lo));
136 let ret_str = self.rewrite_return(&fd.output);
139 let (one_line_budget, multi_line_budget, mut arg_indent) =
140 self.compute_budgets_for_args(&result, indent, ret_str.len(), newline_brace);
142 debug!("rewrite_fn: one_line_budget: {}, multi_line_budget: {}, arg_indent: {}",
143 one_line_budget, multi_line_budget, arg_indent);
145 // Check if vertical layout was forced by compute_budget_for_args.
146 if one_line_budget <= 0 {
147 if config!(fn_args_paren_newline) {
149 result.push_str(&make_indent(arg_indent));
150 arg_indent = arg_indent + 1; // extra space for `(`
153 result.push_str("(\n");
154 result.push_str(&make_indent(arg_indent));
160 result.push_str(&self.rewrite_args(&fd.inputs,
165 span_for_return(&fd.output)));
169 if ret_str.len() > 0 {
170 // If we've already gone multi-line, or the return type would push
171 // over the max width, then put the return type on a new line.
172 if result.contains("\n") ||
173 result.len() + indent + ret_str.len() > config!(max_width) {
174 let indent = match config!(fn_return_indent) {
175 ReturnIndent::WithWhereClause => indent + 4,
176 // TODO we might want to check that using the arg indent doesn't
177 // blow our budget, and if it does, then fallback to the where
183 result.push_str(&make_indent(indent));
187 result.push_str(&ret_str);
189 // Comment between return type and the end of the decl.
190 let snippet_lo = fd.output.span().hi;
191 if where_clause.predicates.len() == 0 {
192 let snippet_hi = span_end;
193 let snippet = self.snippet(codemap::mk_sp(snippet_lo, snippet_hi));
194 let snippet = snippet.trim();
195 if snippet.len() > 0 {
197 result.push_str(snippet);
200 // FIXME it would be nice to catch comments between the return type
201 // and the where clause, but we don't have a span for the where
207 result.push_str(&self.rewrite_where_clause(where_clause, indent, span_end));
212 fn rewrite_args(&self,
214 explicit_self: Option<&ast::ExplicitSelf>,
215 one_line_budget: usize,
216 multi_line_budget: usize,
221 let mut arg_item_strs: Vec<_> = args.iter().map(|a| self.rewrite_fn_input(a)).collect();
222 // Account for sugary self.
223 let mut min_args = 1;
224 if let Some(explicit_self) = explicit_self {
225 match explicit_self.node {
226 ast::ExplicitSelf_::SelfRegion(ref lt, ref m, _) => {
227 let lt_str = match lt {
228 &Some(ref l) => format!("{} ", pprust::lifetime_to_string(l)),
229 &None => String::new(),
231 let mut_str = match m {
232 &ast::Mutability::MutMutable => "mut ".to_owned(),
233 &ast::Mutability::MutImmutable => String::new(),
235 arg_item_strs[0] = format!("&{}{}self", lt_str, mut_str);
238 ast::ExplicitSelf_::SelfExplicit(ref ty, _) => {
239 arg_item_strs[0] = format!("self: {}", pprust::ty_to_string(ty));
241 ast::ExplicitSelf_::SelfValue(_) => {
242 assert!(args.len() >= 1, "&[ast::Arg] shouldn't be empty.");
244 // this hacky solution caused by absence of `Mutability` in `SelfValue`.
246 if let ast::Pat_::PatIdent(ast::BindingMode::BindByValue(mutability), _, _)
249 ast::Mutability::MutMutable => "mut ",
250 ast::Mutability::MutImmutable => "",
253 panic!("there is a bug or change in structure of AST, aborting.");
257 arg_item_strs[0] = format!("{}self", mut_str);
264 // Comments between args
265 let mut arg_comments = Vec::new();
267 arg_comments.push("".to_owned());
269 // TODO if there are no args, there might still be a comment, but without
270 // spans for the comment or parens, there is no chance of getting it right.
271 // You also don't get to put a comment on self, unless it is explicit.
272 if args.len() >= min_args {
273 arg_comments = self.make_comments_for_list(arg_comments,
274 args[min_args-1..].iter(),
277 |arg| arg.pat.span.lo,
278 |arg| arg.ty.span.hi,
282 debug!("comments: {:?}", arg_comments);
284 // If there are // comments, keep them multi-line.
285 let mut list_tactic = ListTactic::HorizontalVertical;
286 if arg_comments.iter().any(|c| c.contains("//")) {
287 list_tactic = ListTactic::Vertical;
290 assert_eq!(arg_item_strs.len(), arg_comments.len());
291 let arg_strs: Vec<_> = arg_item_strs.into_iter().zip(arg_comments.into_iter()).collect();
293 let fmt = ListFormatting {
296 trailing_separator: SeparatorTactic::Never,
298 h_width: one_line_budget,
299 v_width: multi_line_budget,
302 write_list(&arg_strs, &fmt)
305 // Gets comments in between items of a list.
306 fn make_comments_for_list<T, I, F1, F2>(&self,
313 next_span_start: BytePos)
315 where I: Iterator<Item=T>,
316 F1: Fn(&T) -> BytePos,
317 F2: Fn(&T) -> BytePos
319 let mut result = prefix;
321 let mut prev_end = get_hi(&it.next().unwrap());
323 let cur_start = get_lo(&item);
324 let snippet = self.snippet(codemap::mk_sp(prev_end, cur_start));
325 let mut snippet = snippet.trim();
326 let white_space: &[_] = &[' ', '\t'];
327 if snippet.starts_with(separator) {
328 snippet = snippet[separator.len()..].trim_matches(white_space);
329 } else if snippet.ends_with(separator) {
330 snippet = snippet[..snippet.len()-separator.len()].trim_matches(white_space);
332 result.push(snippet.to_owned());
333 prev_end = get_hi(&item);
335 // Get the last commment.
336 // FIXME If you thought the crap with the commas was ugly, just wait.
337 // This is awful. We're going to look from the last item span to the
338 // start of the return type span, then we drop everything after the
339 // first closing paren. Obviously, this will break if there is a
340 // closing paren in the comment.
341 // The fix is comments in the AST or a span for the closing paren.
342 let snippet = self.snippet(codemap::mk_sp(prev_end, next_span_start));
343 let snippet = snippet.trim();
344 let snippet = &snippet[..snippet.find(terminator)
345 .unwrap_or(snippet.find(separator).unwrap_or(snippet.len()))];
346 let snippet = snippet.trim();
347 result.push(snippet.to_owned());
352 fn compute_budgets_for_args(&self,
357 -> (usize, usize, usize)
359 let mut budgets = None;
361 // Try keeping everything on the same line
362 if !result.contains("\n") {
363 // 3 = `() `, space is before ret_string
364 let mut used_space = indent + result.len() + ret_str_len + 3;
368 let one_line_budget = if used_space > config!(max_width) {
371 config!(max_width) - used_space
375 let used_space = indent + result.len() + 2;
376 let max_space = config!(ideal_width) + config!(leeway);
377 debug!("compute_budgets_for_args: used_space: {}, max_space: {}",
378 used_space, max_space);
379 if used_space < max_space {
380 budgets = Some((one_line_budget,
381 max_space - used_space,
382 indent + result.len() + 1));
386 // Didn't work. we must force vertical layout and put args on a newline.
387 if let None = budgets {
388 let new_indent = indent + config!(tab_spaces);
389 let used_space = new_indent + 2; // account for `(` and `)`
390 let max_space = config!(ideal_width) + config!(leeway);
391 if used_space > max_space {
393 // TODO take evasive action, perhaps kill the indent or something.
395 budgets = Some((0, max_space - used_space, new_indent));
402 fn newline_for_brace(&self, where_clause: &ast::WhereClause) -> bool {
403 match config!(fn_brace_style) {
404 BraceStyle::AlwaysNextLine => true,
405 BraceStyle::SameLineWhere if where_clause.predicates.len() > 0 => true,
410 pub fn visit_enum(&mut self,
412 vis: ast::Visibility,
413 enum_def: &ast::EnumDef,
414 generics: &ast::Generics,
417 let header_str = self.format_header("enum", ident, vis);
418 self.changes.push_str_span(span, &header_str);
420 let enum_snippet = self.snippet(span);
421 // FIXME this will give incorrect results if there is a { in a comment.
422 let body_start = span.lo + BytePos(enum_snippet.find('{').unwrap() as u32 + 1);
423 let generics_str = self.format_generics(generics, body_start);
424 self.changes.push_str_span(span, &generics_str);
426 self.last_pos = body_start;
427 self.block_indent += config!(tab_spaces);
428 for (i, f) in enum_def.variants.iter().enumerate() {
429 let next_span_start: BytePos = if i == enum_def.variants.len() - 1 {
432 enum_def.variants[i + 1].span.lo
435 self.visit_variant(f, i == enum_def.variants.len() - 1, next_span_start);
437 self.block_indent -= config!(tab_spaces);
439 self.format_missing_with_indent(span.lo + BytePos(enum_snippet.rfind('}').unwrap() as u32));
440 self.changes.push_str_span(span, "}");
443 // Variant of an enum
444 fn visit_variant(&mut self,
445 field: &ast::Variant,
447 next_span_start: BytePos)
449 if self.visit_attrs(&field.node.attrs) {
453 if let ast::VariantKind::TupleVariantKind(ref types) = field.node.kind {
454 self.format_missing_with_indent(field.span.lo);
456 let vis = format_visibility(field.node.vis);
457 self.changes.push_str_span(field.span, vis);
458 let name = field.node.name.to_string();
459 self.changes.push_str_span(field.span, &name);
461 let mut result = String::new();
464 let comments = self.make_comments_for_list(Vec::new(),
465 types.iter().map(|arg| arg.ty.span),
472 let type_strings: Vec<_> = types.iter()
473 .map(|arg| pprust::ty_to_string(&arg.ty))
474 .zip(comments.into_iter())
479 let indent = self.block_indent
481 + field.node.name.to_string().len()
484 let comma_cost = if config!(enum_trailing_comma) { 1 } else { 0 };
485 let budget = config!(ideal_width) - indent - comma_cost - 1; // 1 = )
487 let fmt = ListFormatting {
488 tactic: ListTactic::HorizontalVertical,
490 trailing_separator: SeparatorTactic::Never,
495 result.push_str(&write_list(&type_strings, &fmt));
499 if let Some(ref expr) = field.node.disr_expr {
500 result.push_str(" = ");
501 let expr_snippet = self.snippet(expr.span);
502 result.push_str(&expr_snippet);
504 // Make sure we do not exceed column limit
506 assert!(config!(max_width) >= vis.len() + name.len() + expr_snippet.len() + 4,
507 "Enum variant exceeded column limit");
510 self.changes.push_str_span(field.span, &result);
512 if !last_field || config!(enum_trailing_comma) {
513 self.changes.push_str_span(field.span, ",");
517 // TODO: deal with struct-like variants
519 self.last_pos = field.span.hi + BytePos(1);
522 pub fn visit_struct(&mut self,
524 vis: ast::Visibility,
525 struct_def: &ast::StructDef,
526 generics: &ast::Generics,
529 let header_str = self.format_header("struct", ident, vis);
530 self.changes.push_str_span(span, &header_str);
532 if struct_def.fields.len() == 0 {
533 assert!(generics.where_clause.predicates.len() == 0,
534 "No-field struct with where clause?");
535 assert!(generics.lifetimes.len() == 0, "No-field struct with generics?");
536 assert!(generics.ty_params.len() == 0, "No-field struct with generics?");
538 self.changes.push_str_span(span, ";");
542 let generics_str = self.format_generics(generics, struct_def.fields[0].span.lo);
543 self.changes.push_str_span(span, &generics_str);
545 let struct_snippet = self.snippet(span);
546 // FIXME this will give incorrect results if there is a { in a comment.
547 self.last_pos = span.lo + BytePos(struct_snippet.find('{').unwrap() as u32 + 1);
549 self.block_indent += config!(tab_spaces);
550 for (i, f) in struct_def.fields.iter().enumerate() {
551 self.visit_field(f, i == struct_def.fields.len() - 1, span.lo, &struct_snippet);
553 self.block_indent -= config!(tab_spaces);
555 self.format_missing_with_indent(span.lo + BytePos(struct_snippet.rfind('}').unwrap() as u32));
556 self.changes.push_str_span(span, "}");
559 fn format_header(&self,
562 vis: ast::Visibility)
565 format!("{}{} {}", format_visibility(vis), item_name, &token::get_ident(ident))
568 fn format_generics(&self,
569 generics: &ast::Generics,
573 let mut result = self.rewrite_generics(generics, self.block_indent, span_end);
575 if generics.where_clause.predicates.len() > 0 {
576 result.push_str(&self.rewrite_where_clause(&generics.where_clause,
579 result.push_str(&make_indent(self.block_indent));
580 result.push_str("\n{");
583 result.push_str(" {");
590 fn visit_field(&mut self,
591 field: &ast::StructField,
593 // These two args are for missing spans hacks.
594 struct_start: BytePos,
595 struct_snippet: &str)
597 if self.visit_attrs(&field.node.attrs) {
600 self.format_missing_with_indent(field.span.lo);
602 let name = match field.node.kind {
603 ast::StructFieldKind::NamedField(ident, _) => Some(token::get_ident(ident)),
604 ast::StructFieldKind::UnnamedField(_) => None,
606 let vis = match field.node.kind {
607 ast::StructFieldKind::NamedField(_, vis) |
608 ast::StructFieldKind::UnnamedField(vis) => format_visibility(vis)
610 let typ = pprust::ty_to_string(&field.node.ty);
612 let mut field_str = match name {
614 let budget = config!(ideal_width) - self.block_indent;
615 // 3 is being conservative and assuming that there will be a trailing comma.
616 if self.block_indent + vis.len() + name.len() + typ.len() + 3 > budget {
617 format!("{}{}:\n{}{}",
620 &make_indent(self.block_indent + config!(tab_spaces)),
623 format!("{}{}: {}", vis, name, typ)
626 None => format!("{}{}", vis, typ),
628 if !last_field || config!(struct_trailing_comma) {
631 self.changes.push_str_span(field.span, &field_str);
633 // This hack makes sure we only add comments etc. after the comma, and
634 // makes sure we don't repeat any commas.
635 let hi = field.span.hi;
636 // FIXME a comma in a comment will break this hack.
637 let comma_pos = match struct_snippet[(hi.0 - struct_start.0) as usize..].find(',') {
641 self.last_pos = hi + BytePos(comma_pos as u32 + 1);
644 fn rewrite_generics(&self, generics: &ast::Generics, indent: usize, span_end: BytePos) -> String {
645 // FIXME convert bounds to where clauses where they get too big or if
646 // there is a where clause at all.
647 let mut result = String::new();
648 let lifetimes: &[_] = &generics.lifetimes;
649 let tys: &[_] = &generics.ty_params;
650 if lifetimes.len() + tys.len() == 0 {
654 let budget = config!(max_width) - indent - 2;
655 // TODO might need to insert a newline if the generics are really long
658 // Strings for the generics.
659 let lt_strs = lifetimes.iter().map(|l| self.rewrite_lifetime_def(l));
660 let ty_strs = tys.iter().map(|ty| self.rewrite_ty_param(ty));
662 // Extract comments between generics.
663 let lt_spans = lifetimes.iter().map(|l| {
664 let hi = if l.bounds.len() == 0 {
667 l.bounds[l.bounds.len() - 1].span.hi
669 codemap::mk_sp(l.lifetime.span.lo, hi)
671 let ty_spans = tys.iter().map(span_for_ty_param);
672 let comments = self.make_comments_for_list(Vec::new(),
673 lt_spans.chain(ty_spans),
680 // If there are // comments, keep them multi-line.
681 let mut list_tactic = ListTactic::HorizontalVertical;
682 if comments.iter().any(|c| c.contains("//")) {
683 list_tactic = ListTactic::Vertical;
686 let generics_strs: Vec<_> = lt_strs.chain(ty_strs).zip(comments.into_iter()).collect();
687 let fmt = ListFormatting {
690 trailing_separator: SeparatorTactic::Never,
695 result.push_str(&write_list(&generics_strs, &fmt));
702 fn rewrite_where_clause(&self,
703 where_clause: &ast::WhereClause,
708 let mut result = String::new();
709 if where_clause.predicates.len() == 0 {
714 result.push_str(&make_indent(indent + 4));
715 result.push_str("where ");
717 let comments = self.make_comments_for_list(Vec::new(),
718 where_clause.predicates.iter(),
721 |pred| span_for_where_pred(pred).lo,
722 |pred| span_for_where_pred(pred).hi,
725 let where_strs: Vec<_> = where_clause.predicates.iter()
726 .map(|p| (self.rewrite_pred(p)))
727 .zip(comments.into_iter())
730 let budget = config!(ideal_width) + config!(leeway) - indent - 10;
731 let fmt = ListFormatting {
732 tactic: ListTactic::Vertical,
734 trailing_separator: SeparatorTactic::Never,
739 result.push_str(&write_list(&where_strs, &fmt));
744 fn rewrite_return(&self, ret: &ast::FunctionRetTy) -> String {
746 ast::FunctionRetTy::DefaultReturn(_) => String::new(),
747 ast::FunctionRetTy::NoReturn(_) => "-> !".to_owned(),
748 ast::FunctionRetTy::Return(ref ty) => "-> ".to_owned() + &pprust::ty_to_string(ty),
752 // TODO we farm this out, but this could spill over the column limit, so we ought to handle it properly
753 fn rewrite_fn_input(&self, arg: &ast::Arg) -> String {
755 pprust::pat_to_string(&arg.pat),
756 pprust::ty_to_string(&arg.ty))
760 fn span_for_return(ret: &ast::FunctionRetTy) -> Span {
762 ast::FunctionRetTy::NoReturn(ref span) |
763 ast::FunctionRetTy::DefaultReturn(ref span) => span.clone(),
764 ast::FunctionRetTy::Return(ref ty) => ty.span,
768 fn span_for_ty_param(ty: &ast::TyParam) -> Span {
769 // Note that ty.span is the span for ty.ident, not the whole item.
771 if let Some(ref def) = ty.default {
772 return codemap::mk_sp(lo, def.span.hi);
774 if ty.bounds.len() == 0 {
777 let hi = match ty.bounds[ty.bounds.len() - 1] {
778 ast::TyParamBound::TraitTyParamBound(ref ptr, _) => ptr.span.hi,
779 ast::TyParamBound::RegionTyParamBound(ref l) => l.span.hi,
781 codemap::mk_sp(lo, hi)
784 fn span_for_where_pred(pred: &ast::WherePredicate) -> Span {
786 ast::WherePredicate::BoundPredicate(ref p) => p.span,
787 ast::WherePredicate::RegionPredicate(ref p) => p.span,
788 ast::WherePredicate::EqPredicate(ref p) => p.span,