Skip to main content

rustfmt_nightly/
pairs.rs

1use rustc_ast::{ast, token};
2use rustc_span::Span;
3
4use crate::config::IndentStyle;
5use crate::config::lists::*;
6use crate::rewrite::{Rewrite, RewriteContext, RewriteErrorExt, RewriteResult};
7use crate::shape::Shape;
8use crate::spanned::Spanned;
9use crate::utils::{
10    first_line_width, is_single_line, last_line_width, trimmed_last_line_width, wrap_str,
11};
12
13/// Sigils that decorate a binop pair.
14#[derive(Clone, Copy)]
15pub(crate) struct PairParts<'a> {
16    prefix: &'a str,
17    infix: &'a str,
18    suffix: &'a str,
19}
20
21impl<'a> PairParts<'a> {
22    pub(crate) const fn new(prefix: &'a str, infix: &'a str, suffix: &'a str) -> Self {
23        Self {
24            prefix,
25            infix,
26            suffix,
27        }
28    }
29    pub(crate) fn infix(infix: &'a str) -> PairParts<'a> {
30        PairParts {
31            prefix: "",
32            infix,
33            suffix: "",
34        }
35    }
36}
37
38// Flattens a tree of pairs into a list and tries to rewrite them all at once.
39// FIXME would be nice to reuse the lists API for this, but because each separator
40// can be different, we can't.
41pub(crate) fn rewrite_all_pairs(
42    expr: &ast::Expr,
43    shape: Shape,
44    context: &RewriteContext<'_>,
45) -> RewriteResult {
46    expr.flatten(context, shape)
47        .unknown_error()
48        .and_then(|list| {
49            if list.let_chain_count() > 0 && !list.can_rewrite_let_chain_single_line() {
50                rewrite_pairs_multiline(&list, shape, context)
51            } else {
52                // First we try formatting on one line.
53                rewrite_pairs_one_line(&list, shape, context)
54                    .unknown_error()
55                    .or_else(|_| rewrite_pairs_multiline(&list, shape, context))
56            }
57        })
58}
59
60// This may return a multi-line result since we allow the last expression to go
61// multiline in a 'single line' formatting.
62fn rewrite_pairs_one_line<T: Rewrite>(
63    list: &PairList<'_, '_, T>,
64    shape: Shape,
65    context: &RewriteContext<'_>,
66) -> Option<String> {
67    assert!(list.list.len() >= 2, "Not a pair?");
68
69    let mut result = String::new();
70    let base_shape = shape.block();
71
72    for ((_, rewrite), s) in list.list.iter().zip(list.separators.iter()) {
73        if let Ok(rewrite) = rewrite {
74            if !is_single_line(rewrite) || result.len() > shape.width {
75                return None;
76            }
77
78            result.push_str(rewrite);
79            result.push(' ');
80            result.push_str(s);
81            result.push(' ');
82        } else {
83            return None;
84        }
85    }
86
87    let prefix_len = result.len();
88    let last = list.list.last()?.0;
89    let cur_shape =
90        base_shape.offset_left_opt(last_line_width(&result, context.config.tab_spaces()))?;
91    let last_rewrite = last.rewrite(context, cur_shape)?;
92    result.push_str(&last_rewrite);
93
94    if first_line_width(&result) > shape.width {
95        return None;
96    }
97
98    // Check the last expression in the list. We sometimes let this expression
99    // go over multiple lines, but we check for some ugly conditions.
100    if !(is_single_line(&result) || last_rewrite.starts_with('{'))
101        && (last_rewrite.starts_with('(') || prefix_len > context.config.tab_spaces())
102    {
103        return None;
104    }
105
106    wrap_str(
107        result,
108        context.config.max_width(),
109        context.config.tab_spaces(),
110        shape,
111    )
112}
113
114fn rewrite_pairs_multiline<T: Rewrite>(
115    list: &PairList<'_, '_, T>,
116    shape: Shape,
117    context: &RewriteContext<'_>,
118) -> RewriteResult {
119    let rhs_offset = shape.rhs_overhead(context.config);
120    let nested_shape = (match context.config.indent_style() {
121        IndentStyle::Visual => shape.visual_indent(0),
122        IndentStyle::Block => shape.block_indent(context.config.tab_spaces()),
123    })
124    .with_max_width(context.config)
125    .sub_width(rhs_offset, list.span)?;
126
127    let indent_str = nested_shape.indent.to_string_with_newline(context.config);
128    let mut result = String::new();
129
130    result.push_str(list.list[0].1.as_ref().map_err(|err| err.clone())?);
131
132    for ((e, default_rw), s) in list.list[1..].iter().zip(list.separators.iter()) {
133        // The following test checks if we should keep two subexprs on the same
134        // line. We do this if not doing so would create an orphan and there is
135        // enough space to do so.
136        let offset = if result.contains('\n') {
137            0
138        } else {
139            shape.used_width()
140        };
141        if last_line_width(&result, context.config.tab_spaces()) + offset
142            <= nested_shape.used_width()
143        {
144            // We must snuggle the next line onto the previous line to avoid an orphan.
145            if let Some(line_shape) =
146                shape.offset_left_opt(s.len() + 2 + trimmed_last_line_width(&result))
147            {
148                if let Ok(rewrite) = e.rewrite_result(context, line_shape) {
149                    result.push(' ');
150                    result.push_str(s);
151                    result.push(' ');
152                    result.push_str(&rewrite);
153                    continue;
154                }
155            }
156        }
157
158        match context.config.binop_separator() {
159            SeparatorPlace::Back => {
160                result.push(' ');
161                result.push_str(s);
162                result.push_str(&indent_str);
163            }
164            SeparatorPlace::Front => {
165                result.push_str(&indent_str);
166                result.push_str(s);
167                result.push(' ');
168            }
169        }
170
171        result.push_str(default_rw.as_ref().map_err(|err| err.clone())?);
172    }
173    Ok(result)
174}
175
176// Rewrites a single pair.
177pub(crate) fn rewrite_pair<LHS, RHS>(
178    lhs: &LHS,
179    rhs: &RHS,
180    pp: PairParts<'_>,
181    context: &RewriteContext<'_>,
182    shape: Shape,
183    separator_place: SeparatorPlace,
184) -> RewriteResult
185where
186    LHS: Rewrite + Spanned,
187    RHS: Rewrite + Spanned,
188{
189    let tab_spaces = context.config.tab_spaces();
190    let lhs_overhead = match separator_place {
191        SeparatorPlace::Back => shape.used_width() + pp.prefix.len() + pp.infix.trim_end().len(),
192        SeparatorPlace::Front => shape.used_width(),
193    };
194    let lhs_shape = Shape {
195        width: context.budget(lhs_overhead),
196        ..shape
197    };
198    let lhs_result = lhs
199        .rewrite_result(context, lhs_shape)
200        .map(|lhs_str| format!("{}{}", pp.prefix, lhs_str))?;
201
202    // Try to put both lhs and rhs on the same line.
203    let rhs_orig_result = shape
204        .offset_left_opt(last_line_width(&lhs_result, tab_spaces) + pp.infix.len())
205        .and_then(|s| s.sub_width_opt(pp.suffix.len()))
206        .and_then(|rhs_shape| rhs.rewrite_result(context, rhs_shape).ok());
207
208    if let Some(ref rhs_result) = rhs_orig_result {
209        // If the length of the lhs is equal to or shorter than the tab width or
210        // the rhs looks like block expression, we put the rhs on the same
211        // line with the lhs even if the rhs is multi-lined.
212        let allow_same_line = lhs_result.len() <= tab_spaces
213            || rhs_result
214                .lines()
215                .next()
216                .map(|first_line| first_line.ends_with('{'))
217                .unwrap_or(false);
218        if !rhs_result.contains('\n') || allow_same_line {
219            let one_line_width = last_line_width(&lhs_result, tab_spaces)
220                + pp.infix.len()
221                + first_line_width(rhs_result)
222                + pp.suffix.len();
223            if one_line_width <= shape.width {
224                return Ok(format!(
225                    "{}{}{}{}",
226                    lhs_result, pp.infix, rhs_result, pp.suffix
227                ));
228            }
229        }
230    }
231
232    // We have to use multiple lines.
233    // Re-evaluate the rhs because we have more space now:
234    let mut rhs_shape = match context.config.indent_style() {
235        IndentStyle::Visual => shape
236            .sub_width(pp.suffix.len() + pp.prefix.len(), rhs.span())?
237            .visual_indent(pp.prefix.len()),
238        IndentStyle::Block => {
239            // Try to calculate the initial constraint on the right hand side.
240            let rhs_overhead = shape.rhs_overhead(context.config);
241            Shape::indented(shape.indent.block_indent(context.config), context.config)
242                .sub_width(rhs_overhead, rhs.span())?
243        }
244    };
245    let infix = match separator_place {
246        SeparatorPlace::Back => pp.infix.trim_end(),
247        SeparatorPlace::Front => pp.infix.trim_start(),
248    };
249    if separator_place == SeparatorPlace::Front {
250        rhs_shape = rhs_shape.offset_left(infix.len(), rhs.span())?;
251    }
252    let rhs_result = rhs.rewrite_result(context, rhs_shape)?;
253    let indent_str = rhs_shape.indent.to_string_with_newline(context.config);
254    let infix_with_sep = match separator_place {
255        SeparatorPlace::Back => format!("{infix}{indent_str}"),
256        SeparatorPlace::Front => format!("{indent_str}{infix}"),
257    };
258    Ok(format!(
259        "{}{}{}{}",
260        lhs_result, infix_with_sep, rhs_result, pp.suffix
261    ))
262}
263
264// A pair which forms a tree and can be flattened (e.g., binops).
265trait FlattenPair: Rewrite + Sized {
266    fn flatten(&self, _: &RewriteContext<'_>, _: Shape) -> Option<PairList<'_, '_, Self>> {
267        None
268    }
269}
270
271struct PairList<'a, 'b, T: Rewrite> {
272    list: Vec<(&'b T, RewriteResult)>,
273    separators: Vec<&'a str>,
274    span: Span,
275}
276
277fn is_ident_or_bool_lit(expr: &ast::Expr) -> bool {
278    match &expr.kind {
279        ast::ExprKind::Path(None, path) if path.segments.len() == 1 => true,
280        ast::ExprKind::Lit(token::Lit {
281            kind: token::LitKind::Bool,
282            ..
283        }) => true,
284        ast::ExprKind::Unary(_, expr)
285        | ast::ExprKind::AddrOf(_, _, expr)
286        | ast::ExprKind::Paren(expr)
287        | ast::ExprKind::Try(expr) => is_ident_or_bool_lit(expr),
288        _ => false,
289    }
290}
291
292impl<'a, 'b> PairList<'a, 'b, ast::Expr> {
293    fn let_chain_count(&self) -> usize {
294        self.list
295            .iter()
296            .filter(|(expr, _)| matches!(expr.kind, ast::ExprKind::Let(..)))
297            .count()
298    }
299
300    fn can_rewrite_let_chain_single_line(&self) -> bool {
301        if self.list.len() != 2 {
302            return false;
303        }
304
305        let fist_item_is_ident_or_bool_lit = is_ident_or_bool_lit(self.list[0].0);
306        let second_item_is_let_chain = matches!(self.list[1].0.kind, ast::ExprKind::Let(..));
307
308        fist_item_is_ident_or_bool_lit && second_item_is_let_chain
309    }
310}
311
312impl FlattenPair for ast::Expr {
313    fn flatten(
314        &self,
315        context: &RewriteContext<'_>,
316        shape: Shape,
317    ) -> Option<PairList<'_, '_, ast::Expr>> {
318        let top_op = match self.kind {
319            ast::ExprKind::Binary(op, _, _) => op.node,
320            _ => return None,
321        };
322
323        let default_rewrite = |node: &ast::Expr, sep: usize, is_first: bool| {
324            if is_first {
325                return node.rewrite_result(context, shape);
326            }
327            let nested_overhead = sep + 1;
328            let rhs_offset = shape.rhs_overhead(context.config);
329            let nested_shape = (match context.config.indent_style() {
330                IndentStyle::Visual => shape.visual_indent(0),
331                IndentStyle::Block => shape.block_indent(context.config.tab_spaces()),
332            })
333            .with_max_width(context.config)
334            .sub_width(rhs_offset, node.span)?;
335            let default_shape = match context.config.binop_separator() {
336                SeparatorPlace::Back => nested_shape.sub_width(nested_overhead, node.span)?,
337                SeparatorPlace::Front => nested_shape.offset_left(nested_overhead, node.span)?,
338            };
339            node.rewrite_result(context, default_shape)
340        };
341
342        // Turn a tree of binop expressions into a list using a depth-first,
343        // in-order traversal.
344        let mut stack = vec![];
345        let mut list = vec![];
346        let mut separators = vec![];
347        let mut node = self;
348        let span = self.span();
349        loop {
350            match node.kind {
351                ast::ExprKind::Binary(op, ref lhs, _) if op.node == top_op => {
352                    stack.push(node);
353                    node = lhs;
354                }
355                _ => {
356                    let op_len = separators.last().map_or(0, |s: &&str| s.len());
357                    let rw = default_rewrite(node, op_len, list.is_empty());
358                    list.push((node, rw));
359                    if let Some(pop) = stack.pop() {
360                        match pop.kind {
361                            ast::ExprKind::Binary(op, _, ref rhs) => {
362                                separators.push(op.node.as_str());
363                                node = rhs;
364                            }
365                            _ => unreachable!(),
366                        }
367                    } else {
368                        break;
369                    }
370                }
371            }
372        }
373
374        assert_eq!(list.len() - 1, separators.len());
375        Some(PairList {
376            list,
377            separators,
378            span,
379        })
380    }
381}
382
383impl FlattenPair for ast::Ty {}
384impl FlattenPair for ast::Pat {}