blob: 07c0519373910ecdca3412d1050f1caa3f2ada21 [file] [log] [blame]
use rustc_ast::ast;
use crate::config::lists::*;
use crate::config::IndentStyle;
use crate::rewrite::{Rewrite, RewriteContext};
use crate::shape::Shape;
use crate::utils::{
first_line_width, is_single_line, last_line_width, trimmed_last_line_width, wrap_str,
};
/// Sigils that decorate a binop pair.
#[derive(Clone, Copy)]
pub(crate) struct PairParts<'a> {
prefix: &'a str,
infix: &'a str,
suffix: &'a str,
}
impl<'a> PairParts<'a> {
pub(crate) const fn new(prefix: &'a str, infix: &'a str, suffix: &'a str) -> Self {
Self {
prefix,
infix,
suffix,
}
}
pub(crate) fn infix(infix: &'a str) -> PairParts<'a> {
PairParts {
prefix: "",
infix,
suffix: "",
}
}
}
// Flattens a tree of pairs into a list and tries to rewrite them all at once.
// FIXME would be nice to reuse the lists API for this, but because each separator
// can be different, we can't.
pub(crate) fn rewrite_all_pairs(
expr: &ast::Expr,
shape: Shape,
context: &RewriteContext<'_>,
) -> Option<String> {
expr.flatten(context, shape).and_then(|list| {
if list.let_chain_count() > 0 && !list.can_rewrite_let_chain_single_line() {
rewrite_pairs_multiline(&list, shape, context)
} else {
// First we try formatting on one line.
rewrite_pairs_one_line(&list, shape, context)
.or_else(|| rewrite_pairs_multiline(&list, shape, context))
}
})
}
// This may return a multi-line result since we allow the last expression to go
// multiline in a 'single line' formatting.
fn rewrite_pairs_one_line<T: Rewrite>(
list: &PairList<'_, '_, T>,
shape: Shape,
context: &RewriteContext<'_>,
) -> Option<String> {
assert!(list.list.len() >= 2, "Not a pair?");
let mut result = String::new();
let base_shape = shape.block();
for ((_, rewrite), s) in list.list.iter().zip(list.separators.iter()) {
if let Some(rewrite) = rewrite {
if !is_single_line(rewrite) || result.len() > shape.width {
return None;
}
result.push_str(rewrite);
result.push(' ');
result.push_str(s);
result.push(' ');
} else {
return None;
}
}
let prefix_len = result.len();
let last = list.list.last()?.0;
let cur_shape = base_shape.offset_left(last_line_width(&result))?;
let last_rewrite = last.rewrite(context, cur_shape)?;
result.push_str(&last_rewrite);
if first_line_width(&result) > shape.width {
return None;
}
// Check the last expression in the list. We sometimes let this expression
// go over multiple lines, but we check for some ugly conditions.
if !(is_single_line(&result) || last_rewrite.starts_with('{'))
&& (last_rewrite.starts_with('(') || prefix_len > context.config.tab_spaces())
{
return None;
}
wrap_str(result, context.config.max_width(), shape)
}
fn rewrite_pairs_multiline<T: Rewrite>(
list: &PairList<'_, '_, T>,
shape: Shape,
context: &RewriteContext<'_>,
) -> Option<String> {
let rhs_offset = shape.rhs_overhead(context.config);
let nested_shape = (match context.config.indent_style() {
IndentStyle::Visual => shape.visual_indent(0),
IndentStyle::Block => shape.block_indent(context.config.tab_spaces()),
})
.with_max_width(context.config)
.sub_width(rhs_offset)?;
let indent_str = nested_shape.indent.to_string_with_newline(context.config);
let mut result = String::new();
result.push_str(list.list[0].1.as_ref()?);
for ((e, default_rw), s) in list.list[1..].iter().zip(list.separators.iter()) {
// The following test checks if we should keep two subexprs on the same
// line. We do this if not doing so would create an orphan and there is
// enough space to do so.
let offset = if result.contains('\n') {
0
} else {
shape.used_width()
};
if last_line_width(&result) + offset <= nested_shape.used_width() {
// We must snuggle the next line onto the previous line to avoid an orphan.
if let Some(line_shape) =
shape.offset_left(s.len() + 2 + trimmed_last_line_width(&result))
{
if let Some(rewrite) = e.rewrite(context, line_shape) {
result.push(' ');
result.push_str(s);
result.push(' ');
result.push_str(&rewrite);
continue;
}
}
}
match context.config.binop_separator() {
SeparatorPlace::Back => {
result.push(' ');
result.push_str(s);
result.push_str(&indent_str);
}
SeparatorPlace::Front => {
result.push_str(&indent_str);
result.push_str(s);
result.push(' ');
}
}
result.push_str(default_rw.as_ref()?);
}
Some(result)
}
// Rewrites a single pair.
pub(crate) fn rewrite_pair<LHS, RHS>(
lhs: &LHS,
rhs: &RHS,
pp: PairParts<'_>,
context: &RewriteContext<'_>,
shape: Shape,
separator_place: SeparatorPlace,
) -> Option<String>
where
LHS: Rewrite,
RHS: Rewrite,
{
let tab_spaces = context.config.tab_spaces();
let lhs_overhead = match separator_place {
SeparatorPlace::Back => shape.used_width() + pp.prefix.len() + pp.infix.trim_end().len(),
SeparatorPlace::Front => shape.used_width(),
};
let lhs_shape = Shape {
width: context.budget(lhs_overhead),
..shape
};
let lhs_result = lhs
.rewrite(context, lhs_shape)
.map(|lhs_str| format!("{}{}", pp.prefix, lhs_str))?;
// Try to put both lhs and rhs on the same line.
let rhs_orig_result = shape
.offset_left(last_line_width(&lhs_result) + pp.infix.len())
.and_then(|s| s.sub_width(pp.suffix.len()))
.and_then(|rhs_shape| rhs.rewrite(context, rhs_shape));
if let Some(ref rhs_result) = rhs_orig_result {
// If the length of the lhs is equal to or shorter than the tab width or
// the rhs looks like block expression, we put the rhs on the same
// line with the lhs even if the rhs is multi-lined.
let allow_same_line = lhs_result.len() <= tab_spaces
|| rhs_result
.lines()
.next()
.map(|first_line| first_line.ends_with('{'))
.unwrap_or(false);
if !rhs_result.contains('\n') || allow_same_line {
let one_line_width = last_line_width(&lhs_result)
+ pp.infix.len()
+ first_line_width(rhs_result)
+ pp.suffix.len();
if one_line_width <= shape.width {
return Some(format!(
"{}{}{}{}",
lhs_result, pp.infix, rhs_result, pp.suffix
));
}
}
}
// We have to use multiple lines.
// Re-evaluate the rhs because we have more space now:
let mut rhs_shape = match context.config.indent_style() {
IndentStyle::Visual => shape
.sub_width(pp.suffix.len() + pp.prefix.len())?
.visual_indent(pp.prefix.len()),
IndentStyle::Block => {
// Try to calculate the initial constraint on the right hand side.
let rhs_overhead = shape.rhs_overhead(context.config);
Shape::indented(shape.indent.block_indent(context.config), context.config)
.sub_width(rhs_overhead)?
}
};
let infix = match separator_place {
SeparatorPlace::Back => pp.infix.trim_end(),
SeparatorPlace::Front => pp.infix.trim_start(),
};
if separator_place == SeparatorPlace::Front {
rhs_shape = rhs_shape.offset_left(infix.len())?;
}
let rhs_result = rhs.rewrite(context, rhs_shape)?;
let indent_str = rhs_shape.indent.to_string_with_newline(context.config);
let infix_with_sep = match separator_place {
SeparatorPlace::Back => format!("{infix}{indent_str}"),
SeparatorPlace::Front => format!("{indent_str}{infix}"),
};
Some(format!(
"{}{}{}{}",
lhs_result, infix_with_sep, rhs_result, pp.suffix
))
}
// A pair which forms a tree and can be flattened (e.g., binops).
trait FlattenPair: Rewrite + Sized {
fn flatten(&self, _: &RewriteContext<'_>, _: Shape) -> Option<PairList<'_, '_, Self>> {
None
}
}
struct PairList<'a, 'b, T: Rewrite> {
list: Vec<(&'b T, Option<String>)>,
separators: Vec<&'a str>,
}
fn is_ident(expr: &ast::Expr) -> bool {
match &expr.kind {
ast::ExprKind::Path(None, path) if path.segments.len() == 1 => true,
ast::ExprKind::Unary(_, expr)
| ast::ExprKind::AddrOf(_, _, expr)
| ast::ExprKind::Paren(expr)
| ast::ExprKind::Try(expr) => is_ident(expr),
_ => false,
}
}
impl<'a, 'b> PairList<'a, 'b, ast::Expr> {
fn let_chain_count(&self) -> usize {
self.list
.iter()
.filter(|(expr, _)| matches!(expr.kind, ast::ExprKind::Let(..)))
.count()
}
fn can_rewrite_let_chain_single_line(&self) -> bool {
if self.list.len() != 2 {
return false;
}
let fist_item_is_ident = is_ident(self.list[0].0);
let second_item_is_let_chain = matches!(self.list[1].0.kind, ast::ExprKind::Let(..));
fist_item_is_ident && second_item_is_let_chain
}
}
impl FlattenPair for ast::Expr {
fn flatten(
&self,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<PairList<'_, '_, ast::Expr>> {
let top_op = match self.kind {
ast::ExprKind::Binary(op, _, _) => op.node,
_ => return None,
};
let default_rewrite = |node: &ast::Expr, sep: usize, is_first: bool| {
if is_first {
return node.rewrite(context, shape);
}
let nested_overhead = sep + 1;
let rhs_offset = shape.rhs_overhead(context.config);
let nested_shape = (match context.config.indent_style() {
IndentStyle::Visual => shape.visual_indent(0),
IndentStyle::Block => shape.block_indent(context.config.tab_spaces()),
})
.with_max_width(context.config)
.sub_width(rhs_offset)?;
let default_shape = match context.config.binop_separator() {
SeparatorPlace::Back => nested_shape.sub_width(nested_overhead)?,
SeparatorPlace::Front => nested_shape.offset_left(nested_overhead)?,
};
node.rewrite(context, default_shape)
};
// Turn a tree of binop expressions into a list using a depth-first,
// in-order traversal.
let mut stack = vec![];
let mut list = vec![];
let mut separators = vec![];
let mut node = self;
loop {
match node.kind {
ast::ExprKind::Binary(op, ref lhs, _) if op.node == top_op => {
stack.push(node);
node = lhs;
}
_ => {
let op_len = separators.last().map_or(0, |s: &&str| s.len());
let rw = default_rewrite(node, op_len, list.is_empty());
list.push((node, rw));
if let Some(pop) = stack.pop() {
match pop.kind {
ast::ExprKind::Binary(op, _, ref rhs) => {
separators.push(op.node.to_string());
node = rhs;
}
_ => unreachable!(),
}
} else {
break;
}
}
}
}
assert_eq!(list.len() - 1, separators.len());
Some(PairList { list, separators })
}
}
impl FlattenPair for ast::Ty {}
impl FlattenPair for ast::Pat {}