]> git.lizzy.rs Git - rust.git/blob - src/expr.rs
Format comments in struct literals
[rust.git] / src / expr.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 lists::{write_list, itemize_list, ListFormatting, SeparatorTactic, ListTactic};
13 use string::{StringFormat, rewrite_string};
14 use utils::span_after;
15
16 use syntax::{ast, ptr};
17 use syntax::codemap::{Pos, Span, BytePos};
18 use syntax::parse::token;
19 use syntax::print::pprust;
20
21 impl Rewrite for ast::Expr {
22     fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Option<String> {
23         match self.node {
24             ast::Expr_::ExprLit(ref l) => {
25                 match l.node {
26                     ast::Lit_::LitStr(ref is, _) => {
27                         let result = rewrite_string_lit(context, &is, l.span, width, offset);
28                         debug!("string lit: `{:?}`", result);
29                         return result;
30                     }
31                     _ => {}
32                 }
33             }
34             ast::Expr_::ExprCall(ref callee, ref args) => {
35                 return rewrite_call(context, callee, args, self.span, width, offset);
36             }
37             ast::Expr_::ExprParen(ref subexpr) => {
38                 return rewrite_paren(context, subexpr, width, offset);
39             }
40             ast::Expr_::ExprStruct(ref path, ref fields, ref base) => {
41                 return rewrite_struct_lit(context,
42                                           path,
43                                           fields,
44                                           base.as_ref().map(|e| &**e),
45                                           self.span,
46                                           width,
47                                           offset);
48             }
49             ast::Expr_::ExprTup(ref items) => {
50                 return rewrite_tuple_lit(context, items, self.span, width, offset);
51             }
52             _ => {}
53         }
54
55         context.codemap.span_to_snippet(self.span).ok()
56     }
57 }
58
59 fn rewrite_string_lit(context: &RewriteContext,
60                       s: &str,
61                       span: Span,
62                       width: usize,
63                       offset: usize)
64     -> Option<String> {
65     // Check if there is anything to fix: we always try to fixup multi-line
66     // strings, or if the string is too long for the line.
67     let l_loc = context.codemap.lookup_char_pos(span.lo);
68     let r_loc = context.codemap.lookup_char_pos(span.hi);
69     if l_loc.line == r_loc.line && r_loc.col.to_usize() <= context.config.max_width {
70         return context.codemap.span_to_snippet(span).ok();
71     }
72     let fmt = StringFormat {
73         opener: "\"",
74         closer: "\"",
75         line_start: " ",
76         line_end: "\\",
77         width: width,
78         offset: offset,
79         trim_end: false
80     };
81
82     Some(rewrite_string(&s.escape_default(), &fmt))
83 }
84
85 fn rewrite_call(context: &RewriteContext,
86                 callee: &ast::Expr,
87                 args: &[ptr::P<ast::Expr>],
88                 span: Span,
89                 width: usize,
90                 offset: usize)
91         -> Option<String> {
92     debug!("rewrite_call, width: {}, offset: {}", width, offset);
93
94     // TODO using byte lens instead of char lens (and probably all over the place too)
95     let callee_str = try_opt!(callee.rewrite(context, width, offset));
96     debug!("rewrite_call, callee_str: `{}`", callee_str);
97
98     if args.len() == 0 {
99         return Some(format!("{}()", callee_str));
100     }
101
102     // 2 is for parens.
103     let remaining_width = width - callee_str.len() - 2;
104     let offset = callee_str.len() + 1 + offset;
105
106     let items = itemize_list(context.codemap,
107                              Vec::new(),
108                              args.iter(),
109                              ",",
110                              ")",
111                              |item| item.span.lo,
112                              |item| item.span.hi,
113                              // Take old span when rewrite fails.
114                              |item| item.rewrite(context, remaining_width, offset)
115                                         .unwrap_or(context.codemap.span_to_snippet(item.span)
116                                                                   .unwrap()),
117                              callee.span.hi + BytePos(1),
118                              span.hi);
119
120     let fmt = ListFormatting {
121         tactic: ListTactic::HorizontalVertical,
122         separator: ",",
123         trailing_separator: SeparatorTactic::Never,
124         indent: offset,
125         h_width: remaining_width,
126         v_width: remaining_width,
127         is_expression: true,
128     };
129
130     Some(format!("{}({})", callee_str, write_list(&items, &fmt)))
131 }
132
133 fn rewrite_paren(context: &RewriteContext, subexpr: &ast::Expr, width: usize, offset: usize) -> Option<String> {
134     debug!("rewrite_paren, width: {}, offset: {}", width, offset);
135     // 1 is for opening paren, 2 is for opening+closing, we want to keep the closing
136     // paren on the same line as the subexpr
137     let subexpr_str = subexpr.rewrite(context, width-2, offset+1);
138     debug!("rewrite_paren, subexpr_str: `{:?}`", subexpr_str);
139     subexpr_str.map(|s| format!("({})", s))
140 }
141
142 fn rewrite_struct_lit<'a>(context: &RewriteContext,
143                           path: &ast::Path,
144                           fields: &'a [ast::Field],
145                           base: Option<&'a ast::Expr>,
146                           span: Span,
147                           width: usize,
148                           offset: usize)
149         -> Option<String>
150 {
151     debug!("rewrite_struct_lit: width {}, offset {}", width, offset);
152     assert!(fields.len() > 0 || base.is_some());
153
154     enum StructLitField<'a> {
155         Regular(&'a ast::Field),
156         Base(&'a ast::Expr)
157     }
158
159     let path_str = pprust::path_to_string(path);
160     // Foo { a: Foo } - indent is +3, width is -5.
161     let indent = offset + path_str.len() + 3;
162     let budget = width - (path_str.len() + 5);
163
164     let field_iter = fields.into_iter().map(StructLitField::Regular)
165                            .chain(base.into_iter().map(StructLitField::Base));
166
167     let items = itemize_list(context.codemap,
168                              Vec::new(),
169                              field_iter,
170                              ",",
171                              "}",
172                              |item| {
173                                  match *item {
174                                      StructLitField::Regular(ref field) => field.span.lo,
175                                      // 2 = ..
176                                      StructLitField::Base(ref expr) => expr.span.lo - BytePos(2)
177                                  }
178                              },
179                              |item| {
180                                  match *item {
181                                      StructLitField::Regular(ref field) => field.span.hi,
182                                      StructLitField::Base(ref expr) => expr.span.hi
183                                  }
184                              },
185                              |item| {
186                                  match *item {
187                                      StructLitField::Regular(ref field) => {
188                                          rewrite_field(context, &field, budget, indent)
189                                             .unwrap_or(context.codemap.span_to_snippet(field.span)
190                                                                       .unwrap())
191                                      },
192                                      StructLitField::Base(ref expr) => {
193                                          // 2 = ..
194                                          expr.rewrite(context, budget - 2, indent + 2)
195                                              .map(|s| format!("..{}", s))
196                                              .unwrap_or(context.codemap.span_to_snippet(expr.span)
197                                                                        .unwrap())
198                                      }
199                                  }
200                              },
201                              span_after(span, "{", context.codemap),
202                              span.hi);
203
204     let fmt = ListFormatting {
205         tactic: ListTactic::HorizontalVertical,
206         separator: ",",
207         trailing_separator: if base.is_some() {
208             SeparatorTactic::Never
209         } else {
210             context.config.struct_lit_trailing_comma
211         },
212         indent: indent,
213         h_width: budget,
214         v_width: budget,
215         is_expression: true,
216     };
217     let fields_str = write_list(&items, &fmt);
218     Some(format!("{} {{ {} }}", path_str, fields_str))
219
220     // FIXME if the usual multi-line layout is too wide, we should fall back to
221     // Foo {
222     //     a: ...,
223     // }
224 }
225
226 fn rewrite_field(context: &RewriteContext, field: &ast::Field, width: usize, offset: usize) -> Option<String> {
227     let name = &token::get_ident(field.ident.node);
228     let overhead = name.len() + 2;
229     let expr = field.expr.rewrite(context, width - overhead, offset + overhead);
230     expr.map(|s| format!("{}: {}", name, s))
231 }
232
233 fn rewrite_tuple_lit(context: &RewriteContext,
234                      items: &[ptr::P<ast::Expr>],
235                      span: Span,
236                      width: usize,
237                      offset: usize)
238     -> Option<String> {
239     let indent = offset + 1;
240
241     let items = itemize_list(context.codemap,
242                              Vec::new(),
243                              items.into_iter(),
244                              ",",
245                              ")",
246                              |item| item.span.lo,
247                              |item| item.span.hi,
248                              |item| item.rewrite(context,
249                                                  context.config.max_width - indent - 2,
250                                                  indent)
251                                         .unwrap_or(context.codemap.span_to_snippet(item.span)
252                                                                   .unwrap()),
253                              span.lo + BytePos(1), // Remove parens
254                              span.hi - BytePos(1));
255
256     // In case of length 1, need a trailing comma
257     let trailing_separator_tactic = if items.len() == 1 {
258         SeparatorTactic::Always
259     } else {
260         SeparatorTactic::Never
261     };
262
263     let fmt = ListFormatting {
264         tactic: ListTactic::HorizontalVertical,
265         separator: ",",
266         trailing_separator: trailing_separator_tactic,
267         indent: indent,
268         h_width: width - 2,
269         v_width: width - 2,
270         is_expression: true,
271     };
272
273     Some(format!("({})", write_list(&items, &fmt)))
274 }