]> git.lizzy.rs Git - rust.git/blob - src/chains.rs
Add tests for chain expressions
[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 use rewrite::{Rewrite, RewriteContext};
12 use utils::{make_indent, extra_offset};
13 use expr::rewrite_call;
14
15 use syntax::{ast, ptr};
16 use syntax::codemap::{mk_sp, Span};
17 use syntax::print::pprust;
18
19 pub fn rewrite_chain(orig_expr: &ast::Expr,
20                      context: &RewriteContext,
21                      width: usize,
22                      offset: usize)
23                      -> Option<String> {
24     let mut expr = orig_expr;
25     let mut rewrites = Vec::new();
26     let indent = offset + context.config.tab_spaces;
27     let max_width = try_opt!(context.config.max_width.checked_sub(indent));
28
29     while let Some(pair) = pop_expr_chain(expr, orig_expr.span, context, max_width, indent) {
30         let (rewrite, parent_expr) = pair;
31
32         rewrites.push(try_opt!(rewrite));
33         expr = parent_expr;
34     }
35
36     let parent_rewrite = try_opt!(expr.rewrite(context, width, offset));
37     let total_width = rewrites.iter().fold(0, |a, b| a + b.len()) + parent_rewrite.len();
38     let fits_single_line = total_width <= width && rewrites.iter().all(|s| !s.contains('\n'));
39
40     if rewrites.len() == 1 && !fits_single_line &&
41        (is_continuable(expr) || parent_rewrite.len() <= context.config.tab_spaces) {
42         let extra_offset = extra_offset(&parent_rewrite, offset);
43         let offset = offset + extra_offset;
44         let max_width = try_opt!(width.checked_sub(extra_offset));
45
46         let rerewrite = pop_expr_chain(orig_expr, orig_expr.span, context, max_width, offset)
47                             .unwrap()
48                             .0;
49
50         return Some(format!("{}{}", parent_rewrite, try_opt!(rerewrite)));
51     }
52
53     let connector = if fits_single_line {
54         String::new()
55     } else {
56         format!("\n{}", make_indent(indent))
57     };
58
59     // FIXME: don't do this. There's a more efficient way. VecDeque?
60     rewrites.reverse();
61
62     // Put the first link on the same line as parent, if it fits.
63     let first_connector = if parent_rewrite.len() + rewrites[0].len() <= width &&
64                              is_continuable(expr) &&
65                              !rewrites[0].contains('\n') ||
66                              parent_rewrite.len() <= context.config.tab_spaces {
67         ""
68     } else {
69         &connector[..]
70     };
71
72     Some(format!("{}{}{}", parent_rewrite, first_connector, rewrites.join(&connector)))
73 }
74
75 // Returns None when the expression is not a chainable. Otherwise, rewrites the
76 // outermost chain element and returns the remaining chain.
77 fn pop_expr_chain<'a>(expr: &'a ast::Expr,
78                       span: Span,
79                       context: &RewriteContext,
80                       width: usize,
81                       offset: usize)
82                       -> Option<(Option<String>, &'a ast::Expr)> {
83     match expr.node {
84         ast::Expr_::ExprMethodCall(ref method_name, ref types, ref expressions) => {
85             Some((rewrite_method_call(method_name.node,
86                                       types,
87                                       expressions,
88                                       span,
89                                       context,
90                                       width,
91                                       offset),
92                   &expressions[0]))
93         }
94         ast::Expr_::ExprField(ref subexpr, ref field) => {
95             Some((Some(format!(".{}", field.node)), subexpr))
96         }
97         ast::Expr_::ExprTupField(ref subexpr, ref field) => {
98             Some((Some(format!(".{}", field.node)), subexpr))
99         }
100         _ => None,
101     }
102 }
103
104 // Determines we can continue formatting a given expression on the same line.
105 fn is_continuable(expr: &ast::Expr) -> bool {
106     match expr.node {
107         ast::Expr_::ExprPath(..) => true,
108         _ => false,
109     }
110 }
111
112 fn rewrite_method_call(method_name: ast::Ident,
113                        types: &[ptr::P<ast::Ty>],
114                        args: &[ptr::P<ast::Expr>],
115                        span: Span,
116                        context: &RewriteContext,
117                        width: usize,
118                        offset: usize)
119                        -> Option<String> {
120     let type_str = if types.is_empty() {
121         String::new()
122     } else {
123         let type_list = types.iter().map(|ty| pprust::ty_to_string(ty)).collect::<Vec<_>>();
124         format!("::<{}>", type_list.join(", "))
125     };
126
127     let callee_str = format!(".{}{}", method_name, type_str);
128     let inner_context = &RewriteContext {
129         block_indent: offset,
130         ..*context
131     };
132
133     rewrite_call(inner_context, &callee_str, args, span, width, offset)
134 }