Skip to content

Commit 4386fc2

Browse files
authored
Merge pull request #2068 from topecongiro/issue-2067
Fix a subtle bug in rewriting chain
2 parents 73b079d + 8b7defd commit 4386fc2

File tree

6 files changed

+83
-28
lines changed

6 files changed

+83
-28
lines changed

src/chains.rs

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ use config::IndentStyle;
6464
use expr::rewrite_call;
6565
use macros::convert_try_mac;
6666
use rewrite::{Rewrite, RewriteContext};
67-
use utils::{first_line_width, last_line_extendable, last_line_width, mk_sp, wrap_str};
67+
use utils::{first_line_width, last_line_extendable, last_line_width, mk_sp,
68+
trimmed_last_line_width, wrap_str};
6869

6970
use std::cmp::min;
7071
use std::iter;
@@ -125,7 +126,7 @@ pub fn rewrite_chain(expr: &ast::Expr, context: &RewriteContext, shape: Shape) -
125126

126127
let first_child_shape = if extend {
127128
let overhead = last_line_width(&parent_rewrite);
128-
let offset = parent_rewrite.lines().rev().next().unwrap().trim().len();
129+
let offset = trimmed_last_line_width(&parent_rewrite);
129130
match context.config.chain_indent() {
130131
IndentStyle::Visual => parent_shape.offset_left(overhead)?,
131132
IndentStyle::Block => parent_shape.block().offset_left(offset)?,
@@ -156,7 +157,7 @@ pub fn rewrite_chain(expr: &ast::Expr, context: &RewriteContext, shape: Shape) -
156157
last_line_width(&parent_rewrite)
157158
} else {
158159
rewrites.iter().fold(0, |a, b| a + b.len()) + parent_rewrite.len()
159-
};
160+
} + suffix_try_num;
160161
let one_line_budget = if rewrites.is_empty() && !context.config.chain_split_single_child() {
161162
shape.width
162163
} else {
@@ -165,25 +166,64 @@ pub fn rewrite_chain(expr: &ast::Expr, context: &RewriteContext, shape: Shape) -
165166
let all_in_one_line = !parent_rewrite_contains_newline
166167
&& rewrites.iter().all(|s| !s.contains('\n'))
167168
&& almost_total < one_line_budget;
168-
let last_shape = if rewrites.is_empty() {
169-
// We only have a single child.
170-
first_child_shape
171-
} else {
172-
match context.config.chain_indent() {
173-
IndentStyle::Visual => other_child_shape.sub_width(shape.rhs_overhead(context.config))?,
174-
IndentStyle::Block => other_child_shape,
175-
}
169+
let last_shape = match context.config.chain_indent() {
170+
IndentStyle::Visual => other_child_shape.sub_width(shape.rhs_overhead(context.config))?,
171+
IndentStyle::Block => other_child_shape,
176172
};
177173
let last_shape = last_shape.sub_width(suffix_try_num)?;
174+
175+
// Rewrite the last child. The last child of a chain requires special treatment. We need to
176+
// know whether 'overflowing' the last child make a better formatting:
177+
//
178+
// A chain with overflowing the last child:
179+
// ```
180+
// parent.child1.child2.last_child(
181+
// a,
182+
// b,
183+
// c,
184+
// )
185+
// ```
186+
//
187+
// A chain without overflowing the last child (in vertical layout):
188+
// ```
189+
// parent
190+
// .child1
191+
// .child2
192+
// .last_child(a, b, c)
193+
// ```
194+
//
195+
// In particular, overflowing is effective when the last child is a method with a multi-lined
196+
// block-like argument (e.g. closure):
197+
// ```
198+
// parent.child1.chlid2.last_child(|a, b, c| {
199+
// let x = foo(a, b, c);
200+
// let y = bar(a, b, c);
201+
//
202+
// // ...
203+
//
204+
// result
205+
// })
206+
// ```
207+
208+
// `rewrite_last` rewrites the last child on its own line. We use a closure here instead of
209+
// directly calling `rewrite_chain_subexpr()` to avoid exponential blowup.
178210
let rewrite_last = || rewrite_chain_subexpr(last_subexpr, total_span, context, last_shape);
179211
let (last_subexpr_str, fits_single_line) = if all_in_one_line || extend_last_subexr {
212+
// First we try to 'overflow' the last child and see if it looks better than using
213+
// vertical layout.
180214
parent_shape.offset_left(almost_total).map(|shape| {
181215
if let Some(rw) = rewrite_chain_subexpr(last_subexpr, total_span, context, shape) {
216+
// We allow overflowing here only if both of the following conditions match:
217+
// 1. The entire chain fits in a single line expect the last child.
218+
// 2. `last_chlid_str.lines().count() >= 5`.
182219
let line_count = rw.lines().count();
183220
let fits_single_line = almost_total + first_line_width(&rw) <= one_line_budget;
184-
if fits_single_line && (line_count >= 5 || extend_last_subexr) {
221+
if fits_single_line && line_count >= 5 {
185222
(Some(rw), true)
186223
} else {
224+
// We could not know whether overflowing is better than using vertical layout,
225+
// just by looking at the overflowed rewrite. Now we rewrite the last child
226+
// on its own line, and compare two rewrites to choose which is better.
187227
match rewrite_last() {
188228
Some(ref new_rw) if !fits_single_line => (Some(new_rw.clone()), false),
189229
Some(ref new_rw) if new_rw.lines().count() >= line_count => {

src/expr.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1871,7 +1871,9 @@ fn rewrite_pat_expr(
18711871
} else {
18721872
format!("{} ", matcher)
18731873
};
1874-
let pat_shape = shape.offset_left(matcher.len())?.sub_width(connector.len())?;
1874+
let pat_shape = shape
1875+
.offset_left(matcher.len())?
1876+
.sub_width(connector.len())?;
18751877
let pat_string = pat.rewrite(context, pat_shape)?;
18761878
let result = format!("{}{}{}", matcher, pat_string, connector);
18771879
return rewrite_assign_rhs(context, result, expr, shape);

src/items.rs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2037,9 +2037,7 @@ fn rewrite_args(
20372037
generics_str_contains_newline: bool,
20382038
) -> Option<String> {
20392039
let mut arg_item_strs = args.iter()
2040-
.map(|arg| {
2041-
arg.rewrite(context, Shape::legacy(multi_line_budget, arg_indent))
2042-
})
2040+
.map(|arg| arg.rewrite(context, Shape::legacy(multi_line_budget, arg_indent)))
20432041
.collect::<Option<Vec<_>>>()?;
20442042

20452043
// Account for sugary self.
@@ -2713,17 +2711,15 @@ impl Rewrite for ast::ForeignItem {
27132711
let span = mk_sp(self.span.lo(), self.span.hi() - BytePos(1));
27142712

27152713
let item_str = match self.node {
2716-
ast::ForeignItemKind::Fn(ref fn_decl, ref generics) => {
2717-
rewrite_fn_base(
2718-
context,
2719-
shape.indent,
2720-
self.ident,
2721-
&FnSig::new(fn_decl, generics, self.vis.clone()),
2722-
span,
2723-
false,
2724-
false,
2725-
).map(|(s, _)| format!("{};", s))
2726-
}
2714+
ast::ForeignItemKind::Fn(ref fn_decl, ref generics) => rewrite_fn_base(
2715+
context,
2716+
shape.indent,
2717+
self.ident,
2718+
&FnSig::new(fn_decl, generics, self.vis.clone()),
2719+
span,
2720+
false,
2721+
false,
2722+
).map(|(s, _)| format!("{};", s)),
27272723
ast::ForeignItemKind::Static(ref ty, is_mutable) => {
27282724
// FIXME(#21): we're dropping potential comments in between the
27292725
// function keywords here.

src/string.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ pub fn rewrite_string<'a>(orig: &str, fmt: &StringFormat<'a>) -> Option<String>
6767
let ender_length = fmt.line_end.len();
6868
// If we cannot put at least a single character per line, the rewrite won't
6969
// succeed.
70-
let max_chars = shape.width.checked_sub(fmt.opener.len() + ender_length + 1)? + 1;
70+
let max_chars = shape
71+
.width
72+
.checked_sub(fmt.opener.len() + ender_length + 1)? + 1;
7173

7274
// Snip a line at a time from `orig` until it is used up. Push the snippet
7375
// onto result.

tests/source/chains.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,10 @@ fn issue1392() {
163163
}
164164
"#.trim());
165165
}
166+
167+
// #2067
168+
impl Settings {
169+
fn save(&self) -> Result<()> {
170+
let mut file = File::create(&settings_path).chain_err(|| ErrorKind::WriteError(settings_path.clone()))?;
171+
}
172+
}

tests/target/chains.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,11 @@ fn issue1392() {
183183
"#.trim(),
184184
);
185185
}
186+
187+
// #2067
188+
impl Settings {
189+
fn save(&self) -> Result<()> {
190+
let mut file = File::create(&settings_path)
191+
.chain_err(|| ErrorKind::WriteError(settings_path.clone()))?;
192+
}
193+
}

0 commit comments

Comments
 (0)