Skip to content

Commit 0b7b3c8

Browse files
committed
Merge pull request #216 from marcusklaas/format-fields
WIP: Format expression chains
2 parents 1af301c + 7f576b0 commit 0b7b3c8

30 files changed

+740
-360
lines changed

src/bin/rustfmt.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ fn main() {
8282
}
8383

8484
fn print_usage<S: Into<String>>(reason: S) {
85-
println!("{}\n\r usage: rustfmt [-h Help] [--write-mode=[replace|overwrite|display|diff]] <file_name>", reason.into());
85+
println!("{}\n\r usage: rustfmt [-h Help] [--write-mode=[replace|overwrite|display|diff]] \
86+
<file_name>",
87+
reason.into());
8688
}
8789

8890
fn determine_params<I>(args: I) -> Option<(Vec<String>, WriteMode)>
@@ -123,6 +125,5 @@ fn determine_params<I>(args: I) -> Option<(Vec<String>, WriteMode)>
123125
return None;
124126
}
125127

126-
127128
Some((rustc_args, write_mode))
128129
}

src/chains.rs

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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::{first_line_width, 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 mut rewrites = try_opt!(subexpr_list.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+
// Total of all items excluding the last.
66+
let almost_total = rewrites.split_last()
67+
.unwrap()
68+
.1
69+
.iter()
70+
.fold(0, |a, b| a + first_line_width(b)) +
71+
parent_rewrite.len();
72+
let total_width = almost_total + first_line_width(rewrites.last().unwrap());
73+
let veto_single_line = if context.config.take_source_hints && subexpr_list.len() > 1 {
74+
// Look at the source code. Unless all chain elements start on the same
75+
// line, we won't consider putting them on a single line either.
76+
let first_line_no = context.codemap.lookup_char_pos(subexpr_list[0].span.lo).line;
77+
78+
subexpr_list[1..]
79+
.iter()
80+
.any(|ex| context.codemap.lookup_char_pos(ex.span.hi).line != first_line_no)
81+
} else {
82+
false
83+
};
84+
85+
let fits_single_line = !veto_single_line &&
86+
match subexpr_list[0].node {
87+
ast::Expr_::ExprMethodCall(ref method_name, ref types, ref expressions)
88+
if context.config.chains_overflow_last => {
89+
let (last, init) = rewrites.split_last_mut().unwrap();
90+
91+
if init.iter().all(|s| !s.contains('\n')) && total_width <= width {
92+
let last_rewrite = width.checked_sub(almost_total)
93+
.and_then(|inner_width| {
94+
rewrite_method_call(method_name.node,
95+
types,
96+
expressions,
97+
total_span,
98+
context,
99+
inner_width,
100+
offset + almost_total)
101+
});
102+
match last_rewrite {
103+
Some(mut string) => {
104+
::std::mem::swap(&mut string, last);
105+
true
106+
}
107+
None => false,
108+
}
109+
} else {
110+
false
111+
}
112+
}
113+
_ => total_width <= width && rewrites.iter().all(|s| !s.contains('\n')),
114+
};
115+
116+
let connector = if fits_single_line {
117+
String::new()
118+
} else {
119+
format!("\n{}", make_indent(indent))
120+
};
121+
122+
let first_connector = if extend {
123+
""
124+
} else {
125+
&connector[..]
126+
};
127+
128+
Some(format!("{}{}{}", parent_rewrite, first_connector, rewrites.join(&connector)))
129+
}
130+
131+
fn pop_expr_chain<'a>(expr: &'a ast::Expr) -> Option<&'a ast::Expr> {
132+
match expr.node {
133+
ast::Expr_::ExprMethodCall(_, _, ref expressions) => {
134+
Some(&expressions[0])
135+
}
136+
ast::Expr_::ExprTupField(ref subexpr, _) |
137+
ast::Expr_::ExprField(ref subexpr, _) => {
138+
Some(subexpr)
139+
}
140+
_ => None,
141+
}
142+
}
143+
144+
fn rewrite_chain_expr(expr: &ast::Expr,
145+
span: Span,
146+
context: &RewriteContext,
147+
width: usize,
148+
offset: usize)
149+
-> Option<String> {
150+
match expr.node {
151+
ast::Expr_::ExprMethodCall(ref method_name, ref types, ref expressions) => {
152+
let inner = &RewriteContext {
153+
block_indent: offset,
154+
..*context
155+
};
156+
rewrite_method_call(method_name.node, types, expressions, span, inner, width, offset)
157+
}
158+
ast::Expr_::ExprField(_, ref field) => {
159+
Some(format!(".{}", field.node))
160+
}
161+
ast::Expr_::ExprTupField(_, ref field) => {
162+
Some(format!(".{}", field.node))
163+
}
164+
_ => unreachable!(),
165+
}
166+
}
167+
168+
// Determines we can continue formatting a given expression on the same line.
169+
fn is_continuable(expr: &ast::Expr) -> bool {
170+
match expr.node {
171+
ast::Expr_::ExprPath(..) => true,
172+
_ => false,
173+
}
174+
}
175+
176+
fn rewrite_method_call(method_name: ast::Ident,
177+
types: &[ptr::P<ast::Ty>],
178+
args: &[ptr::P<ast::Expr>],
179+
span: Span,
180+
context: &RewriteContext,
181+
width: usize,
182+
offset: usize)
183+
-> Option<String> {
184+
let type_str = if types.is_empty() {
185+
String::new()
186+
} else {
187+
let type_list = types.iter().map(|ty| pprust::ty_to_string(ty)).collect::<Vec<_>>();
188+
format!("::<{}>", type_list.join(", "))
189+
};
190+
191+
let callee_str = format!(".{}{}", method_name, type_str);
192+
let span = mk_sp(args[0].span.hi, span.hi);
193+
194+
rewrite_call(context, &callee_str, &args[1..], span, width, offset)
195+
}

src/comment.rs

Lines changed: 49 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ pub fn rewrite_comment(orig: &str, block_style: bool, width: usize, offset: usiz
2525
("// ", "", "// ")
2626
};
2727

28-
let max_chars = width.checked_sub(closer.len()).unwrap_or(1)
29-
.checked_sub(opener.len()).unwrap_or(1);
28+
let max_chars = width.checked_sub(closer.len() + opener.len()).unwrap_or(1);
3029

3130
let fmt = StringFormat {
3231
opener: "",
@@ -41,44 +40,45 @@ pub fn rewrite_comment(orig: &str, block_style: bool, width: usize, offset: usiz
4140
let indent_str = make_indent(offset);
4241
let line_breaks = s.chars().filter(|&c| c == '\n').count();
4342

44-
let (_, mut s) = s.lines().enumerate()
45-
.map(|(i, mut line)| {
46-
line = line.trim();
47-
48-
// Drop old closer.
49-
if i == line_breaks && line.ends_with("*/") && !line.starts_with("//") {
50-
line = &line[..(line.len() - 2)];
51-
}
52-
53-
line.trim_right()
54-
})
55-
.map(left_trim_comment_line)
56-
.map(|line| {
57-
if line_breaks == 0 {
58-
line.trim_left()
59-
} else {
60-
line
61-
}
62-
})
63-
.fold((true, opener.to_owned()), |(first, mut acc), line| {
64-
if !first {
65-
acc.push('\n');
66-
acc.push_str(&indent_str);
67-
acc.push_str(line_start);
68-
}
69-
70-
if line.len() > max_chars {
71-
acc.push_str(&rewrite_string(line, &fmt));
72-
} else {
73-
if line.len() == 0 {
74-
acc.pop(); // Remove space if this is an empty comment.
75-
} else {
76-
acc.push_str(line);
77-
}
78-
}
79-
80-
(false, acc)
81-
});
43+
let (_, mut s) = s.lines()
44+
.enumerate()
45+
.map(|(i, mut line)| {
46+
line = line.trim();
47+
// Drop old closer.
48+
if i == line_breaks && line.ends_with("*/") && !line.starts_with("//") {
49+
line = &line[..(line.len() - 2)];
50+
}
51+
52+
line.trim_right()
53+
})
54+
.map(left_trim_comment_line)
55+
.map(|line| {
56+
if line_breaks == 0 {
57+
line.trim_left()
58+
} else {
59+
line
60+
}
61+
})
62+
.fold((true, opener.to_owned()),
63+
|(first, mut acc), line| {
64+
if !first {
65+
acc.push('\n');
66+
acc.push_str(&indent_str);
67+
acc.push_str(line_start);
68+
}
69+
70+
if line.len() > max_chars {
71+
acc.push_str(&rewrite_string(line, &fmt));
72+
} else {
73+
if line.len() == 0 {
74+
acc.pop(); // Remove space if this is an empty comment.
75+
} else {
76+
acc.push_str(line);
77+
}
78+
}
79+
80+
(false, acc)
81+
});
8282

8383
s.push_str(closer);
8484

@@ -146,24 +146,11 @@ pub fn find_comment_end(s: &str) -> Option<usize> {
146146
}
147147
}
148148

149-
#[test]
150-
fn comment_end() {
151-
assert_eq!(Some(6), find_comment_end("// hi\n"));
152-
assert_eq!(Some(9), find_comment_end("/* sup */ "));
153-
assert_eq!(Some(9), find_comment_end("/*/**/ */ "));
154-
assert_eq!(Some(6), find_comment_end("/*/ */ weird!"));
155-
assert_eq!(None, find_comment_end("/* hi /* test */"));
156-
assert_eq!(None, find_comment_end("// hi /* test */"));
157-
assert_eq!(Some(9), find_comment_end("// hi /*\n."));
158-
}
159-
160-
161149
/// Returns true if text contains any comment.
162150
pub fn contains_comment(text: &str) -> bool {
163-
CharClasses::new(text.chars()).any(|(kind, _)| kind == CodeCharKind::Comment )
151+
CharClasses::new(text.chars()).any(|(kind, _)| kind == CodeCharKind::Comment)
164152
}
165153

166-
167154
struct CharClasses<T>
168155
where T: Iterator,
169156
T::Item: RichChar
@@ -324,10 +311,14 @@ mod test {
324311
// This is probably intended to be a non-test fn, but it is not used. I'm
325312
// keeping it around unless it helps us test stuff.
326313
fn uncommented(text: &str) -> String {
327-
CharClasses::new(text.chars()).filter_map(|(s, c)| match s {
328-
CodeCharKind::Normal => Some(c),
329-
CodeCharKind::Comment => None
330-
}).collect()
314+
CharClasses::new(text.chars())
315+
.filter_map(|(s, c)| {
316+
match s {
317+
CodeCharKind::Normal => Some(c),
318+
CodeCharKind::Comment => None,
319+
}
320+
})
321+
.collect()
331322
}
332323

333324
#[test]

src/config.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ create_config! {
133133
fn_args_density: Density,
134134
fn_args_layout: StructLitStyle,
135135
fn_arg_indent: BlockIndentStyle,
136-
where_density: Density, // Should we at least try to put the where clause on the same line as
137-
// the rest of the function decl?
136+
where_density: Density, // Should we at least try to put the where clause on
137+
// the same line as the rest of the function decl?
138138
where_indent: BlockIndentStyle, // Visual will be treated like Tabbed
139139
where_layout: ListTactic,
140140
where_pred_indent: BlockIndentStyle,
@@ -147,14 +147,14 @@ create_config! {
147147
report_todo: ReportTactic,
148148
report_fixme: ReportTactic,
149149
reorder_imports: bool, // Alphabetically, case sensitive.
150-
expr_indent_style: BlockIndentStyle,
151-
closure_indent_style: BlockIndentStyle,
152150
single_line_if_else: bool,
153151
format_strings: bool,
152+
chains_overflow_last: bool,
153+
take_source_hints: bool, // Retain some formatting characteristics from
154+
// the source code.
154155
}
155156

156157
impl Default for Config {
157-
158158
fn default() -> Config {
159159
Config {
160160
max_width: 100,
@@ -181,11 +181,10 @@ impl Default for Config {
181181
report_todo: ReportTactic::Always,
182182
report_fixme: ReportTactic::Never,
183183
reorder_imports: false,
184-
expr_indent_style: BlockIndentStyle::Tabbed,
185-
closure_indent_style: BlockIndentStyle::Visual,
186184
single_line_if_else: false,
187185
format_strings: true,
186+
chains_overflow_last: true,
187+
take_source_hints: true,
188188
}
189189
}
190-
191190
}

0 commit comments

Comments
 (0)