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