]> git.lizzy.rs Git - rust.git/blob - src/functions.rs
Extract some methods for functions.
[rust.git] / src / functions.rs
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.
4 //
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.
10
11 use {FmtVisitor, ReturnIndent, make_indent, MAX_WIDTH, BraceStyle,
12      IDEAL_WIDTH, LEEWAY, FN_BRACE_STYLE, FN_RETURN_INDENT};
13 use lists::{write_list, ListFormatting, SeparatorTactic, ListTactic};
14 use syntax::{ast, abi};
15 use syntax::print::pprust;
16 use syntax::parse::token;
17
18 impl<'a> FmtVisitor<'a> {
19     // TODO extract methods for args and generics
20     pub fn rewrite_fn(&mut self,
21                       indent: usize,
22                       ident: ast::Ident,
23                       fd: &ast::FnDecl,
24                       explicit_self: Option<&ast::ExplicitSelf>,
25                       generics: &ast::Generics,
26                       unsafety: &ast::Unsafety,
27                       abi: &abi::Abi,
28                       vis: ast::Visibility)
29         -> String
30     {
31         // FIXME we'll lose any comments in between parts of the function decl, but anyone
32         // who comments there probably deserves what they get.
33
34         let where_clause = &generics.where_clause;
35         let newline_brace = self.newline_for_brace(where_clause);
36
37         let mut result = String::with_capacity(1024);
38         // Vis unsafety abi.
39         if vis == ast::Visibility::Public {
40             result.push_str("pub ");
41         }
42         if let &ast::Unsafety::Unsafe = unsafety {
43             result.push_str("unsafe ");
44         }
45         if *abi != abi::Rust {
46             result.push_str("extern ");
47             result.push_str(&abi.to_string());
48             result.push(' ');
49         }
50
51         // fn foo
52         result.push_str("fn ");
53         result.push_str(&token::get_ident(ident));
54
55         // Generics.
56         // FIXME convert bounds to where clauses where they get too big or if
57         // there is a where clause at all.
58         let lifetimes: &[_] = &generics.lifetimes;
59         let tys: &[_] = &generics.ty_params;
60         if lifetimes.len() + tys.len() > 0 {
61             let budget = MAX_WIDTH - indent - result.len() - 2;
62             // TODO might need to insert a newline if the generics are really long
63             result.push('<');
64
65             let lt_strs = lifetimes.iter().map(|l| self.rewrite_lifetime_def(l));
66             let ty_strs = tys.iter().map(|ty| self.rewrite_ty_param(ty));
67             let generics_strs: Vec<_> = lt_strs.chain(ty_strs).map(|s| (s, String::new())).collect();
68             let fmt = ListFormatting {
69                 tactic: ListTactic::HorizontalVertical,
70                 separator: ",",
71                 trailing_separator: SeparatorTactic::Never,
72                 indent: indent + result.len() + 1,
73                 h_width: budget,
74                 v_width: budget,
75             };
76             result.push_str(&write_list(&generics_strs, &fmt));
77
78             result.push('>');
79         }
80
81         let ret_str = self.rewrite_return(&fd.output);
82
83         // Args.
84         let args = &fd.inputs;
85
86         let mut budgets = None;
87
88         // Try keeping everything on the same line
89         if !result.contains("\n") {
90             // 3 = `() `, space is before ret_string
91             let mut used_space = indent + result.len() + 3 + ret_str.len();
92             if newline_brace {
93                 used_space += 2;
94             }
95             let one_line_budget = if used_space > MAX_WIDTH {
96                 0
97             } else {
98                 MAX_WIDTH - used_space
99             };
100
101             let used_space = indent + result.len() + 2;
102             let max_space = IDEAL_WIDTH + LEEWAY;
103             if used_space < max_space {
104                 budgets = Some((one_line_budget,
105                                 // 2 = `()`
106                                 max_space - used_space,
107                                 indent + result.len() + 1));
108             }
109         }
110
111         // Didn't work. we must force vertical layout and put args on a newline.
112         if let None = budgets {
113             result.push('\n');
114             result.push_str(&make_indent(indent + 4));
115             // 6 = new indent + `()`
116             let used_space = indent + 6;
117             let max_space = IDEAL_WIDTH + LEEWAY;
118             if used_space > max_space {
119                 // Whoops! bankrupt.
120                 // TODO take evasive action, perhaps kill the indent or something.
121             } else {
122                 // 5 = new indent + `(`
123                 budgets = Some((0, max_space - used_space, indent + 5));
124             }
125         }
126
127         let (one_line_budget, multi_line_budget, arg_indent) = budgets.unwrap();
128         result.push('(');
129
130         let fmt = ListFormatting {
131             tactic: ListTactic::HorizontalVertical,
132             separator: ",",
133             trailing_separator: SeparatorTactic::Never,
134             indent: arg_indent,
135             h_width: one_line_budget,
136             v_width: multi_line_budget,
137         };
138         // TODO dead spans
139         let mut arg_strs: Vec<_> = args.iter().map(|a| (self.rewrite_fn_input(a), String::new())).collect();
140         // Account for sugary self.
141         if let Some(explicit_self) = explicit_self {
142             match explicit_self.node {
143                 ast::ExplicitSelf_::SelfRegion(ref lt, ref m, _) => {
144                     let lt_str = match lt {
145                         &Some(ref l) => format!("{} ", pprust::lifetime_to_string(l)),
146                         &None => String::new(),
147                     };
148                     let mut_str = match m {
149                         &ast::Mutability::MutMutable => "mut ".to_string(),
150                         &ast::Mutability::MutImmutable => String::new(),
151                     };
152                     arg_strs[0].0 = format!("&{}{}self", lt_str, mut_str);
153                 }
154                 ast::ExplicitSelf_::SelfExplicit(ref ty, _) => {
155                     arg_strs[0].0 = format!("self: {}", pprust::ty_to_string(ty));
156                 }
157                 ast::ExplicitSelf_::SelfValue(_) => {
158                     arg_strs[0].0 = "self".to_string();
159                 }
160                 _ => {}
161             }
162         }
163         result.push_str(&write_list(&arg_strs, &fmt));
164
165         result.push(')');
166
167         // Where clause.
168         result.push_str(&self.rewrite_where_clause(where_clause, indent));
169
170         // Return type.
171         if ret_str.len() > 0 {
172             // If we've already gone multi-line, or the return type would push
173             // over the max width, then put the return type on a new line.
174             if result.contains("\n") ||
175                result.len() + indent + ret_str.len() > MAX_WIDTH {
176                 let indent = match FN_RETURN_INDENT {
177                     ReturnIndent::WithWhereClause => indent + 4,
178                     // TODO we might want to check that using the arg indent doesn't
179                     // blow our budget, and if it does, then fallback to the where
180                     // clause indent.
181                     ReturnIndent::WithArgs => arg_indent,
182                 };
183
184                 result.push('\n');
185                 result.push_str(&make_indent(indent));
186             } else {
187                 result.push(' ');
188             }
189             result.push_str(&ret_str);
190         }
191
192         // Prepare for the function body by possibly adding a newline and indent.
193         // FIXME we'll miss anything between the end of the signature and the start
194         // of the body, but we need more spans from the compiler to solve this.
195         if newline_brace {
196             result.push('\n');
197             result.push_str(&make_indent(self.block_indent));
198         } else {
199             result.push(' ');
200         }
201
202         result
203     }
204
205     fn newline_for_brace(&self, where_clause: &ast::WhereClause) -> bool {
206         match FN_BRACE_STYLE {
207             BraceStyle::AlwaysNextLine => true,
208             BraceStyle::SameLineWhere if where_clause.predicates.len() > 0 => true,
209             _ => false,
210         }
211     }
212
213     fn rewrite_where_clause(&self, where_clause: &ast::WhereClause, indent: usize) -> String {
214         let mut result = String::new();
215         if where_clause.predicates.len() == 0 {
216             return result;
217         }
218
219         result.push('\n');
220         result.push_str(&make_indent(indent + 4));
221         result.push_str("where ");
222
223         let budget = IDEAL_WIDTH + LEEWAY - indent - 10;
224         let fmt = ListFormatting {
225             tactic: ListTactic::Vertical,
226             separator: ",",
227             trailing_separator: SeparatorTactic::Always,
228             indent: indent + 10,
229             h_width: budget,
230             v_width: budget,
231         };
232         let where_strs: Vec<_> = where_clause.predicates.iter().map(|p| (self.rewrite_pred(p), String::new())).collect();
233         result.push_str(&write_list(&where_strs, &fmt));
234
235         result
236     }
237
238     fn rewrite_return(&self, ret: &ast::FunctionRetTy) -> String {
239         match *ret {
240             ast::FunctionRetTy::DefaultReturn(_) => String::new(),
241             ast::FunctionRetTy::NoReturn(_) => "-> !".to_string(),
242             ast::FunctionRetTy::Return(ref ty) => "-> ".to_string() + &pprust::ty_to_string(ty),
243         }        
244     }
245
246     // TODO we farm this out, but this could spill over the column limit, so we ought to handle it properly
247     fn rewrite_fn_input(&self, arg: &ast::Arg) -> String {
248         format!("{}: {}",
249                 pprust::pat_to_string(&arg.pat),
250                 pprust::ty_to_string(&arg.ty))
251     }
252 }