]> git.lizzy.rs Git - rust.git/blob - src/overflow.rs
Factor out default_tactic and fix indentation
[rust.git] / src / overflow.rs
1 // Copyright 2018 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 //! Rewrite a list some items with overflow.
12 // FIXME: Replace `ToExpr` with some enum.
13
14 use config::lists::*;
15 use syntax::ast;
16 use syntax::codemap::Span;
17
18 use closures;
19 use codemap::SpanUtils;
20 use expr::{is_nested_call, maybe_get_args_offset, ToExpr};
21 use lists::{definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator};
22 use rewrite::{Rewrite, RewriteContext};
23 use shape::Shape;
24 use spanned::Spanned;
25 use utils::{count_newlines, extra_offset, first_line_width, last_line_width, mk_sp, paren_overhead};
26
27 use std::cmp::min;
28
29 pub fn rewrite_with_parens<T>(
30     context: &RewriteContext,
31     ident: &str,
32     items: &[&T],
33     shape: Shape,
34     span: Span,
35     item_max_width: usize,
36     force_separator_tactic: Option<SeparatorTactic>,
37 ) -> Option<String>
38 where
39     T: Rewrite + ToExpr + Spanned,
40 {
41     Context::new(
42         context,
43         items,
44         ident,
45         shape,
46         span,
47         "(",
48         ")",
49         item_max_width,
50         force_separator_tactic,
51     ).rewrite(shape)
52 }
53
54 pub fn rewrite_with_angle_brackets<T>(
55     context: &RewriteContext,
56     ident: &str,
57     items: &[&T],
58     shape: Shape,
59     span: Span,
60 ) -> Option<String>
61 where
62     T: Rewrite + ToExpr + Spanned,
63 {
64     Context::new(
65         context,
66         items,
67         ident,
68         shape,
69         span,
70         "<",
71         ">",
72         context.config.max_width(),
73         None,
74     ).rewrite(shape)
75 }
76
77 struct Context<'a, T: 'a> {
78     context: &'a RewriteContext<'a>,
79     items: &'a [&'a T],
80     ident: &'a str,
81     prefix: &'static str,
82     suffix: &'static str,
83     one_line_shape: Shape,
84     nested_shape: Shape,
85     span: Span,
86     item_max_width: usize,
87     one_line_width: usize,
88     force_separator_tactic: Option<SeparatorTactic>,
89 }
90
91 impl<'a, T: 'a + Rewrite + ToExpr + Spanned> Context<'a, T> {
92     pub fn new(
93         context: &'a RewriteContext,
94         items: &'a [&'a T],
95         ident: &'a str,
96         shape: Shape,
97         span: Span,
98         prefix: &'static str,
99         suffix: &'static str,
100         item_max_width: usize,
101         force_separator_tactic: Option<SeparatorTactic>,
102     ) -> Context<'a, T> {
103         // 2 = `( `, 1 = `(`
104         let paren_overhead = if context.config.spaces_within_parens_and_brackets() {
105             2
106         } else {
107             1
108         };
109         let used_width = extra_offset(ident, shape);
110         let one_line_width = shape
111             .width
112             .checked_sub(used_width + 2 * paren_overhead)
113             .unwrap_or(0);
114
115         // 1 = "(" or ")"
116         let one_line_shape = shape
117             .offset_left(last_line_width(ident) + 1)
118             .and_then(|shape| shape.sub_width(1))
119             .unwrap_or(Shape { width: 0, ..shape });
120         let nested_shape = shape_from_indent_style(
121             context,
122             shape,
123             used_width + 2 * paren_overhead,
124             used_width + paren_overhead,
125         );
126         Context {
127             context,
128             items,
129             ident,
130             one_line_shape,
131             nested_shape,
132             span,
133             prefix,
134             suffix,
135             item_max_width,
136             one_line_width,
137             force_separator_tactic,
138         }
139     }
140
141     fn last_item(&self) -> Option<&&T> {
142         self.items.last()
143     }
144
145     fn items_span(&self) -> Span {
146         let span_lo = self.context
147             .snippet_provider
148             .span_after(self.span, self.prefix);
149         mk_sp(span_lo, self.span.hi())
150     }
151
152     fn rewrite_last_item_with_overflow(
153         &self,
154         last_list_item: &mut ListItem,
155         shape: Shape,
156     ) -> Option<String> {
157         let last_item = self.last_item()?;
158         let rewrite = if let Some(expr) = last_item.to_expr() {
159             match expr.node {
160                 // When overflowing the closure which consists of a single control flow expression,
161                 // force to use block if its condition uses multi line.
162                 ast::ExprKind::Closure(..) => {
163                     // If the argument consists of multiple closures, we do not overflow
164                     // the last closure.
165                     if closures::args_have_many_closure(self.items) {
166                         None
167                     } else {
168                         closures::rewrite_last_closure(self.context, expr, shape)
169                     }
170                 }
171                 _ => expr.rewrite(self.context, shape),
172             }
173         } else {
174             last_item.rewrite(self.context, shape)
175         };
176
177         if let Some(rewrite) = rewrite {
178             let rewrite_first_line = Some(rewrite[..first_line_width(&rewrite)].to_owned());
179             last_list_item.item = rewrite_first_line;
180             Some(rewrite)
181         } else {
182             None
183         }
184     }
185
186     fn default_tactic(&self, list_items: &[ListItem]) -> DefinitiveListTactic {
187         definitive_tactic(
188             list_items,
189             ListTactic::LimitedHorizontalVertical(self.item_max_width),
190             Separator::Comma,
191             self.one_line_width,
192         )
193     }
194
195     fn try_overflow_last_item(&self, list_items: &mut Vec<ListItem>) -> DefinitiveListTactic {
196         // 1 = "("
197         let combine_arg_with_callee = self.items.len() == 1 && self.items[0].to_expr().is_some()
198             && self.ident.len() + 1 <= self.context.config.tab_spaces();
199         let overflow_last = combine_arg_with_callee || can_be_overflowed(self.context, self.items);
200
201         // Replace the last item with its first line to see if it fits with
202         // first arguments.
203         let placeholder = if overflow_last {
204             let old_value = *self.context.force_one_line_chain.borrow();
205             if !combine_arg_with_callee {
206                 if let Some(expr) = self.last_item().and_then(|item| item.to_expr()) {
207                     if let ast::ExprKind::MethodCall(..) = expr.node {
208                         self.context.force_one_line_chain.replace(true);
209                     }
210                 }
211             }
212             let result = last_item_shape(
213                 self.items,
214                 list_items,
215                 self.one_line_shape,
216                 self.item_max_width,
217             ).and_then(|arg_shape| {
218                 self.rewrite_last_item_with_overflow(
219                     &mut list_items[self.items.len() - 1],
220                     arg_shape,
221                 )
222             });
223             self.context.force_one_line_chain.replace(old_value);
224             result
225         } else {
226             None
227         };
228
229         let mut tactic = definitive_tactic(
230             &*list_items,
231             ListTactic::LimitedHorizontalVertical(self.item_max_width),
232             Separator::Comma,
233             self.one_line_width,
234         );
235
236         // Replace the stub with the full overflowing last argument if the rewrite
237         // succeeded and its first line fits with the other arguments.
238         match (overflow_last, tactic, placeholder) {
239             (true, DefinitiveListTactic::Horizontal, Some(ref overflowed))
240                 if self.items.len() == 1 =>
241             {
242                 // When we are rewriting a nested function call, we restrict the
243                 // bugdet for the inner function to avoid them being deeply nested.
244                 // However, when the inner function has a prefix or a suffix
245                 // (e.g. `foo() as u32`), this budget reduction may produce poorly
246                 // formatted code, where a prefix or a suffix being left on its own
247                 // line. Here we explicitlly check those cases.
248                 if count_newlines(overflowed) == 1 {
249                     let rw = self.items
250                         .last()
251                         .and_then(|last_item| last_item.rewrite(self.context, self.nested_shape));
252                     let no_newline = rw.as_ref().map_or(false, |s| !s.contains('\n'));
253                     if no_newline {
254                         list_items[self.items.len() - 1].item = rw;
255                     } else {
256                         list_items[self.items.len() - 1].item = Some(overflowed.to_owned());
257                     }
258                 } else {
259                     list_items[self.items.len() - 1].item = Some(overflowed.to_owned());
260                 }
261             }
262             (true, DefinitiveListTactic::Horizontal, placeholder @ Some(..)) => {
263                 list_items[self.items.len() - 1].item = placeholder;
264             }
265             _ if self.items.len() >= 1 => {
266                 list_items[self.items.len() - 1].item = self.items
267                     .last()
268                     .and_then(|last_item| last_item.rewrite(self.context, self.nested_shape));
269
270                 // Use horizontal layout for a function with a single argument as long as
271                 // everything fits in a single line.
272                 // `self.one_line_width == 0` means vertical layout is forced.
273                 if self.items.len() == 1 && self.one_line_width != 0 && !list_items[0].has_comment()
274                     && !list_items[0].inner_as_ref().contains('\n')
275                     && ::lists::total_item_width(&list_items[0]) <= self.one_line_width
276                 {
277                     tactic = DefinitiveListTactic::Horizontal;
278                 } else {
279                     tactic = self.default_tactic(list_items);
280
281                     if tactic == DefinitiveListTactic::Vertical {
282                         if let Some((all_simple, num_args_before)) =
283                             maybe_get_args_offset(self.ident, self.items)
284                         {
285                             let one_line = all_simple
286                                 && definitive_tactic(
287                                     &list_items[..num_args_before],
288                                     ListTactic::HorizontalVertical,
289                                     Separator::Comma,
290                                     self.nested_shape.width,
291                                 )
292                                     == DefinitiveListTactic::Horizontal
293                                 && definitive_tactic(
294                                     &list_items[num_args_before + 1..],
295                                     ListTactic::HorizontalVertical,
296                                     Separator::Comma,
297                                     self.nested_shape.width,
298                                 )
299                                     == DefinitiveListTactic::Horizontal;
300
301                             if one_line {
302                                 tactic = DefinitiveListTactic::SpecialMacro(num_args_before);
303                             };
304                         }
305                     }
306                 }
307             }
308             _ => (),
309         }
310
311         tactic
312     }
313
314     fn rewrite_items(&self) -> Option<(bool, String)> {
315         let span = self.items_span();
316         let items = itemize_list(
317             self.context.snippet_provider,
318             self.items.iter(),
319             self.suffix,
320             ",",
321             |item| item.span().lo(),
322             |item| item.span().hi(),
323             |item| item.rewrite(self.context, self.nested_shape),
324             span.lo(),
325             span.hi(),
326             true,
327         );
328         let mut list_items: Vec<_> = items.collect();
329
330         // Try letting the last argument overflow to the next line with block
331         // indentation. If its first line fits on one line with the other arguments,
332         // we format the function arguments horizontally.
333         let tactic = self.try_overflow_last_item(&mut list_items);
334
335         let fmt = ListFormatting {
336             tactic,
337             separator: ",",
338             trailing_separator: if let Some(tactic) = self.force_separator_tactic {
339                 tactic
340             } else if !self.context.use_block_indent() {
341                 SeparatorTactic::Never
342             } else {
343                 self.context.config.trailing_comma()
344             },
345             separator_place: SeparatorPlace::Back,
346             shape: self.nested_shape,
347             ends_with_newline: self.context.use_block_indent()
348                 && tactic == DefinitiveListTactic::Vertical,
349             preserve_newline: false,
350             config: self.context.config,
351         };
352
353         write_list(&list_items, &fmt)
354             .map(|items_str| (tactic == DefinitiveListTactic::Horizontal, items_str))
355     }
356
357     fn wrap_items(&self, items_str: &str, shape: Shape, is_extendable: bool) -> String {
358         let shape = Shape {
359             width: shape
360                 .width
361                 .checked_sub(last_line_width(self.ident))
362                 .unwrap_or(0),
363             ..shape
364         };
365
366         let paren_overhead = paren_overhead(self.context);
367         let fits_one_line = items_str.len() + paren_overhead <= shape.width;
368         let extend_width = if items_str.is_empty() {
369             paren_overhead
370         } else {
371             paren_overhead / 2
372         };
373         let nested_indent_str = self.nested_shape
374             .indent
375             .to_string_with_newline(self.context.config);
376         let indent_str = shape
377             .block()
378             .indent
379             .to_string_with_newline(self.context.config);
380         let mut result = String::with_capacity(
381             self.ident.len() + items_str.len() + 2 + indent_str.len() + nested_indent_str.len(),
382         );
383         result.push_str(self.ident);
384         result.push_str(self.prefix);
385         if !self.context.use_block_indent()
386             || (self.context.inside_macro() && !items_str.contains('\n') && fits_one_line)
387             || (is_extendable && extend_width <= shape.width)
388         {
389             if self.context.config.spaces_within_parens_and_brackets() && !items_str.is_empty() {
390                 result.push(' ');
391                 result.push_str(items_str);
392                 result.push(' ');
393             } else {
394                 result.push_str(items_str);
395             }
396         } else {
397             if !items_str.is_empty() {
398                 result.push_str(&nested_indent_str);
399                 result.push_str(items_str);
400             }
401             result.push_str(&indent_str);
402         }
403         result.push_str(self.suffix);
404         result
405     }
406
407     fn rewrite(&self, shape: Shape) -> Option<String> {
408         let (extendable, items_str) = self.rewrite_items()?;
409
410         // If we are using visual indent style and failed to format, retry with block indent.
411         if !self.context.use_block_indent() && need_block_indent(&items_str, self.nested_shape)
412             && !extendable
413         {
414             self.context.use_block.replace(true);
415             let result = self.rewrite(shape);
416             self.context.use_block.replace(false);
417             return result;
418         }
419
420         Some(self.wrap_items(&items_str, shape, extendable))
421     }
422 }
423
424 fn need_block_indent(s: &str, shape: Shape) -> bool {
425     s.lines().skip(1).any(|s| {
426         s.find(|c| !char::is_whitespace(c))
427             .map_or(false, |w| w + 1 < shape.indent.width())
428     })
429 }
430
431 fn can_be_overflowed<'a, T>(context: &RewriteContext, items: &[&T]) -> bool
432 where
433     T: Rewrite + Spanned + ToExpr + 'a,
434 {
435     items
436         .last()
437         .map_or(false, |x| x.can_be_overflowed(context, items.len()))
438 }
439
440 /// Returns a shape for the last argument which is going to be overflowed.
441 fn last_item_shape<T>(
442     lists: &[&T],
443     items: &[ListItem],
444     shape: Shape,
445     args_max_width: usize,
446 ) -> Option<Shape>
447 where
448     T: Rewrite + Spanned + ToExpr,
449 {
450     let is_nested_call = lists
451         .iter()
452         .next()
453         .and_then(|item| item.to_expr())
454         .map_or(false, is_nested_call);
455     if items.len() == 1 && !is_nested_call {
456         return Some(shape);
457     }
458     let offset = items.iter().rev().skip(1).fold(0, |acc, i| {
459         // 2 = ", "
460         acc + 2 + i.inner_as_ref().len()
461     });
462     Shape {
463         width: min(args_max_width, shape.width),
464         ..shape
465     }.offset_left(offset)
466 }
467
468 fn shape_from_indent_style(
469     context: &RewriteContext,
470     shape: Shape,
471     overhead: usize,
472     offset: usize,
473 ) -> Shape {
474     if context.use_block_indent() {
475         // 1 = ","
476         shape
477             .block()
478             .block_indent(context.config.tab_spaces())
479             .with_max_width(context.config)
480             .sub_width(1)
481             .unwrap()
482     } else {
483         let shape = shape.visual_indent(offset);
484         if let Some(shape) = shape.sub_width(overhead) {
485             shape
486         } else {
487             Shape { width: 0, ..shape }
488         }
489     }
490 }