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.
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.
11 //! Rewrite a list some items with overflow.
12 // FIXME: Replace `ToExpr` with some enum.
16 use syntax::parse::token::DelimToken;
17 use syntax::source_map::Span;
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};
24 use source_map::SpanUtils;
26 use utils::{count_newlines, extra_offset, first_line_width, last_line_width, mk_sp};
30 const SHORT_ITEM_THRESHOLD: usize = 10;
32 pub fn rewrite_with_parens<'a, 'b, T: 'a>(
33 context: &'a RewriteContext,
35 items: impl Iterator<Item = &'a T>,
38 item_max_width: usize,
39 force_separator_tactic: Option<SeparatorTactic>,
42 T: Rewrite + ToExpr + Spanned,
53 force_separator_tactic,
59 pub fn rewrite_with_angle_brackets<'a, T: 'a>(
60 context: &'a RewriteContext,
62 items: impl Iterator<Item = &'a T>,
67 T: Rewrite + ToExpr + Spanned,
77 context.config.max_width(),
84 pub fn rewrite_with_square_brackets<'a, T: 'a>(
85 context: &'a RewriteContext,
87 items: impl Iterator<Item = &'a T>,
90 force_separator_tactic: Option<SeparatorTactic>,
91 delim_token: Option<DelimToken>,
94 T: Rewrite + ToExpr + Spanned,
96 let (lhs, rhs) = match delim_token {
97 Some(DelimToken::Paren) => ("(", ")"),
98 Some(DelimToken::Brace) => ("{", "}"),
109 context.config.width_heuristics().array_width,
110 force_separator_tactic,
116 struct Context<'a, T: 'a> {
117 context: &'a RewriteContext<'a>,
120 prefix: &'static str,
121 suffix: &'static str,
122 one_line_shape: Shape,
125 item_max_width: usize,
126 one_line_width: usize,
127 force_separator_tactic: Option<SeparatorTactic>,
128 custom_delims: Option<(&'a str, &'a str)>,
131 impl<'a, T: 'a + Rewrite + ToExpr + Spanned> Context<'a, T> {
133 context: &'a RewriteContext,
134 items: impl Iterator<Item = &'a T>,
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);
146 let one_line_width = shape.width.saturating_sub(used_width + 2);
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);
156 items: items.collect(),
165 force_separator_tactic,
170 fn last_item(&self) -> Option<&&T> {
174 fn items_span(&self) -> Span {
178 .span_after(self.span, self.prefix);
179 mk_sp(span_lo, self.span.hi())
182 fn rewrite_last_item_with_overflow(
184 last_list_item: &mut ListItem,
186 ) -> Option<String> {
187 let last_item = self.last_item()?;
188 let rewrite = if let Some(expr) = last_item.to_expr() {
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
195 if closures::args_have_many_closure(&self.items) {
198 closures::rewrite_last_closure(self.context, expr, shape)
201 _ => expr.rewrite(self.context, shape),
204 last_item.rewrite(self.context, shape)
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;
216 fn default_tactic(&self, list_items: &[ListItem]) -> DefinitiveListTactic {
219 ListTactic::LimitedHorizontalVertical(self.item_max_width),
225 fn try_overflow_last_item(&self, list_items: &mut Vec<ListItem>) -> DefinitiveListTactic {
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);
232 // Replace the last item with its first line to see if it fits with
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);
243 let result = last_item_shape(
249 .and_then(|arg_shape| {
250 self.rewrite_last_item_with_overflow(
251 &mut list_items[self.items.len() - 1],
255 self.context.force_one_line_chain.replace(old_value);
261 let mut tactic = definitive_tactic(
263 ListTactic::LimitedHorizontalVertical(self.item_max_width),
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 =>
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 {
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'));
287 list_items[self.items.len() - 1].item = rw;
289 list_items[self.items.len() - 1].item = Some(overflowed.to_owned());
292 list_items[self.items.len() - 1].item = Some(overflowed.to_owned());
295 (true, DefinitiveListTactic::Horizontal, placeholder @ Some(..)) => {
296 list_items[self.items.len() - 1].item = placeholder;
298 _ if !self.items.is_empty() => {
299 list_items[self.items.len() - 1].item = self
302 .and_then(|last_item| last_item.rewrite(self.context, self.nested_shape));
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
313 tactic = DefinitiveListTactic::Horizontal;
315 tactic = self.default_tactic(list_items);
317 if tactic == DefinitiveListTactic::Vertical {
318 if let Some((all_simple, num_args_before)) =
319 maybe_get_args_offset(self.ident, &self.items)
321 let one_line = all_simple
322 && definitive_tactic(
323 &list_items[..num_args_before],
324 ListTactic::HorizontalVertical,
326 self.nested_shape.width,
327 ) == DefinitiveListTactic::Horizontal
328 && definitive_tactic(
329 &list_items[num_args_before + 1..],
330 ListTactic::HorizontalVertical,
332 self.nested_shape.width,
333 ) == DefinitiveListTactic::Horizontal;
336 tactic = DefinitiveListTactic::SpecialMacro(num_args_before);
338 } else if is_every_expr_simple(&self.items) && no_long_items(list_items) {
339 tactic = DefinitiveListTactic::Mixed;
350 fn rewrite_items(&self) -> Option<(bool, String)> {
351 let span = self.items_span();
352 let items = itemize_list(
353 self.context.snippet_provider,
357 |item| item.span().lo(),
358 |item| item.span().hi(),
359 |item| item.rewrite(self.context, self.nested_shape),
364 let mut list_items: Vec<_> = items.collect();
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 {
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
378 self.context.config.trailing_comma()
380 let ends_with_newline = match tactic {
381 DefinitiveListTactic::Vertical | DefinitiveListTactic::Mixed => {
382 self.context.use_block_indent()
387 let fmt = ListFormatting::new(self.nested_shape, self.context.config)
389 .trailing_separator(trailing_separator)
390 .ends_with_newline(ends_with_newline);
392 write_list(&list_items, &fmt)
393 .map(|items_str| (tactic == DefinitiveListTactic::Horizontal, items_str))
396 fn wrap_items(&self, items_str: &str, shape: Shape, is_extendable: bool) -> String {
398 width: shape.width.saturating_sub(last_line_width(self.ident)),
402 let (prefix, suffix) = match self.custom_delims {
403 Some((lhs, rhs)) => (lhs, rhs),
404 _ => (self.prefix, self.suffix),
408 let fits_one_line = items_str.len() + 2 <= shape.width;
409 let extend_width = if items_str.is_empty() {
412 first_line_width(items_str) + 1
414 let nested_indent_str = self
417 .to_string_with_newline(self.context.config);
418 let indent_str = shape
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(),
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)
431 result.push_str(items_str);
433 if !items_str.is_empty() {
434 result.push_str(&nested_indent_str);
435 result.push_str(items_str);
437 result.push_str(&indent_str);
439 result.push_str(suffix);
443 fn rewrite(&self, shape: Shape) -> Option<String> {
444 let (extendable, items_str) = self.rewrite_items()?;
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)
451 self.context.use_block.replace(true);
452 let result = self.rewrite(shape);
453 self.context.use_block.replace(false);
457 Some(self.wrap_items(&items_str, shape, extendable))
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())
468 fn can_be_overflowed<'a, T>(context: &RewriteContext, items: &[&T]) -> bool
470 T: Rewrite + Spanned + ToExpr + 'a,
474 .map_or(false, |x| x.can_be_overflowed(context, items.len()))
477 /// Returns a shape for the last argument which is going to be overflowed.
478 fn last_item_shape<T>(
482 args_max_width: usize,
485 T: Rewrite + Spanned + ToExpr,
487 let is_nested_call = lists
490 .and_then(|item| item.to_expr())
491 .map_or(false, is_nested_call);
492 if items.len() == 1 && !is_nested_call {
495 let offset = items.iter().rev().skip(1).fold(0, |acc, i| {
497 acc + 2 + i.inner_as_ref().len()
500 width: min(args_max_width, shape.width),
506 fn shape_from_indent_style(
507 context: &RewriteContext,
512 let (shape, overhead) = if context.use_block_indent() {
515 .block_indent(context.config.tab_spaces())
516 .with_max_width(context.config);
517 (shape, 1) // 1 = ","
519 (shape.visual_indent(offset), overhead)
522 width: shape.width.saturating_sub(overhead),
527 fn no_long_items(list: &[ListItem]) -> bool {
529 .all(|item| item.inner_as_ref().len() <= SHORT_ITEM_THRESHOLD)