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