]> git.lizzy.rs Git - rust.git/blob - src/chains.rs
Handle prefix and suffix try operators differently
[rust.git] / src / chains.rs
1 // Copyright 2015 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 /// Formatting of chained expressions, i.e. expressions which are chained by
12 /// dots: struct and enum field access, method calls, and try shorthand (?).
13 ///
14 /// Instead of walking these subexpressions one-by-one, as is our usual strategy
15 /// for expression formatting, we collect maximal sequences of these expressions
16 /// and handle them simultaneously.
17 ///
18 /// Whenever possible, the entire chain is put on a single line. If that fails,
19 /// we put each subexpression on a separate, much like the (default) function
20 /// argument function argument strategy.
21 ///
22 /// Depends on config options: `chain_indent` is the indent to use for
23 /// blocks in the parent/root/base of the chain (and the rest of the chain's
24 /// alignment).
25 /// E.g., `let foo = { aaaa; bbb; ccc }.bar.baz();`, we would layout for the
26 /// following values of `chain_indent`:
27 /// Visual:
28 /// ```
29 /// let foo = {
30 ///               aaaa;
31 ///               bbb;
32 ///               ccc
33 ///           }
34 ///           .bar
35 ///           .baz();
36 /// ```
37 /// Inherit:
38 /// ```
39 /// let foo = {
40 ///     aaaa;
41 ///     bbb;
42 ///     ccc
43 /// }
44 /// .bar
45 /// .baz();
46 /// ```
47 /// Tabbed:
48 /// ```
49 /// let foo = {
50 ///         aaaa;
51 ///         bbb;
52 ///         ccc
53 ///     }
54 ///     .bar
55 ///     .baz();
56 /// ```
57 ///
58 /// If the first item in the chain is a block expression, we align the dots with
59 /// the braces.
60 /// Visual:
61 /// ```
62 /// let a = foo.bar
63 ///            .baz()
64 ///            .qux
65 /// ```
66 /// Inherit:
67 /// ```
68 /// let a = foo.bar
69 /// .baz()
70 /// .qux
71 /// ```
72 /// Tabbed:
73 /// ```
74 /// let a = foo.bar
75 ///     .baz()
76 ///     .qux
77 /// ```
78
79 use Shape;
80 use config::IndentStyle;
81 use expr::rewrite_call;
82 use macros::convert_try_mac;
83 use rewrite::{Rewrite, RewriteContext};
84 use utils::{first_line_width, last_line_extendable, last_line_width, mk_sp, wrap_str};
85
86 use std::cmp::min;
87 use std::iter;
88 use syntax::{ast, ptr};
89 use syntax::codemap::Span;
90
91 pub fn rewrite_chain(expr: &ast::Expr, context: &RewriteContext, shape: Shape) -> Option<String> {
92     debug!("rewrite_chain {:?}", shape);
93     let total_span = expr.span;
94     let (parent, subexpr_list) = make_subexpr_list(expr, context);
95
96     // Bail out if the chain is just try sugar, i.e., an expression followed by
97     // any number of `?`s.
98     if chain_only_try(&subexpr_list) {
99         return rewrite_try(&parent, subexpr_list.len(), context, shape);
100     }
101     let suffix_try_num = subexpr_list.iter().take_while(|e| is_try(e)).count();
102     let prefix_try_num = subexpr_list.iter().rev().take_while(|e| is_try(e)).count();
103
104     // Parent is the first item in the chain, e.g., `foo` in `foo.bar.baz()`.
105     let parent_shape = if is_block_expr(context, &parent, "\n") {
106         match context.config.chain_indent() {
107             IndentStyle::Visual => shape.visual_indent(0),
108             IndentStyle::Block => shape.block(),
109         }
110     } else {
111         shape
112     };
113     let parent_rewrite = try_opt!(
114         parent
115             .rewrite(context, parent_shape)
116             .map(|parent_rw| parent_rw + &repeat_try(prefix_try_num))
117     );
118     let parent_rewrite_contains_newline = parent_rewrite.contains('\n');
119     let is_small_parent = parent_rewrite.len() <= context.config.tab_spaces();
120
121     // Decide how to layout the rest of the chain. `extend` is true if we can
122     // put the first non-parent item on the same line as the parent.
123     let (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) {
124         (
125             chain_indent(context, shape.add_offset(parent_rewrite.len())),
126             context.config.chain_indent() == IndentStyle::Visual || is_small_parent,
127         )
128     } else if is_block_expr(context, &parent, &parent_rewrite) {
129         match context.config.chain_indent() {
130             // Try to put the first child on the same line with parent's last line
131             IndentStyle::Block => (parent_shape.block_indent(context.config.tab_spaces()), true),
132             // The parent is a block, so align the rest of the chain with the closing
133             // brace.
134             IndentStyle::Visual => (parent_shape, false),
135         }
136     } else if parent_rewrite_contains_newline {
137         (chain_indent(context, parent_shape), false)
138     } else {
139         (
140             shape
141                 .block_indent(context.config.tab_spaces())
142                 .with_max_width(context.config),
143             false,
144         )
145     };
146
147     let other_child_shape = nested_shape.with_max_width(context.config);
148
149     let first_child_shape = if extend {
150         let overhead = last_line_width(&parent_rewrite);
151         let offset = parent_rewrite.lines().rev().next().unwrap().trim().len();
152         match context.config.chain_indent() {
153             IndentStyle::Visual => try_opt!(parent_shape.offset_left(overhead)),
154             IndentStyle::Block => try_opt!(parent_shape.block().offset_left(offset)),
155         }
156     } else {
157         other_child_shape
158     };
159     debug!(
160         "child_shapes {:?} {:?}",
161         first_child_shape,
162         other_child_shape
163     );
164
165     let child_shape_iter = Some(first_child_shape)
166         .into_iter()
167         .chain(iter::repeat(other_child_shape));
168     let subexpr_num = subexpr_list.len();
169     let subexpr_list = &subexpr_list[suffix_try_num..subexpr_num - prefix_try_num];
170     let iter = subexpr_list.iter().rev().zip(child_shape_iter);
171     let mut rewrites = try_opt!(
172         iter.map(|(e, shape)| {
173             rewrite_chain_subexpr(e, total_span, context, shape)
174         }).collect::<Option<Vec<_>>>()
175     );
176
177     // Total of all items excluding the last.
178     let rewrites_len = rewrites.len();
179     let almost_total = rewrites[0..(rewrites_len - 1)]
180         .iter()
181         .fold(0, |a, b| a + first_line_width(b)) + parent_rewrite.len();
182     let one_line_len =
183         rewrites.iter().fold(0, |a, r| a + first_line_width(r)) + parent_rewrite.len();
184
185     let one_line_budget = min(shape.width, context.config.chain_one_line_max());
186     let veto_single_line = if one_line_len > one_line_budget {
187         if rewrites.len() > 1 {
188             true
189         } else if rewrites.len() == 1 {
190             context.config.chain_split_single_child() || one_line_len > shape.width
191         } else {
192             false
193         }
194     } else if context.config.take_source_hints() && rewrites.len() > 1 {
195         // Look at the source code. Unless all chain elements start on the same
196         // line, we won't consider putting them on a single line either.
197         let last_span = context.snippet(mk_sp(subexpr_list[1].span.hi, total_span.hi));
198         let first_span = context.snippet(subexpr_list[1].span);
199         let last_iter = last_span.chars().take_while(|c| c.is_whitespace());
200
201         first_span.chars().chain(last_iter).any(|c| c == '\n')
202     } else {
203         false
204     };
205
206     let mut fits_single_line = !veto_single_line && almost_total <= shape.width;
207     if fits_single_line {
208         let len = rewrites.len();
209         let (init, last) = rewrites.split_at_mut(len - 1);
210         fits_single_line = init.iter().all(|s| !s.contains('\n'));
211
212         if fits_single_line {
213             fits_single_line = match expr.node {
214                 ref e @ ast::ExprKind::MethodCall(..) => {
215                     if rewrite_method_call_with_overflow(
216                         e,
217                         &mut last[0],
218                         almost_total,
219                         total_span,
220                         context,
221                         shape,
222                     ) {
223                         // If the first line of the last method does not fit into a single line
224                         // after the others, allow new lines.
225                         almost_total + first_line_width(&last[0]) < context.config.max_width()
226                     } else {
227                         false
228                     }
229                 }
230                 _ => !last[0].contains('\n'),
231             }
232         }
233     }
234
235     // Try overflowing the last element if we are using block indent and it goes multi line
236     // or it fits in a single line but goes over the max width.
237     if !fits_single_line && context.use_block_indent() {
238         let last_expr_index = rewrites.len() - 1;
239         let (init, last) = rewrites.split_at_mut(last_expr_index);
240         let almost_single_line = init.iter().all(|s| !s.contains('\n'));
241         if almost_single_line && last[0].contains('\n') {
242             let overflow_shape = Shape {
243                 width: one_line_budget,
244                 ..parent_shape
245             };
246             fits_single_line = rewrite_last_child_with_overflow(
247                 context,
248                 &subexpr_list[0],
249                 overflow_shape,
250                 total_span,
251                 almost_total,
252                 one_line_budget,
253                 &mut last[0],
254             );
255         }
256     }
257
258     let connector = if fits_single_line && !parent_rewrite_contains_newline {
259         // Yay, we can put everything on one line.
260         String::new()
261     } else {
262         // Use new lines.
263         if context.force_one_line_chain {
264             return None;
265         }
266         format!("\n{}", nested_shape.indent.to_string(context.config))
267     };
268
269     let first_connector = choose_first_connector(
270         context,
271         &parent_rewrite,
272         &rewrites[0],
273         &connector,
274         &subexpr_list,
275         extend,
276     );
277
278     let result = if is_small_parent && rewrites.len() > 1 {
279         let second_connector = choose_first_connector(
280             context,
281             &rewrites[0],
282             &rewrites[1],
283             &connector,
284             &subexpr_list[..subexpr_num - 1],
285             false,
286         );
287         format!(
288             "{}{}{}{}{}",
289             parent_rewrite,
290             first_connector,
291             rewrites[0],
292             second_connector,
293             join_rewrites(&rewrites[1..], &subexpr_list[..subexpr_num - 1], &connector)
294         )
295     } else {
296         format!(
297             "{}{}{}",
298             parent_rewrite,
299             first_connector,
300             join_rewrites(&rewrites, &subexpr_list, &connector)
301         )
302     };
303     let result = format!("{}{}", result, repeat_try(suffix_try_num));
304     if context.config.chain_indent() == IndentStyle::Block {
305         Some(result)
306     } else {
307         wrap_str(result, context.config.max_width(), shape)
308     }
309 }
310
311 fn is_extendable_parent(context: &RewriteContext, parent_str: &str) -> bool {
312     context.config.chain_indent() == IndentStyle::Block && last_line_extendable(parent_str)
313 }
314
315 // True if the chain is only `?`s.
316 fn chain_only_try(exprs: &[ast::Expr]) -> bool {
317     exprs.iter().all(|e| if let ast::ExprKind::Try(_) = e.node {
318         true
319     } else {
320         false
321     })
322 }
323
324 // Try to rewrite and replace the last non-try child. Return `true` if
325 // replacing succeeds.
326 fn rewrite_last_child_with_overflow(
327     context: &RewriteContext,
328     expr: &ast::Expr,
329     shape: Shape,
330     span: Span,
331     almost_total: usize,
332     one_line_budget: usize,
333     last_child: &mut String,
334 ) -> bool {
335     if let Some(shape) = shape.shrink_left(almost_total) {
336         if let Some(ref mut rw) = rewrite_chain_subexpr(expr, span, context, shape) {
337             if almost_total + first_line_width(rw) <= one_line_budget && rw.lines().count() > 3 {
338                 ::std::mem::swap(last_child, rw);
339                 return true;
340             }
341         }
342     }
343     false
344 }
345
346 fn repeat_try(try_count: usize) -> String {
347     iter::repeat("?").take(try_count).collect::<String>()
348 }
349
350 fn rewrite_try(
351     expr: &ast::Expr,
352     try_count: usize,
353     context: &RewriteContext,
354     shape: Shape,
355 ) -> Option<String> {
356     let sub_expr = try_opt!(expr.rewrite(context, try_opt!(shape.sub_width(try_count))));
357     Some(format!("{}{}", sub_expr, repeat_try(try_count)))
358 }
359
360 fn join_rewrites(rewrites: &[String], subexps: &[ast::Expr], connector: &str) -> String {
361     let mut rewrite_iter = rewrites.iter();
362     let mut result = rewrite_iter.next().unwrap().clone();
363     let mut subexpr_iter = subexps.iter().rev();
364     subexpr_iter.next();
365
366     for (rewrite, expr) in rewrite_iter.zip(subexpr_iter) {
367         match expr.node {
368             ast::ExprKind::Try(_) => (),
369             _ => result.push_str(connector),
370         };
371         result.push_str(&rewrite[..]);
372     }
373
374     result
375 }
376
377 // States whether an expression's last line exclusively consists of closing
378 // parens, braces, and brackets in its idiomatic formatting.
379 fn is_block_expr(context: &RewriteContext, expr: &ast::Expr, repr: &str) -> bool {
380     match expr.node {
381         ast::ExprKind::Mac(..) | ast::ExprKind::Call(..) => {
382             context.use_block_indent() && repr.contains('\n')
383         }
384         ast::ExprKind::Struct(..) |
385         ast::ExprKind::While(..) |
386         ast::ExprKind::WhileLet(..) |
387         ast::ExprKind::If(..) |
388         ast::ExprKind::IfLet(..) |
389         ast::ExprKind::Block(..) |
390         ast::ExprKind::Loop(..) |
391         ast::ExprKind::ForLoop(..) |
392         ast::ExprKind::Match(..) => repr.contains('\n'),
393         ast::ExprKind::Paren(ref expr) |
394         ast::ExprKind::Binary(_, _, ref expr) |
395         ast::ExprKind::Index(_, ref expr) |
396         ast::ExprKind::Unary(_, ref expr) => is_block_expr(context, expr, repr),
397         _ => false,
398     }
399 }
400
401 // Returns the root of the chain and a Vec of the prefixes of the rest of the chain.
402 // E.g., for input `a.b.c` we return (`a`, [`a.b.c`, `a.b`])
403 fn make_subexpr_list(expr: &ast::Expr, context: &RewriteContext) -> (ast::Expr, Vec<ast::Expr>) {
404     let mut subexpr_list = vec![expr.clone()];
405
406     while let Some(subexpr) = pop_expr_chain(subexpr_list.last().unwrap(), context) {
407         subexpr_list.push(subexpr.clone());
408     }
409
410     let parent = subexpr_list.pop().unwrap();
411     (parent, subexpr_list)
412 }
413
414 fn chain_indent(context: &RewriteContext, shape: Shape) -> Shape {
415     match context.config.chain_indent() {
416         IndentStyle::Visual => shape.visual_indent(0),
417         IndentStyle::Block => shape
418             .block_indent(context.config.tab_spaces())
419             .with_max_width(context.config),
420     }
421 }
422
423 fn rewrite_method_call_with_overflow(
424     expr_kind: &ast::ExprKind,
425     last: &mut String,
426     almost_total: usize,
427     total_span: Span,
428     context: &RewriteContext,
429     shape: Shape,
430 ) -> bool {
431     if let &ast::ExprKind::MethodCall(ref segment, ref expressions) = expr_kind {
432         let shape = match shape.shrink_left(almost_total) {
433             Some(b) => b,
434             None => return false,
435         };
436         let types = match segment.parameters {
437             Some(ref params) => match **params {
438                 ast::PathParameters::AngleBracketed(ref data) => &data.types[..],
439                 _ => &[],
440             },
441             _ => &[],
442         };
443         let mut last_rewrite = rewrite_method_call(
444             segment.identifier,
445             types,
446             expressions,
447             total_span,
448             context,
449             shape,
450         );
451
452         if let Some(ref mut s) = last_rewrite {
453             ::std::mem::swap(s, last);
454             true
455         } else {
456             false
457         }
458     } else {
459         unreachable!();
460     }
461 }
462
463 // Returns the expression's subexpression, if it exists. When the subexpr
464 // is a try! macro, we'll convert it to shorthand when the option is set.
465 fn pop_expr_chain(expr: &ast::Expr, context: &RewriteContext) -> Option<ast::Expr> {
466     match expr.node {
467         ast::ExprKind::MethodCall(_, ref expressions) => {
468             Some(convert_try(&expressions[0], context))
469         }
470         ast::ExprKind::TupField(ref subexpr, _) |
471         ast::ExprKind::Field(ref subexpr, _) |
472         ast::ExprKind::Try(ref subexpr) => Some(convert_try(subexpr, context)),
473         _ => None,
474     }
475 }
476
477 fn convert_try(expr: &ast::Expr, context: &RewriteContext) -> ast::Expr {
478     match expr.node {
479         ast::ExprKind::Mac(ref mac) if context.config.use_try_shorthand() => {
480             if let Some(subexpr) = convert_try_mac(mac, context) {
481                 subexpr
482             } else {
483                 expr.clone()
484             }
485         }
486         _ => expr.clone(),
487     }
488 }
489
490 // Rewrite the last element in the chain `expr`. E.g., given `a.b.c` we rewrite
491 // `.c`.
492 fn rewrite_chain_subexpr(
493     expr: &ast::Expr,
494     span: Span,
495     context: &RewriteContext,
496     shape: Shape,
497 ) -> Option<String> {
498     let rewrite_element = |expr_str: String| if expr_str.len() <= shape.width {
499         Some(expr_str)
500     } else {
501         None
502     };
503
504     match expr.node {
505         ast::ExprKind::MethodCall(ref segment, ref expressions) => {
506             let types = match segment.parameters {
507                 Some(ref params) => match **params {
508                     ast::PathParameters::AngleBracketed(ref data) => &data.types[..],
509                     _ => &[],
510                 },
511                 _ => &[],
512             };
513             rewrite_method_call(segment.identifier, types, expressions, span, context, shape)
514         }
515         ast::ExprKind::Field(_, ref field) => rewrite_element(format!(".{}", field.node)),
516         ast::ExprKind::TupField(ref expr, ref field) => {
517             let space = match expr.node {
518                 ast::ExprKind::TupField(..) => " ",
519                 _ => "",
520             };
521             rewrite_element(format!("{}.{}", space, field.node))
522         }
523         ast::ExprKind::Try(_) => rewrite_element(String::from("?")),
524         _ => unreachable!(),
525     }
526 }
527
528 // Determines if we can continue formatting a given expression on the same line.
529 fn is_continuable(expr: &ast::Expr) -> bool {
530     match expr.node {
531         ast::ExprKind::Path(..) => true,
532         _ => false,
533     }
534 }
535
536 fn is_try(expr: &ast::Expr) -> bool {
537     match expr.node {
538         ast::ExprKind::Try(..) => true,
539         _ => false,
540     }
541 }
542
543 fn choose_first_connector<'a>(
544     context: &RewriteContext,
545     parent_str: &str,
546     first_child_str: &str,
547     connector: &'a str,
548     subexpr_list: &[ast::Expr],
549     extend: bool,
550 ) -> &'a str {
551     if subexpr_list.is_empty() {
552         ""
553     } else if extend || subexpr_list.last().map_or(false, is_try) ||
554         is_extendable_parent(context, parent_str)
555     {
556         // 1 = ";", being conservative here.
557         if last_line_width(parent_str) + first_line_width(first_child_str) + 1 <=
558             context.config.max_width()
559         {
560             ""
561         } else {
562             connector
563         }
564     } else {
565         connector
566     }
567 }
568
569 fn rewrite_method_call(
570     method_name: ast::Ident,
571     types: &[ptr::P<ast::Ty>],
572     args: &[ptr::P<ast::Expr>],
573     span: Span,
574     context: &RewriteContext,
575     shape: Shape,
576 ) -> Option<String> {
577     let (lo, type_str) = if types.is_empty() {
578         (args[0].span.hi, String::new())
579     } else {
580         let type_list: Vec<_> =
581             try_opt!(types.iter().map(|ty| ty.rewrite(context, shape)).collect());
582
583         let type_str = if context.config.spaces_within_angle_brackets() && type_list.len() > 0 {
584             format!("::< {} >", type_list.join(", "))
585         } else {
586             format!("::<{}>", type_list.join(", "))
587         };
588
589         (types.last().unwrap().span.hi, type_str)
590     };
591
592     let callee_str = format!(".{}{}", method_name, type_str);
593     let span = mk_sp(lo, span.hi);
594
595     rewrite_call(context, &callee_str, &args[1..], span, shape)
596 }