Repository: dtolnay/syn
Pull request #1785 (2024-11-29 21:14:47)

Lower the precedence of expressions containing outer attrs
`thing.field` does not need parentheses around `thing`, but `(#[attr] thing).field` does. So the presence of outer attributes is relevant to the effective precedence of a subexpression.


Patch:
diff --git a/src/precedence.rs b/src/precedence.rs
index 936a0c51..1891bfc2 100644
--- a/src/precedence.rs
+++ b/src/precedence.rs
@@ -1,7 +1,14 @@
+#[cfg(all(feature = "printing", feature = "full"))]
+use crate::attr::{AttrStyle, Attribute};
 #[cfg(feature = "printing")]
 use crate::expr::Expr;
 #[cfg(all(feature = "printing", feature = "full"))]
-use crate::expr::{ExprBreak, ExprReturn, ExprYield};
+use crate::expr::{
+    ExprArray, ExprAsync, ExprAwait, ExprBlock, ExprBreak, ExprCall, ExprConst, ExprContinue,
+    ExprField, ExprForLoop, ExprGroup, ExprIf, ExprIndex, ExprInfer, ExprLit, ExprLoop, ExprMacro,
+    ExprMatch, ExprMethodCall, ExprParen, ExprPath, ExprRepeat, ExprReturn, ExprStruct, ExprTry,
+    ExprTryBlock, ExprTuple, ExprUnsafe, ExprWhile, ExprYield,
+};
 use crate::op::BinOp;
 #[cfg(all(feature = "printing", feature = "full"))]
 use crate::ty::ReturnType;
@@ -82,11 +89,21 @@ impl Precedence {
 
     #[cfg(feature = "printing")]
     pub(crate) fn of(e: &Expr) -> Self {
+        #[cfg(feature = "full")]
+        fn prefix_attrs(attrs: &[Attribute]) -> Precedence {
+            for attr in attrs {
+                if let AttrStyle::Outer = attr.style {
+                    return Precedence::Prefix;
+                }
+            }
+            Precedence::Unambiguous
+        }
+
         match e {
             #[cfg(feature = "full")]
             Expr::Closure(e) => match e.output {
                 ReturnType::Default => Precedence::Jump,
-                ReturnType::Type(..) => Precedence::Unambiguous,
+                ReturnType::Type(..) => prefix_attrs(&e.attrs),
             },
 
             #[cfg(feature = "full")]
@@ -104,6 +121,36 @@ impl Precedence {
             Expr::Cast(_) => Precedence::Cast,
             Expr::RawAddr(_) | Expr::Reference(_) | Expr::Unary(_) => Precedence::Prefix,
 
+            #[cfg(feature = "full")]
+            Expr::Array(ExprArray { attrs, .. })
+            | Expr::Async(ExprAsync { attrs, .. })
+            | Expr::Await(ExprAwait { attrs, .. })
+            | Expr::Block(ExprBlock { attrs, .. })
+            | Expr::Call(ExprCall { attrs, .. })
+            | Expr::Const(ExprConst { attrs, .. })
+            | Expr::Continue(ExprContinue { attrs, .. })
+            | Expr::Field(ExprField { attrs, .. })
+            | Expr::ForLoop(ExprForLoop { attrs, .. })
+            | Expr::Group(ExprGroup { attrs, .. })
+            | Expr::If(ExprIf { attrs, .. })
+            | Expr::Index(ExprIndex { attrs, .. })
+            | Expr::Infer(ExprInfer { attrs, .. })
+            | Expr::Lit(ExprLit { attrs, .. })
+            | Expr::Loop(ExprLoop { attrs, .. })
+            | Expr::Macro(ExprMacro { attrs, .. })
+            | Expr::Match(ExprMatch { attrs, .. })
+            | Expr::MethodCall(ExprMethodCall { attrs, .. })
+            | Expr::Paren(ExprParen { attrs, .. })
+            | Expr::Path(ExprPath { attrs, .. })
+            | Expr::Repeat(ExprRepeat { attrs, .. })
+            | Expr::Struct(ExprStruct { attrs, .. })
+            | Expr::Try(ExprTry { attrs, .. })
+            | Expr::TryBlock(ExprTryBlock { attrs, .. })
+            | Expr::Tuple(ExprTuple { attrs, .. })
+            | Expr::Unsafe(ExprUnsafe { attrs, .. })
+            | Expr::While(ExprWhile { attrs, .. }) => prefix_attrs(attrs),
+
+            #[cfg(not(feature = "full"))]
             Expr::Array(_)
             | Expr::Async(_)
             | Expr::Await(_)
@@ -130,9 +177,10 @@ impl Precedence {
             | Expr::TryBlock(_)
             | Expr::Tuple(_)
             | Expr::Unsafe(_)
-            | Expr::Verbatim(_)
             | Expr::While(_) => Precedence::Unambiguous,
 
+            Expr::Verbatim(_) => Precedence::Unambiguous,
+
             #[cfg(not(feature = "full"))]
             Expr::Break(_) | Expr::Closure(_) | Expr::Return(_) | Expr::Yield(_) => unreachable!(),
         }


Test patch:
diff --git a/tests/test_expr.rs b/tests/test_expr.rs
index f01fcb8c..6dd87622 100644
--- a/tests/test_expr.rs
+++ b/tests/test_expr.rs
@@ -702,6 +702,7 @@ fn test_fixup() {
         quote! { (..) += () },
         quote! { (1 < 2) == (3 < 4) },
         quote! { { (let _ = ()) } },
+        quote! { (#[attr] thing).field },
     ] {
         let original: Expr = syn::parse2(tokens).unwrap();