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