]> git.lizzy.rs Git - rust.git/commitdiff
Be careful about where we change braces in closures
authorNick Cameron <ncameron@mozilla.com>
Wed, 13 Apr 2016 20:36:59 +0000 (08:36 +1200)
committerNick Cameron <ncameron@mozilla.com>
Wed, 13 Apr 2016 21:05:42 +0000 (09:05 +1200)
And some general refactoring of closure code.

Fixes #863

src/expr.rs
src/utils.rs
tests/source/closure.rs
tests/target/chains-no-overflow.rs
tests/target/chains.rs
tests/target/closure.rs

index 0c30aaf3f6fec3d2f69e4265aa787705ae4f6203..6cc8c3aa4a906406250d8a3a203504882d239209 100644 (file)
@@ -21,7 +21,7 @@
             DefinitiveListTactic, definitive_tactic, ListItem, format_item_list};
 use string::{StringFormat, rewrite_string};
 use utils::{CodeMapSpanUtils, extra_offset, last_line_width, wrap_str, binary_search,
-            first_line_width, semicolon_for_stmt, trimmed_last_line_width};
+            first_line_width, semicolon_for_stmt, trimmed_last_line_width, left_most_sub_expr};
 use visitor::FmtVisitor;
 use config::{Config, StructLitStyle, MultilineStyle};
 use comment::{FindUncommented, rewrite_comment, contains_comment, recover_comment_removed};
@@ -32,6 +32,7 @@
 
 use syntax::{ast, ptr};
 use syntax::codemap::{CodeMap, Span, BytePos, mk_sp};
+use syntax::parse::classify;
 use syntax::visit::Visitor;
 
 impl Rewrite for ast::Expr {
@@ -308,8 +309,13 @@ pub fn rewrite_array<'a, I>(expr_iter: I,
     Some(format!("[{}]", list_str))
 }
 
-// This functions is pretty messy because of the wrapping and unwrapping of
-// expressions into and from blocks. See rust issue #27872.
+// This functions is pretty messy because of the rules around closures and blocks:
+//   * the body of a closure is represented by an ast::Block, but that does not
+//     imply there are `{}` (unless the block is empty) (see rust issue #27872),
+//   * if there is a return type, then there must be braces,
+//   * if the first expression in the body ends with a block (i.e., is a
+//     statement without needing a semi-colon), then adding or removing braces
+//     can change whether it is treated as an expression or statement.
 fn rewrite_closure(capture: ast::CaptureBy,
                    fn_decl: &ast::FnDecl,
                    body: &ast::Block,
@@ -331,6 +337,8 @@ fn rewrite_closure(capture: ast::CaptureBy,
     // 1 = |
     let argument_offset = offset + 1;
     let ret_str = try_opt!(fn_decl.output.rewrite(context, budget, argument_offset));
+    let force_block = !ret_str.is_empty();
+
     // 1 = space between arguments and return type.
     let horizontal_budget = budget.checked_sub(ret_str.len() + 1).unwrap_or(0);
 
@@ -371,51 +379,91 @@ fn rewrite_closure(capture: ast::CaptureBy,
         prefix.push_str(&ret_str);
     }
 
-    // Try to format closure body as a single line expression without braces.
-    if is_simple_block(body, context.codemap) && !prefix.contains('\n') {
-        let (spacer, closer) = if ret_str.is_empty() {
-            (" ", "")
-        } else {
-            (" { ", " }")
-        };
-        let expr = body.expr.as_ref().unwrap();
-        // All closure bodies are blocks in the eyes of the AST, but we may not
-        // want to unwrap them when they only contain a single expression.
-        let inner_expr = match expr.node {
-            ast::ExprKind::Block(ref inner) if inner.stmts.is_empty() && inner.expr.is_some() &&
-                                               inner.rules == ast::BlockCheckMode::Default => {
-                inner.expr.as_ref().unwrap()
+    assert!(body.stmts.is_empty(),
+            "unexpected statements in closure: `{}`",
+            context.snippet(span));
+
+    if body.expr.is_none() {
+        return Some(format!("{} {{}}", prefix));
+    }
+
+    // 1 = space between `|...|` and body.
+    let extra_offset = extra_offset(&prefix, offset) + 1;
+    let budget = try_opt!(width.checked_sub(extra_offset));
+
+    // This is where we figure out whether to use braces or not.
+    let mut had_braces = false;
+    let mut inner_block = body;
+    if let ast::ExprKind::Block(ref inner) = inner_block.expr.as_ref().unwrap().node {
+        had_braces = true;
+        inner_block = inner;
+    };
+    assert!(!force_block || !had_braces,
+            "Closure requires braces, but they weren't present. How did this parse? `{}`",
+            context.snippet(span));
+
+    let try_single_line = is_simple_block(inner_block, context.codemap) &&
+                          inner_block.rules == ast::BlockCheckMode::Default;
+
+    if try_single_line && !force_block {
+        let must_preserve_braces =
+            !classify::expr_requires_semi_to_be_stmt(left_most_sub_expr(inner_block.expr
+                                                                                   .as_ref()
+                                                                                   .unwrap()));
+        if !(must_preserve_braces && had_braces) &&
+           (must_preserve_braces || !prefix.contains('\n')) {
+            // If we got here, then we can try to format without braces.
+
+            let inner_expr = inner_block.expr.as_ref().unwrap();
+            let mut rewrite = inner_expr.rewrite(context, budget, offset + extra_offset);
+
+            if must_preserve_braces {
+                // If we are here, then failure to rewrite is unacceptable.
+                if rewrite.is_none() {
+                    return None;
+                }
+            } else {
+                // Checks if rewrite succeeded and fits on a single line.
+                rewrite = and_one_line(rewrite);
             }
-            _ => expr,
-        };
-        let extra_offset = extra_offset(&prefix, offset) + spacer.len();
-        let budget = try_opt!(width.checked_sub(extra_offset + closer.len()));
-        let rewrite = inner_expr.rewrite(context, budget, offset + extra_offset);
+
+            if let Some(rewrite) = rewrite {
+                return Some(format!("{} {}", prefix, rewrite));
+            }
+        }
+    }
+
+    // If we fell through the above block, then we need braces, but we might
+    // still prefer a one-liner (we might also have fallen through because of
+    // lack of space).
+    if try_single_line && !prefix.contains('\n') {
+        let inner_expr = inner_block.expr.as_ref().unwrap();
+        // 4 = braces and spaces.
+        let mut rewrite = inner_expr.rewrite(context, budget - 4, offset + extra_offset);
 
         // Checks if rewrite succeeded and fits on a single line.
-        let accept_rewrite = rewrite.as_ref().map_or(false, |result| !result.contains('\n'));
+        rewrite = and_one_line(rewrite);
 
-        if accept_rewrite {
-            return Some(format!("{}{}{}{}", prefix, spacer, rewrite.unwrap(), closer));
+        if let Some(rewrite) = rewrite {
+            return Some(format!("{} {{ {} }}", prefix, rewrite));
         }
     }
 
     // We couldn't format the closure body as a single line expression; fall
     // back to block formatting.
-    let body_rewrite = body.expr
-                           .as_ref()
-                           .and_then(|body_expr| {
-                               if let ast::ExprKind::Block(ref inner) = body_expr.node {
-                                   Some(inner.rewrite(&context, 2, Indent::empty()))
-                               } else {
-                                   None
-                               }
-                           })
-                           .unwrap_or_else(|| body.rewrite(&context, 2, Indent::empty()));
+    let body_rewrite = inner_block.rewrite(&context, budget, Indent::empty());
 
     Some(format!("{} {}", prefix, try_opt!(body_rewrite)))
 }
 
+fn and_one_line(x: Option<String>) -> Option<String> {
+    x.and_then(|x| if x.contains('\n') {
+        None
+    } else {
+        Some(x)
+    })
+}
+
 fn nop_block_collapse(block_str: Option<String>, budget: usize) -> Option<String> {
     block_str.map(|block_str| {
         if block_str.starts_with("{") && budget >= 2 &&
index 862ceb1e13eb0974c4497e3a576f7f24060b08d3..a3c7c036879d66be2abb3a5f3645883cfeab9ca8 100644 (file)
@@ -334,3 +334,22 @@ fn bin_search_test() {
     assert_eq!(Some(()), binary_search(4, 125, &closure));
     assert_eq!(None, binary_search(6, 100, &closure));
 }
+
+pub fn left_most_sub_expr(e: &ast::Expr) -> &ast::Expr {
+    match e.node {
+        ast::ExprKind::InPlace(ref e, _) |
+        ast::ExprKind::Call(ref e, _) |
+        ast::ExprKind::Binary(_, ref e, _) |
+        ast::ExprKind::Cast(ref e, _) |
+        ast::ExprKind::Type(ref e, _) |
+        ast::ExprKind::Assign(ref e, _) |
+        ast::ExprKind::AssignOp(_, ref e, _) |
+        ast::ExprKind::Field(ref e, _) |
+        ast::ExprKind::TupField(ref e, _) |
+        ast::ExprKind::Index(ref e, _) |
+        ast::ExprKind::Range(Some(ref e), _, _) => left_most_sub_expr(e),
+        // FIXME needs Try in Syntex
+        // ast::ExprKind::Try(ref f) => left_most_sub_expr(e),
+        _ => e,
+    }
+}
index 6abd7f9c0a7197dea61636e6e953d581eb336e24..e125a683dc69b316a4a21699b0ccae4bd8cd0d42 100644 (file)
@@ -50,3 +50,10 @@ fn issue311() {
 
     (func)(0.0);
 }
+
+fn issue863() {
+    let closure = |x| match x {
+        0 => true,
+        _ => false,
+    } == true;
+}
index 400db5f8df50753edab873484c159ee74aa9a919..2479157bb4fe9f63a736d18fca99edab764ddf7f 100644 (file)
@@ -9,20 +9,16 @@ fn main() {
                        .ddddddddddddddddddddddddddd
                        .eeeeeeee();
 
-    x().y(|| {
-           match cond() {
-               true => (),
-               false => (),
-           }
+    x().y(|| match cond() {
+           true => (),
+           false => (),
        });
 
     loong_func()
-        .quux(move || {
-            if true {
-                1
-            } else {
-                2
-            }
+        .quux(move || if true {
+            1
+        } else {
+            2
         });
 
     fffffffffffffffffffffffffffffffffff(a,
index d8fca6d40fe294f22fcc8d777696262f4166429d..392ff355b8693101739e80adde61246906673c00 100644 (file)
@@ -16,19 +16,15 @@ fn main() {
 
     // Test case where first chain element isn't a path, but is shorter than
     // the size of a tab.
-    x().y(|| {
-        match cond() {
-            true => (),
-            false => (),
-        }
+    x().y(|| match cond() {
+        true => (),
+        false => (),
     });
 
-    loong_func().quux(move || {
-        if true {
-            1
-        } else {
-            2
-        }
+    loong_func().quux(move || if true {
+        1
+    } else {
+        2
     });
 
     some_fuuuuuuuuunction().method_call_a(aaaaa, bbbbb, |c| {
index 382919ce4035fc28ac0e18645d99ffae7cee62d8..467b07b38cac27a4b76bbe38ce0c5b5ceb1c5c21 100644 (file)
@@ -26,12 +26,10 @@ fn main() {
         }
     };
 
-    let block_me = |field| {
-        if true_story() {
-            1
-        } else {
-            2
-        }
+    let block_me = |field| if true_story() {
+        1
+    } else {
+        2
     };
 
     let unblock_me = |trivial| closure();
@@ -77,3 +75,10 @@ fn issue311() {
 
     (func)(0.0);
 }
+
+fn issue863() {
+    let closure = |x| match x {
+        0 => true,
+        _ => false,
+    } == true;
+}