]> git.lizzy.rs Git - rust.git/blob - src/overflow.rs
b5634def086a958a1c83c421b08b626061e36f14
[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 lists::{definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator};
21 use rewrite::{Rewrite, RewriteContext};
22 use expr::{is_nested_call, maybe_get_args_offset, ToExpr};
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_trailing_comma: bool,
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_trailing_comma,
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         false,
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_trailing_comma: bool,
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_trailing_comma: bool,
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_trailing_comma,
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 try_overflow_last_item(&self, list_items: &mut Vec<ListItem>) -> DefinitiveListTactic {
187         // 1 = "("
188         let combine_arg_with_callee = self.items.len() == 1 && self.items[0].to_expr().is_some()
189             && self.ident.len() + 1 <= self.context.config.tab_spaces();
190         let overflow_last = combine_arg_with_callee || can_be_overflowed(self.context, self.items);
191
192         // Replace the last item with its first line to see if it fits with
193         // first arguments.
194         let placeholder = if overflow_last {
195             let old_value = *self.context.force_one_line_chain.borrow();
196             if !combine_arg_with_callee {
197                 if let Some(expr) = self.last_item().and_then(|item| item.to_expr()) {
198                     if let ast::ExprKind::MethodCall(..) = expr.node {
199                         self.context.force_one_line_chain.replace(true);
200                     }
201                 }
202             }
203             let result = last_item_shape(
204                 self.items,
205                 list_items,
206                 self.one_line_shape,
207                 self.item_max_width,
208             ).and_then(|arg_shape| {
209                 self.rewrite_last_item_with_overflow(
210                     &mut list_items[self.items.len() - 1],
211                     arg_shape,
212                 )
213             });
214             self.context.force_one_line_chain.replace(old_value);
215             result
216         } else {
217             None
218         };
219
220         let mut tactic = definitive_tactic(
221             &*list_items,
222             ListTactic::LimitedHorizontalVertical(self.item_max_width),
223             Separator::Comma,
224             self.one_line_width,
225         );
226
227         // Replace the stub with the full overflowing last argument if the rewrite
228         // succeeded and its first line fits with the other arguments.
229         match (overflow_last, tactic, placeholder) {
230             (true, DefinitiveListTactic::Horizontal, Some(ref overflowed))
231                 if self.items.len() == 1 =>
232             {
233                 // When we are rewriting a nested function call, we restrict the
234                 // bugdet for the inner function to avoid them being deeply nested.
235                 // However, when the inner function has a prefix or a suffix
236                 // (e.g. `foo() as u32`), this budget reduction may produce poorly
237                 // formatted code, where a prefix or a suffix being left on its own
238                 // line. Here we explicitlly check those cases.
239                 if count_newlines(overflowed) == 1 {
240                     let rw = self.items
241                         .last()
242                         .and_then(|last_item| last_item.rewrite(self.context, self.nested_shape));
243                     let no_newline = rw.as_ref().map_or(false, |s| !s.contains('\n'));
244                     if no_newline {
245                         list_items[self.items.len() - 1].item = rw;
246                     } else {
247                         list_items[self.items.len() - 1].item = Some(overflowed.to_owned());
248                     }
249                 } else {
250                     list_items[self.items.len() - 1].item = Some(overflowed.to_owned());
251                 }
252             }
253             (true, DefinitiveListTactic::Horizontal, placeholder @ Some(..)) => {
254                 list_items[self.items.len() - 1].item = placeholder;
255             }
256             _ if self.items.len() >= 1 => {
257                 list_items[self.items.len() - 1].item = self.items
258                     .last()
259                     .and_then(|last_item| last_item.rewrite(self.context, self.nested_shape));
260
261                 let default_tactic = || {
262                     definitive_tactic(
263                         &*list_items,
264                         ListTactic::LimitedHorizontalVertical(self.item_max_width),
265                         Separator::Comma,
266                         self.one_line_width,
267                     )
268                 };
269
270                 // Use horizontal layout for a function with a single argument as long as
271                 // everything fits in a single line.
272                 if self.items.len() == 1
273                 && self.one_line_width != 0 // Vertical layout is forced.
274                 && !list_items[0].has_comment()
275                     && !list_items[0].inner_as_ref().contains('\n')
276                     && ::lists::total_item_width(&list_items[0]) <= self.one_line_width
277                 {
278                     tactic = DefinitiveListTactic::Horizontal;
279                 } else {
280                     tactic = default_tactic();
281
282                     if tactic == DefinitiveListTactic::Vertical {
283                         if let Some((all_simple, num_args_before)) =
284                             maybe_get_args_offset(self.ident, self.items)
285                         {
286                             let one_line = all_simple
287                                 && definitive_tactic(
288                                     &list_items[..num_args_before],
289                                     ListTactic::HorizontalVertical,
290                                     Separator::Comma,
291                                     self.nested_shape.width,
292                                 )
293                                     == DefinitiveListTactic::Horizontal
294                                 && definitive_tactic(
295                                     &list_items[num_args_before + 1..],
296                                     ListTactic::HorizontalVertical,
297                                     Separator::Comma,
298                                     self.nested_shape.width,
299                                 )
300                                     == DefinitiveListTactic::Horizontal;
301
302                             if one_line {
303                                 tactic = DefinitiveListTactic::SpecialMacro(num_args_before);
304                             };
305                         }
306                     }
307                 }
308             }
309             _ => (),
310         }
311
312         tactic
313     }
314
315     fn rewrite_items(&self) -> Option<(bool, String)> {
316         let span = self.items_span();
317         let items = itemize_list(
318             self.context.snippet_provider,
319             self.items.iter(),
320             self.suffix,
321             ",",
322             |item| item.span().lo(),
323             |item| item.span().hi(),
324             |item| item.rewrite(self.context, self.nested_shape),
325             span.lo(),
326             span.hi(),
327             true,
328         );
329         let mut list_items: Vec<_> = items.collect();
330
331         // Try letting the last argument overflow to the next line with block
332         // indentation. If its first line fits on one line with the other arguments,
333         // we format the function arguments horizontally.
334         let tactic = self.try_overflow_last_item(&mut list_items);
335
336         let fmt = ListFormatting {
337             tactic,
338             separator: ",",
339             trailing_separator: if self.force_trailing_comma {
340                 SeparatorTactic::Always
341             } else if self.context.inside_macro || !self.context.use_block_indent() {
342                 SeparatorTactic::Never
343             } else {
344                 self.context.config.trailing_comma()
345             },
346             separator_place: SeparatorPlace::Back,
347             shape: self.nested_shape,
348             ends_with_newline: self.context.use_block_indent()
349                 && tactic == DefinitiveListTactic::Vertical,
350             preserve_newline: false,
351             config: self.context.config,
352         };
353
354         write_list(&list_items, &fmt)
355             .map(|items_str| (tactic == DefinitiveListTactic::Horizontal, items_str))
356     }
357
358     fn wrap_items(&self, items_str: &str, shape: Shape, is_extendable: bool) -> String {
359         let shape = Shape {
360             width: shape
361                 .width
362                 .checked_sub(last_line_width(self.ident))
363                 .unwrap_or(0),
364             ..shape
365         };
366
367         let paren_overhead = paren_overhead(self.context);
368         let fits_one_line = items_str.len() + paren_overhead <= shape.width;
369         let extend_width = if items_str.is_empty() {
370             paren_overhead
371         } else {
372             paren_overhead / 2
373         };
374         let nested_indent_str = self.nested_shape
375             .indent
376             .to_string_with_newline(self.context.config);
377         let indent_str = shape
378             .block()
379             .indent
380             .to_string_with_newline(self.context.config);
381         let mut result = String::with_capacity(
382             self.ident.len() + items_str.len() + 2 + indent_str.len() + nested_indent_str.len(),
383         );
384         result.push_str(self.ident);
385         result.push_str(self.prefix);
386         if !self.context.use_block_indent()
387             || (self.context.inside_macro && !items_str.contains('\n') && fits_one_line)
388             || (is_extendable && extend_width <= shape.width)
389         {
390             if self.context.config.spaces_within_parens_and_brackets() && !items_str.is_empty() {
391                 result.push(' ');
392                 result.push_str(items_str);
393                 result.push(' ');
394             } else {
395                 result.push_str(items_str);
396             }
397         } else {
398             if !items_str.is_empty() {
399                 result.push_str(&nested_indent_str);
400                 result.push_str(items_str);
401             }
402             result.push_str(&indent_str);
403         }
404         result.push_str(self.suffix);
405         result
406     }
407
408     fn rewrite(&self, shape: Shape) -> Option<String> {
409         let (extendable, items_str) = self.rewrite_items()?;
410
411         // If we are using visual indent style and failed to format, retry with block indent.
412         if !self.context.use_block_indent() && need_block_indent(&items_str, self.nested_shape)
413             && !extendable
414         {
415             self.context.use_block.replace(true);
416             let result = self.rewrite(shape);
417             self.context.use_block.replace(false);
418             return result;
419         }
420
421         Some(self.wrap_items(&items_str, shape, extendable))
422     }
423 }
424
425 fn need_block_indent(s: &str, shape: Shape) -> bool {
426     s.lines().skip(1).any(|s| {
427         s.find(|c| !char::is_whitespace(c))
428             .map_or(false, |w| w + 1 < shape.indent.width())
429     })
430 }
431
432 fn can_be_overflowed<'a, T>(context: &RewriteContext, items: &[&T]) -> bool
433 where
434     T: Rewrite + Spanned + ToExpr + 'a,
435 {
436     items
437         .last()
438         .map_or(false, |x| x.can_be_overflowed(context, items.len()))
439 }
440
441 /// Returns a shape for the last argument which is going to be overflowed.
442 fn last_item_shape<T>(
443     lists: &[&T],
444     items: &[ListItem],
445     shape: Shape,
446     args_max_width: usize,
447 ) -> Option<Shape>
448 where
449     T: Rewrite + Spanned + ToExpr,
450 {
451     let is_nested_call = lists
452         .iter()
453         .next()
454         .and_then(|item| item.to_expr())
455         .map_or(false, is_nested_call);
456     if items.len() == 1 && !is_nested_call {
457         return Some(shape);
458     }
459     let offset = items.iter().rev().skip(1).fold(0, |acc, i| {
460         // 2 = ", "
461         acc + 2 + i.inner_as_ref().len()
462     });
463     Shape {
464         width: min(args_max_width, shape.width),
465         ..shape
466     }.offset_left(offset)
467 }
468
469 fn shape_from_indent_style(
470     context: &RewriteContext,
471     shape: Shape,
472     overhead: usize,
473     offset: usize,
474 ) -> Shape {
475     if context.use_block_indent() {
476         // 1 = ","
477         shape
478             .block()
479             .block_indent(context.config.tab_spaces())
480             .with_max_width(context.config)
481             .sub_width(1)
482             .unwrap()
483     } else {
484         let shape = shape.visual_indent(offset);
485         if let Some(shape) = shape.sub_width(overhead) {
486             shape
487         } else {
488             Shape { width: 0, ..shape }
489         }
490     }
491 }