]> git.lizzy.rs Git - rust.git/blobdiff - src/expr.rs
Format the last expression-statement as expression (#3631)
[rust.git] / src / expr.rs
index 1dbcf73618a26e675b6faf2fdc89909a2e38284c..d7640bf98caf451340819bcb43fa0d6cf5a34db4 100644 (file)
@@ -1,5 +1,6 @@
 use std::borrow::Cow;
 use std::cmp::min;
+use std::iter;
 
 use itertools::Itertools;
 use syntax::parse::token::DelimToken;
@@ -32,7 +33,7 @@
 use crate::utils::{
     colon_spaces, contains_skip, count_newlines, first_line_ends_with, inner_attributes,
     last_line_extendable, last_line_width, mk_sp, outer_attributes, ptr_vec_to_ref_vec,
-    semicolon_for_expr, semicolon_for_stmt, wrap_str,
+    semicolon_for_expr, unicode_str_width, wrap_str,
 };
 use crate::vertical::rewrite_with_alignment;
 use crate::visitor::FmtVisitor;
@@ -44,12 +45,12 @@ fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String>
 }
 
 #[derive(Copy, Clone, PartialEq)]
-pub enum ExprType {
+pub(crate) enum ExprType {
     Statement,
     SubExpression,
 }
 
-pub fn format_expr(
+pub(crate) fn format_expr(
     expr: &ast::Expr,
     expr_type: ExprType,
     context: &RewriteContext<'_>,
@@ -102,6 +103,7 @@ pub fn format_expr(
             path,
             fields,
             base.as_ref().map(|e| &**e),
+            &expr.attrs,
             expr.span,
             shape,
         ),
@@ -182,9 +184,9 @@ pub fn format_expr(
                 Some("yield".to_string())
             }
         }
-        ast::ExprKind::Closure(capture, asyncness, movability, ref fn_decl, ref body, _) => {
+        ast::ExprKind::Closure(capture, ref is_async, movability, ref fn_decl, ref body, _) => {
             closures::rewrite_closure(
-                capture, asyncness, movability, fn_decl, body, expr.span, context, shape,
+                capture, is_async, movability, fn_decl, body, expr.span, context, shape,
             )
         }
         ast::ExprKind::Try(..) | ast::ExprKind::Field(..) | ast::ExprKind::MethodCall(..) => {
@@ -334,10 +336,6 @@ fn needs_space_after_range(rhs: &ast::Expr) -> bool {
                 ))
             }
         }
-        ast::ExprKind::ObsoleteInPlace(ref lhs, ref rhs) => lhs
-            .rewrite(context, shape)
-            .map(|s| s + " <-")
-            .and_then(|lhs| rewrite_assign_rhs(context, lhs, &**rhs, shape)),
         ast::ExprKind::Async(capture_by, _node_id, ref block) => {
             let mover = if capture_by == ast::CaptureBy::Value {
                 "move "
@@ -370,6 +368,18 @@ fn needs_space_after_range(rhs: &ast::Expr) -> bool {
                 ))
             }
         }
+        ast::ExprKind::Await(ast::AwaitOrigin::FieldLike, _) => rewrite_chain(expr, context, shape),
+        ast::ExprKind::Await(ast::AwaitOrigin::MacroLike, ref nested) => {
+            overflow::rewrite_with_parens(
+                context,
+                "await!",
+                iter::once(nested),
+                shape,
+                expr.span,
+                context.config.max_width(),
+                None,
+            )
+        }
         ast::ExprKind::Err => None,
     };
 
@@ -386,7 +396,7 @@ fn needs_space_after_range(rhs: &ast::Expr) -> bool {
         })
 }
 
-pub fn rewrite_array<'a, T: 'a + IntoOverflowableItem<'a>>(
+pub(crate) fn rewrite_array<'a, T: 'a + IntoOverflowableItem<'a>>(
     name: &'a str,
     exprs: impl Iterator<Item = &'a T>,
     span: Span,
@@ -492,7 +502,7 @@ fn rewrite_single_line_block(
     None
 }
 
-pub fn rewrite_block_with_visitor(
+pub(crate) fn rewrite_block_with_visitor(
     context: &RewriteContext<'_>,
     prefix: &str,
     block: &ast::Block,
@@ -558,30 +568,8 @@ fn rewrite_block(
     result
 }
 
-impl Rewrite for ast::Stmt {
-    fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
-        skip_out_of_file_lines_range!(context, self.span());
-
-        let result = match self.node {
-            ast::StmtKind::Local(ref local) => local.rewrite(context, shape),
-            ast::StmtKind::Expr(ref ex) | ast::StmtKind::Semi(ref ex) => {
-                let suffix = if semicolon_for_stmt(context, self) {
-                    ";"
-                } else {
-                    ""
-                };
-
-                let shape = shape.sub_width(suffix.len())?;
-                format_expr(ex, ExprType::Statement, context, shape).map(|s| s + suffix)
-            }
-            ast::StmtKind::Mac(..) | ast::StmtKind::Item(..) => None,
-        };
-        result.and_then(|res| recover_comment_removed(res, self.span(), context))
-    }
-}
-
 // Rewrite condition if the given expression has one.
-pub fn rewrite_cond(
+pub(crate) fn rewrite_cond(
     context: &RewriteContext<'_>,
     expr: &ast::Expr,
     shape: Shape,
@@ -1128,7 +1116,7 @@ fn extract_comment(span: Span, context: &RewriteContext<'_>, shape: Shape) -> Op
     }
 }
 
-pub fn block_contains_comment(block: &ast::Block, source_map: &SourceMap) -> bool {
+pub(crate) fn block_contains_comment(block: &ast::Block, source_map: &SourceMap) -> bool {
     let snippet = source_map.span_to_snippet(block.span).unwrap();
     contains_comment(&snippet)
 }
@@ -1137,7 +1125,7 @@ pub fn block_contains_comment(block: &ast::Block, source_map: &SourceMap) -> boo
 // attributes.
 // FIXME: incorrectly returns false when comment is contained completely within
 // the expression.
-pub fn is_simple_block(
+pub(crate) fn is_simple_block(
     block: &ast::Block,
     attrs: Option<&[ast::Attribute]>,
     source_map: &SourceMap,
@@ -1150,7 +1138,7 @@ pub fn is_simple_block(
 
 /// Checks whether a block contains at most one statement or expression, and no
 /// comments or attributes.
-pub fn is_simple_block_stmt(
+pub(crate) fn is_simple_block_stmt(
     block: &ast::Block,
     attrs: Option<&[ast::Attribute]>,
     source_map: &SourceMap,
@@ -1162,7 +1150,7 @@ pub fn is_simple_block_stmt(
 
 /// Checks whether a block contains no statements, expressions, comments, or
 /// inner attributes.
-pub fn is_empty_block(
+pub(crate) fn is_empty_block(
     block: &ast::Block,
     attrs: Option<&[ast::Attribute]>,
     source_map: &SourceMap,
@@ -1172,24 +1160,14 @@ pub fn is_empty_block(
         && attrs.map_or(true, |a| inner_attributes(a).is_empty())
 }
 
-pub fn stmt_is_expr(stmt: &ast::Stmt) -> bool {
+pub(crate) fn stmt_is_expr(stmt: &ast::Stmt) -> bool {
     match stmt.node {
         ast::StmtKind::Expr(..) => true,
         _ => false,
     }
 }
 
-pub(crate) fn stmt_is_if(stmt: &ast::Stmt) -> bool {
-    match stmt.node {
-        ast::StmtKind::Expr(ref e) => match e.node {
-            ast::ExprKind::If(..) => true,
-            _ => false,
-        },
-        _ => false,
-    }
-}
-
-pub fn is_unsafe_block(block: &ast::Block) -> bool {
+pub(crate) fn is_unsafe_block(block: &ast::Block) -> bool {
     if let ast::BlockCheckMode::Unsafe(..) = block.rules {
         true
     } else {
@@ -1197,7 +1175,7 @@ pub fn is_unsafe_block(block: &ast::Block) -> bool {
     }
 }
 
-pub fn rewrite_multiple_patterns(
+pub(crate) fn rewrite_multiple_patterns(
     context: &RewriteContext<'_>,
     pats: &[&ast::Pat],
     shape: Shape,
@@ -1230,7 +1208,11 @@ pub fn rewrite_multiple_patterns(
     write_list(&items, &fmt)
 }
 
-pub fn rewrite_literal(context: &RewriteContext<'_>, l: &ast::Lit, shape: Shape) -> Option<String> {
+pub(crate) fn rewrite_literal(
+    context: &RewriteContext<'_>,
+    l: &ast::Lit,
+    shape: Shape,
+) -> Option<String> {
     match l.node {
         ast::LitKind::Str(_, ast::StrStyle::Cooked) => rewrite_string_lit(context, l.span, shape),
         _ => wrap_str(
@@ -1297,7 +1279,7 @@ fn choose_separator_tactic(context: &RewriteContext<'_>, span: Span) -> Option<S
     }
 }
 
-pub fn rewrite_call(
+pub(crate) fn rewrite_call(
     context: &RewriteContext<'_>,
     callee: &str,
     args: &[ptr::P<ast::Expr>],
@@ -1315,7 +1297,7 @@ pub fn rewrite_call(
     )
 }
 
-pub fn is_simple_expr(expr: &ast::Expr) -> bool {
+pub(crate) fn is_simple_expr(expr: &ast::Expr) -> bool {
     match expr.node {
         ast::ExprKind::Lit(..) => true,
         ast::ExprKind::Path(ref qself, ref path) => qself.is_none() && path.segments.len() <= 1,
@@ -1333,16 +1315,17 @@ pub fn is_simple_expr(expr: &ast::Expr) -> bool {
     }
 }
 
-pub fn is_every_expr_simple(lists: &[OverflowableItem<'_>]) -> bool {
+pub(crate) fn is_every_expr_simple(lists: &[OverflowableItem<'_>]) -> bool {
     lists.iter().all(OverflowableItem::is_simple)
 }
 
-pub fn can_be_overflowed_expr(
+pub(crate) fn can_be_overflowed_expr(
     context: &RewriteContext<'_>,
     expr: &ast::Expr,
     args_len: usize,
 ) -> bool {
     match expr.node {
+        _ if !expr.attrs.is_empty() => false,
         ast::ExprKind::Match(..) => {
             (context.use_block_indent() && args_len == 1)
                 || (context.config.indent_style() == IndentStyle::Visual && args_len > 1)
@@ -1387,7 +1370,7 @@ pub fn can_be_overflowed_expr(
     }
 }
 
-pub fn is_nested_call(expr: &ast::Expr) -> bool {
+pub(crate) fn is_nested_call(expr: &ast::Expr) -> bool {
     match expr.node {
         ast::ExprKind::Call(..) | ast::ExprKind::Mac(..) => true,
         ast::ExprKind::AddrOf(_, ref expr)
@@ -1402,7 +1385,7 @@ pub fn is_nested_call(expr: &ast::Expr) -> bool {
 /// Returns `true` if a function call or a method call represented by the given span ends with a
 /// trailing comma. This function is used when rewriting macro, as adding or removing a trailing
 /// comma from macro can potentially break the code.
-pub fn span_ends_with_comma(context: &RewriteContext<'_>, span: Span) -> bool {
+pub(crate) fn span_ends_with_comma(context: &RewriteContext<'_>, span: Span) -> bool {
     let mut result: bool = Default::default();
     let mut prev_char: char = Default::default();
     let closing_delimiters = &[')', '}', ']'];
@@ -1565,6 +1548,7 @@ fn rewrite_struct_lit<'a>(
     path: &ast::Path,
     fields: &'a [ast::Field],
     base: Option<&'a ast::Expr>,
+    attrs: &[ast::Attribute],
     span: Span,
     shape: Shape,
 ) -> Option<String> {
@@ -1659,42 +1643,57 @@ enum StructLitField<'a> {
         write_list(&item_vec, &fmt)?
     };
 
-    let fields_str = wrap_struct_field(context, &fields_str, shape, v_shape, one_line_width);
+    let fields_str =
+        wrap_struct_field(context, &attrs, &fields_str, shape, v_shape, one_line_width)?;
     Some(format!("{} {{{}}}", path_str, fields_str))
 
     // FIXME if context.config.indent_style() == Visual, but we run out
     // of space, we should fall back to BlockIndent.
 }
 
-pub fn wrap_struct_field(
+pub(crate) fn wrap_struct_field(
     context: &RewriteContext<'_>,
+    attrs: &[ast::Attribute],
     fields_str: &str,
     shape: Shape,
     nested_shape: Shape,
     one_line_width: usize,
-) -> String {
-    if context.config.indent_style() == IndentStyle::Block
+) -> Option<String> {
+    let should_vertical = context.config.indent_style() == IndentStyle::Block
         && (fields_str.contains('\n')
             || !context.config.struct_lit_single_line()
-            || fields_str.len() > one_line_width)
-    {
-        format!(
-            "{}{}{}",
+            || fields_str.len() > one_line_width);
+
+    let inner_attrs = &inner_attributes(attrs);
+    if inner_attrs.is_empty() {
+        if should_vertical {
+            Some(format!(
+                "{}{}{}",
+                nested_shape.indent.to_string_with_newline(context.config),
+                fields_str,
+                shape.indent.to_string_with_newline(context.config)
+            ))
+        } else {
+            // One liner or visual indent.
+            Some(format!(" {} ", fields_str))
+        }
+    } else {
+        Some(format!(
+            "{}{}{}{}{}",
+            nested_shape.indent.to_string_with_newline(context.config),
+            inner_attrs.rewrite(context, shape)?,
             nested_shape.indent.to_string_with_newline(context.config),
             fields_str,
             shape.indent.to_string_with_newline(context.config)
-        )
-    } else {
-        // One liner or visual indent.
-        format!(" {} ", fields_str)
+        ))
     }
 }
 
-pub fn struct_lit_field_separator(config: &Config) -> &str {
+pub(crate) fn struct_lit_field_separator(config: &Config) -> &str {
     colon_spaces(config)
 }
 
-pub fn rewrite_field(
+pub(crate) fn rewrite_field(
     context: &RewriteContext<'_>,
     field: &ast::Field,
     shape: Shape,
@@ -1791,7 +1790,7 @@ fn rewrite_tuple_in_visual_indent_style<'a, T: 'a + IntoOverflowableItem<'a>>(
     Some(format!("({})", list_str))
 }
 
-pub fn rewrite_tuple<'a, T: 'a + IntoOverflowableItem<'a>>(
+pub(crate) fn rewrite_tuple<'a, T: 'a + IntoOverflowableItem<'a>>(
     context: &'a RewriteContext<'_>,
     items: impl Iterator<Item = &'a T>,
     span: Span,
@@ -1826,7 +1825,7 @@ pub fn rewrite_tuple<'a, T: 'a + IntoOverflowableItem<'a>>(
     }
 }
 
-pub fn rewrite_unary_prefix<R: Rewrite>(
+pub(crate) fn rewrite_unary_prefix<R: Rewrite>(
     context: &RewriteContext<'_>,
     prefix: &str,
     rewrite: &R,
@@ -1839,7 +1838,7 @@ pub fn rewrite_unary_prefix<R: Rewrite>(
 
 // FIXME: this is probably not correct for multi-line Rewrites. we should
 // subtract suffix.len() from the last line budget, not the first!
-pub fn rewrite_unary_suffix<R: Rewrite>(
+pub(crate) fn rewrite_unary_suffix<R: Rewrite>(
     context: &RewriteContext<'_>,
     suffix: &str,
     rewrite: &R,
@@ -1884,16 +1883,19 @@ fn rewrite_assignment(
 
 /// Controls where to put the rhs.
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
-pub enum RhsTactics {
+pub(crate) enum RhsTactics {
     /// Use heuristics.
     Default,
     /// Put the rhs on the next line if it uses multiple line, without extra indentation.
     ForceNextLineWithoutIndent,
+    /// Allow overflowing max width if neither `Default` nor `ForceNextLineWithoutIndent`
+    /// did not work.
+    AllowOverflow,
 }
 
 // The left hand side must contain everything up to, and including, the
 // assignment operator.
-pub fn rewrite_assign_rhs<S: Into<String>, R: Rewrite>(
+pub(crate) fn rewrite_assign_rhs<S: Into<String>, R: Rewrite>(
     context: &RewriteContext<'_>,
     lhs: S,
     ex: &R,
@@ -1902,7 +1904,7 @@ pub fn rewrite_assign_rhs<S: Into<String>, R: Rewrite>(
     rewrite_assign_rhs_with(context, lhs, ex, shape, RhsTactics::Default)
 }
 
-pub fn rewrite_assign_rhs_with<S: Into<String>, R: Rewrite>(
+pub(crate) fn rewrite_assign_rhs_with<S: Into<String>, R: Rewrite>(
     context: &RewriteContext<'_>,
     lhs: S,
     ex: &R,
@@ -1939,7 +1941,9 @@ fn choose_rhs<R: Rewrite>(
     rhs_tactics: RhsTactics,
 ) -> Option<String> {
     match orig_rhs {
-        Some(ref new_str) if !new_str.contains('\n') && new_str.len() <= shape.width => {
+        Some(ref new_str)
+            if !new_str.contains('\n') && unicode_str_width(new_str) <= shape.width =>
+        {
             Some(format!(" {}", new_str))
         }
         _ => {
@@ -1965,6 +1969,10 @@ fn choose_rhs<R: Rewrite>(
                     Some(format!("{}{}", new_indent_str, new_rhs))
                 }
                 (None, Some(ref new_rhs)) => Some(format!("{}{}", new_indent_str, new_rhs)),
+                (None, None) if rhs_tactics == RhsTactics::AllowOverflow => {
+                    let shape = shape.infinite_width();
+                    expr.rewrite(context, shape).map(|s| format!(" {}", s))
+                }
                 (None, None) => None,
                 (Some(orig_rhs), _) => Some(format!(" {}", orig_rhs)),
             }
@@ -1981,14 +1989,27 @@ fn shape_from_rhs_tactic(
         RhsTactics::ForceNextLineWithoutIndent => shape
             .with_max_width(context.config)
             .sub_width(shape.indent.width()),
-        RhsTactics::Default => {
+        RhsTactics::Default | RhsTactics::AllowOverflow => {
             Shape::indented(shape.indent.block_indent(context.config), context.config)
                 .sub_width(shape.rhs_overhead(context.config))
         }
     }
 }
 
-pub fn prefer_next_line(orig_rhs: &str, next_line_rhs: &str, rhs_tactics: RhsTactics) -> bool {
+/// Returns true if formatting next_line_rhs is better on a new line when compared to the
+/// original's line formatting.
+///
+/// It is considered better if:
+/// 1. the tactic is ForceNextLineWithoutIndent
+/// 2. next_line_rhs doesn't have newlines
+/// 3. the original line has more newlines than next_line_rhs
+/// 4. the original formatting of the first line ends with `(`, `{`, or `[` and next_line_rhs
+///    doesn't
+pub(crate) fn prefer_next_line(
+    orig_rhs: &str,
+    next_line_rhs: &str,
+    rhs_tactics: RhsTactics,
+) -> bool {
     rhs_tactics == RhsTactics::ForceNextLineWithoutIndent
         || !next_line_rhs.contains('\n')
         || count_newlines(orig_rhs) > count_newlines(next_line_rhs) + 1
@@ -2010,7 +2031,7 @@ fn rewrite_expr_addrof(
     rewrite_unary_prefix(context, operator_str, expr, shape)
 }
 
-pub fn is_method_call(expr: &ast::Expr) -> bool {
+pub(crate) fn is_method_call(expr: &ast::Expr) -> bool {
     match expr.node {
         ast::ExprKind::MethodCall(..) => true,
         ast::ExprKind::AddrOf(_, ref expr)