Repository: dtolnay/syn
Pull request #1703 (2024-07-07 06:08:52)

Incorporate parenthesization of casts into FixupContext


Patch:
diff --git a/src/classify.rs b/src/classify.rs
index cc176d06..ab735f14 100644
--- a/src/classify.rs
+++ b/src/classify.rs
@@ -3,11 +3,9 @@ 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 = "full")]
 pub(crate) fn requires_semi_to_be_stmt(expr: &Expr) -> bool {
     match expr {
         Expr::Macro(expr) => !expr.mac.delimiter.is_brace(),
@@ -15,7 +13,6 @@ 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(_)
@@ -60,7 +57,7 @@ pub(crate) fn requires_comma_to_be_match_arm(expr: &Expr) -> bool {
     }
 }
 
-#[cfg(all(feature = "printing", feature = "full"))]
+#[cfg(feature = "printing")]
 pub(crate) fn confusable_with_adjacent_block(mut expr: &Expr) -> bool {
     let mut stack = Vec::new();
 
@@ -146,84 +143,37 @@ pub(crate) fn confusable_with_adjacent_block(mut expr: &Expr) -> bool {
 }
 
 #[cfg(feature = "printing")]
-pub(crate) fn confusable_with_adjacent_lt(mut expr: &Expr) -> bool {
+pub(crate) fn trailing_unparameterized_path(mut ty: &Type) -> 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,
-                },
+        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,
-            }
+            Type::Array(_)
+            | Type::Group(_)
+            | Type::Infer(_)
+            | Type::Macro(_)
+            | Type::Never(_)
+            | Type::Paren(_)
+            | Type::Slice(_)
+            | Type::Tuple(_)
+            | Type::Verbatim(_) => return false,
         }
     }
 
@@ -249,7 +199,7 @@ pub(crate) fn confusable_with_adjacent_lt(mut expr: &Expr) -> bool {
 }
 
 /// Whether the expression's first token is the label of a loop/block.
-#[cfg(all(feature = "printing", feature = "full"))]
+#[cfg(feature = "printing")]
 pub(crate) fn expr_leading_label(mut expr: &Expr) -> bool {
     loop {
         match expr {
@@ -302,7 +252,6 @@ pub(crate) fn expr_leading_label(mut expr: &Expr) -> bool {
 }
 
 /// 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 80a4b1a1..c130fa92 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -2981,6 +2981,7 @@ 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::{
@@ -2996,6 +2997,7 @@ pub(crate) mod printing {
     };
     #[cfg(feature = "full")]
     use crate::fixup::FixupContext;
+    #[cfg(feature = "full")]
     use crate::op::BinOp;
     use crate::path;
     use crate::precedence::Precedence;
@@ -3222,17 +3224,23 @@ pub(crate) mod printing {
         outer_attrs_to_tokens(&e.attrs, tokens);
 
         #[cfg(feature = "full")]
-        let left_fixup = fixup.leftmost_subexpression_with_begin_operator(match &e.op {
-            BinOp::Sub(_)
-            | BinOp::Mul(_)
-            | BinOp::And(_)
-            | BinOp::Or(_)
-            | BinOp::BitAnd(_)
-            | BinOp::BitOr(_)
-            | BinOp::Shl(_)
-            | BinOp::Lt(_) => true,
-            _ => false,
-        });
+        let left_fixup = fixup.leftmost_subexpression_with_begin_operator(
+            match &e.op {
+                BinOp::Sub(_)
+                | BinOp::Mul(_)
+                | BinOp::And(_)
+                | BinOp::Or(_)
+                | BinOp::BitAnd(_)
+                | BinOp::BitOr(_)
+                | BinOp::Shl(_)
+                | BinOp::Lt(_) => true,
+                _ => false,
+            },
+            match &e.op {
+                BinOp::Shl(_) | BinOp::Lt(_) => true,
+                _ => false,
+            },
+        );
         let left_prec = leading_precedence(
             &e.left,
             #[cfg(feature = "full")]
@@ -3246,21 +3254,12 @@ pub(crate) mod printing {
         );
 
         let binop_prec = Precedence::of_binop(&e.op);
-        let (mut left_needs_group, right_needs_group) = match binop_prec {
+        let (left_needs_group, right_needs_group) = match binop_prec {
             Precedence::Assign => (left_prec <= Precedence::Range, right_prec < binop_prec),
             Precedence::Compare => (left_prec <= binop_prec, right_prec <= binop_prec),
             _ => (left_prec < binop_prec, right_prec <= binop_prec),
         };
 
-        // These cases require parenthesization independently of precedence.
-        if let BinOp::Lt(_) | BinOp::Shl(_) = &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.
-            left_needs_group |= classify::confusable_with_adjacent_lt(&e.left);
-        }
-
         print_subexpression(
             &e.left,
             left_needs_group,
@@ -3341,7 +3340,7 @@ pub(crate) mod printing {
             Precedence::Unambiguous
         };
         #[cfg(feature = "full")]
-        let func_fixup = fixup.leftmost_subexpression_with_begin_operator(true);
+        let func_fixup = fixup.leftmost_subexpression_with_begin_operator(true, false);
         let func_precedence = leading_precedence(
             &e.func,
             #[cfg(feature = "full")]
@@ -3550,7 +3549,7 @@ pub(crate) mod printing {
     ) {
         outer_attrs_to_tokens(&e.attrs, tokens);
         #[cfg(feature = "full")]
-        let obj_fixup = fixup.leftmost_subexpression_with_begin_operator(true);
+        let obj_fixup = fixup.leftmost_subexpression_with_begin_operator(true, false);
         let obj_precedence = leading_precedence(
             &e.expr,
             #[cfg(feature = "full")]
diff --git a/src/fixup.rs b/src/fixup.rs
index 536da4e4..b8605a7d 100644
--- a/src/fixup.rs
+++ b/src/fixup.rs
@@ -101,6 +101,15 @@ pub(crate) struct FixupContext {
     //     let _ = return + 1;  // no paren because '+' cannot begin expr
     //
     next_operator_can_begin_expr: bool,
+
+    // This is the difference between:
+    //
+    //     let _ = x as u8 + T;
+    //
+    //     let _ = (x as u8) < T;
+    //
+    // Without parens, the latter would want to parse `u8<T...` as a type.
+    next_operator_can_begin_generics: bool,
 }
 
 impl FixupContext {
@@ -114,6 +123,7 @@ impl FixupContext {
         parenthesize_exterior_struct_lit: false,
         parenthesize_exterior_jump: false,
         next_operator_can_begin_expr: false,
+        next_operator_can_begin_generics: false,
     };
 
     /// Create the initial fixup for printing an expression in statement
@@ -189,9 +199,11 @@ impl FixupContext {
     pub fn leftmost_subexpression_with_begin_operator(
         self,
         next_operator_can_begin_expr: bool,
+        next_operator_can_begin_generics: bool,
     ) -> Self {
         FixupContext {
             next_operator_can_begin_expr,
+            next_operator_can_begin_generics,
             ..self.leftmost_subexpression()
         }
     }
@@ -246,15 +258,14 @@ impl FixupContext {
     /// expressions have lower precedence when adjacent to particular operators.
     pub fn leading_precedence(self, expr: &Expr) -> Precedence {
         if self.next_operator_can_begin_expr {
-            match expr {
-                // Decrease precedence of value-less jumps when followed by an
-                // operator that would otherwise get interpreted as beginning a
-                // value for the jump.
-                Expr::Break(_) | Expr::Return(_) | Expr::Yield(_) => return Precedence::Jump,
-                _ => {}
+            // Decrease precedence of value-less jumps when followed by an
+            // operator that would otherwise get interpreted as beginning a
+            // value for the jump.
+            if let Expr::Break(_) | Expr::Return(_) | Expr::Yield(_) = expr {
+                return Precedence::Jump;
             }
         }
-        Precedence::of(expr)
+        self.precedence(expr)
     }
 
     /// Determines the effective precedence of a right subexpression. Some
@@ -276,6 +287,17 @@ impl FixupContext {
                 _ => {}
             }
         }
+        self.precedence(expr)
+    }
+
+    fn precedence(self, expr: &Expr) -> Precedence {
+        if self.next_operator_can_begin_generics {
+            if let Expr::Cast(cast) = expr {
+                if classify::trailing_unparameterized_path(&cast.ty) {
+                    return Precedence::MIN;
+                }
+            }
+        }
         Precedence::of(expr)
     }
 }
diff --git a/src/lib.rs b/src/lib.rs
index 96596844..1ecf817f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -329,10 +329,7 @@ mod bigint;
 #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))]
 pub mod buffer;
 
-#[cfg(any(
-    all(feature = "parsing", feature = "full"),
-    all(feature = "printing", any(feature = "full", feature = "derive")),
-))]
+#[cfg(all(any(feature = "parsing", feature = "printing"), feature = "full"))]
 mod classify;
 
 mod custom_keyword;


Test patch:
diff --git a/tests/test_expr.rs b/tests/test_expr.rs
index 11d0f86c..d20cce8d 100644
--- a/tests/test_expr.rs
+++ b/tests/test_expr.rs
@@ -660,7 +660,7 @@ fn test_fixup() {
         quote! { 0 + (0 + 0) },
         quote! { (a = b) = c },
         quote! { (x as i32) < 0 },
-        quote! { (1 + x as i32) < 0 },
+        quote! { 1 + (x as i32) < 0 },
         quote! { (1 + 1).abs() },
         quote! { (lo..hi)[..] },
         quote! { (a..b)..(c..d) },