]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | //! Rewrite a list some items with overflow. |
2 | ||
3 | use std::cmp::min; | |
4 | ||
5 | use itertools::Itertools; | |
04454e1e | 6 | use rustc_ast::token::Delimiter; |
f20569fa XL |
7 | use rustc_ast::{ast, ptr}; |
8 | use rustc_span::Span; | |
9 | ||
10 | use crate::closures; | |
11 | use crate::config::lists::*; | |
12 | use crate::config::Version; | |
13 | use crate::expr::{ | |
14 | can_be_overflowed_expr, is_every_expr_simple, is_method_call, is_nested_call, is_simple_expr, | |
15 | rewrite_cond, | |
16 | }; | |
17 | use crate::lists::{ | |
18 | definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator, | |
19 | }; | |
20 | use crate::macros::MacroArg; | |
21 | use crate::patterns::{can_be_overflowed_pat, TuplePatField}; | |
22 | use crate::rewrite::{Rewrite, RewriteContext}; | |
23 | use crate::shape::Shape; | |
24 | use crate::source_map::SpanUtils; | |
25 | use crate::spanned::Spanned; | |
26 | use crate::types::{can_be_overflowed_type, SegmentParam}; | |
27 | use crate::utils::{count_newlines, extra_offset, first_line_width, last_line_width, mk_sp}; | |
28 | ||
f20569fa XL |
29 | /// A list of `format!`-like macros, that take a long format string and a list of arguments to |
30 | /// format. | |
31 | /// | |
32 | /// Organized as a list of `(&str, usize)` tuples, giving the name of the macro and the number of | |
33 | /// arguments before the format string (none for `format!("format", ...)`, one for `assert!(result, | |
34 | /// "format", ...)`, two for `assert_eq!(left, right, "format", ...)`). | |
35 | const SPECIAL_MACRO_WHITELIST: &[(&str, usize)] = &[ | |
36 | // format! like macros | |
37 | // From the Rust Standard Library. | |
38 | ("eprint!", 0), | |
39 | ("eprintln!", 0), | |
40 | ("format!", 0), | |
41 | ("format_args!", 0), | |
42 | ("print!", 0), | |
43 | ("println!", 0), | |
44 | ("panic!", 0), | |
45 | ("unreachable!", 0), | |
46 | // From the `log` crate. | |
47 | ("debug!", 0), | |
48 | ("error!", 0), | |
49 | ("info!", 0), | |
50 | ("warn!", 0), | |
51 | // write! like macros | |
52 | ("assert!", 1), | |
53 | ("debug_assert!", 1), | |
54 | ("write!", 1), | |
55 | ("writeln!", 1), | |
56 | // assert_eq! like macros | |
57 | ("assert_eq!", 2), | |
58 | ("assert_ne!", 2), | |
59 | ("debug_assert_eq!", 2), | |
60 | ("debug_assert_ne!", 2), | |
61 | ]; | |
62 | ||
63 | const SPECIAL_ATTR_WHITELIST: &[(&str, usize)] = &[ | |
64 | // From the `failure` crate. | |
65 | ("fail", 0), | |
66 | ]; | |
67 | ||
68 | #[derive(Debug)] | |
69 | pub(crate) enum OverflowableItem<'a> { | |
70 | Expr(&'a ast::Expr), | |
71 | GenericParam(&'a ast::GenericParam), | |
72 | MacroArg(&'a MacroArg), | |
73 | NestedMetaItem(&'a ast::NestedMetaItem), | |
74 | SegmentParam(&'a SegmentParam<'a>), | |
cdc7bbd5 | 75 | FieldDef(&'a ast::FieldDef), |
f20569fa XL |
76 | TuplePatField(&'a TuplePatField<'a>), |
77 | Ty(&'a ast::Ty), | |
3c0e092e | 78 | Pat(&'a ast::Pat), |
f20569fa XL |
79 | } |
80 | ||
81 | impl<'a> Rewrite for OverflowableItem<'a> { | |
82 | fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> { | |
83 | self.map(|item| item.rewrite(context, shape)) | |
84 | } | |
85 | } | |
86 | ||
87 | impl<'a> Spanned for OverflowableItem<'a> { | |
88 | fn span(&self) -> Span { | |
89 | self.map(|item| item.span()) | |
90 | } | |
91 | } | |
92 | ||
93 | impl<'a> OverflowableItem<'a> { | |
94 | fn has_attrs(&self) -> bool { | |
95 | match self { | |
96 | OverflowableItem::Expr(ast::Expr { attrs, .. }) | |
97 | | OverflowableItem::GenericParam(ast::GenericParam { attrs, .. }) => !attrs.is_empty(), | |
cdc7bbd5 | 98 | OverflowableItem::FieldDef(ast::FieldDef { attrs, .. }) => !attrs.is_empty(), |
f20569fa XL |
99 | OverflowableItem::MacroArg(MacroArg::Expr(expr)) => !expr.attrs.is_empty(), |
100 | OverflowableItem::MacroArg(MacroArg::Item(item)) => !item.attrs.is_empty(), | |
101 | _ => false, | |
102 | } | |
103 | } | |
104 | ||
105 | pub(crate) fn map<F, T>(&self, f: F) -> T | |
106 | where | |
107 | F: Fn(&dyn IntoOverflowableItem<'a>) -> T, | |
108 | { | |
109 | match self { | |
110 | OverflowableItem::Expr(expr) => f(*expr), | |
111 | OverflowableItem::GenericParam(gp) => f(*gp), | |
112 | OverflowableItem::MacroArg(macro_arg) => f(*macro_arg), | |
113 | OverflowableItem::NestedMetaItem(nmi) => f(*nmi), | |
114 | OverflowableItem::SegmentParam(sp) => f(*sp), | |
cdc7bbd5 | 115 | OverflowableItem::FieldDef(sf) => f(*sf), |
f20569fa XL |
116 | OverflowableItem::TuplePatField(pat) => f(*pat), |
117 | OverflowableItem::Ty(ty) => f(*ty), | |
3c0e092e | 118 | OverflowableItem::Pat(pat) => f(*pat), |
f20569fa XL |
119 | } |
120 | } | |
121 | ||
122 | pub(crate) fn is_simple(&self) -> bool { | |
123 | match self { | |
124 | OverflowableItem::Expr(expr) => is_simple_expr(expr), | |
125 | OverflowableItem::MacroArg(MacroArg::Keyword(..)) => true, | |
126 | OverflowableItem::MacroArg(MacroArg::Expr(expr)) => is_simple_expr(expr), | |
127 | OverflowableItem::NestedMetaItem(nested_meta_item) => match nested_meta_item { | |
128 | ast::NestedMetaItem::Literal(..) => true, | |
94222f64 XL |
129 | ast::NestedMetaItem::MetaItem(ref meta_item) => { |
130 | matches!(meta_item.kind, ast::MetaItemKind::Word) | |
131 | } | |
f20569fa XL |
132 | }, |
133 | _ => false, | |
134 | } | |
135 | } | |
136 | ||
137 | pub(crate) fn is_expr(&self) -> bool { | |
94222f64 XL |
138 | matches!( |
139 | self, | |
140 | OverflowableItem::Expr(..) | OverflowableItem::MacroArg(MacroArg::Expr(..)) | |
141 | ) | |
f20569fa XL |
142 | } |
143 | ||
144 | pub(crate) fn is_nested_call(&self) -> bool { | |
145 | match self { | |
146 | OverflowableItem::Expr(expr) => is_nested_call(expr), | |
147 | OverflowableItem::MacroArg(MacroArg::Expr(expr)) => is_nested_call(expr), | |
148 | _ => false, | |
149 | } | |
150 | } | |
151 | ||
152 | pub(crate) fn to_expr(&self) -> Option<&'a ast::Expr> { | |
153 | match self { | |
154 | OverflowableItem::Expr(expr) => Some(expr), | |
94222f64 | 155 | OverflowableItem::MacroArg(MacroArg::Expr(ref expr)) => Some(expr), |
f20569fa XL |
156 | _ => None, |
157 | } | |
158 | } | |
159 | ||
160 | pub(crate) fn can_be_overflowed(&self, context: &RewriteContext<'_>, len: usize) -> bool { | |
161 | match self { | |
162 | OverflowableItem::Expr(expr) => can_be_overflowed_expr(context, expr, len), | |
163 | OverflowableItem::MacroArg(macro_arg) => match macro_arg { | |
164 | MacroArg::Expr(ref expr) => can_be_overflowed_expr(context, expr, len), | |
165 | MacroArg::Ty(ref ty) => can_be_overflowed_type(context, ty, len), | |
166 | MacroArg::Pat(..) => false, | |
167 | MacroArg::Item(..) => len == 1, | |
168 | MacroArg::Keyword(..) => false, | |
169 | }, | |
170 | OverflowableItem::NestedMetaItem(nested_meta_item) if len == 1 => { | |
171 | match nested_meta_item { | |
172 | ast::NestedMetaItem::Literal(..) => false, | |
173 | ast::NestedMetaItem::MetaItem(..) => true, | |
174 | } | |
175 | } | |
94222f64 XL |
176 | OverflowableItem::SegmentParam(SegmentParam::Type(ty)) => { |
177 | can_be_overflowed_type(context, ty, len) | |
178 | } | |
f20569fa XL |
179 | OverflowableItem::TuplePatField(pat) => can_be_overflowed_pat(context, pat, len), |
180 | OverflowableItem::Ty(ty) => can_be_overflowed_type(context, ty, len), | |
181 | _ => false, | |
182 | } | |
183 | } | |
184 | ||
185 | fn whitelist(&self) -> &'static [(&'static str, usize)] { | |
186 | match self { | |
187 | OverflowableItem::MacroArg(..) => SPECIAL_MACRO_WHITELIST, | |
188 | OverflowableItem::NestedMetaItem(..) => SPECIAL_ATTR_WHITELIST, | |
189 | _ => &[], | |
190 | } | |
191 | } | |
192 | } | |
193 | ||
194 | pub(crate) trait IntoOverflowableItem<'a>: Rewrite + Spanned { | |
195 | fn into_overflowable_item(&'a self) -> OverflowableItem<'a>; | |
196 | } | |
197 | ||
198 | impl<'a, T: 'a + IntoOverflowableItem<'a>> IntoOverflowableItem<'a> for ptr::P<T> { | |
199 | fn into_overflowable_item(&'a self) -> OverflowableItem<'a> { | |
200 | (**self).into_overflowable_item() | |
201 | } | |
202 | } | |
203 | ||
204 | macro_rules! impl_into_overflowable_item_for_ast_node { | |
205 | ($($ast_node:ident),*) => { | |
206 | $( | |
207 | impl<'a> IntoOverflowableItem<'a> for ast::$ast_node { | |
208 | fn into_overflowable_item(&'a self) -> OverflowableItem<'a> { | |
209 | OverflowableItem::$ast_node(self) | |
210 | } | |
211 | } | |
212 | )* | |
213 | } | |
214 | } | |
215 | ||
216 | macro_rules! impl_into_overflowable_item_for_rustfmt_types { | |
217 | ([$($ty:ident),*], [$($ty_with_lifetime:ident),*]) => { | |
218 | $( | |
219 | impl<'a> IntoOverflowableItem<'a> for $ty { | |
220 | fn into_overflowable_item(&'a self) -> OverflowableItem<'a> { | |
221 | OverflowableItem::$ty(self) | |
222 | } | |
223 | } | |
224 | )* | |
225 | $( | |
226 | impl<'a> IntoOverflowableItem<'a> for $ty_with_lifetime<'a> { | |
227 | fn into_overflowable_item(&'a self) -> OverflowableItem<'a> { | |
228 | OverflowableItem::$ty_with_lifetime(self) | |
229 | } | |
230 | } | |
231 | )* | |
232 | } | |
233 | } | |
234 | ||
3c0e092e | 235 | impl_into_overflowable_item_for_ast_node!(Expr, GenericParam, NestedMetaItem, FieldDef, Ty, Pat); |
f20569fa XL |
236 | impl_into_overflowable_item_for_rustfmt_types!([MacroArg], [SegmentParam, TuplePatField]); |
237 | ||
238 | pub(crate) fn into_overflowable_list<'a, T>( | |
239 | iter: impl Iterator<Item = &'a T>, | |
240 | ) -> impl Iterator<Item = OverflowableItem<'a>> | |
241 | where | |
242 | T: 'a + IntoOverflowableItem<'a>, | |
243 | { | |
244 | iter.map(|x| IntoOverflowableItem::into_overflowable_item(x)) | |
245 | } | |
246 | ||
247 | pub(crate) fn rewrite_with_parens<'a, T: 'a + IntoOverflowableItem<'a>>( | |
248 | context: &'a RewriteContext<'_>, | |
249 | ident: &'a str, | |
250 | items: impl Iterator<Item = &'a T>, | |
251 | shape: Shape, | |
252 | span: Span, | |
253 | item_max_width: usize, | |
254 | force_separator_tactic: Option<SeparatorTactic>, | |
255 | ) -> Option<String> { | |
256 | Context::new( | |
257 | context, | |
258 | items, | |
259 | ident, | |
260 | shape, | |
261 | span, | |
262 | "(", | |
263 | ")", | |
264 | item_max_width, | |
265 | force_separator_tactic, | |
266 | None, | |
267 | ) | |
268 | .rewrite(shape) | |
269 | } | |
270 | ||
271 | pub(crate) fn rewrite_with_angle_brackets<'a, T: 'a + IntoOverflowableItem<'a>>( | |
272 | context: &'a RewriteContext<'_>, | |
273 | ident: &'a str, | |
274 | items: impl Iterator<Item = &'a T>, | |
275 | shape: Shape, | |
276 | span: Span, | |
277 | ) -> Option<String> { | |
278 | Context::new( | |
279 | context, | |
280 | items, | |
281 | ident, | |
282 | shape, | |
283 | span, | |
284 | "<", | |
285 | ">", | |
286 | context.config.max_width(), | |
287 | None, | |
288 | None, | |
289 | ) | |
290 | .rewrite(shape) | |
291 | } | |
292 | ||
293 | pub(crate) fn rewrite_with_square_brackets<'a, T: 'a + IntoOverflowableItem<'a>>( | |
294 | context: &'a RewriteContext<'_>, | |
295 | name: &'a str, | |
296 | items: impl Iterator<Item = &'a T>, | |
297 | shape: Shape, | |
298 | span: Span, | |
299 | force_separator_tactic: Option<SeparatorTactic>, | |
04454e1e | 300 | delim_token: Option<Delimiter>, |
f20569fa XL |
301 | ) -> Option<String> { |
302 | let (lhs, rhs) = match delim_token { | |
04454e1e FG |
303 | Some(Delimiter::Parenthesis) => ("(", ")"), |
304 | Some(Delimiter::Brace) => ("{", "}"), | |
f20569fa XL |
305 | _ => ("[", "]"), |
306 | }; | |
307 | Context::new( | |
308 | context, | |
309 | items, | |
310 | name, | |
311 | shape, | |
312 | span, | |
313 | lhs, | |
314 | rhs, | |
cdc7bbd5 | 315 | context.config.array_width(), |
f20569fa XL |
316 | force_separator_tactic, |
317 | Some(("[", "]")), | |
318 | ) | |
319 | .rewrite(shape) | |
320 | } | |
321 | ||
322 | struct Context<'a> { | |
323 | context: &'a RewriteContext<'a>, | |
324 | items: Vec<OverflowableItem<'a>>, | |
325 | ident: &'a str, | |
326 | prefix: &'static str, | |
327 | suffix: &'static str, | |
328 | one_line_shape: Shape, | |
329 | nested_shape: Shape, | |
330 | span: Span, | |
331 | item_max_width: usize, | |
332 | one_line_width: usize, | |
333 | force_separator_tactic: Option<SeparatorTactic>, | |
334 | custom_delims: Option<(&'a str, &'a str)>, | |
335 | } | |
336 | ||
337 | impl<'a> Context<'a> { | |
338 | fn new<T: 'a + IntoOverflowableItem<'a>>( | |
339 | context: &'a RewriteContext<'_>, | |
340 | items: impl Iterator<Item = &'a T>, | |
341 | ident: &'a str, | |
342 | shape: Shape, | |
343 | span: Span, | |
344 | prefix: &'static str, | |
345 | suffix: &'static str, | |
346 | item_max_width: usize, | |
347 | force_separator_tactic: Option<SeparatorTactic>, | |
348 | custom_delims: Option<(&'a str, &'a str)>, | |
349 | ) -> Context<'a> { | |
350 | let used_width = extra_offset(ident, shape); | |
351 | // 1 = `()` | |
352 | let one_line_width = shape.width.saturating_sub(used_width + 2); | |
353 | ||
354 | // 1 = "(" or ")" | |
355 | let one_line_shape = shape | |
356 | .offset_left(last_line_width(ident) + 1) | |
357 | .and_then(|shape| shape.sub_width(1)) | |
358 | .unwrap_or(Shape { width: 0, ..shape }); | |
359 | let nested_shape = shape_from_indent_style(context, shape, used_width + 2, used_width + 1); | |
360 | Context { | |
361 | context, | |
362 | items: into_overflowable_list(items).collect(), | |
363 | ident, | |
364 | one_line_shape, | |
365 | nested_shape, | |
366 | span, | |
367 | prefix, | |
368 | suffix, | |
369 | item_max_width, | |
370 | one_line_width, | |
371 | force_separator_tactic, | |
372 | custom_delims, | |
373 | } | |
374 | } | |
375 | ||
376 | fn last_item(&self) -> Option<&OverflowableItem<'_>> { | |
377 | self.items.last() | |
378 | } | |
379 | ||
380 | fn items_span(&self) -> Span { | |
381 | let span_lo = self | |
382 | .context | |
383 | .snippet_provider | |
384 | .span_after(self.span, self.prefix); | |
385 | mk_sp(span_lo, self.span.hi()) | |
386 | } | |
387 | ||
388 | fn rewrite_last_item_with_overflow( | |
389 | &self, | |
390 | last_list_item: &mut ListItem, | |
391 | shape: Shape, | |
392 | ) -> Option<String> { | |
393 | let last_item = self.last_item()?; | |
394 | let rewrite = match last_item { | |
3c0e092e | 395 | OverflowableItem::Expr(expr) => { |
f20569fa XL |
396 | match expr.kind { |
397 | // When overflowing the closure which consists of a single control flow | |
398 | // expression, force to use block if its condition uses multi line. | |
399 | ast::ExprKind::Closure(..) => { | |
400 | // If the argument consists of multiple closures, we do not overflow | |
401 | // the last closure. | |
402 | if closures::args_have_many_closure(&self.items) { | |
403 | None | |
404 | } else { | |
405 | closures::rewrite_last_closure(self.context, expr, shape) | |
406 | } | |
407 | } | |
408 | ||
409 | // When overflowing the expressions which consists of a control flow | |
410 | // expression, avoid condition to use multi line. | |
411 | ast::ExprKind::If(..) | |
412 | | ast::ExprKind::ForLoop(..) | |
413 | | ast::ExprKind::Loop(..) | |
414 | | ast::ExprKind::While(..) | |
415 | | ast::ExprKind::Match(..) => { | |
416 | let multi_line = rewrite_cond(self.context, expr, shape) | |
417 | .map_or(false, |cond| cond.contains('\n')); | |
418 | ||
419 | if multi_line { | |
420 | None | |
421 | } else { | |
422 | expr.rewrite(self.context, shape) | |
423 | } | |
424 | } | |
425 | ||
426 | _ => expr.rewrite(self.context, shape), | |
427 | } | |
428 | } | |
429 | item => item.rewrite(self.context, shape), | |
430 | }; | |
431 | ||
432 | if let Some(rewrite) = rewrite { | |
433 | // splitn(2, *).next().unwrap() is always safe. | |
434 | let rewrite_first_line = Some(rewrite.splitn(2, '\n').next().unwrap().to_owned()); | |
435 | last_list_item.item = rewrite_first_line; | |
436 | Some(rewrite) | |
437 | } else { | |
438 | None | |
439 | } | |
440 | } | |
441 | ||
442 | fn default_tactic(&self, list_items: &[ListItem]) -> DefinitiveListTactic { | |
443 | definitive_tactic( | |
444 | list_items, | |
445 | ListTactic::LimitedHorizontalVertical(self.item_max_width), | |
446 | Separator::Comma, | |
447 | self.one_line_width, | |
448 | ) | |
449 | } | |
450 | ||
451 | fn try_overflow_last_item(&self, list_items: &mut Vec<ListItem>) -> DefinitiveListTactic { | |
452 | // 1 = "(" | |
453 | let combine_arg_with_callee = self.items.len() == 1 | |
454 | && self.items[0].is_expr() | |
455 | && !self.items[0].has_attrs() | |
456 | && self.ident.len() < self.context.config.tab_spaces(); | |
457 | let overflow_last = combine_arg_with_callee || can_be_overflowed(self.context, &self.items); | |
458 | ||
459 | // Replace the last item with its first line to see if it fits with | |
460 | // first arguments. | |
461 | let placeholder = if overflow_last { | |
462 | let old_value = self.context.force_one_line_chain.get(); | |
463 | match self.last_item() { | |
464 | Some(OverflowableItem::Expr(expr)) | |
465 | if !combine_arg_with_callee && is_method_call(expr) => | |
466 | { | |
467 | self.context.force_one_line_chain.replace(true); | |
468 | } | |
469 | Some(OverflowableItem::MacroArg(MacroArg::Expr(expr))) | |
470 | if !combine_arg_with_callee | |
471 | && is_method_call(expr) | |
472 | && self.context.config.version() == Version::Two => | |
473 | { | |
474 | self.context.force_one_line_chain.replace(true); | |
475 | } | |
476 | _ => (), | |
477 | } | |
478 | let result = last_item_shape( | |
479 | &self.items, | |
480 | list_items, | |
481 | self.one_line_shape, | |
482 | self.item_max_width, | |
483 | ) | |
484 | .and_then(|arg_shape| { | |
485 | self.rewrite_last_item_with_overflow( | |
486 | &mut list_items[self.items.len() - 1], | |
487 | arg_shape, | |
488 | ) | |
489 | }); | |
490 | self.context.force_one_line_chain.replace(old_value); | |
491 | result | |
492 | } else { | |
493 | None | |
494 | }; | |
495 | ||
496 | let mut tactic = definitive_tactic( | |
497 | &*list_items, | |
498 | ListTactic::LimitedHorizontalVertical(self.item_max_width), | |
499 | Separator::Comma, | |
500 | self.one_line_width, | |
501 | ); | |
502 | ||
503 | // Replace the stub with the full overflowing last argument if the rewrite | |
504 | // succeeded and its first line fits with the other arguments. | |
505 | match (overflow_last, tactic, placeholder) { | |
506 | (true, DefinitiveListTactic::Horizontal, Some(ref overflowed)) | |
507 | if self.items.len() == 1 => | |
508 | { | |
509 | // When we are rewriting a nested function call, we restrict the | |
510 | // budget for the inner function to avoid them being deeply nested. | |
511 | // However, when the inner function has a prefix or a suffix | |
512 | // (e.g., `foo() as u32`), this budget reduction may produce poorly | |
513 | // formatted code, where a prefix or a suffix being left on its own | |
514 | // line. Here we explicitlly check those cases. | |
515 | if count_newlines(overflowed) == 1 { | |
516 | let rw = self | |
517 | .items | |
518 | .last() | |
519 | .and_then(|last_item| last_item.rewrite(self.context, self.nested_shape)); | |
520 | let no_newline = rw.as_ref().map_or(false, |s| !s.contains('\n')); | |
521 | if no_newline { | |
522 | list_items[self.items.len() - 1].item = rw; | |
523 | } else { | |
524 | list_items[self.items.len() - 1].item = Some(overflowed.to_owned()); | |
525 | } | |
526 | } else { | |
527 | list_items[self.items.len() - 1].item = Some(overflowed.to_owned()); | |
528 | } | |
529 | } | |
530 | (true, DefinitiveListTactic::Horizontal, placeholder @ Some(..)) => { | |
531 | list_items[self.items.len() - 1].item = placeholder; | |
532 | } | |
533 | _ if !self.items.is_empty() => { | |
534 | list_items[self.items.len() - 1].item = self | |
535 | .items | |
536 | .last() | |
537 | .and_then(|last_item| last_item.rewrite(self.context, self.nested_shape)); | |
538 | ||
539 | // Use horizontal layout for a function with a single argument as long as | |
540 | // everything fits in a single line. | |
541 | // `self.one_line_width == 0` means vertical layout is forced. | |
542 | if self.items.len() == 1 | |
543 | && self.one_line_width != 0 | |
544 | && !list_items[0].has_comment() | |
545 | && !list_items[0].inner_as_ref().contains('\n') | |
546 | && crate::lists::total_item_width(&list_items[0]) <= self.one_line_width | |
547 | { | |
548 | tactic = DefinitiveListTactic::Horizontal; | |
549 | } else { | |
550 | tactic = self.default_tactic(list_items); | |
551 | ||
552 | if tactic == DefinitiveListTactic::Vertical { | |
553 | if let Some((all_simple, num_args_before)) = | |
554 | maybe_get_args_offset(self.ident, &self.items) | |
555 | { | |
556 | let one_line = all_simple | |
557 | && definitive_tactic( | |
558 | &list_items[..num_args_before], | |
559 | ListTactic::HorizontalVertical, | |
560 | Separator::Comma, | |
561 | self.nested_shape.width, | |
562 | ) == DefinitiveListTactic::Horizontal | |
563 | && definitive_tactic( | |
564 | &list_items[num_args_before + 1..], | |
565 | ListTactic::HorizontalVertical, | |
566 | Separator::Comma, | |
567 | self.nested_shape.width, | |
568 | ) == DefinitiveListTactic::Horizontal; | |
569 | ||
570 | if one_line { | |
571 | tactic = DefinitiveListTactic::SpecialMacro(num_args_before); | |
572 | }; | |
5e7ed085 FG |
573 | } else if is_every_expr_simple(&self.items) |
574 | && no_long_items( | |
575 | list_items, | |
576 | self.context.config.short_array_element_width_threshold(), | |
577 | ) | |
578 | { | |
f20569fa XL |
579 | tactic = DefinitiveListTactic::Mixed; |
580 | } | |
581 | } | |
582 | } | |
583 | } | |
584 | _ => (), | |
585 | } | |
586 | ||
587 | tactic | |
588 | } | |
589 | ||
590 | fn rewrite_items(&self) -> Option<(bool, String)> { | |
591 | let span = self.items_span(); | |
592 | let items = itemize_list( | |
593 | self.context.snippet_provider, | |
594 | self.items.iter(), | |
595 | self.suffix, | |
596 | ",", | |
597 | |item| item.span().lo(), | |
598 | |item| item.span().hi(), | |
599 | |item| item.rewrite(self.context, self.nested_shape), | |
600 | span.lo(), | |
601 | span.hi(), | |
602 | true, | |
603 | ); | |
604 | let mut list_items: Vec<_> = items.collect(); | |
605 | ||
606 | // Try letting the last argument overflow to the next line with block | |
607 | // indentation. If its first line fits on one line with the other arguments, | |
608 | // we format the function arguments horizontally. | |
609 | let tactic = self.try_overflow_last_item(&mut list_items); | |
610 | let trailing_separator = if let Some(tactic) = self.force_separator_tactic { | |
611 | tactic | |
612 | } else if !self.context.use_block_indent() { | |
613 | SeparatorTactic::Never | |
614 | } else { | |
615 | self.context.config.trailing_comma() | |
616 | }; | |
617 | let ends_with_newline = match tactic { | |
618 | DefinitiveListTactic::Vertical | DefinitiveListTactic::Mixed => { | |
619 | self.context.use_block_indent() | |
620 | } | |
621 | _ => false, | |
622 | }; | |
623 | ||
624 | let fmt = ListFormatting::new(self.nested_shape, self.context.config) | |
625 | .tactic(tactic) | |
626 | .trailing_separator(trailing_separator) | |
627 | .ends_with_newline(ends_with_newline); | |
628 | ||
629 | write_list(&list_items, &fmt) | |
630 | .map(|items_str| (tactic == DefinitiveListTactic::Horizontal, items_str)) | |
631 | } | |
632 | ||
633 | fn wrap_items(&self, items_str: &str, shape: Shape, is_extendable: bool) -> String { | |
634 | let shape = Shape { | |
635 | width: shape.width.saturating_sub(last_line_width(self.ident)), | |
636 | ..shape | |
637 | }; | |
638 | ||
639 | let (prefix, suffix) = match self.custom_delims { | |
640 | Some((lhs, rhs)) => (lhs, rhs), | |
641 | _ => (self.prefix, self.suffix), | |
642 | }; | |
643 | ||
644 | let extend_width = if items_str.is_empty() { | |
645 | 2 | |
646 | } else { | |
647 | first_line_width(items_str) + 1 | |
648 | }; | |
649 | let nested_indent_str = self | |
650 | .nested_shape | |
651 | .indent | |
652 | .to_string_with_newline(self.context.config); | |
653 | let indent_str = shape | |
654 | .block() | |
655 | .indent | |
656 | .to_string_with_newline(self.context.config); | |
657 | let mut result = String::with_capacity( | |
658 | self.ident.len() + items_str.len() + 2 + indent_str.len() + nested_indent_str.len(), | |
659 | ); | |
660 | result.push_str(self.ident); | |
661 | result.push_str(prefix); | |
662 | let force_single_line = if self.context.config.version() == Version::Two { | |
663 | !self.context.use_block_indent() || (is_extendable && extend_width <= shape.width) | |
664 | } else { | |
665 | // 2 = `()` | |
666 | let fits_one_line = items_str.len() + 2 <= shape.width; | |
667 | !self.context.use_block_indent() | |
668 | || (self.context.inside_macro() && !items_str.contains('\n') && fits_one_line) | |
669 | || (is_extendable && extend_width <= shape.width) | |
670 | }; | |
671 | if force_single_line { | |
672 | result.push_str(items_str); | |
673 | } else { | |
674 | if !items_str.is_empty() { | |
675 | result.push_str(&nested_indent_str); | |
676 | result.push_str(items_str); | |
677 | } | |
678 | result.push_str(&indent_str); | |
679 | } | |
680 | result.push_str(suffix); | |
681 | result | |
682 | } | |
683 | ||
684 | fn rewrite(&self, shape: Shape) -> Option<String> { | |
685 | let (extendable, items_str) = self.rewrite_items()?; | |
686 | ||
687 | // If we are using visual indent style and failed to format, retry with block indent. | |
688 | if !self.context.use_block_indent() | |
689 | && need_block_indent(&items_str, self.nested_shape) | |
690 | && !extendable | |
691 | { | |
692 | self.context.use_block.replace(true); | |
693 | let result = self.rewrite(shape); | |
694 | self.context.use_block.replace(false); | |
695 | return result; | |
696 | } | |
697 | ||
698 | Some(self.wrap_items(&items_str, shape, extendable)) | |
699 | } | |
700 | } | |
701 | ||
702 | fn need_block_indent(s: &str, shape: Shape) -> bool { | |
703 | s.lines().skip(1).any(|s| { | |
704 | s.find(|c| !char::is_whitespace(c)) | |
705 | .map_or(false, |w| w + 1 < shape.indent.width()) | |
706 | }) | |
707 | } | |
708 | ||
709 | fn can_be_overflowed(context: &RewriteContext<'_>, items: &[OverflowableItem<'_>]) -> bool { | |
710 | items | |
711 | .last() | |
712 | .map_or(false, |x| x.can_be_overflowed(context, items.len())) | |
713 | } | |
714 | ||
715 | /// Returns a shape for the last argument which is going to be overflowed. | |
716 | fn last_item_shape( | |
717 | lists: &[OverflowableItem<'_>], | |
718 | items: &[ListItem], | |
719 | shape: Shape, | |
720 | args_max_width: usize, | |
721 | ) -> Option<Shape> { | |
722 | if items.len() == 1 && !lists.get(0)?.is_nested_call() { | |
723 | return Some(shape); | |
724 | } | |
725 | let offset = items | |
726 | .iter() | |
727 | .dropping_back(1) | |
728 | .map(|i| { | |
729 | // 2 = ", " | |
730 | 2 + i.inner_as_ref().len() | |
731 | }) | |
732 | .sum(); | |
733 | Shape { | |
734 | width: min(args_max_width, shape.width), | |
735 | ..shape | |
736 | } | |
737 | .offset_left(offset) | |
738 | } | |
739 | ||
740 | fn shape_from_indent_style( | |
741 | context: &RewriteContext<'_>, | |
742 | shape: Shape, | |
743 | overhead: usize, | |
744 | offset: usize, | |
745 | ) -> Shape { | |
746 | let (shape, overhead) = if context.use_block_indent() { | |
747 | let shape = shape | |
748 | .block() | |
749 | .block_indent(context.config.tab_spaces()) | |
750 | .with_max_width(context.config); | |
751 | (shape, 1) // 1 = "," | |
752 | } else { | |
753 | (shape.visual_indent(offset), overhead) | |
754 | }; | |
755 | Shape { | |
756 | width: shape.width.saturating_sub(overhead), | |
757 | ..shape | |
758 | } | |
759 | } | |
760 | ||
5e7ed085 | 761 | fn no_long_items(list: &[ListItem], short_array_element_width_threshold: usize) -> bool { |
f20569fa | 762 | list.iter() |
5e7ed085 | 763 | .all(|item| item.inner_as_ref().len() <= short_array_element_width_threshold) |
f20569fa XL |
764 | } |
765 | ||
766 | /// In case special-case style is required, returns an offset from which we start horizontal layout. | |
767 | pub(crate) fn maybe_get_args_offset( | |
768 | callee_str: &str, | |
769 | args: &[OverflowableItem<'_>], | |
770 | ) -> Option<(bool, usize)> { | |
771 | if let Some(&(_, num_args_before)) = args | |
772 | .get(0)? | |
773 | .whitelist() | |
774 | .iter() | |
775 | .find(|&&(s, _)| s == callee_str) | |
776 | { | |
777 | let all_simple = args.len() > num_args_before | |
778 | && is_every_expr_simple(&args[0..num_args_before]) | |
779 | && is_every_expr_simple(&args[num_args_before + 1..]); | |
780 | ||
781 | Some((all_simple, num_args_before)) | |
782 | } else { | |
783 | None | |
784 | } | |
785 | } |