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 use {ReturnIndent, MAX_WIDTH, BraceStyle,
12 IDEAL_WIDTH, LEEWAY, FN_BRACE_STYLE, FN_RETURN_INDENT};
13 use utils::make_indent;
14 use lists::{write_list, ListFormatting, SeparatorTactic, ListTactic};
15 use visitor::FmtVisitor;
16 use syntax::{ast, abi};
17 use syntax::codemap::{self, Span, BytePos};
18 use syntax::print::pprust;
19 use syntax::parse::token;
21 impl<'a> FmtVisitor<'a> {
22 pub fn rewrite_fn(&mut self,
26 explicit_self: Option<&ast::ExplicitSelf>,
27 generics: &ast::Generics,
28 unsafety: &ast::Unsafety,
31 next_span: Span) // next_span is a nasty hack, its a loose upper
32 // bound on any comments after the where clause.
35 // FIXME we'll lose any comments in between parts of the function decl, but anyone
36 // who comments there probably deserves what they get.
38 let where_clause = &generics.where_clause;
39 let newline_brace = self.newline_for_brace(where_clause);
41 let mut result = String::with_capacity(1024);
43 if vis == ast::Visibility::Public {
44 result.push_str("pub ");
46 if let &ast::Unsafety::Unsafe = unsafety {
47 result.push_str("unsafe ");
49 if *abi != abi::Rust {
50 result.push_str("extern ");
51 result.push_str(&abi.to_string());
56 result.push_str("fn ");
57 result.push_str(&token::get_ident(ident));
60 let generics_indent = indent + result.len();
61 result.push_str(&self.rewrite_generics(generics,
63 span_for_return(&fd.output)));
65 let ret_str = self.rewrite_return(&fd.output);
68 let (one_line_budget, multi_line_budget, arg_indent) =
69 self.compute_budgets_for_args(&mut result, indent, ret_str.len(), newline_brace);
71 debug!("rewrite_fn: one_line_budget: {}, multi_line_budget: {}, arg_indent: {}",
72 one_line_budget, multi_line_budget, arg_indent);
75 result.push_str(&self.rewrite_args(&fd.inputs,
80 span_for_return(&fd.output)));
84 if ret_str.len() > 0 {
85 // If we've already gone multi-line, or the return type would push
86 // over the max width, then put the return type on a new line.
87 if result.contains("\n") ||
88 result.len() + indent + ret_str.len() > MAX_WIDTH {
89 let indent = match FN_RETURN_INDENT {
90 ReturnIndent::WithWhereClause => indent + 4,
91 // TODO we might want to check that using the arg indent doesn't
92 // blow our budget, and if it does, then fallback to the where
98 result.push_str(&make_indent(indent));
102 result.push_str(&ret_str);
104 // Comment between return type and the end of the decl.
105 let snippet_lo = fd.output.span().hi;
106 if where_clause.predicates.len() == 0 {
107 let snippet_hi = next_span.lo;
108 let snippet = self.snippet(codemap::mk_sp(snippet_lo, snippet_hi));
109 let snippet = snippet.trim();
110 if snippet.len() > 0 {
112 result.push_str(snippet);
115 // FIXME it would be nice to catch comments between the return type
116 // and the where clause, but we don't have a span for the where
122 result.push_str(&self.rewrite_where_clause(where_clause, indent, next_span));
124 // Prepare for the function body by possibly adding a newline and indent.
125 // FIXME we'll miss anything between the end of the signature and the start
126 // of the body, but we need more spans from the compiler to solve this.
129 result.push_str(&make_indent(self.block_indent));
137 fn rewrite_args(&self,
139 explicit_self: Option<&ast::ExplicitSelf>,
140 one_line_budget: usize,
141 multi_line_budget: usize,
146 let mut arg_item_strs: Vec<_> = args.iter().map(|a| self.rewrite_fn_input(a)).collect();
147 // Account for sugary self.
148 let mut min_args = 1;
149 if let Some(explicit_self) = explicit_self {
150 match explicit_self.node {
151 ast::ExplicitSelf_::SelfRegion(ref lt, ref m, _) => {
152 let lt_str = match lt {
153 &Some(ref l) => format!("{} ", pprust::lifetime_to_string(l)),
154 &None => String::new(),
156 let mut_str = match m {
157 &ast::Mutability::MutMutable => "mut ".to_owned(),
158 &ast::Mutability::MutImmutable => String::new(),
160 arg_item_strs[0] = format!("&{}{}self", lt_str, mut_str);
163 ast::ExplicitSelf_::SelfExplicit(ref ty, _) => {
164 arg_item_strs[0] = format!("self: {}", pprust::ty_to_string(ty));
166 ast::ExplicitSelf_::SelfValue(_) => {
167 assert!(args.len() >= 1, "&[ast::Arg] shouldn't be empty.");
169 // this hacky solution caused by absence of `Mutability` in `SelfValue`.
171 if let ast::Pat_::PatIdent(ast::BindingMode::BindByValue(mutability), _, _)
174 ast::Mutability::MutMutable => "mut ",
175 ast::Mutability::MutImmutable => "",
178 panic!("there is a bug or change in structure of AST, aborting.");
182 arg_item_strs[0] = format!("{}self", mut_str);
189 // Comments between args
190 let mut arg_comments = Vec::new();
192 arg_comments.push("".to_owned());
194 // TODO if there are no args, there might still be a comment, but without
195 // spans for the comment or parens, there is no chance of getting it right.
196 // You also don't get to put a comment on self, unless it is explicit.
197 if args.len() >= min_args {
198 arg_comments = self.make_comments_for_list(arg_comments,
199 args[min_args-1..].iter(),
202 |arg| arg.pat.span.lo,
203 |arg| arg.ty.span.hi,
207 debug!("comments: {:?}", arg_comments);
209 // If there are // comments, keep them multi-line.
210 let mut list_tactic = ListTactic::HorizontalVertical;
211 if arg_comments.iter().any(|c| c.contains("//")) {
212 list_tactic = ListTactic::Vertical;
215 assert_eq!(arg_item_strs.len(), arg_comments.len());
216 let arg_strs: Vec<_> = arg_item_strs.into_iter().zip(arg_comments.into_iter()).collect();
218 let fmt = ListFormatting {
221 trailing_separator: SeparatorTactic::Never,
223 h_width: one_line_budget,
224 v_width: multi_line_budget,
227 write_list(&arg_strs, &fmt)
230 // Gets comments in between items of a list.
231 fn make_comments_for_list<T, I, F1, F2>(&self,
238 next_span_start: BytePos)
240 where I: Iterator<Item=T>,
241 F1: Fn(&T) -> BytePos,
242 F2: Fn(&T) -> BytePos
244 let mut result = prefix;
246 let mut prev_end = get_hi(&it.next().unwrap());
248 let cur_start = get_lo(&item);
249 let snippet = self.snippet(codemap::mk_sp(prev_end, cur_start));
250 let mut snippet = snippet.trim();
251 let white_space: &[_] = &[' ', '\t'];
252 if snippet.starts_with(separator) {
253 snippet = snippet[separator.len()..].trim_matches(white_space);
254 } else if snippet.ends_with(separator) {
255 snippet = snippet[..snippet.len()-separator.len()].trim_matches(white_space);
257 result.push(snippet.to_owned());
258 prev_end = get_hi(&item);
260 // Get the last commment.
261 // FIXME If you thought the crap with the commas was ugly, just wait.
262 // This is awful. We're going to look from the last item span to the
263 // start of the return type span, then we drop everything after the
264 // first closing paren. Obviously, this will break if there is a
265 // closing paren in the comment.
266 // The fix is comments in the AST or a span for the closing paren.
267 let snippet = self.snippet(codemap::mk_sp(prev_end, next_span_start));
268 let snippet = snippet.trim();
269 let snippet = &snippet[..snippet.find(terminator)
270 .unwrap_or(snippet.find(separator).unwrap_or(snippet.len()))];
271 let snippet = snippet.trim();
272 result.push(snippet.to_owned());
277 fn compute_budgets_for_args(&self,
282 -> (usize, usize, usize)
284 let mut budgets = None;
286 // Try keeping everything on the same line
287 if !result.contains("\n") {
288 // 3 = `() `, space is before ret_string
289 let mut used_space = indent + result.len() + ret_str_len + 3;
293 let one_line_budget = if used_space > MAX_WIDTH {
296 MAX_WIDTH - used_space
300 let used_space = indent + result.len() + 2;
301 let max_space = IDEAL_WIDTH + LEEWAY;
302 debug!("compute_budgets_for_args: used_space: {}, max_space: {}",
303 used_space, max_space);
304 if used_space < max_space {
305 budgets = Some((one_line_budget,
306 max_space - used_space,
307 indent + result.len() + 1));
311 // Didn't work. we must force vertical layout and put args on a newline.
312 if let None = budgets {
314 result.push_str(&make_indent(indent + 4));
315 // 6 = new indent + `()`
316 let used_space = indent + 6;
317 let max_space = IDEAL_WIDTH + LEEWAY;
318 if used_space > max_space {
320 // TODO take evasive action, perhaps kill the indent or something.
322 // 5 = new indent + `(`
323 budgets = Some((0, max_space - used_space, indent + 5));
330 fn newline_for_brace(&self, where_clause: &ast::WhereClause) -> bool {
331 match FN_BRACE_STYLE {
332 BraceStyle::AlwaysNextLine => true,
333 BraceStyle::SameLineWhere if where_clause.predicates.len() > 0 => true,
338 fn rewrite_generics(&self, generics: &ast::Generics, indent: usize, ret_span: Span) -> String {
339 // FIXME convert bounds to where clauses where they get too big or if
340 // there is a where clause at all.
341 let mut result = String::new();
342 let lifetimes: &[_] = &generics.lifetimes;
343 let tys: &[_] = &generics.ty_params;
344 if lifetimes.len() + tys.len() == 0 {
348 let budget = MAX_WIDTH - indent - 2;
349 // TODO might need to insert a newline if the generics are really long
352 // Strings for the generics.
353 let lt_strs = lifetimes.iter().map(|l| self.rewrite_lifetime_def(l));
354 let ty_strs = tys.iter().map(|ty| self.rewrite_ty_param(ty));
356 // Extract comments between generics.
357 let lt_spans = lifetimes.iter().map(|l| {
358 let hi = if l.bounds.len() == 0 {
361 l.bounds[l.bounds.len() - 1].span.hi
363 codemap::mk_sp(l.lifetime.span.lo, hi)
365 let ty_spans = tys.iter().map(span_for_ty_param);
366 let comments = self.make_comments_for_list(Vec::new(),
367 lt_spans.chain(ty_spans),
374 // If there are // comments, keep them multi-line.
375 let mut list_tactic = ListTactic::HorizontalVertical;
376 if comments.iter().any(|c| c.contains("//")) {
377 list_tactic = ListTactic::Vertical;
380 let generics_strs: Vec<_> = lt_strs.chain(ty_strs).zip(comments.into_iter()).collect();
381 let fmt = ListFormatting {
384 trailing_separator: SeparatorTactic::Never,
389 result.push_str(&write_list(&generics_strs, &fmt));
396 fn rewrite_where_clause(&self,
397 where_clause: &ast::WhereClause,
402 let mut result = String::new();
403 if where_clause.predicates.len() == 0 {
408 result.push_str(&make_indent(indent + 4));
409 result.push_str("where ");
411 let comments = self.make_comments_for_list(Vec::new(),
412 where_clause.predicates.iter(),
415 |pred| span_for_where_pred(pred).lo,
416 |pred| span_for_where_pred(pred).hi,
418 let where_strs: Vec<_> = where_clause.predicates.iter()
419 .map(|p| (self.rewrite_pred(p)))
420 .zip(comments.into_iter())
423 let budget = IDEAL_WIDTH + LEEWAY - indent - 10;
424 let fmt = ListFormatting {
425 tactic: ListTactic::Vertical,
427 trailing_separator: SeparatorTactic::Never,
432 result.push_str(&write_list(&where_strs, &fmt));
437 fn rewrite_return(&self, ret: &ast::FunctionRetTy) -> String {
439 ast::FunctionRetTy::DefaultReturn(_) => String::new(),
440 ast::FunctionRetTy::NoReturn(_) => "-> !".to_owned(),
441 ast::FunctionRetTy::Return(ref ty) => "-> ".to_owned() + &pprust::ty_to_string(ty),
445 // TODO we farm this out, but this could spill over the column limit, so we ought to handle it properly
446 fn rewrite_fn_input(&self, arg: &ast::Arg) -> String {
448 pprust::pat_to_string(&arg.pat),
449 pprust::ty_to_string(&arg.ty))
453 fn span_for_return(ret: &ast::FunctionRetTy) -> Span {
455 ast::FunctionRetTy::NoReturn(ref span) |
456 ast::FunctionRetTy::DefaultReturn(ref span) => span.clone(),
457 ast::FunctionRetTy::Return(ref ty) => ty.span,
461 fn span_for_ty_param(ty: &ast::TyParam) -> Span {
462 // Note that ty.span is the span for ty.ident, not the whole item.
464 if let Some(ref def) = ty.default {
465 return codemap::mk_sp(lo, def.span.hi);
467 if ty.bounds.len() == 0 {
470 let hi = match ty.bounds[ty.bounds.len() - 1] {
471 ast::TyParamBound::TraitTyParamBound(ref ptr, _) => ptr.span.hi,
472 ast::TyParamBound::RegionTyParamBound(ref l) => l.span.hi,
474 codemap::mk_sp(lo, hi)
477 fn span_for_where_pred(pred: &ast::WherePredicate) -> Span {
479 ast::WherePredicate::BoundPredicate(ref p) => p.span,
480 ast::WherePredicate::RegionPredicate(ref p) => p.span,
481 ast::WherePredicate::EqPredicate(ref p) => p.span,