]> git.lizzy.rs Git - rust.git/blob - src/chains.rs
Align dots in chained 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 // Formatting of chained expressions, i.e. expressions which are chained by
12 // dots: struct and enum field access and method calls.
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 use rewrite::{Rewrite, RewriteContext};
23 use utils::make_indent;
24 use expr::rewrite_call;
25
26 use syntax::{ast, ptr};
27 use syntax::codemap::{mk_sp, Span};
28 use syntax::print::pprust;
29
30 pub fn rewrite_chain(mut expr: &ast::Expr,
31                      context: &RewriteContext,
32                      width: usize,
33                      offset: usize)
34                      -> Option<String> {
35     let total_span = expr.span;
36     let mut subexpr_list = vec![expr];
37
38     while let Some(subexpr) = pop_expr_chain(expr) {
39         subexpr_list.push(subexpr);
40         expr = subexpr;
41     }
42
43     let parent = subexpr_list.pop().unwrap();
44     let parent_rewrite = try_opt!(expr.rewrite(context, width, offset));
45     let (extra_indent, extend) = if !parent_rewrite.contains('\n') && is_continuable(parent) ||
46                                     parent_rewrite.len() <= context.config.tab_spaces {
47         (parent_rewrite.len(), true)
48     } else {
49         (context.config.tab_spaces, false)
50     };
51     let indent = offset + extra_indent;
52
53     let max_width = try_opt!(width.checked_sub(extra_indent));
54     let rewrites = try_opt!(subexpr_list.into_iter()
55                                         .rev()
56                                         .map(|e| {
57                                             rewrite_chain_expr(e,
58                                                                total_span,
59                                                                context,
60                                                                max_width,
61                                                                indent)
62                                         })
63                                         .collect::<Option<Vec<_>>>());
64
65     let total_width = rewrites.iter().fold(0, |a, b| a + b.len()) + parent_rewrite.len();
66     let fits_single_line = total_width <= width && rewrites.iter().all(|s| !s.contains('\n'));
67
68     let connector = if fits_single_line {
69         String::new()
70     } else {
71         format!("\n{}", make_indent(indent))
72     };
73
74     let first_connector = if extend {
75         ""
76     } else {
77         &connector[..]
78     };
79
80     Some(format!("{}{}{}", parent_rewrite, first_connector, rewrites.join(&connector)))
81 }
82
83 fn pop_expr_chain<'a>(expr: &'a ast::Expr) -> Option<&'a ast::Expr> {
84     match expr.node {
85         ast::Expr_::ExprMethodCall(_, _, ref expressions) => {
86             Some(&expressions[0])
87         }
88         ast::Expr_::ExprTupField(ref subexpr, _) |
89         ast::Expr_::ExprField(ref subexpr, _) => {
90             Some(subexpr)
91         }
92         _ => None,
93     }
94 }
95
96 fn rewrite_chain_expr(expr: &ast::Expr,
97                       span: Span,
98                       context: &RewriteContext,
99                       width: usize,
100                       offset: usize)
101                       -> Option<String> {
102     match expr.node {
103         ast::Expr_::ExprMethodCall(ref method_name, ref types, ref expressions) => {
104             rewrite_method_call(method_name.node, types, expressions, span, context, width, offset)
105         }
106         ast::Expr_::ExprField(_, ref field) => {
107             Some(format!(".{}", field.node))
108         }
109         ast::Expr_::ExprTupField(_, ref field) => {
110             Some(format!(".{}", field.node))
111         }
112         _ => unreachable!(),
113     }
114 }
115
116 // Determines we can continue formatting a given expression on the same line.
117 fn is_continuable(expr: &ast::Expr) -> bool {
118     match expr.node {
119         ast::Expr_::ExprPath(..) => true,
120         _ => false,
121     }
122 }
123
124 fn rewrite_method_call(method_name: ast::Ident,
125                        types: &[ptr::P<ast::Ty>],
126                        args: &[ptr::P<ast::Expr>],
127                        span: Span,
128                        context: &RewriteContext,
129                        width: usize,
130                        offset: usize)
131                        -> Option<String> {
132     let type_str = if types.is_empty() {
133         String::new()
134     } else {
135         let type_list = types.iter().map(|ty| pprust::ty_to_string(ty)).collect::<Vec<_>>();
136         format!("::<{}>", type_list.join(", "))
137     };
138
139     let callee_str = format!(".{}{}", method_name, type_str);
140     let inner_context = &RewriteContext {
141         block_indent: offset,
142         ..*context
143     };
144
145     rewrite_call(inner_context, &callee_str, args, span, width, offset)
146 }