Repository: dtolnay/syn
Pull request #1659 (2024-05-16 05:41:41)

Automatically insert necessary parentheses in ToTokens for Expr
Ported in large part from these rustc PRs:

- https://github.com/rust-lang/rust/pull/119105
- https://github.com/rust-lang/rust/pull/118726
- https://github.com/rust-lang/rust/pull/124191


Patch:
diff --git a/src/classify.rs b/src/classify.rs
index 7b1bbc32..1b0ff300 100644
--- a/src/classify.rs
+++ b/src/classify.rs
@@ -3,10 +3,11 @@ use crate::generics::TypeParamBound;
 use crate::path::{Path, PathArguments};
 use crate::punctuated::Punctuated;
 use crate::ty::{ReturnType, Type};
+#[cfg(feature = "full")]
 use proc_macro2::{Delimiter, TokenStream, TokenTree};
 use std::ops::ControlFlow;
 
-#[cfg(feature = "parsing")]
+#[cfg(feature = "full")]
 pub(crate) fn requires_semi_to_be_stmt(expr: &Expr) -> bool {
     match expr {
         Expr::Macro(expr) => !expr.mac.delimiter.is_brace(),
@@ -14,6 +15,7 @@ pub(crate) fn requires_semi_to_be_stmt(expr: &Expr) -> bool {
     }
 }
 
+#[cfg(feature = "full")]
 pub(crate) fn requires_comma_to_be_match_arm(expr: &Expr) -> bool {
     match expr {
         Expr::If(_)
@@ -58,7 +60,196 @@ pub(crate) fn requires_comma_to_be_match_arm(expr: &Expr) -> bool {
     }
 }
 
+#[cfg(all(feature = "printing", feature = "full"))]
+pub(crate) fn confusable_with_adjacent_block(mut expr: &Expr) -> bool {
+    let mut stack = Vec::new();
+
+    while let Some(next) = match expr {
+        Expr::Assign(e) => {
+            stack.push(&e.right);
+            Some(&e.left)
+        }
+        Expr::Await(e) => Some(&e.base),
+        Expr::Binary(e) => {
+            stack.push(&e.right);
+            Some(&e.left)
+        }
+        Expr::Break(e) => {
+            if let Some(Expr::Block(_)) = e.expr.as_deref() {
+                return true;
+            }
+            stack.pop()
+        }
+        Expr::Call(e) => Some(&e.func),
+        Expr::Cast(e) => Some(&e.expr),
+        Expr::Closure(e) => Some(&e.body),
+        Expr::Field(e) => Some(&e.base),
+        Expr::Index(e) => Some(&e.expr),
+        Expr::MethodCall(e) => Some(&e.receiver),
+        Expr::Range(e) => {
+            if let Some(Expr::Block(_)) = e.end.as_deref() {
+                return true;
+            }
+            match (&e.start, &e.end) {
+                (Some(start), end) => {
+                    stack.extend(end);
+                    Some(start)
+                }
+                (None, Some(end)) => Some(end),
+                (None, None) => stack.pop(),
+            }
+        }
+        Expr::Reference(e) => Some(&e.expr),
+        Expr::Return(e) => {
+            if e.expr.is_none() && stack.is_empty() {
+                return true;
+            }
+            stack.pop()
+        }
+        Expr::Struct(_) => return true,
+        Expr::Try(e) => Some(&e.expr),
+        Expr::Unary(e) => Some(&e.expr),
+        Expr::Yield(e) => {
+            if e.expr.is_none() && stack.is_empty() {
+                return true;
+            }
+            stack.pop()
+        }
+
+        Expr::Array(_)
+        | Expr::Async(_)
+        | Expr::Block(_)
+        | Expr::Const(_)
+        | Expr::Continue(_)
+        | Expr::ForLoop(_)
+        | Expr::Group(_)
+        | Expr::If(_)
+        | Expr::Infer(_)
+        | Expr::Let(_)
+        | Expr::Lit(_)
+        | Expr::Loop(_)
+        | Expr::Macro(_)
+        | Expr::Match(_)
+        | Expr::Paren(_)
+        | Expr::Path(_)
+        | Expr::Repeat(_)
+        | Expr::TryBlock(_)
+        | Expr::Tuple(_)
+        | Expr::Unsafe(_)
+        | Expr::Verbatim(_)
+        | Expr::While(_) => stack.pop(),
+    } {
+        expr = next;
+    }
+
+    false
+}
+
+#[cfg(feature = "printing")]
+pub(crate) fn confusable_with_adjacent_lt(mut expr: &Expr) -> bool {
+    loop {
+        match expr {
+            Expr::Binary(e) => expr = &e.right,
+            Expr::Cast(e) => return trailing_unparameterized_path(&e.ty),
+            Expr::Reference(e) => expr = &e.expr,
+            Expr::Unary(e) => expr = &e.expr,
+
+            Expr::Array(_)
+            | Expr::Assign(_)
+            | Expr::Async(_)
+            | Expr::Await(_)
+            | Expr::Block(_)
+            | Expr::Break(_)
+            | Expr::Call(_)
+            | Expr::Closure(_)
+            | Expr::Const(_)
+            | Expr::Continue(_)
+            | Expr::Field(_)
+            | Expr::ForLoop(_)
+            | Expr::Group(_)
+            | Expr::If(_)
+            | Expr::Index(_)
+            | Expr::Infer(_)
+            | Expr::Let(_)
+            | Expr::Lit(_)
+            | Expr::Loop(_)
+            | Expr::Macro(_)
+            | Expr::Match(_)
+            | Expr::MethodCall(_)
+            | Expr::Paren(_)
+            | Expr::Path(_)
+            | Expr::Range(_)
+            | Expr::Repeat(_)
+            | Expr::Return(_)
+            | Expr::Struct(_)
+            | Expr::Try(_)
+            | Expr::TryBlock(_)
+            | Expr::Tuple(_)
+            | Expr::Unsafe(_)
+            | Expr::Verbatim(_)
+            | Expr::While(_)
+            | Expr::Yield(_) => return false,
+        }
+    }
+
+    fn trailing_unparameterized_path(mut ty: &Type) -> bool {
+        loop {
+            match ty {
+                Type::BareFn(t) => match &t.output {
+                    ReturnType::Default => return false,
+                    ReturnType::Type(_, ret) => ty = ret,
+                },
+                Type::ImplTrait(t) => match last_type_in_bounds(&t.bounds) {
+                    ControlFlow::Break(trailing_path) => return trailing_path,
+                    ControlFlow::Continue(t) => ty = t,
+                },
+                Type::Path(t) => match last_type_in_path(&t.path) {
+                    ControlFlow::Break(trailing_path) => return trailing_path,
+                    ControlFlow::Continue(t) => ty = t,
+                },
+                Type::Ptr(t) => ty = &t.elem,
+                Type::Reference(t) => ty = &t.elem,
+                Type::TraitObject(t) => match last_type_in_bounds(&t.bounds) {
+                    ControlFlow::Break(trailing_path) => return trailing_path,
+                    ControlFlow::Continue(t) => ty = t,
+                },
+
+                Type::Array(_)
+                | Type::Group(_)
+                | Type::Infer(_)
+                | Type::Macro(_)
+                | Type::Never(_)
+                | Type::Paren(_)
+                | Type::Slice(_)
+                | Type::Tuple(_)
+                | Type::Verbatim(_) => return false,
+            }
+        }
+    }
+
+    fn last_type_in_path(path: &Path) -> ControlFlow<bool, &Type> {
+        match &path.segments.last().unwrap().arguments {
+            PathArguments::None => ControlFlow::Break(true),
+            PathArguments::AngleBracketed(_) => ControlFlow::Break(false),
+            PathArguments::Parenthesized(arg) => match &arg.output {
+                ReturnType::Default => ControlFlow::Break(false),
+                ReturnType::Type(_, ret) => ControlFlow::Continue(ret),
+            },
+        }
+    }
+
+    fn last_type_in_bounds(
+        bounds: &Punctuated<TypeParamBound, Token![+]>,
+    ) -> ControlFlow<bool, &Type> {
+        match bounds.last().unwrap() {
+            TypeParamBound::Trait(t) => last_type_in_path(&t.path),
+            TypeParamBound::Lifetime(_) | TypeParamBound::Verbatim(_) => ControlFlow::Break(false),
+        }
+    }
+}
+
 /// Whether the expression's last token is `}`.
+#[cfg(feature = "full")]
 pub(crate) fn expr_trailing_brace(mut expr: &Expr) -> bool {
     loop {
         match expr {
diff --git a/src/expr.rs b/src/expr.rs
index 2a9478da..6a1e92db 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -2976,41 +2976,30 @@ pub(crate) mod printing {
     use crate::attr::Attribute;
     #[cfg(feature = "full")]
     use crate::attr::FilterAttrs;
-    #[cfg(feature = "full")]
     use crate::classify;
     #[cfg(feature = "full")]
     use crate::expr::{
-        Arm, Expr, ExprArray, ExprAssign, ExprAsync, ExprAwait, ExprBlock, ExprBreak, ExprClosure,
+        Arm, ExprArray, ExprAssign, ExprAsync, ExprAwait, ExprBlock, ExprBreak, ExprClosure,
         ExprConst, ExprContinue, ExprForLoop, ExprIf, ExprInfer, ExprLet, ExprLoop, ExprMatch,
         ExprRange, ExprRepeat, ExprReturn, ExprTry, ExprTryBlock, ExprTuple, ExprUnsafe, ExprWhile,
         ExprYield, Label, RangeLimits,
     };
     use crate::expr::{
-        ExprBinary, ExprCall, ExprCast, ExprField, ExprGroup, ExprIndex, ExprLit, ExprMacro,
+        Expr, ExprBinary, ExprCall, ExprCast, ExprField, ExprGroup, ExprIndex, ExprLit, ExprMacro,
         ExprMethodCall, ExprParen, ExprPath, ExprReference, ExprStruct, ExprUnary, FieldValue,
         Index, Member,
     };
-    use crate::path;
     #[cfg(feature = "full")]
+    use crate::fixup::FixupContext;
+    use crate::op::BinOp;
+    use crate::path;
+    use crate::precedence::Precedence;
     use crate::token;
     #[cfg(feature = "full")]
     use crate::ty::ReturnType;
     use proc_macro2::{Literal, Span, TokenStream};
     use quote::{ToTokens, TokenStreamExt};
 
-    // If the given expression is a bare `ExprStruct`, wraps it in parenthesis
-    // before appending it to `TokenStream`.
-    #[cfg(feature = "full")]
-    fn wrap_bare_struct(tokens: &mut TokenStream, e: &Expr) {
-        if let Expr::Struct(_) = *e {
-            token::Paren::default().surround(tokens, |tokens| {
-                e.to_tokens(tokens);
-            });
-        } else {
-            e.to_tokens(tokens);
-        }
-    }
-
     #[cfg(feature = "full")]
     pub(crate) fn outer_attrs_to_tokens(attrs: &[Attribute], tokens: &mut TokenStream) {
         tokens.append_all(attrs.outer());
@@ -3024,6 +3013,107 @@ pub(crate) mod printing {
     #[cfg(not(feature = "full"))]
     pub(crate) fn outer_attrs_to_tokens(_attrs: &[Attribute], _tokens: &mut TokenStream) {}
 
+    #[cfg(feature = "full")]
+    fn print_condition(expr: &Expr, tokens: &mut TokenStream) {
+        print_subexpression(
+            expr,
+            classify::confusable_with_adjacent_block(expr),
+            tokens,
+            FixupContext::new_condition(),
+        );
+    }
+
+    fn print_subexpression(
+        expr: &Expr,
+        needs_group: bool,
+        tokens: &mut TokenStream,
+        #[cfg(feature = "full")] mut fixup: FixupContext,
+    ) {
+        #[cfg(not(feature = "full"))]
+        let do_print_expr = |tokens: &mut TokenStream| expr.to_tokens(tokens);
+
+        #[cfg(feature = "full")]
+        let do_print_expr = {
+            // If we are surrounding the whole cond in parentheses, such as:
+            //
+            //     if (return Struct {}) {}
+            //
+            // then there is no need for parenthesizing the individual struct
+            // expressions within. On the other hand if the whole cond is not
+            // parenthesized, then print_expr must parenthesize exterior struct
+            // literals.
+            //
+            //     if x == (Struct {}) {}
+            //
+            if needs_group {
+                fixup = FixupContext::default();
+            }
+            |tokens: &mut TokenStream| print_expr(expr, tokens, fixup)
+        };
+
+        if needs_group {
+            token::Paren::default().surround(tokens, do_print_expr);
+        } else {
+            do_print_expr(tokens);
+        }
+    }
+
+    #[cfg(feature = "full")]
+    pub(crate) fn print_expr(expr: &Expr, tokens: &mut TokenStream, mut fixup: FixupContext) {
+        let needs_group = fixup.would_cause_statement_boundary(expr);
+        if needs_group {
+            fixup = FixupContext::default();
+        }
+
+        let do_print_expr = |tokens: &mut TokenStream| match expr {
+            Expr::Array(e) => e.to_tokens(tokens),
+            Expr::Assign(e) => print_expr_assign(e, tokens, fixup),
+            Expr::Async(e) => e.to_tokens(tokens),
+            Expr::Await(e) => print_expr_await(e, tokens, fixup),
+            Expr::Binary(e) => print_expr_binary(e, tokens, fixup),
+            Expr::Block(e) => e.to_tokens(tokens),
+            Expr::Break(e) => print_expr_break(e, tokens, fixup),
+            Expr::Call(e) => print_expr_call(e, tokens, fixup),
+            Expr::Cast(e) => print_expr_cast(e, tokens, fixup),
+            Expr::Closure(e) => e.to_tokens(tokens),
+            Expr::Const(e) => e.to_tokens(tokens),
+            Expr::Continue(e) => e.to_tokens(tokens),
+            Expr::Field(e) => print_expr_field(e, tokens, fixup),
+            Expr::ForLoop(e) => e.to_tokens(tokens),
+            Expr::Group(e) => e.to_tokens(tokens),
+            Expr::If(e) => e.to_tokens(tokens),
+            Expr::Index(e) => print_expr_index(e, tokens, fixup),
+            Expr::Infer(e) => e.to_tokens(tokens),
+            Expr::Let(e) => print_expr_let(e, tokens, fixup),
+            Expr::Lit(e) => e.to_tokens(tokens),
+            Expr::Loop(e) => e.to_tokens(tokens),
+            Expr::Macro(e) => e.to_tokens(tokens),
+            Expr::Match(e) => e.to_tokens(tokens),
+            Expr::MethodCall(e) => print_expr_method_call(e, tokens, fixup),
+            Expr::Paren(e) => e.to_tokens(tokens),
+            Expr::Path(e) => e.to_tokens(tokens),
+            Expr::Range(e) => print_expr_range(e, tokens, fixup),
+            Expr::Reference(e) => print_expr_reference(e, tokens, fixup),
+            Expr::Repeat(e) => e.to_tokens(tokens),
+            Expr::Return(e) => print_expr_return(e, tokens, fixup),
+            Expr::Struct(e) => e.to_tokens(tokens),
+            Expr::Try(e) => print_expr_try(e, tokens, fixup),
+            Expr::TryBlock(e) => e.to_tokens(tokens),
+            Expr::Tuple(e) => e.to_tokens(tokens),
+            Expr::Unary(e) => print_expr_unary(e, tokens, fixup),
+            Expr::Unsafe(e) => e.to_tokens(tokens),
+            Expr::Verbatim(e) => e.to_tokens(tokens),
+            Expr::While(e) => e.to_tokens(tokens),
+            Expr::Yield(e) => print_expr_yield(e, tokens, fixup),
+        };
+
+        if needs_group {
+            token::Paren::default().surround(tokens, do_print_expr);
+        } else {
+            do_print_expr(tokens);
+        }
+    }
+
     #[cfg(feature = "full")]
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprArray {
@@ -3039,13 +3129,29 @@ pub(crate) mod printing {
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprAssign {
         fn to_tokens(&self, tokens: &mut TokenStream) {
-            outer_attrs_to_tokens(&self.attrs, tokens);
-            self.left.to_tokens(tokens);
-            self.eq_token.to_tokens(tokens);
-            self.right.to_tokens(tokens);
+            let fixup = FixupContext::default();
+            print_expr_assign(self, tokens, fixup);
         }
     }
 
+    #[cfg(feature = "full")]
+    fn print_expr_assign(e: &ExprAssign, tokens: &mut TokenStream, fixup: FixupContext) {
+        outer_attrs_to_tokens(&e.attrs, tokens);
+        print_subexpression(
+            &e.left,
+            Precedence::of(&e.left) <= Precedence::Assign,
+            tokens,
+            fixup.leftmost_subexpression(),
+        );
+        e.eq_token.to_tokens(tokens);
+        print_subexpression(
+            &e.right,
+            Precedence::of_rhs(&e.right) < Precedence::Assign,
+            tokens,
+            fixup.subsequent_subexpression(),
+        );
+    }
+
     #[cfg(feature = "full")]
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprAsync {
@@ -3061,21 +3167,94 @@ pub(crate) mod printing {
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprAwait {
         fn to_tokens(&self, tokens: &mut TokenStream) {
-            outer_attrs_to_tokens(&self.attrs, tokens);
-            self.base.to_tokens(tokens);
-            self.dot_token.to_tokens(tokens);
-            self.await_token.to_tokens(tokens);
+            let fixup = FixupContext::default();
+            print_expr_await(self, tokens, fixup);
         }
     }
 
+    #[cfg(feature = "full")]
+    fn print_expr_await(e: &ExprAwait, tokens: &mut TokenStream, fixup: FixupContext) {
+        outer_attrs_to_tokens(&e.attrs, tokens);
+        print_subexpression(
+            &e.base,
+            Precedence::of(&e.base) < Precedence::Postfix,
+            tokens,
+            fixup.leftmost_subexpression_with_dot(),
+        );
+        e.dot_token.to_tokens(tokens);
+        e.await_token.to_tokens(tokens);
+    }
+
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprBinary {
         fn to_tokens(&self, tokens: &mut TokenStream) {
-            outer_attrs_to_tokens(&self.attrs, tokens);
-            self.left.to_tokens(tokens);
-            self.op.to_tokens(tokens);
-            self.right.to_tokens(tokens);
+            print_expr_binary(
+                self,
+                tokens,
+                #[cfg(feature = "full")]
+                FixupContext::default(),
+            );
+        }
+    }
+
+    fn print_expr_binary(
+        e: &ExprBinary,
+        tokens: &mut TokenStream,
+        #[cfg(feature = "full")] fixup: FixupContext,
+    ) {
+        outer_attrs_to_tokens(&e.attrs, tokens);
+
+        let binop_prec = Precedence::of_binop(&e.op);
+        let left_prec = Precedence::of(&e.left);
+        let right_prec = Precedence::of_rhs(&e.right);
+        let (mut left_needs_group, right_needs_group) = if let Precedence::Assign = binop_prec {
+            (left_prec <= binop_prec, right_prec < binop_prec)
+        } else {
+            (left_prec < binop_prec, right_prec <= binop_prec)
+        };
+
+        // These cases require parenthesization independently of precedence.
+        match (&*e.left, &e.op) {
+            // `x as i32 < y` has the parser thinking that `i32 < y` is the
+            // beginning of a path type. It starts trying to parse `x as (i32 <
+            // y ...` instead of `(x as i32) < ...`. We need to convince it
+            // _not_ to do that.
+            (_, BinOp::Lt(_) | BinOp::Shl(_)) if classify::confusable_with_adjacent_lt(&e.left) => {
+                left_needs_group = true;
+            }
+
+            // We are given `(let _ = a) OP b`.
+            //
+            // - When `OP <= LAnd` we should print `let _ = a OP b` to avoid
+            //   redundant parens as the parser will interpret this as `(let _ =
+            //   a) OP b`.
+            //
+            // - Otherwise, e.g. when we have `(let a = b) < c` in AST, parens
+            //   are required since the parser would interpret `let a = b < c`
+            //   as `let a = (b < c)`. To achieve this, we force parens.
+            #[cfg(feature = "full")]
+            (Expr::Let(_), _) if binop_prec > Precedence::And => {
+                left_needs_group = true;
+            }
+
+            _ => {}
         }
+
+        print_subexpression(
+            &e.left,
+            left_needs_group,
+            tokens,
+            #[cfg(feature = "full")]
+            fixup.leftmost_subexpression(),
+        );
+        e.op.to_tokens(tokens);
+        print_subexpression(
+            &e.right,
+            right_needs_group,
+            tokens,
+            #[cfg(feature = "full")]
+            fixup.subsequent_subexpression(),
+        );
     }
 
     #[cfg(feature = "full")]
@@ -3095,34 +3274,87 @@ pub(crate) mod printing {
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprBreak {
         fn to_tokens(&self, tokens: &mut TokenStream) {
-            outer_attrs_to_tokens(&self.attrs, tokens);
-            self.break_token.to_tokens(tokens);
-            self.label.to_tokens(tokens);
-            self.expr.to_tokens(tokens);
+            let fixup = FixupContext::default();
+            print_expr_break(self, tokens, fixup);
+        }
+    }
+
+    #[cfg(feature = "full")]
+    fn print_expr_break(e: &ExprBreak, tokens: &mut TokenStream, fixup: FixupContext) {
+        outer_attrs_to_tokens(&e.attrs, tokens);
+        e.break_token.to_tokens(tokens);
+        e.label.to_tokens(tokens);
+        if let Some(expr) = &e.expr {
+            print_expr(expr, tokens, fixup.subsequent_subexpression());
         }
     }
 
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprCall {
         fn to_tokens(&self, tokens: &mut TokenStream) {
-            outer_attrs_to_tokens(&self.attrs, tokens);
-            self.func.to_tokens(tokens);
-            self.paren_token.surround(tokens, |tokens| {
-                self.args.to_tokens(tokens);
-            });
+            print_expr_call(
+                self,
+                tokens,
+                #[cfg(feature = "full")]
+                FixupContext::default(),
+            );
         }
     }
 
+    fn print_expr_call(
+        e: &ExprCall,
+        tokens: &mut TokenStream,
+        #[cfg(feature = "full")] fixup: FixupContext,
+    ) {
+        outer_attrs_to_tokens(&e.attrs, tokens);
+
+        let precedence = if let Expr::Field(_) = &*e.func {
+            Precedence::Any
+        } else {
+            Precedence::Postfix
+        };
+        print_subexpression(
+            &e.func,
+            Precedence::of(&e.func) < precedence,
+            tokens,
+            #[cfg(feature = "full")]
+            fixup.leftmost_subexpression(),
+        );
+
+        e.paren_token.surround(tokens, |tokens| {
+            e.args.to_tokens(tokens);
+        });
+    }
+
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprCast {
         fn to_tokens(&self, tokens: &mut TokenStream) {
-            outer_attrs_to_tokens(&self.attrs, tokens);
-            self.expr.to_tokens(tokens);
-            self.as_token.to_tokens(tokens);
-            self.ty.to_tokens(tokens);
+            print_expr_cast(
+                self,
+                tokens,
+                #[cfg(feature = "full")]
+                FixupContext::default(),
+            );
         }
     }
 
+    fn print_expr_cast(
+        e: &ExprCast,
+        tokens: &mut TokenStream,
+        #[cfg(feature = "full")] fixup: FixupContext,
+    ) {
+        outer_attrs_to_tokens(&e.attrs, tokens);
+        print_subexpression(
+            &e.expr,
+            Precedence::of(&e.expr) < Precedence::Cast,
+            tokens,
+            #[cfg(feature = "full")]
+            fixup.leftmost_subexpression(),
+        );
+        e.as_token.to_tokens(tokens);
+        e.ty.to_tokens(tokens);
+    }
+
     #[cfg(feature = "full")]
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprClosure {
@@ -3141,7 +3373,7 @@ pub(crate) mod printing {
                 self.body.to_tokens(tokens);
             } else {
                 token::Brace::default().surround(tokens, |tokens| {
-                    self.body.to_tokens(tokens);
+                    print_expr(&self.body, tokens, FixupContext::new_stmt());
                 });
             }
         }
@@ -3173,13 +3405,32 @@ pub(crate) mod printing {
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprField {
         fn to_tokens(&self, tokens: &mut TokenStream) {
-            outer_attrs_to_tokens(&self.attrs, tokens);
-            self.base.to_tokens(tokens);
-            self.dot_token.to_tokens(tokens);
-            self.member.to_tokens(tokens);
+            print_expr_field(
+                self,
+                tokens,
+                #[cfg(feature = "full")]
+                FixupContext::default(),
+            );
         }
     }
 
+    fn print_expr_field(
+        e: &ExprField,
+        tokens: &mut TokenStream,
+        #[cfg(feature = "full")] fixup: FixupContext,
+    ) {
+        outer_attrs_to_tokens(&e.attrs, tokens);
+        print_subexpression(
+            &e.base,
+            Precedence::of(&e.base) < Precedence::Postfix,
+            tokens,
+            #[cfg(feature = "full")]
+            fixup.leftmost_subexpression_with_dot(),
+        );
+        e.dot_token.to_tokens(tokens);
+        e.member.to_tokens(tokens);
+    }
+
     #[cfg(feature = "full")]
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprForLoop {
@@ -3189,7 +3440,7 @@ pub(crate) mod printing {
             self.for_token.to_tokens(tokens);
             self.pat.to_tokens(tokens);
             self.in_token.to_tokens(tokens);
-            wrap_bare_struct(tokens, &self.expr);
+            print_condition(&self.expr, tokens);
             self.body.brace_token.surround(tokens, |tokens| {
                 inner_attrs_to_tokens(&self.attrs, tokens);
                 tokens.append_all(&self.body.stmts);
@@ -3216,7 +3467,7 @@ pub(crate) mod printing {
             let mut expr = self;
             loop {
                 expr.if_token.to_tokens(tokens);
-                wrap_bare_struct(tokens, &expr.cond);
+                print_condition(&expr.cond, tokens);
                 expr.then_branch.to_tokens(tokens);
 
                 let (else_token, else_) = match &expr.else_branch {
@@ -3236,7 +3487,9 @@ pub(crate) mod printing {
                     // If this is not one of the valid expressions to exist in
                     // an else clause, wrap it in a block.
                     other => {
-                        token::Brace::default().surround(tokens, |tokens| other.to_tokens(tokens));
+                        token::Brace::default().surround(tokens, |tokens| {
+                            print_expr(other, tokens, FixupContext::new_stmt());
+                        });
                         break;
                     }
                 }
@@ -3247,14 +3500,33 @@ pub(crate) mod printing {
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprIndex {
         fn to_tokens(&self, tokens: &mut TokenStream) {
-            outer_attrs_to_tokens(&self.attrs, tokens);
-            self.expr.to_tokens(tokens);
-            self.bracket_token.surround(tokens, |tokens| {
-                self.index.to_tokens(tokens);
-            });
+            print_expr_index(
+                self,
+                tokens,
+                #[cfg(feature = "full")]
+                FixupContext::default(),
+            );
         }
     }
 
+    fn print_expr_index(
+        e: &ExprIndex,
+        tokens: &mut TokenStream,
+        #[cfg(feature = "full")] fixup: FixupContext,
+    ) {
+        outer_attrs_to_tokens(&e.attrs, tokens);
+        print_subexpression(
+            &e.expr,
+            Precedence::of(&e.expr) < Precedence::Postfix,
+            tokens,
+            #[cfg(feature = "full")]
+            fixup.leftmost_subexpression(),
+        );
+        e.bracket_token.surround(tokens, |tokens| {
+            e.index.to_tokens(tokens);
+        });
+    }
+
     #[cfg(feature = "full")]
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprInfer {
@@ -3268,14 +3540,25 @@ pub(crate) mod printing {
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprLet {
         fn to_tokens(&self, tokens: &mut TokenStream) {
-            outer_attrs_to_tokens(&self.attrs, tokens);
-            self.let_token.to_tokens(tokens);
-            self.pat.to_tokens(tokens);
-            self.eq_token.to_tokens(tokens);
-            wrap_bare_struct(tokens, &self.expr);
+            let fixup = FixupContext::default();
+            print_expr_let(self, tokens, fixup);
         }
     }
 
+    #[cfg(feature = "full")]
+    fn print_expr_let(e: &ExprLet, tokens: &mut TokenStream, fixup: FixupContext) {
+        outer_attrs_to_tokens(&e.attrs, tokens);
+        e.let_token.to_tokens(tokens);
+        e.pat.to_tokens(tokens);
+        e.eq_token.to_tokens(tokens);
+        print_subexpression(
+            &e.expr,
+            fixup.needs_group_as_let_scrutinee(&e.expr),
+            tokens,
+            FixupContext::default(),
+        );
+    }
+
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprLit {
         fn to_tokens(&self, tokens: &mut TokenStream) {
@@ -3312,7 +3595,7 @@ pub(crate) mod printing {
         fn to_tokens(&self, tokens: &mut TokenStream) {
             outer_attrs_to_tokens(&self.attrs, tokens);
             self.match_token.to_tokens(tokens);
-            wrap_bare_struct(tokens, &self.expr);
+            print_condition(&self.expr, tokens);
             self.brace_token.surround(tokens, |tokens| {
                 inner_attrs_to_tokens(&self.attrs, tokens);
                 for (i, arm) in self.arms.iter().enumerate() {
@@ -3334,17 +3617,36 @@ pub(crate) mod printing {
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprMethodCall {
         fn to_tokens(&self, tokens: &mut TokenStream) {
-            outer_attrs_to_tokens(&self.attrs, tokens);
-            self.receiver.to_tokens(tokens);
-            self.dot_token.to_tokens(tokens);
-            self.method.to_tokens(tokens);
-            self.turbofish.to_tokens(tokens);
-            self.paren_token.surround(tokens, |tokens| {
-                self.args.to_tokens(tokens);
-            });
+            print_expr_method_call(
+                self,
+                tokens,
+                #[cfg(feature = "full")]
+                FixupContext::default(),
+            );
         }
     }
 
+    fn print_expr_method_call(
+        e: &ExprMethodCall,
+        tokens: &mut TokenStream,
+        #[cfg(feature = "full")] fixup: FixupContext,
+    ) {
+        outer_attrs_to_tokens(&e.attrs, tokens);
+        print_subexpression(
+            &e.receiver,
+            Precedence::of(&e.receiver) < Precedence::Postfix,
+            tokens,
+            #[cfg(feature = "full")]
+            fixup.leftmost_subexpression_with_dot(),
+        );
+        e.dot_token.to_tokens(tokens);
+        e.method.to_tokens(tokens);
+        e.turbofish.to_tokens(tokens);
+        e.paren_token.surround(tokens, |tokens| {
+            e.args.to_tokens(tokens);
+        });
+    }
+
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprParen {
         fn to_tokens(&self, tokens: &mut TokenStream) {
@@ -3367,21 +3669,60 @@ pub(crate) mod printing {
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprRange {
         fn to_tokens(&self, tokens: &mut TokenStream) {
-            outer_attrs_to_tokens(&self.attrs, tokens);
-            self.start.to_tokens(tokens);
-            self.limits.to_tokens(tokens);
-            self.end.to_tokens(tokens);
+            let fixup = FixupContext::default();
+            print_expr_range(self, tokens, fixup);
+        }
+    }
+
+    #[cfg(feature = "full")]
+    fn print_expr_range(e: &ExprRange, tokens: &mut TokenStream, fixup: FixupContext) {
+        outer_attrs_to_tokens(&e.attrs, tokens);
+        if let Some(start) = &e.start {
+            print_subexpression(
+                start,
+                Precedence::of(start) <= Precedence::Range,
+                tokens,
+                fixup.leftmost_subexpression(),
+            );
+        }
+        e.limits.to_tokens(tokens);
+        if let Some(end) = &e.end {
+            print_subexpression(
+                end,
+                Precedence::of_rhs(end) <= Precedence::Range,
+                tokens,
+                fixup.subsequent_subexpression(),
+            );
         }
     }
 
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprReference {
         fn to_tokens(&self, tokens: &mut TokenStream) {
-            outer_attrs_to_tokens(&self.attrs, tokens);
-            self.and_token.to_tokens(tokens);
-            self.mutability.to_tokens(tokens);
-            self.expr.to_tokens(tokens);
-        }
+            print_expr_reference(
+                self,
+                tokens,
+                #[cfg(feature = "full")]
+                FixupContext::default(),
+            );
+        }
+    }
+
+    fn print_expr_reference(
+        e: &ExprReference,
+        tokens: &mut TokenStream,
+        #[cfg(feature = "full")] fixup: FixupContext,
+    ) {
+        outer_attrs_to_tokens(&e.attrs, tokens);
+        e.and_token.to_tokens(tokens);
+        e.mutability.to_tokens(tokens);
+        print_subexpression(
+            &e.expr,
+            Precedence::of_rhs(&e.expr) < Precedence::Prefix,
+            tokens,
+            #[cfg(feature = "full")]
+            fixup.subsequent_subexpression(),
+        );
     }
 
     #[cfg(feature = "full")]
@@ -3401,9 +3742,17 @@ pub(crate) mod printing {
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprReturn {
         fn to_tokens(&self, tokens: &mut TokenStream) {
-            outer_attrs_to_tokens(&self.attrs, tokens);
-            self.return_token.to_tokens(tokens);
-            self.expr.to_tokens(tokens);
+            let fixup = FixupContext::default();
+            print_expr_return(self, tokens, fixup);
+        }
+    }
+
+    #[cfg(feature = "full")]
+    fn print_expr_return(e: &ExprReturn, tokens: &mut TokenStream, fixup: FixupContext) {
+        outer_attrs_to_tokens(&e.attrs, tokens);
+        e.return_token.to_tokens(tokens);
+        if let Some(expr) = &e.expr {
+            print_expr(expr, tokens, fixup.subsequent_subexpression());
         }
     }
 
@@ -3428,12 +3777,23 @@ pub(crate) mod printing {
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprTry {
         fn to_tokens(&self, tokens: &mut TokenStream) {
-            outer_attrs_to_tokens(&self.attrs, tokens);
-            self.expr.to_tokens(tokens);
-            self.question_token.to_tokens(tokens);
+            let fixup = FixupContext::default();
+            print_expr_try(self, tokens, fixup);
         }
     }
 
+    #[cfg(feature = "full")]
+    fn print_expr_try(e: &ExprTry, tokens: &mut TokenStream, fixup: FixupContext) {
+        outer_attrs_to_tokens(&e.attrs, tokens);
+        print_subexpression(
+            &e.expr,
+            Precedence::of(&e.expr) < Precedence::Postfix,
+            tokens,
+            fixup.leftmost_subexpression_with_dot(),
+        );
+        e.question_token.to_tokens(tokens);
+    }
+
     #[cfg(feature = "full")]
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprTryBlock {
@@ -3463,10 +3823,29 @@ pub(crate) mod printing {
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprUnary {
         fn to_tokens(&self, tokens: &mut TokenStream) {
-            outer_attrs_to_tokens(&self.attrs, tokens);
-            self.op.to_tokens(tokens);
-            self.expr.to_tokens(tokens);
-        }
+            print_expr_unary(
+                self,
+                tokens,
+                #[cfg(feature = "full")]
+                FixupContext::default(),
+            );
+        }
+    }
+
+    fn print_expr_unary(
+        e: &ExprUnary,
+        tokens: &mut TokenStream,
+        #[cfg(feature = "full")] fixup: FixupContext,
+    ) {
+        outer_attrs_to_tokens(&e.attrs, tokens);
+        e.op.to_tokens(tokens);
+        print_subexpression(
+            &e.expr,
+            Precedence::of_rhs(&e.expr) < Precedence::Prefix,
+            tokens,
+            #[cfg(feature = "full")]
+            fixup.subsequent_subexpression(),
+        );
     }
 
     #[cfg(feature = "full")]
@@ -3489,7 +3868,7 @@ pub(crate) mod printing {
             outer_attrs_to_tokens(&self.attrs, tokens);
             self.label.to_tokens(tokens);
             self.while_token.to_tokens(tokens);
-            wrap_bare_struct(tokens, &self.cond);
+            print_condition(&self.cond, tokens);
             self.body.brace_token.surround(tokens, |tokens| {
                 inner_attrs_to_tokens(&self.attrs, tokens);
                 tokens.append_all(&self.body.stmts);
@@ -3501,9 +3880,17 @@ pub(crate) mod printing {
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for ExprYield {
         fn to_tokens(&self, tokens: &mut TokenStream) {
-            outer_attrs_to_tokens(&self.attrs, tokens);
-            self.yield_token.to_tokens(tokens);
-            self.expr.to_tokens(tokens);
+            let fixup = FixupContext::default();
+            print_expr_yield(self, tokens, fixup);
+        }
+    }
+
+    #[cfg(feature = "full")]
+    fn print_expr_yield(e: &ExprYield, tokens: &mut TokenStream, fixup: FixupContext) {
+        outer_attrs_to_tokens(&e.attrs, tokens);
+        e.yield_token.to_tokens(tokens);
+        if let Some(expr) = &e.expr {
+            print_expr(expr, tokens, fixup.subsequent_subexpression());
         }
     }
 
@@ -3518,7 +3905,7 @@ pub(crate) mod printing {
                 guard.to_tokens(tokens);
             }
             self.fat_arrow_token.to_tokens(tokens);
-            self.body.to_tokens(tokens);
+            print_expr(&self.body, tokens, FixupContext::new_match_arm());
             self.comma.to_tokens(tokens);
         }
     }
diff --git a/src/fixup.rs b/src/fixup.rs
new file mode 100644
index 00000000..b8b867d9
--- /dev/null
+++ b/src/fixup.rs
@@ -0,0 +1,215 @@
+use crate::classify;
+use crate::expr::Expr;
+use crate::precedence::Precedence;
+
+#[derive(Copy, Clone, Debug)]
+pub(crate) struct FixupContext {
+    // Print expression such that it can be parsed back as a statement
+    // consisting of the original expression.
+    //
+    // The effect of this is for binary operators in statement position to set
+    // `leftmost_subexpression_in_stmt` when printing their left-hand operand.
+    //
+    //     (match x {}) - 1;  // match needs parens when LHS of binary operator
+    //
+    //     match x {};  // not when its own statement
+    //
+    stmt: bool,
+
+    // This is the difference between:
+    //
+    //     (match x {}) - 1;  // subexpression needs parens
+    //
+    //     let _ = match x {} - 1;  // no parens
+    //
+    // There are 3 distinguishable contexts in which `print_expr` might be
+    // called with the expression `$match` as its argument, where `$match`
+    // represents an expression of kind `ExprKind::Match`:
+    //
+    //   - stmt=false leftmost_subexpression_in_stmt=false
+    //
+    //     Example: `let _ = $match - 1;`
+    //
+    //     No parentheses required.
+    //
+    //   - stmt=false leftmost_subexpression_in_stmt=true
+    //
+    //     Example: `$match - 1;`
+    //
+    //     Must parenthesize `($match)`, otherwise parsing back the output as a
+    //     statement would terminate the statement after the closing brace of
+    //     the match, parsing `-1;` as a separate statement.
+    //
+    //   - stmt=true leftmost_subexpression_in_stmt=false
+    //
+    //     Example: `$match;`
+    //
+    //     No parentheses required.
+    leftmost_subexpression_in_stmt: bool,
+
+    // Print expression such that it can be parsed as a match arm.
+    //
+    // This is almost equivalent to `stmt`, but the grammar diverges a tiny bit
+    // between statements and match arms when it comes to braced macro calls.
+    // Macro calls with brace delimiter terminate a statement without a
+    // semicolon, but do not terminate a match-arm without comma.
+    //
+    //     m! {} - 1;  // two statements: a macro call followed by -1 literal
+    //
+    //     match () {
+    //         _ => m! {} - 1,  // binary subtraction operator
+    //     }
+    //
+    match_arm: bool,
+
+    // This is almost equivalent to `leftmost_subexpression_in_stmt`, other than
+    // for braced macro calls.
+    //
+    // If we have `m! {} - 1` as an expression, the leftmost subexpression
+    // `m! {}` will need to be parenthesized in the statement case but not the
+    // match-arm case.
+    //
+    //     (m! {}) - 1;  // subexpression needs parens
+    //
+    //     match () {
+    //         _ => m! {} - 1,  // no parens
+    //     }
+    //
+    leftmost_subexpression_in_match_arm: bool,
+
+    // This is the difference between:
+    //
+    //     if let _ = (Struct {}) {}  // needs parens
+    //
+    //     match () {
+    //         () if let _ = Struct {} => {}  // no parens
+    //     }
+    //
+    parenthesize_exterior_struct_lit: bool,
+}
+
+/// The default amount of fixing is minimal fixing. Fixups should be turned on
+/// in a targeted fashion where needed.
+impl Default for FixupContext {
+    fn default() -> Self {
+        FixupContext {
+            stmt: false,
+            leftmost_subexpression_in_stmt: false,
+            match_arm: false,
+            leftmost_subexpression_in_match_arm: false,
+            parenthesize_exterior_struct_lit: false,
+        }
+    }
+}
+
+impl FixupContext {
+    /// Create the initial fixup for printing an expression in statement
+    /// position.
+    pub fn new_stmt() -> Self {
+        FixupContext {
+            stmt: true,
+            ..FixupContext::default()
+        }
+    }
+
+    /// Create the initial fixup for printing an expression as the right-hand
+    /// side of a match arm.
+    pub fn new_match_arm() -> Self {
+        FixupContext {
+            match_arm: true,
+            ..FixupContext::default()
+        }
+    }
+
+    /// Create the initial fixup for printing an expression as the "condition"
+    /// of an `if` or `while`. There are a few other positions which are
+    /// grammatically equivalent and also use this, such as the iterator
+    /// expression in `for` and the scrutinee in `match`.
+    pub fn new_condition() -> Self {
+        FixupContext {
+            parenthesize_exterior_struct_lit: true,
+            ..FixupContext::default()
+        }
+    }
+
+    /// Transform this fixup into the one that should apply when printing the
+    /// leftmost subexpression of the current expression.
+    ///
+    /// The leftmost subexpression is any subexpression that has the same first
+    /// token as the current expression, but has a different last token.
+    ///
+    /// For example in `$a + $b` and `$a.method()`, the subexpression `$a` is a
+    /// leftmost subexpression.
+    ///
+    /// Not every expression has a leftmost subexpression. For example neither
+    /// `-$a` nor `[$a]` have one.
+    pub fn leftmost_subexpression(self) -> Self {
+        FixupContext {
+            stmt: false,
+            leftmost_subexpression_in_stmt: self.stmt || self.leftmost_subexpression_in_stmt,
+            match_arm: false,
+            leftmost_subexpression_in_match_arm: self.match_arm
+                || self.leftmost_subexpression_in_match_arm,
+            ..self
+        }
+    }
+
+    /// Transform this fixup into the one that should apply when printing a
+    /// leftmost subexpression followed by a `.` or `?` token, which confer
+    /// different statement boundary rules compared to other leftmost
+    /// subexpressions.
+    pub fn leftmost_subexpression_with_dot(self) -> Self {
+        FixupContext {
+            stmt: self.stmt || self.leftmost_subexpression_in_stmt,
+            leftmost_subexpression_in_stmt: false,
+            match_arm: self.match_arm || self.leftmost_subexpression_in_match_arm,
+            leftmost_subexpression_in_match_arm: false,
+            ..self
+        }
+    }
+
+    /// Transform this fixup into the one that should apply when printing any
+    /// subexpression that is neither a leftmost subexpression nor surrounded in
+    /// delimiters.
+    ///
+    /// This is for any subexpression that has a different first token than the
+    /// current expression, and is not surrounded by a paren/bracket/brace. For
+    /// example the `$b` in `$a + $b` and `-$b`, but not the one in `[$b]` or
+    /// `$a.f($b)`.
+    pub fn subsequent_subexpression(self) -> Self {
+        FixupContext {
+            stmt: false,
+            leftmost_subexpression_in_stmt: false,
+            match_arm: false,
+            leftmost_subexpression_in_match_arm: false,
+            ..self
+        }
+    }
+
+    /// Determine whether parentheses are needed around the given expression to
+    /// head off an unintended statement boundary.
+    ///
+    /// The documentation on `FixupContext::leftmost_subexpression_in_stmt` has
+    /// examples.
+    pub fn would_cause_statement_boundary(self, expr: &Expr) -> bool {
+        (self.leftmost_subexpression_in_stmt && !classify::requires_semi_to_be_stmt(expr))
+            || (self.leftmost_subexpression_in_match_arm
+                && !classify::requires_comma_to_be_match_arm(expr))
+    }
+
+    /// Determine whether parentheses are needed around the given `let`
+    /// scrutinee.
+    ///
+    /// In `if let _ = $e {}`, some examples of `$e` that would need parentheses
+    /// are:
+    ///
+    ///   - `Struct {}.f()`, because otherwise the `{` would be misinterpreted
+    ///     as the opening of the if's then-block.
+    ///
+    ///   - `true && false`, because otherwise this would be misinterpreted as a
+    ///     "let chain".
+    pub fn needs_group_as_let_scrutinee(self, expr: &Expr) -> bool {
+        self.parenthesize_exterior_struct_lit && classify::confusable_with_adjacent_block(expr)
+            || Precedence::of_rhs(expr) <= Precedence::And
+    }
+}
diff --git a/src/generics.rs b/src/generics.rs
index 4440ce8b..0b677ff6 100644
--- a/src/generics.rs
+++ b/src/generics.rs
@@ -991,7 +991,11 @@ pub(crate) mod parsing {
 #[cfg(feature = "printing")]
 pub(crate) mod printing {
     use crate::attr::FilterAttrs;
+    #[cfg(feature = "full")]
+    use crate::expr;
     use crate::expr::Expr;
+    #[cfg(feature = "full")]
+    use crate::fixup::FixupContext;
     use crate::generics::{
         BoundLifetimes, ConstParam, GenericParam, Generics, ImplGenerics, LifetimeParam,
         PredicateLifetime, PredicateType, TraitBound, TraitBoundModifier, Turbofish, TypeGenerics,
@@ -1271,6 +1275,10 @@ pub(crate) mod printing {
             // ERROR CORRECTION: Add braces to make sure that the
             // generated code is valid.
             _ => token::Brace::default().surround(tokens, |tokens| {
+                #[cfg(feature = "full")]
+                expr::printing::print_expr(expr, tokens, FixupContext::new_stmt());
+
+                #[cfg(not(feature = "full"))]
                 expr.to_tokens(tokens);
             }),
         }
diff --git a/src/lib.rs b/src/lib.rs
index b05a0ce8..c73bd062 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -328,8 +328,10 @@ mod bigint;
 #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
 pub mod buffer;
 
-#[cfg(any(feature = "parsing", feature = "printing"))]
-#[cfg(feature = "full")]
+#[cfg(any(
+    all(feature = "parsing", feature = "full"),
+    all(feature = "printing", any(feature = "full", feature = "derive")),
+))]
 mod classify;
 
 mod custom_keyword;
@@ -383,6 +385,9 @@ mod file;
 #[cfg_attr(doc_cfg, doc(cfg(feature = "full")))]
 pub use crate::file::File;
 
+#[cfg(all(feature = "full", feature = "printing"))]
+mod fixup;
+
 #[cfg(any(feature = "full", feature = "derive"))]
 mod generics;
 #[cfg(any(feature = "full", feature = "derive"))]
@@ -478,7 +483,10 @@ pub use crate::path::{
     ParenthesizedGenericArguments, Path, PathArguments, PathSegment, QSelf,
 };
 
-#[cfg(all(any(feature = "full", feature = "derive"), feature = "parsing"))]
+#[cfg(all(
+    any(feature = "full", feature = "derive"),
+    any(feature = "parsing", feature = "printing")
+))]
 mod precedence;
 
 #[cfg(all(any(feature = "full", feature = "derive"), feature = "printing"))]
diff --git a/src/precedence.rs b/src/precedence.rs
index 25d90b4d..450958ad 100644
--- a/src/precedence.rs
+++ b/src/precedence.rs
@@ -1,4 +1,8 @@
+#[cfg(feature = "printing")]
+use crate::expr::Expr;
 use crate::op::BinOp;
+#[cfg(all(feature = "printing", feature = "full"))]
+use crate::ty::ReturnType;
 use std::cmp::Ordering;
 
 // Reference: https://doc.rust-lang.org/reference/expressions.html#expression-precedence
@@ -29,6 +33,15 @@ pub(crate) enum Precedence {
     Term,
     // as
     Cast,
+    // unary - * ! & &mut
+    #[cfg(feature = "printing")]
+    Prefix,
+    // function calls, array indexing, field expressions, method calls, ?
+    #[cfg(feature = "printing")]
+    Postfix,
+    // paths, loops
+    #[cfg(feature = "printing")]
+    Unambiguous,
 }
 
 impl Precedence {
@@ -42,12 +55,14 @@ impl Precedence {
             BinOp::BitAnd(_) => Precedence::BitAnd,
             BinOp::BitOr(_) => Precedence::BitOr,
             BinOp::Shl(_) | BinOp::Shr(_) => Precedence::Shift,
+
             BinOp::Eq(_)
             | BinOp::Lt(_)
             | BinOp::Le(_)
             | BinOp::Ne(_)
             | BinOp::Ge(_)
             | BinOp::Gt(_) => Precedence::Compare,
+
             BinOp::AddAssign(_)
             | BinOp::SubAssign(_)
             | BinOp::MulAssign(_)
@@ -60,6 +75,69 @@ impl Precedence {
             | BinOp::ShrAssign(_) => Precedence::Assign,
         }
     }
+
+    #[cfg(feature = "printing")]
+    pub(crate) fn of(e: &Expr) -> Self {
+        match e {
+            #[cfg(feature = "full")]
+            Expr::Closure(e) => match e.output {
+                ReturnType::Default => Precedence::Any,
+                ReturnType::Type(..) => Precedence::Unambiguous,
+            },
+
+            Expr::Break(_) | Expr::Return(_) | Expr::Yield(_) => Precedence::Any,
+            Expr::Assign(_) => Precedence::Assign,
+            Expr::Range(_) => Precedence::Range,
+            Expr::Binary(e) => Precedence::of_binop(&e.op),
+            Expr::Cast(_) => Precedence::Cast,
+            Expr::Let(_) | Expr::Reference(_) | Expr::Unary(_) => Precedence::Prefix,
+
+            Expr::Await(_)
+            | Expr::Call(_)
+            | Expr::MethodCall(_)
+            | Expr::Field(_)
+            | Expr::Index(_)
+            | Expr::Try(_) => Precedence::Postfix,
+
+            Expr::Array(_)
+            | Expr::Async(_)
+            | Expr::Block(_)
+            | Expr::Const(_)
+            | Expr::Continue(_)
+            | Expr::ForLoop(_)
+            | Expr::Group(_)
+            | Expr::If(_)
+            | Expr::Infer(_)
+            | Expr::Lit(_)
+            | Expr::Loop(_)
+            | Expr::Macro(_)
+            | Expr::Match(_)
+            | Expr::Paren(_)
+            | Expr::Path(_)
+            | Expr::Repeat(_)
+            | Expr::Struct(_)
+            | Expr::TryBlock(_)
+            | Expr::Tuple(_)
+            | Expr::Unsafe(_)
+            | Expr::Verbatim(_)
+            | Expr::While(_) => Precedence::Unambiguous,
+
+            #[cfg(not(feature = "full"))]
+            Expr::Closure(_) => unreachable!(),
+        }
+    }
+
+    #[cfg(feature = "printing")]
+    pub(crate) fn of_rhs(e: &Expr) -> Self {
+        match e {
+            Expr::Break(_) | Expr::Closure(_) | Expr::Return(_) | Expr::Yield(_) => {
+                Precedence::Prefix
+            }
+            #[cfg(feature = "full")]
+            Expr::Range(e) if e.start.is_none() => Precedence::Prefix,
+            _ => Precedence::of(e),
+        }
+    }
 }
 
 impl Copy for Precedence {}
diff --git a/src/stmt.rs b/src/stmt.rs
index fa6cf022..e2e7b5e0 100644
--- a/src/stmt.rs
+++ b/src/stmt.rs
@@ -410,9 +410,10 @@ pub(crate) mod parsing {
 }
 
 #[cfg(feature = "printing")]
-mod printing {
+pub(crate) mod printing {
     use crate::classify;
     use crate::expr::{self, Expr};
+    use crate::fixup::FixupContext;
     use crate::stmt::{Block, Local, Stmt, StmtMacro};
     use crate::token;
     use proc_macro2::TokenStream;
@@ -434,7 +435,7 @@ mod printing {
                 Stmt::Local(local) => local.to_tokens(tokens),
                 Stmt::Item(item) => item.to_tokens(tokens),
                 Stmt::Expr(expr, semi) => {
-                    expr.to_tokens(tokens);
+                    expr::printing::print_expr(expr, tokens, FixupContext::new_stmt());
                     semi.to_tokens(tokens);
                 }
                 Stmt::Macro(mac) => mac.to_tokens(tokens),
@@ -459,8 +460,9 @@ mod printing {
                     else_token.to_tokens(tokens);
                     match &**diverge {
                         Expr::Block(diverge) => diverge.to_tokens(tokens),
-                        _ => token::Brace::default()
-                            .surround(tokens, |tokens| diverge.to_tokens(tokens)),
+                        _ => token::Brace::default().surround(tokens, |tokens| {
+                            expr::printing::print_expr(diverge, tokens, FixupContext::new_stmt());
+                        }),
                     }
                 }
             }


Test patch:
diff --git a/tests/test_expr.rs b/tests/test_expr.rs
index f39200d3..961e3c4e 100644
--- a/tests/test_expr.rs
+++ b/tests/test_expr.rs
@@ -5,7 +5,9 @@ mod macros;
 
 use proc_macro2::{Delimiter, Group};
 use quote::{quote, ToTokens as _};
+use std::mem;
 use syn::punctuated::Punctuated;
+use syn::visit_mut::{self, VisitMut};
 use syn::{parse_quote, token, Expr, ExprRange, ExprTuple, Stmt, Token};
 
 #[test]
@@ -639,3 +641,52 @@ fn test_assign_range_precedence() {
     syn::parse_str::<Expr>("() .. () = ()").unwrap_err();
     syn::parse_str::<Expr>("() .. () += ()").unwrap_err();
 }
+
+#[test]
+fn test_fixup() {
+    struct FlattenParens;
+
+    impl VisitMut for FlattenParens {
+        fn visit_expr_mut(&mut self, e: &mut Expr) {
+            while let Expr::Paren(paren) = e {
+                *e = mem::replace(&mut *paren.expr, Expr::PLACEHOLDER);
+            }
+            visit_mut::visit_expr_mut(self, e);
+        }
+    }
+
+    for tokens in [
+        quote! { 2 * (1 + 1) },
+        quote! { 0 + (0 + 0) },
+        quote! { (a = b) = c },
+        quote! { (x as i32) < 0 },
+        quote! { (1 + x as i32) < 0 },
+        quote! { (1 + 1).abs() },
+        quote! { (lo..hi)[..] },
+        quote! { (a..b)..(c..d) },
+        quote! { (&mut fut).await },
+        quote! { &mut (x as i32) },
+        quote! { -(x as i32) },
+        quote! { if (S {} == 1) {} },
+        quote! { { (m! {}) - 1 } },
+        quote! { match m { _ => ({}) - 1 } },
+        quote! { if let _ = (a && b) && c {} },
+        quote! { if let _ = (S {}) {} },
+    ] {
+        let original: Expr = syn::parse2(tokens).unwrap();
+
+        let mut flat = original.clone();
+        FlattenParens.visit_expr_mut(&mut flat);
+        let reconstructed: Expr = match syn::parse2(flat.to_token_stream()) {
+            Ok(reconstructed) => reconstructed,
+            Err(err) => panic!("failed to parse `{}`: {}", flat.to_token_stream(), err),
+        };
+
+        assert!(
+            original == reconstructed,
+            "original: {}\nreconstructed: {}",
+            original.to_token_stream(),
+            reconstructed.to_token_stream(),
+        );
+    }
+}