Update prettyplease to 0.2.29

Test: m
Change-Id: I24ddf188c4fc98cf832d1137d581c838a3da154f
diff --git a/crates/prettyplease/.android-checksum.json b/crates/prettyplease/.android-checksum.json
index 00205b2..0df1f50 100644
--- a/crates/prettyplease/.android-checksum.json
+++ b/crates/prettyplease/.android-checksum.json
@@ -1 +1 @@
-{"package":null,"files":{".cargo-checksum.json":"7ae9e7a781bba8e321bc4192826c5653545bb4b05a74de388147bcbde6e2163c","Android.bp":"af0d7dff9013da50fad4d4ae2a6a72b3edcf9e1fb1e642ca3d25f7f27413e2cf","Cargo.toml":"6f04fc844b204a1332d7af8c9fe5c82dae95bfa01698382a272a609933f468bc","LICENSE":"50f827348bfd5def2df4f30cb41264072cf9c180bd9ed698437e8d85b2482754","LICENSE-APACHE":"50f827348bfd5def2df4f30cb41264072cf9c180bd9ed698437e8d85b2482754","LICENSE-MIT":"38620a3cfaeec97a9197e8c39e436ea7f0bc86699b1f1c35f1aa41785b6d4eac","METADATA":"ab3385c35023c2dee8d11dd63dd9a20099b563c2b891c541f33ffd17fb298444","MODULE_LICENSE_APACHE2":"0d6f8afa3940b7f06bebee651376d43bc8b0d5b437337be2696d30377451e93a","README.md":"6cd24eece6714dc53f7f9f979abdc9cae292e172340688029b4a6ef5ae91ae24","build.rs":"53df216257bb8fbdc2212e23f60cf6261007366a73e9dc390dde3dcbde3d6251","cargo_embargo.json":"0a3aea02264006cd539344c6021258d9fc7c2f88bbe7510b351f5e3485244e08","examples/input.rs":"c54e04743b9eb5665e7a2c0ea6cdd7d3a31fecec1258086b9268b8edf63c9bf7","examples/output.prettyplease.rs":"11211b3ec0b461c64494cec9f240eac5c60084d55a2f9048d41dcca66ebb6f02","examples/output.rustc.rs":"fe3e54f99bba0ad1aebace5e6a06cdf79548dac62524210064f1654a72968f96","examples/output.rustfmt.rs":"f5bcc4d1f873d76ce87834f9fa0d2e5c46042a6f0a2258b22ea4855a85e8b018","src/algorithm.rs":"0631caf6ac9fb58caf49dd2a321a096b21e6b7b4ee3b1e148a4b0e6529d59220","src/attr.rs":"fecce86077baf55f95f0c595579fb5ab1001aa27235a8ad48eaafe1a2aefd8a5","src/convenience.rs":"faae49b9678e3fa882f287594cac24d2544308dbd21a5762b396a174ceb975cd","src/data.rs":"dcb2d7eaa744b5d9190fc90d7cca25d64bff38dc57f746581d6097b79ca67d03","src/expr.rs":"ef67c23508873d9f33004fcb6b63d2163d8a0873a118d98576fea193dc6a5e05","src/file.rs":"359101ac28e7e39a275b9837053e6841765a48d022ea1482d8ef80e34d860d44","src/generics.rs":"73491a105c69318d01a6d090778d57102937f89fd9cd4cb8f2857c12a79e8dd5","src/item.rs":"c8f065dba1547b57c369e5b11956420d9e0979a6d5536225b370901a8160b7cc","src/iter.rs":"63d7cf3313d8ed3fa1637c5c01d80ab48b8a742538b71fd792604246822d4d9d","src/lib.rs":"bcce5af8455aa032e41a165cbe064b719b9289af13627057c89a6526066fd1cf","src/lifetime.rs":"b2f01d35e9faddd4a042dec30bb0f1668683ef11a9477e660e9eb4d480a08d61","src/lit.rs":"39cd69038e2143b16dad6007033015ef4c8e116b4af78e12937e916aa92b8376","src/mac.rs":"3df8a64cdb74ad433afe6a99f4d305bafbed0e7b2773f60fd7afaba91890a208","src/pat.rs":"d3ff013f425aa46d26d8c1c075c4bdd9e924f198125d5793d409941c31bf1ae9","src/path.rs":"c2c9a6f5fe868446427bb76ee17bbe14e6fb1fcd0d3d325fd3115f4057586638","src/ring.rs":"d2f765e033eea3aad1b8fd4351e8a3d32ff0c79be13a0104683d5fd38d0e131b","src/stmt.rs":"bce93e2b3af162871814b77ef1c350a4d0f71e423f74d1e5e93276dc39f1e23d","src/token.rs":"881884adcb68dcd01f6d66459c7f49df2f6f8019f0718d0a146d9c23ea375af5","src/ty.rs":"526800996453fd7068fc8f3e7e22a873f20bbcd89cd29f63e0cd32692b7559c1","tests/test.rs":"42b5f9d824aff128e6caf5df9602fe36b279b32d5debcaecf68969fbff09af4f"}}
\ No newline at end of file
+{"package":null,"files":{".cargo-checksum.json":"8754f5c95b4603c91b2fee04c7b22563bd3dac2e437fd8f33f861b95deda886d","Android.bp":"daa37880cfc6788215c9ac3b857bba421107f5dde60828f412396b57b64bddc5","Cargo.toml":"5ea38f196bfa1d0123a5f3129ff7cd7c2da6f9d1987c015d370236a0522722c3","LICENSE":"50f827348bfd5def2df4f30cb41264072cf9c180bd9ed698437e8d85b2482754","LICENSE-APACHE":"50f827348bfd5def2df4f30cb41264072cf9c180bd9ed698437e8d85b2482754","LICENSE-MIT":"38620a3cfaeec97a9197e8c39e436ea7f0bc86699b1f1c35f1aa41785b6d4eac","METADATA":"c6c17d8cdb6c025f663b86e078bd9350f16593db38063b9ff5708c981b48d2fd","MODULE_LICENSE_APACHE2":"0d6f8afa3940b7f06bebee651376d43bc8b0d5b437337be2696d30377451e93a","README.md":"6cd24eece6714dc53f7f9f979abdc9cae292e172340688029b4a6ef5ae91ae24","build.rs":"53df216257bb8fbdc2212e23f60cf6261007366a73e9dc390dde3dcbde3d6251","cargo_embargo.json":"0a3aea02264006cd539344c6021258d9fc7c2f88bbe7510b351f5e3485244e08","examples/input.rs":"c54e04743b9eb5665e7a2c0ea6cdd7d3a31fecec1258086b9268b8edf63c9bf7","examples/output.prettyplease.rs":"11211b3ec0b461c64494cec9f240eac5c60084d55a2f9048d41dcca66ebb6f02","examples/output.rustc.rs":"fe3e54f99bba0ad1aebace5e6a06cdf79548dac62524210064f1654a72968f96","examples/output.rustfmt.rs":"f5bcc4d1f873d76ce87834f9fa0d2e5c46042a6f0a2258b22ea4855a85e8b018","src/algorithm.rs":"b7ae58dedbca564ea449f02160084aa9a4f38a7b724afbdb19e81f7e524867ce","src/attr.rs":"dd3e5c4c2c4a65040f3b567c86405e823e8eb1f963aaa8b8a8547fc1de22ca31","src/classify.rs":"e17fd6644f59523bd6f7a198acf3645fd1a0930f5cbfebe5552918ea8841e38e","src/convenience.rs":"faae49b9678e3fa882f287594cac24d2544308dbd21a5762b396a174ceb975cd","src/data.rs":"25457e0091113479a29087a215dc22f32eb28c9f4e29fb43f8864f81d6b98bf3","src/expr.rs":"fe7d9645bc3f86c63a58bc72aeb69b4ad11bc0651cc37b4e4f6e3b5a20708275","src/file.rs":"359101ac28e7e39a275b9837053e6841765a48d022ea1482d8ef80e34d860d44","src/fixup.rs":"2f258a9d48fc6851ec32dc7bfc492f1187c2438fe3504b3a73ea0dfa91087d32","src/generics.rs":"eab714ff712509d0e2b23a635eb6ebabeb711d82b8e77350ace505180e5f60ef","src/item.rs":"21d89d8eb6376057bc3ffcfe7e0f18cb72803e1656fac5ec6919a93a85ef2939","src/iter.rs":"63d7cf3313d8ed3fa1637c5c01d80ab48b8a742538b71fd792604246822d4d9d","src/lib.rs":"c4700c7472d0a9eeb43c25f9f42187e75d203dd0d07416649c5d588ef64f721c","src/lifetime.rs":"b2f01d35e9faddd4a042dec30bb0f1668683ef11a9477e660e9eb4d480a08d61","src/lit.rs":"39cd69038e2143b16dad6007033015ef4c8e116b4af78e12937e916aa92b8376","src/mac.rs":"131791d63273f8ae09cbb301635f9e2cbee3f8b5dd89a55c376590b15b7de911","src/pat.rs":"40e2926be48bcdfd151e6de00880ad4aba6c7822db3eb340e3f12a6a50edae4b","src/path.rs":"68c7066f63d9eb570d0d5a613225f2948d0d547257ecd49ea29e7cf693587d24","src/precedence.rs":"939141754675dbb9bc6091123a847d62cf8493db0aa701686f833b0435d3fc40","src/ring.rs":"cd1a93144b2f76a6a47ad8847ddc90d9b3d13bf674243e774f7848d7baebbcd8","src/stmt.rs":"10372120c97c4f3840f7b494bf973c36c97625b68f60f178854726b622b5fc0d","src/token.rs":"881884adcb68dcd01f6d66459c7f49df2f6f8019f0718d0a146d9c23ea375af5","src/ty.rs":"e8d75df0fe6e83a50a288b6b0ffe2285d698810043e6ba6a59408b62ecb04383","tests/test.rs":"adf75b6cb0fe9c700ea366366e2b0b9a2e166983b4dfd5abe87c1bcc986cbdea","tests/test_precedence.rs":"64413a1314a13c027b8e5e6afbfcff4d3375f5db80131fffc7b341d89fb775a6"}}
\ No newline at end of file
diff --git a/crates/prettyplease/.cargo-checksum.json b/crates/prettyplease/.cargo-checksum.json
index 434d068..823e7b4 100644
--- a/crates/prettyplease/.cargo-checksum.json
+++ b/crates/prettyplease/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"0e165ac6d5ca01781b9c1586f976e6abeedaa534f1ba0dc0adf607f9b30795e0","LICENSE-APACHE":"62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"a7e6d152cdc6ea603077e50b8d55af374d9d21fd9f62d08a008588b17d785e6e","build.rs":"79a5b2d260aa97aeac7105fbfa00774982f825cd708c100ea96d01c39974bb88","examples/input.rs":"53350088f12a346a99034af41ef432dedcc9e5d581c5592d9aae3807c42656c1","examples/output.prettyplease.rs":"fa63c118daadb64c456ec5b8d5e46e5d7fabbbeb6a6e61a08eabc23360a18fbd","examples/output.rustc.rs":"0c66f8929fa40a2822d4ea1aec3d8b83db598aba043b17f3c1a6133f0d657135","examples/output.rustfmt.rs":"914a9aea1c51e097bfd80c9af4011811e6126c9df5fb0eac3d40b1203fba7c58","src/algorithm.rs":"7a4a7b62f5300aabf7fcff3cd8c8aaa3ae1e3d1dc717eea1e4797988ce0e18c7","src/attr.rs":"54e829ae468f22c8e2853d669515575f1444bfee026cfd9b19538f77caf10ab7","src/convenience.rs":"dd392b009b691d3587c7d8e3caeaacf450303c4223792b5f89c336358e371c39","src/data.rs":"9db6623d3ccc79b541a28bdc88875ad0036576689e085007eb362819f8e9a2d3","src/expr.rs":"45a30de0afdf8e0564c221ebcb4cf11e2d8948b5b85cc99d5d55d1293e65dea9","src/file.rs":"5689efa3c5959a6a0d8cfc2c13bf8a37ab0669e2b81dbded3f3c28884a88fca0","src/generics.rs":"14a01f0602c005939f0324ef5e6b6624f477a531552bc764447e08875fb2ebbb","src/item.rs":"6793b19d8be3dd7cb75a984370a771d0592b9c4548d294cf246424947a3c8535","src/iter.rs":"38b2cd3b38719c6024fb6b3aa739f6f8736c83193fd21e2365d4f6c27bc41666","src/lib.rs":"a6c8e3e455f6b2f5b0800213464076f8ac49fc64aeddd77f3e4f0f9dbf554b18","src/lifetime.rs":"6d420430168185b2da3409bc38a45f63cced9443915f04e6aec71367fc070dcf","src/lit.rs":"9ea6d25533e64df4ff01c084fa1c31ddf64fb3b159409eec7d80dbf281e5171e","src/mac.rs":"62911747c308187afc750b4bd4f8bd24ee825081043d78da68a001ea55ab5853","src/pat.rs":"d130b141a3fd8098913cb179efe6600a5f010a2719447ecb3d4fb29e9b546220","src/path.rs":"4d3f7caa0bfe821bdb9ad00ee1d7d61101aef8a5cbea35d565b6da6217feefca","src/ring.rs":"e23d133209b977e457b07b0cd93b3711d01f4172d7cfa4cf6a7247637390e606","src/stmt.rs":"763f617a5535f8e61593b0cb1c6c9f5caef032085671dbce509b691d94d39835","src/token.rs":"c288b1d81f2a35673d4ca1dd10d3386670b067460121df3038303e1ed73b41a7","src/ty.rs":"b5fa5f318c5e4593ed3eed4707e63dceaa62eea4ecdfba0f59bd946917ca3dbd","tests/test.rs":"04994ad0a37c43457390f11ade6ab74fc26c5e879b0a374f0b64acb530a0a496"},"package":"64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033"}
\ No newline at end of file
+{"files":{"Cargo.toml":"06e68c5a91a65141eef6ddfbc87652ffa90a3ab71175b31021d61e6eb8e7b8e1","LICENSE-APACHE":"62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"a7e6d152cdc6ea603077e50b8d55af374d9d21fd9f62d08a008588b17d785e6e","build.rs":"79a5b2d260aa97aeac7105fbfa00774982f825cd708c100ea96d01c39974bb88","examples/input.rs":"53350088f12a346a99034af41ef432dedcc9e5d581c5592d9aae3807c42656c1","examples/output.prettyplease.rs":"fa63c118daadb64c456ec5b8d5e46e5d7fabbbeb6a6e61a08eabc23360a18fbd","examples/output.rustc.rs":"0c66f8929fa40a2822d4ea1aec3d8b83db598aba043b17f3c1a6133f0d657135","examples/output.rustfmt.rs":"914a9aea1c51e097bfd80c9af4011811e6126c9df5fb0eac3d40b1203fba7c58","src/algorithm.rs":"bae517cd89743dd4bccb7cf7bf659c9e138d68cf4b06030446effb794811a36c","src/attr.rs":"c11f2ed0d16f2a7c1a0023f2fc5c81a074276ff399679b2814ab762edb8dd792","src/classify.rs":"2ce2d63ad9071aac10b1037e6382703736e0147d96b3ccf32a53182d12883f1b","src/convenience.rs":"dd392b009b691d3587c7d8e3caeaacf450303c4223792b5f89c336358e371c39","src/data.rs":"5bc2dce1cfa1aa5c1324ccdc2d76a6bd5df2382530c7e863d2bb50dea60cc4bc","src/expr.rs":"e99c5ef631cff8a0b0c2666deb35cd0c3eee2ee3585b113ff37cffe3b12435d4","src/file.rs":"5689efa3c5959a6a0d8cfc2c13bf8a37ab0669e2b81dbded3f3c28884a88fca0","src/fixup.rs":"ee279996cd5c57eb308a7e8529cd1f2999617426047df2e641a95ddc82ff44ce","src/generics.rs":"f10b95f4b011f5bf6510d3a77e38227716dccf0a8aeb8a8344e776be9f90f54e","src/item.rs":"4dc320bf73f4ca7d2c6741c31700b5150a02106bfe688969070aff53c2ce455a","src/iter.rs":"38b2cd3b38719c6024fb6b3aa739f6f8736c83193fd21e2365d4f6c27bc41666","src/lib.rs":"f92f24de5bc5231d107bf97c10d845f83e6c0ae922c8d33841f8c34d07c76130","src/lifetime.rs":"6d420430168185b2da3409bc38a45f63cced9443915f04e6aec71367fc070dcf","src/lit.rs":"9ea6d25533e64df4ff01c084fa1c31ddf64fb3b159409eec7d80dbf281e5171e","src/mac.rs":"36c62d1b721c6c56af799c55371ee43e9c446c4787ba1f69021b9cb6acda76b9","src/pat.rs":"8e53fd1b5382bb068210162bfab9921246093cfdd80dd93cd8627fcfdae39940","src/path.rs":"e73d83dc38f5c6c0c82f824da7eb090a16027f32fc40446b185580ee5e99be58","src/precedence.rs":"a8ce97ba0a25f442b5f238c64f078d70f4114b4b0f9df82764d533dd39a47abb","src/ring.rs":"517b1a02f8e0a9c1316830117daad1e30d17e1fcf6428c6b438c626aa43286ae","src/stmt.rs":"e17ab9647fed9daa4f5b2fbd007015128f2a7fc65686a988593444a37242f885","src/token.rs":"c288b1d81f2a35673d4ca1dd10d3386670b067460121df3038303e1ed73b41a7","src/ty.rs":"a1e3e5a08124673826948f97e70c11800081d2bca7f3aec12d84d0d00837290f","tests/test.rs":"c6f8c7830b7491fca1d56e41aa4acc6256b683a3556a48982f57ae62d38aaaa2","tests/test_precedence.rs":"de0c770b9a72e5eba8a52dcac0614d6db8ff5041ba601e1e67f113d68c9afd50"},"package":"6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac"}
\ No newline at end of file
diff --git a/crates/prettyplease/Android.bp b/crates/prettyplease/Android.bp
index 8e5fd1a..1e0bada 100644
--- a/crates/prettyplease/Android.bp
+++ b/crates/prettyplease/Android.bp
@@ -18,7 +18,7 @@
     host_cross_supported: false,
     crate_name: "prettyplease",
     cargo_env_compat: true,
-    cargo_pkg_version: "0.2.25",
+    cargo_pkg_version: "0.2.29",
     crate_root: "src/lib.rs",
     edition: "2021",
     rustlibs: [
diff --git a/crates/prettyplease/Cargo.toml b/crates/prettyplease/Cargo.toml
index b39c5e3..14dd304 100644
--- a/crates/prettyplease/Cargo.toml
+++ b/crates/prettyplease/Cargo.toml
@@ -11,9 +11,9 @@
 
 [package]
 edition = "2021"
-rust-version = "1.61"
+rust-version = "1.62"
 name = "prettyplease"
-version = "0.2.25"
+version = "0.2.29"
 authors = ["David Tolnay <[email protected]>"]
 build = "build.rs"
 links = "prettyplease02"
@@ -38,6 +38,9 @@
 [package.metadata.playground]
 features = ["verbatim"]
 
+[features]
+verbatim = ["syn/parsing"]
+
 [lib]
 name = "prettyplease"
 path = "src/lib.rs"
@@ -47,12 +50,16 @@
 name = "test"
 path = "tests/test.rs"
 
+[[test]]
+name = "test_precedence"
+path = "tests/test_precedence.rs"
+
 [dependencies.proc-macro2]
 version = "1.0.80"
 default-features = false
 
 [dependencies.syn]
-version = "2.0.81"
+version = "2.0.96"
 features = ["full"]
 default-features = false
 
@@ -68,9 +75,12 @@
 default-features = false
 
 [dev-dependencies.syn]
-version = "2.0.81"
-features = ["parsing"]
+version = "2.0.96"
+features = [
+    "clone-impls",
+    "extra-traits",
+    "parsing",
+    "printing",
+    "visit-mut",
+]
 default-features = false
-
-[features]
-verbatim = ["syn/parsing"]
diff --git a/crates/prettyplease/METADATA b/crates/prettyplease/METADATA
index 13f0ff2..8344b06 100644
--- a/crates/prettyplease/METADATA
+++ b/crates/prettyplease/METADATA
@@ -1,17 +1,17 @@
 name: "prettyplease"
 description: "A minimal `syn` syntax tree pretty-printer"
 third_party {
-  version: "0.2.25"
+  version: "0.2.29"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2024
-    month: 12
-    day: 20
+    year: 2025
+    month: 1
+    day: 14
   }
   homepage: "https://crates.io/crates/prettyplease"
   identifier {
     type: "Archive"
-    value: "https://static.crates.io/crates/prettyplease/prettyplease-0.2.25.crate"
-    version: "0.2.25"
+    value: "https://static.crates.io/crates/prettyplease/prettyplease-0.2.29.crate"
+    version: "0.2.29"
   }
 }
diff --git a/crates/prettyplease/src/algorithm.rs b/crates/prettyplease/src/algorithm.rs
index 6e2b961..ecb68c3 100644
--- a/crates/prettyplease/src/algorithm.rs
+++ b/crates/prettyplease/src/algorithm.rs
@@ -19,7 +19,7 @@
     pub offset: isize,
     pub blank_space: usize,
     pub pre_break: Option<char>,
-    pub post_break: Option<char>,
+    pub post_break: &'static str,
     pub no_break: Option<char>,
     pub if_nonempty: bool,
     pub never_break: bool,
@@ -211,9 +211,18 @@
         self.scan_end();
     }
 
+    pub fn ends_with(&self, ch: char) -> bool {
+        for i in self.buf.index_range().rev() {
+            if let Token::String(token) = &self.buf[i].token {
+                return token.ends_with(ch);
+            }
+        }
+        self.out.ends_with(ch)
+    }
+
     fn check_stream(&mut self) {
         while self.right_total - self.left_total > self.space {
-            if *self.scan_stack.front().unwrap() == self.buf.index_of_first() {
+            if *self.scan_stack.front().unwrap() == self.buf.index_range().start {
                 self.scan_stack.pop_front().unwrap();
                 self.buf.first_mut().size = SIZE_INFINITY;
             }
@@ -353,10 +362,10 @@
             let indent = self.indent as isize + token.offset;
             self.pending_indentation = usize::try_from(indent).unwrap();
             self.space = cmp::max(MARGIN - indent, MIN_SPACE);
-            if let Some(post_break) = token.post_break {
+            if !token.post_break.is_empty() {
                 self.print_indent();
-                self.out.push(post_break);
-                self.space -= post_break.len_utf8() as isize;
+                self.out.push_str(token.post_break);
+                self.space -= token.post_break.len() as isize;
             }
         }
     }
diff --git a/crates/prettyplease/src/attr.rs b/crates/prettyplease/src/attr.rs
index 0388d66..d7c2250 100644
--- a/crates/prettyplease/src/attr.rs
+++ b/crates/prettyplease/src/attr.rs
@@ -1,4 +1,5 @@
 use crate::algorithm::Printer;
+use crate::fixup::FixupContext;
 use crate::path::PathKind;
 use crate::INDENT;
 use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
@@ -102,7 +103,7 @@
     fn meta_name_value(&mut self, meta: &MetaNameValue) {
         self.path(&meta.path, PathKind::Simple);
         self.word(" = ");
-        self.expr(&meta.value);
+        self.expr(&meta.value, FixupContext::NONE);
     }
 
     fn attr_tokens(&mut self, tokens: TokenStream) {
diff --git a/crates/prettyplease/src/classify.rs b/crates/prettyplease/src/classify.rs
new file mode 100644
index 0000000..17648f6
--- /dev/null
+++ b/crates/prettyplease/src/classify.rs
@@ -0,0 +1,324 @@
+use proc_macro2::{Delimiter, TokenStream, TokenTree};
+use std::ops::ControlFlow;
+use syn::punctuated::Punctuated;
+use syn::{Expr, MacroDelimiter, Path, PathArguments, ReturnType, Token, Type, TypeParamBound};
+
+pub(crate) fn requires_semi_to_be_stmt(expr: &Expr) -> bool {
+    match expr {
+        Expr::Macro(expr) => !matches!(expr.mac.delimiter, MacroDelimiter::Brace(_)),
+        _ => requires_comma_to_be_match_arm(expr),
+    }
+}
+
+pub(crate) fn requires_comma_to_be_match_arm(mut expr: &Expr) -> bool {
+    loop {
+        match expr {
+            #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
+            Expr::If(_)
+            | Expr::Match(_)
+            | Expr::Block(_) | Expr::Unsafe(_) // both under ExprKind::Block in rustc
+            | Expr::While(_)
+            | Expr::Loop(_)
+            | Expr::ForLoop(_)
+            | Expr::TryBlock(_)
+            | Expr::Const(_) => return false,
+
+            Expr::Array(_)
+            | Expr::Assign(_)
+            | Expr::Async(_)
+            | Expr::Await(_)
+            | Expr::Binary(_)
+            | Expr::Break(_)
+            | Expr::Call(_)
+            | Expr::Cast(_)
+            | Expr::Closure(_)
+            | Expr::Continue(_)
+            | Expr::Field(_)
+            | Expr::Index(_)
+            | Expr::Infer(_)
+            | Expr::Let(_)
+            | Expr::Lit(_)
+            | Expr::Macro(_)
+            | Expr::MethodCall(_)
+            | Expr::Paren(_)
+            | Expr::Path(_)
+            | Expr::Range(_)
+            | Expr::RawAddr(_)
+            | Expr::Reference(_)
+            | Expr::Repeat(_)
+            | Expr::Return(_)
+            | Expr::Struct(_)
+            | Expr::Try(_)
+            | Expr::Tuple(_)
+            | Expr::Unary(_)
+            | Expr::Yield(_)
+            | Expr::Verbatim(_) => return true,
+
+            Expr::Group(group) => expr = &group.expr,
+
+            _ => return true,
+        }
+    }
+}
+
+pub(crate) fn trailing_unparameterized_path(mut ty: &Type) -> bool {
+    loop {
+        match ty {
+            #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
+            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,
+
+            _ => 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() {
+            #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
+            TypeParamBound::Trait(t) => last_type_in_path(&t.path),
+            TypeParamBound::Lifetime(_)
+            | TypeParamBound::PreciseCapture(_)
+            | TypeParamBound::Verbatim(_) => ControlFlow::Break(false),
+            _ => ControlFlow::Break(false),
+        }
+    }
+}
+
+/// Whether the expression's first token is the label of a loop/block.
+pub(crate) fn expr_leading_label(mut expr: &Expr) -> bool {
+    loop {
+        match expr {
+            #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
+            Expr::Block(e) => return e.label.is_some(),
+            Expr::ForLoop(e) => return e.label.is_some(),
+            Expr::Loop(e) => return e.label.is_some(),
+            Expr::While(e) => return e.label.is_some(),
+
+            Expr::Assign(e) => expr = &e.left,
+            Expr::Await(e) => expr = &e.base,
+            Expr::Binary(e) => expr = &e.left,
+            Expr::Call(e) => expr = &e.func,
+            Expr::Cast(e) => expr = &e.expr,
+            Expr::Field(e) => expr = &e.base,
+            Expr::Index(e) => expr = &e.expr,
+            Expr::MethodCall(e) => expr = &e.receiver,
+            Expr::Range(e) => match &e.start {
+                Some(start) => expr = start,
+                None => return false,
+            },
+            Expr::Try(e) => expr = &e.expr,
+
+            Expr::Array(_)
+            | Expr::Async(_)
+            | Expr::Break(_)
+            | Expr::Closure(_)
+            | Expr::Const(_)
+            | Expr::Continue(_)
+            | Expr::If(_)
+            | Expr::Infer(_)
+            | Expr::Let(_)
+            | Expr::Lit(_)
+            | Expr::Macro(_)
+            | Expr::Match(_)
+            | Expr::Paren(_)
+            | Expr::Path(_)
+            | Expr::RawAddr(_)
+            | Expr::Reference(_)
+            | Expr::Repeat(_)
+            | Expr::Return(_)
+            | Expr::Struct(_)
+            | Expr::TryBlock(_)
+            | Expr::Tuple(_)
+            | Expr::Unary(_)
+            | Expr::Unsafe(_)
+            | Expr::Verbatim(_)
+            | Expr::Yield(_) => return false,
+
+            Expr::Group(e) => {
+                if !e.attrs.is_empty() {
+                    return false;
+                }
+                expr = &e.expr;
+            }
+
+            _ => return false,
+        }
+    }
+}
+
+/// Whether the expression's last token is `}`.
+pub(crate) fn expr_trailing_brace(mut expr: &Expr) -> bool {
+    loop {
+        match expr {
+            #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
+            Expr::Async(_)
+            | Expr::Block(_)
+            | Expr::Const(_)
+            | Expr::ForLoop(_)
+            | Expr::If(_)
+            | Expr::Loop(_)
+            | Expr::Match(_)
+            | Expr::Struct(_)
+            | Expr::TryBlock(_)
+            | Expr::Unsafe(_)
+            | Expr::While(_) => return true,
+
+            Expr::Assign(e) => expr = &e.right,
+            Expr::Binary(e) => expr = &e.right,
+            Expr::Break(e) => match &e.expr {
+                Some(e) => expr = e,
+                None => return false,
+            },
+            Expr::Cast(e) => return type_trailing_brace(&e.ty),
+            Expr::Closure(e) => expr = &e.body,
+            Expr::Group(e) => expr = &e.expr,
+            Expr::Let(e) => expr = &e.expr,
+            Expr::Macro(e) => return matches!(e.mac.delimiter, MacroDelimiter::Brace(_)),
+            Expr::Range(e) => match &e.end {
+                Some(end) => expr = end,
+                None => return false,
+            },
+            Expr::RawAddr(e) => expr = &e.expr,
+            Expr::Reference(e) => expr = &e.expr,
+            Expr::Return(e) => match &e.expr {
+                Some(e) => expr = e,
+                None => return false,
+            },
+            Expr::Unary(e) => expr = &e.expr,
+            Expr::Verbatim(e) => return tokens_trailing_brace(e),
+            Expr::Yield(e) => match &e.expr {
+                Some(e) => expr = e,
+                None => return false,
+            },
+
+            Expr::Array(_)
+            | Expr::Await(_)
+            | Expr::Call(_)
+            | Expr::Continue(_)
+            | Expr::Field(_)
+            | Expr::Index(_)
+            | Expr::Infer(_)
+            | Expr::Lit(_)
+            | Expr::MethodCall(_)
+            | Expr::Paren(_)
+            | Expr::Path(_)
+            | Expr::Repeat(_)
+            | Expr::Try(_)
+            | Expr::Tuple(_) => return false,
+
+            _ => return false,
+        }
+    }
+
+    fn type_trailing_brace(mut ty: &Type) -> bool {
+        loop {
+            match ty {
+                #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
+                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_brace) => return trailing_brace,
+                    ControlFlow::Continue(t) => ty = t,
+                },
+                Type::Macro(t) => return matches!(t.mac.delimiter, MacroDelimiter::Brace(_)),
+                Type::Path(t) => match last_type_in_path(&t.path) {
+                    Some(t) => ty = t,
+                    None => return false,
+                },
+                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_brace) => return trailing_brace,
+                    ControlFlow::Continue(t) => ty = t,
+                },
+                Type::Verbatim(t) => return tokens_trailing_brace(t),
+
+                Type::Array(_)
+                | Type::Group(_)
+                | Type::Infer(_)
+                | Type::Never(_)
+                | Type::Paren(_)
+                | Type::Slice(_)
+                | Type::Tuple(_) => return false,
+
+                _ => return false,
+            }
+        }
+    }
+
+    fn last_type_in_path(path: &Path) -> Option<&Type> {
+        match &path.segments.last().unwrap().arguments {
+            PathArguments::None | PathArguments::AngleBracketed(_) => None,
+            PathArguments::Parenthesized(arg) => match &arg.output {
+                ReturnType::Default => None,
+                ReturnType::Type(_, ret) => Some(ret),
+            },
+        }
+    }
+
+    fn last_type_in_bounds(
+        bounds: &Punctuated<TypeParamBound, Token![+]>,
+    ) -> ControlFlow<bool, &Type> {
+        match bounds.last().unwrap() {
+            #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
+            TypeParamBound::Trait(t) => match last_type_in_path(&t.path) {
+                Some(t) => ControlFlow::Continue(t),
+                None => ControlFlow::Break(false),
+            },
+            TypeParamBound::Lifetime(_) | TypeParamBound::PreciseCapture(_) => {
+                ControlFlow::Break(false)
+            }
+            TypeParamBound::Verbatim(t) => ControlFlow::Break(tokens_trailing_brace(t)),
+            _ => ControlFlow::Break(false),
+        }
+    }
+
+    fn tokens_trailing_brace(tokens: &TokenStream) -> bool {
+        if let Some(TokenTree::Group(last)) = tokens.clone().into_iter().last() {
+            last.delimiter() == Delimiter::Brace
+        } else {
+            false
+        }
+    }
+}
diff --git a/crates/prettyplease/src/data.rs b/crates/prettyplease/src/data.rs
index d823ee3..3561a49 100644
--- a/crates/prettyplease/src/data.rs
+++ b/crates/prettyplease/src/data.rs
@@ -1,4 +1,5 @@
 use crate::algorithm::Printer;
+use crate::fixup::FixupContext;
 use crate::iter::IterDelimited;
 use crate::path::PathKind;
 use crate::INDENT;
@@ -31,7 +32,7 @@
         }
         if let Some((_eq_token, discriminant)) = &variant.discriminant {
             self.word(" = ");
-            self.expr(discriminant);
+            self.expr(discriminant, FixupContext::NONE);
         }
     }
 
diff --git a/crates/prettyplease/src/expr.rs b/crates/prettyplease/src/expr.rs
index 52c9e5a..7b223b8 100644
--- a/crates/prettyplease/src/expr.rs
+++ b/crates/prettyplease/src/expr.rs
@@ -1,7 +1,10 @@
 use crate::algorithm::{BreakToken, Printer};
 use crate::attr;
+use crate::classify;
+use crate::fixup::FixupContext;
 use crate::iter::IterDelimited;
 use crate::path::PathKind;
+use crate::precedence::Precedence;
 use crate::stmt;
 use crate::INDENT;
 use proc_macro2::TokenStream;
@@ -17,94 +20,130 @@
 };
 
 impl Printer {
-    pub fn expr(&mut self, expr: &Expr) {
+    pub fn expr(&mut self, expr: &Expr, mut fixup: FixupContext) {
+        let needs_paren = fixup.parenthesize(expr);
+        if needs_paren {
+            self.word("(");
+            fixup = FixupContext::NONE;
+        }
+
         let beginning_of_line = false;
+
         match expr {
             #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
             Expr::Array(expr) => self.expr_array(expr),
-            Expr::Assign(expr) => self.expr_assign(expr),
+            Expr::Assign(expr) => self.expr_assign(expr, fixup),
             Expr::Async(expr) => self.expr_async(expr),
-            Expr::Await(expr) => self.expr_await(expr, beginning_of_line),
-            Expr::Binary(expr) => self.expr_binary(expr),
+            Expr::Await(expr) => self.expr_await(expr, beginning_of_line, fixup),
+            Expr::Binary(expr) => self.expr_binary(expr, fixup),
             Expr::Block(expr) => self.expr_block(expr),
-            Expr::Break(expr) => self.expr_break(expr),
-            Expr::Call(expr) => self.expr_call(expr, beginning_of_line),
-            Expr::Cast(expr) => self.expr_cast(expr),
-            Expr::Closure(expr) => self.expr_closure(expr),
+            Expr::Break(expr) => self.expr_break(expr, fixup),
+            Expr::Call(expr) => self.expr_call(expr, beginning_of_line, fixup),
+            Expr::Cast(expr) => self.expr_cast(expr, fixup),
+            Expr::Closure(expr) => self.expr_closure(expr, fixup),
             Expr::Const(expr) => self.expr_const(expr),
             Expr::Continue(expr) => self.expr_continue(expr),
-            Expr::Field(expr) => self.expr_field(expr, beginning_of_line),
+            Expr::Field(expr) => self.expr_field(expr, beginning_of_line, fixup),
             Expr::ForLoop(expr) => self.expr_for_loop(expr),
-            Expr::Group(expr) => self.expr_group(expr),
+            Expr::Group(expr) => self.expr_group(expr, fixup),
             Expr::If(expr) => self.expr_if(expr),
-            Expr::Index(expr) => self.expr_index(expr, beginning_of_line),
+            Expr::Index(expr) => self.expr_index(expr, beginning_of_line, fixup),
             Expr::Infer(expr) => self.expr_infer(expr),
-            Expr::Let(expr) => self.expr_let(expr),
+            Expr::Let(expr) => self.expr_let(expr, fixup),
             Expr::Lit(expr) => self.expr_lit(expr),
             Expr::Loop(expr) => self.expr_loop(expr),
             Expr::Macro(expr) => self.expr_macro(expr),
             Expr::Match(expr) => self.expr_match(expr),
-            Expr::MethodCall(expr) => self.expr_method_call(expr, beginning_of_line),
+            Expr::MethodCall(expr) => self.expr_method_call(expr, beginning_of_line, fixup),
             Expr::Paren(expr) => self.expr_paren(expr),
             Expr::Path(expr) => self.expr_path(expr),
-            Expr::Range(expr) => self.expr_range(expr),
-            Expr::RawAddr(expr) => self.expr_raw_addr(expr),
-            Expr::Reference(expr) => self.expr_reference(expr),
+            Expr::Range(expr) => self.expr_range(expr, fixup),
+            Expr::RawAddr(expr) => self.expr_raw_addr(expr, fixup),
+            Expr::Reference(expr) => self.expr_reference(expr, fixup),
             Expr::Repeat(expr) => self.expr_repeat(expr),
-            Expr::Return(expr) => self.expr_return(expr),
+            Expr::Return(expr) => self.expr_return(expr, fixup),
             Expr::Struct(expr) => self.expr_struct(expr),
-            Expr::Try(expr) => self.expr_try(expr, beginning_of_line),
+            Expr::Try(expr) => self.expr_try(expr, beginning_of_line, fixup),
             Expr::TryBlock(expr) => self.expr_try_block(expr),
             Expr::Tuple(expr) => self.expr_tuple(expr),
-            Expr::Unary(expr) => self.expr_unary(expr),
+            Expr::Unary(expr) => self.expr_unary(expr, fixup),
             Expr::Unsafe(expr) => self.expr_unsafe(expr),
-            Expr::Verbatim(expr) => self.expr_verbatim(expr),
+            Expr::Verbatim(expr) => self.expr_verbatim(expr, fixup),
             Expr::While(expr) => self.expr_while(expr),
-            Expr::Yield(expr) => self.expr_yield(expr),
+            Expr::Yield(expr) => self.expr_yield(expr, fixup),
             _ => unimplemented!("unknown Expr"),
         }
-    }
 
-    pub fn expr_beginning_of_line(&mut self, expr: &Expr, beginning_of_line: bool) {
-        match expr {
-            Expr::Await(expr) => self.expr_await(expr, beginning_of_line),
-            Expr::Field(expr) => self.expr_field(expr, beginning_of_line),
-            Expr::Index(expr) => self.expr_index(expr, beginning_of_line),
-            Expr::MethodCall(expr) => self.expr_method_call(expr, beginning_of_line),
-            Expr::Try(expr) => self.expr_try(expr, beginning_of_line),
-            _ => self.expr(expr),
-        }
-    }
-
-    fn subexpr(&mut self, expr: &Expr, beginning_of_line: bool) {
-        match expr {
-            Expr::Await(expr) => self.subexpr_await(expr, beginning_of_line),
-            Expr::Call(expr) => self.subexpr_call(expr),
-            Expr::Field(expr) => self.subexpr_field(expr, beginning_of_line),
-            Expr::Index(expr) => self.subexpr_index(expr, beginning_of_line),
-            Expr::MethodCall(expr) => {
-                let unindent_call_args = false;
-                self.subexpr_method_call(expr, beginning_of_line, unindent_call_args);
-            }
-            Expr::Try(expr) => self.subexpr_try(expr, beginning_of_line),
-            _ => {
-                self.cbox(-INDENT);
-                self.expr(expr);
-                self.end();
-            }
-        }
-    }
-
-    fn wrap_exterior_struct(&mut self, expr: &Expr) {
-        let needs_paren = contains_exterior_struct_lit(expr);
-        if needs_paren {
-            self.word("(");
-        }
-        self.cbox(0);
-        self.expr(expr);
         if needs_paren {
             self.word(")");
         }
+    }
+
+    pub fn expr_beginning_of_line(
+        &mut self,
+        expr: &Expr,
+        mut needs_paren: bool,
+        beginning_of_line: bool,
+        mut fixup: FixupContext,
+    ) {
+        needs_paren |= fixup.parenthesize(expr);
+        if needs_paren {
+            self.word("(");
+            fixup = FixupContext::NONE;
+        }
+
+        match expr {
+            Expr::Await(expr) => self.expr_await(expr, beginning_of_line, fixup),
+            Expr::Field(expr) => self.expr_field(expr, beginning_of_line, fixup),
+            Expr::Index(expr) => self.expr_index(expr, beginning_of_line, fixup),
+            Expr::MethodCall(expr) => self.expr_method_call(expr, beginning_of_line, fixup),
+            Expr::Try(expr) => self.expr_try(expr, beginning_of_line, fixup),
+            _ => self.expr(expr, fixup),
+        }
+
+        if needs_paren {
+            self.word(")");
+        }
+    }
+
+    fn prefix_subexpr(
+        &mut self,
+        expr: &Expr,
+        mut needs_paren: bool,
+        beginning_of_line: bool,
+        mut fixup: FixupContext,
+    ) {
+        needs_paren |= fixup.parenthesize(expr);
+        if needs_paren {
+            self.word("(");
+            fixup = FixupContext::NONE;
+        }
+
+        match expr {
+            Expr::Await(expr) => self.prefix_subexpr_await(expr, beginning_of_line, fixup),
+            Expr::Call(expr) => self.prefix_subexpr_call(expr, fixup),
+            Expr::Field(expr) => self.prefix_subexpr_field(expr, beginning_of_line, fixup),
+            Expr::Index(expr) => self.prefix_subexpr_index(expr, beginning_of_line, fixup),
+            Expr::MethodCall(expr) => {
+                let unindent_call_args = false;
+                self.prefix_subexpr_method_call(expr, beginning_of_line, unindent_call_args, fixup);
+            }
+            Expr::Try(expr) => self.prefix_subexpr_try(expr, beginning_of_line, fixup),
+            _ => {
+                self.cbox(-INDENT);
+                self.expr(expr, fixup);
+                self.end();
+            }
+        }
+
+        if needs_paren {
+            self.word(")");
+        }
+    }
+
+    fn expr_condition(&mut self, expr: &Expr) {
+        self.cbox(0);
+        self.expr(expr, FixupContext::new_condition());
         if needs_newline_if_wrap(expr) {
             self.space();
         } else {
@@ -113,13 +152,26 @@
         self.end();
     }
 
+    pub fn subexpr(&mut self, expr: &Expr, needs_paren: bool, mut fixup: FixupContext) {
+        if needs_paren {
+            self.word("(");
+            fixup = FixupContext::NONE;
+        }
+
+        self.expr(expr, fixup);
+
+        if needs_paren {
+            self.word(")");
+        }
+    }
+
     fn expr_array(&mut self, expr: &ExprArray) {
         self.outer_attrs(&expr.attrs);
         self.word("[");
         self.cbox(INDENT);
         self.zerobreak();
         for element in expr.elems.iter().delimited() {
-            self.expr(&element);
+            self.expr(&element, FixupContext::NONE);
             self.trailing_comma(element.is_last);
         }
         self.offset(-INDENT);
@@ -127,13 +179,21 @@
         self.word("]");
     }
 
-    fn expr_assign(&mut self, expr: &ExprAssign) {
+    fn expr_assign(&mut self, expr: &ExprAssign, fixup: FixupContext) {
+        let (left_prec, left_fixup) = fixup.leftmost_subexpression_with_operator(
+            &expr.left,
+            false,
+            false,
+            Precedence::Assign,
+        );
+        let right_fixup = fixup.rightmost_subexpression_fixup(false, false, Precedence::Assign);
+
         self.outer_attrs(&expr.attrs);
         self.ibox(0);
-        self.expr(&expr.left);
+        self.subexpr(&expr.left, left_prec <= Precedence::Range, left_fixup);
         self.word(" = ");
         self.neverbreak();
-        self.expr(&expr.right);
+        self.expr(&expr.right, right_fixup);
         self.end();
     }
 
@@ -148,29 +208,75 @@
         self.end();
     }
 
-    fn expr_await(&mut self, expr: &ExprAwait, beginning_of_line: bool) {
+    fn expr_await(&mut self, expr: &ExprAwait, beginning_of_line: bool, fixup: FixupContext) {
         self.outer_attrs(&expr.attrs);
         self.cbox(INDENT);
-        self.subexpr_await(expr, beginning_of_line);
+        self.prefix_subexpr_await(expr, beginning_of_line, fixup);
         self.end();
     }
 
-    fn subexpr_await(&mut self, expr: &ExprAwait, beginning_of_line: bool) {
-        self.subexpr(&expr.base, beginning_of_line);
-        self.zerobreak_unless_short_ident(beginning_of_line, &expr.base);
+    fn prefix_subexpr_await(
+        &mut self,
+        expr: &ExprAwait,
+        beginning_of_line: bool,
+        fixup: FixupContext,
+    ) {
+        let (left_prec, left_fixup) = fixup.leftmost_subexpression_with_dot(&expr.base);
+
+        self.prefix_subexpr(
+            &expr.base,
+            left_prec < Precedence::Unambiguous,
+            beginning_of_line,
+            left_fixup,
+        );
+        if !(beginning_of_line && is_short_ident(&expr.base)) {
+            self.scan_break(BreakToken {
+                no_break: self.ends_with('.').then_some(' '),
+                ..BreakToken::default()
+            });
+        }
         self.word(".await");
     }
 
-    fn expr_binary(&mut self, expr: &ExprBinary) {
+    fn expr_binary(&mut self, expr: &ExprBinary, fixup: FixupContext) {
+        let binop_prec = Precedence::of_binop(&expr.op);
+        let (left_prec, left_fixup) = fixup.leftmost_subexpression_with_operator(
+            &expr.left,
+            match &expr.op {
+                BinOp::Sub(_)
+                | BinOp::Mul(_)
+                | BinOp::And(_)
+                | BinOp::Or(_)
+                | BinOp::BitAnd(_)
+                | BinOp::BitOr(_)
+                | BinOp::Shl(_)
+                | BinOp::Lt(_) => true,
+                _ => false,
+            },
+            match &expr.op {
+                BinOp::Shl(_) | BinOp::Lt(_) => true,
+                _ => false,
+            },
+            binop_prec,
+        );
+        let left_needs_group = match binop_prec {
+            Precedence::Assign => left_prec <= Precedence::Range,
+            Precedence::Compare => left_prec <= binop_prec,
+            _ => left_prec < binop_prec,
+        };
+        let right_fixup = fixup.rightmost_subexpression_fixup(false, false, binop_prec);
+        let right_needs_group = binop_prec != Precedence::Assign
+            && right_fixup.rightmost_subexpression_precedence(&expr.right) <= binop_prec;
+
         self.outer_attrs(&expr.attrs);
         self.ibox(INDENT);
         self.ibox(-INDENT);
-        self.expr(&expr.left);
+        self.subexpr(&expr.left, left_needs_group, left_fixup);
         self.end();
         self.space();
         self.binary_operator(&expr.op);
         self.nbsp();
-        self.expr(&expr.right);
+        self.subexpr(&expr.right, right_needs_group, right_fixup);
         self.end();
     }
 
@@ -184,7 +290,7 @@
         self.end();
     }
 
-    fn expr_break(&mut self, expr: &ExprBreak) {
+    fn expr_break(&mut self, expr: &ExprBreak, fixup: FixupContext) {
         self.outer_attrs(&expr.attrs);
         self.word("break");
         if let Some(lifetime) = &expr.label {
@@ -193,31 +299,62 @@
         }
         if let Some(value) = &expr.expr {
             self.nbsp();
-            self.expr(value);
+            self.subexpr(
+                value,
+                expr.label.is_none() && classify::expr_leading_label(value),
+                fixup.rightmost_subexpression_fixup(true, true, Precedence::Jump),
+            );
         }
     }
 
-    fn expr_call(&mut self, expr: &ExprCall, beginning_of_line: bool) {
+    fn expr_call(&mut self, expr: &ExprCall, beginning_of_line: bool, fixup: FixupContext) {
+        let (left_prec, left_fixup) = fixup.leftmost_subexpression_with_operator(
+            &expr.func,
+            true,
+            false,
+            Precedence::Unambiguous,
+        );
+        let needs_paren = if let Expr::Field(func) = &*expr.func {
+            matches!(func.member, Member::Named(_))
+        } else {
+            left_prec < Precedence::Unambiguous
+        };
+
         self.outer_attrs(&expr.attrs);
-        self.expr_beginning_of_line(&expr.func, beginning_of_line);
+        self.expr_beginning_of_line(&expr.func, needs_paren, beginning_of_line, left_fixup);
         self.word("(");
         self.call_args(&expr.args);
         self.word(")");
     }
 
-    fn subexpr_call(&mut self, expr: &ExprCall) {
+    fn prefix_subexpr_call(&mut self, expr: &ExprCall, fixup: FixupContext) {
+        let (left_prec, left_fixup) = fixup.leftmost_subexpression_with_operator(
+            &expr.func,
+            true,
+            false,
+            Precedence::Unambiguous,
+        );
+        let needs_paren = if let Expr::Field(func) = &*expr.func {
+            matches!(func.member, Member::Named(_))
+        } else {
+            left_prec < Precedence::Unambiguous
+        };
+
         let beginning_of_line = false;
-        self.subexpr(&expr.func, beginning_of_line);
+        self.prefix_subexpr(&expr.func, needs_paren, beginning_of_line, left_fixup);
         self.word("(");
         self.call_args(&expr.args);
         self.word(")");
     }
 
-    fn expr_cast(&mut self, expr: &ExprCast) {
+    fn expr_cast(&mut self, expr: &ExprCast, fixup: FixupContext) {
+        let (left_prec, left_fixup) =
+            fixup.leftmost_subexpression_with_operator(&expr.expr, false, false, Precedence::Cast);
+
         self.outer_attrs(&expr.attrs);
         self.ibox(INDENT);
         self.ibox(-INDENT);
-        self.expr(&expr.expr);
+        self.subexpr(&expr.expr, left_prec < Precedence::Cast, left_fixup);
         self.end();
         self.space();
         self.word("as ");
@@ -225,7 +362,7 @@
         self.end();
     }
 
-    fn expr_closure(&mut self, expr: &ExprClosure) {
+    fn expr_closure(&mut self, expr: &ExprClosure, fixup: FixupContext) {
         self.outer_attrs(&expr.attrs);
         self.ibox(0);
         if let Some(bound_lifetimes) = &expr.lifetimes {
@@ -275,16 +412,22 @@
                         pre_break: Some(if okay_to_brace { '{' } else { '(' }),
                         ..BreakToken::default()
                     });
-                    self.expr(&expr.body);
+                    self.expr(
+                        &expr.body,
+                        fixup.rightmost_subexpression_fixup(false, false, Precedence::Jump),
+                    );
                     self.scan_break(BreakToken {
                         offset: -INDENT,
-                        pre_break: (okay_to_brace && stmt::add_semi(&expr.body)).then(|| ';'),
-                        post_break: Some(if okay_to_brace { '}' } else { ')' }),
+                        pre_break: (okay_to_brace && stmt::add_semi(&expr.body)).then_some(';'),
+                        post_break: if okay_to_brace { "}" } else { ")" },
                         ..BreakToken::default()
                     });
                     self.end();
                 } else {
-                    self.expr(&expr.body);
+                    self.expr(
+                        &expr.body,
+                        fixup.rightmost_subexpression_fixup(false, false, Precedence::Jump),
+                    );
                 }
             }
             ReturnType::Type(_arrow, ty) => {
@@ -298,7 +441,17 @@
                 self.ty(ty);
                 self.nbsp();
                 self.neverbreak();
-                self.expr(&expr.body);
+                if matches!(&*expr.body, Expr::Block(body) if body.attrs.is_empty() && body.label.is_none())
+                {
+                    self.expr(
+                        &expr.body,
+                        fixup.rightmost_subexpression_fixup(false, false, Precedence::Jump),
+                    );
+                } else {
+                    self.cbox(INDENT);
+                    self.expr_as_small_block(&expr.body, 0);
+                    self.end();
+                }
             }
         }
         self.end();
@@ -321,16 +474,33 @@
         }
     }
 
-    fn expr_field(&mut self, expr: &ExprField, beginning_of_line: bool) {
+    fn expr_field(&mut self, expr: &ExprField, beginning_of_line: bool, fixup: FixupContext) {
         self.outer_attrs(&expr.attrs);
         self.cbox(INDENT);
-        self.subexpr_field(expr, beginning_of_line);
+        self.prefix_subexpr_field(expr, beginning_of_line, fixup);
         self.end();
     }
 
-    fn subexpr_field(&mut self, expr: &ExprField, beginning_of_line: bool) {
-        self.subexpr(&expr.base, beginning_of_line);
-        self.zerobreak_unless_short_ident(beginning_of_line, &expr.base);
+    fn prefix_subexpr_field(
+        &mut self,
+        expr: &ExprField,
+        beginning_of_line: bool,
+        fixup: FixupContext,
+    ) {
+        let (left_prec, left_fixup) = fixup.leftmost_subexpression_with_dot(&expr.base);
+
+        self.prefix_subexpr(
+            &expr.base,
+            left_prec < Precedence::Unambiguous,
+            beginning_of_line,
+            left_fixup,
+        );
+        if !(beginning_of_line && is_short_ident(&expr.base)) {
+            self.scan_break(BreakToken {
+                no_break: self.ends_with('.').then_some(' '),
+                ..BreakToken::default()
+            });
+        }
         self.word(".");
         self.member(&expr.member);
     }
@@ -345,14 +515,14 @@
         self.pat(&expr.pat);
         self.word(" in ");
         self.neverbreak();
-        self.wrap_exterior_struct(&expr.expr);
+        self.expr_condition(&expr.expr);
         self.word("{");
         self.neverbreak();
         self.cbox(INDENT);
         self.hardbreak_if_nonempty();
         self.inner_attrs(&expr.attrs);
-        for stmt in &expr.body.stmts {
-            self.stmt(stmt);
+        for stmt in expr.body.stmts.iter().delimited() {
+            self.stmt(&stmt, stmt.is_last);
         }
         self.offset(-INDENT);
         self.end();
@@ -360,9 +530,9 @@
         self.end();
     }
 
-    fn expr_group(&mut self, expr: &ExprGroup) {
+    fn expr_group(&mut self, expr: &ExprGroup, fixup: FixupContext) {
         self.outer_attrs(&expr.attrs);
-        self.expr(&expr.expr);
+        self.expr(&expr.expr, fixup);
     }
 
     fn expr_if(&mut self, expr: &ExprIf) {
@@ -370,7 +540,7 @@
         self.cbox(INDENT);
         self.word("if ");
         self.cbox(-INDENT);
-        self.wrap_exterior_struct(&expr.cond);
+        self.expr_condition(&expr.cond);
         self.end();
         if let Some((_else_token, else_branch)) = &expr.else_branch {
             let mut else_branch = &**else_branch;
@@ -381,7 +551,7 @@
                     Expr::If(expr) => {
                         self.word("if ");
                         self.cbox(-INDENT);
-                        self.wrap_exterior_struct(&expr.cond);
+                        self.expr_condition(&expr.cond);
                         self.end();
                         self.small_block(&expr.then_branch, &[]);
                         if let Some((_else_token, next)) = &expr.else_branch {
@@ -394,16 +564,7 @@
                     }
                     // If not one of the valid expressions to exist in an else
                     // clause, wrap in a block.
-                    other => {
-                        self.word("{");
-                        self.space();
-                        self.ibox(INDENT);
-                        self.expr(other);
-                        self.end();
-                        self.space();
-                        self.offset(-INDENT);
-                        self.word("}");
-                    }
+                    other => self.expr_as_small_block(other, INDENT),
                 }
                 break;
             }
@@ -412,8 +573,8 @@
         } else {
             self.word("{");
             self.hardbreak();
-            for stmt in &expr.then_branch.stmts {
-                self.stmt(stmt);
+            for stmt in expr.then_branch.stmts.iter().delimited() {
+                self.stmt(&stmt, stmt.is_last);
             }
             self.offset(-INDENT);
             self.word("}");
@@ -421,18 +582,47 @@
         self.end();
     }
 
-    fn expr_index(&mut self, expr: &ExprIndex, beginning_of_line: bool) {
+    fn expr_index(&mut self, expr: &ExprIndex, beginning_of_line: bool, fixup: FixupContext) {
+        let (left_prec, left_fixup) = fixup.leftmost_subexpression_with_operator(
+            &expr.expr,
+            true,
+            false,
+            Precedence::Unambiguous,
+        );
+
         self.outer_attrs(&expr.attrs);
-        self.expr_beginning_of_line(&expr.expr, beginning_of_line);
+        self.expr_beginning_of_line(
+            &expr.expr,
+            left_prec < Precedence::Unambiguous,
+            beginning_of_line,
+            left_fixup,
+        );
         self.word("[");
-        self.expr(&expr.index);
+        self.expr(&expr.index, FixupContext::NONE);
         self.word("]");
     }
 
-    fn subexpr_index(&mut self, expr: &ExprIndex, beginning_of_line: bool) {
-        self.subexpr(&expr.expr, beginning_of_line);
+    fn prefix_subexpr_index(
+        &mut self,
+        expr: &ExprIndex,
+        beginning_of_line: bool,
+        fixup: FixupContext,
+    ) {
+        let (left_prec, left_fixup) = fixup.leftmost_subexpression_with_operator(
+            &expr.expr,
+            true,
+            false,
+            Precedence::Unambiguous,
+        );
+
+        self.prefix_subexpr(
+            &expr.expr,
+            left_prec < Precedence::Unambiguous,
+            beginning_of_line,
+            left_fixup,
+        );
         self.word("[");
-        self.expr(&expr.index);
+        self.expr(&expr.index, FixupContext::NONE);
         self.word("]");
     }
 
@@ -441,7 +631,9 @@
         self.word("_");
     }
 
-    fn expr_let(&mut self, expr: &ExprLet) {
+    fn expr_let(&mut self, expr: &ExprLet, fixup: FixupContext) {
+        let (right_prec, right_fixup) = fixup.rightmost_subexpression(&expr.expr, Precedence::Let);
+
         self.outer_attrs(&expr.attrs);
         self.ibox(0);
         self.word("let ");
@@ -451,14 +643,7 @@
         self.word(" = ");
         self.neverbreak();
         self.ibox(0);
-        let needs_paren = contains_exterior_struct_lit(&expr.expr);
-        if needs_paren {
-            self.word("(");
-        }
-        self.expr(&expr.expr);
-        if needs_paren {
-            self.word(")");
-        }
+        self.subexpr(&expr.expr, right_prec < Precedence::Let, right_fixup);
         self.end();
         self.end();
     }
@@ -477,8 +662,8 @@
         self.cbox(INDENT);
         self.hardbreak_if_nonempty();
         self.inner_attrs(&expr.attrs);
-        for stmt in &expr.body.stmts {
-            self.stmt(stmt);
+        for stmt in expr.body.stmts.iter().delimited() {
+            self.stmt(&stmt, stmt.is_last);
         }
         self.offset(-INDENT);
         self.end();
@@ -495,7 +680,7 @@
         self.outer_attrs(&expr.attrs);
         self.ibox(0);
         self.word("match ");
-        self.wrap_exterior_struct(&expr.expr);
+        self.expr_condition(&expr.expr);
         self.word("{");
         self.neverbreak();
         self.cbox(INDENT);
@@ -511,22 +696,40 @@
         self.end();
     }
 
-    fn expr_method_call(&mut self, expr: &ExprMethodCall, beginning_of_line: bool) {
+    fn expr_method_call(
+        &mut self,
+        expr: &ExprMethodCall,
+        beginning_of_line: bool,
+        fixup: FixupContext,
+    ) {
         self.outer_attrs(&expr.attrs);
         self.cbox(INDENT);
         let unindent_call_args = beginning_of_line && is_short_ident(&expr.receiver);
-        self.subexpr_method_call(expr, beginning_of_line, unindent_call_args);
+        self.prefix_subexpr_method_call(expr, beginning_of_line, unindent_call_args, fixup);
         self.end();
     }
 
-    fn subexpr_method_call(
+    fn prefix_subexpr_method_call(
         &mut self,
         expr: &ExprMethodCall,
         beginning_of_line: bool,
         unindent_call_args: bool,
+        fixup: FixupContext,
     ) {
-        self.subexpr(&expr.receiver, beginning_of_line);
-        self.zerobreak_unless_short_ident(beginning_of_line, &expr.receiver);
+        let (left_prec, left_fixup) = fixup.leftmost_subexpression_with_dot(&expr.receiver);
+
+        self.prefix_subexpr(
+            &expr.receiver,
+            left_prec < Precedence::Unambiguous,
+            beginning_of_line,
+            left_fixup,
+        );
+        if !(beginning_of_line && is_short_ident(&expr.receiver)) {
+            self.scan_break(BreakToken {
+                no_break: self.ends_with('.').then_some(' '),
+                ..BreakToken::default()
+            });
+        }
         self.word(".");
         self.ident(&expr.method);
         if let Some(turbofish) = &expr.turbofish {
@@ -542,7 +745,7 @@
     fn expr_paren(&mut self, expr: &ExprParen) {
         self.outer_attrs(&expr.attrs);
         self.word("(");
-        self.expr(&expr.expr);
+        self.expr(&expr.expr, FixupContext::NONE);
         self.word(")");
     }
 
@@ -551,52 +754,67 @@
         self.qpath(&expr.qself, &expr.path, PathKind::Expr);
     }
 
-    pub fn expr_range(&mut self, expr: &ExprRange) {
+    pub fn expr_range(&mut self, expr: &ExprRange, fixup: FixupContext) {
         self.outer_attrs(&expr.attrs);
         if let Some(start) = &expr.start {
-            self.expr(start);
+            let (left_prec, left_fixup) =
+                fixup.leftmost_subexpression_with_operator(start, true, false, Precedence::Range);
+            self.subexpr(start, left_prec <= Precedence::Range, left_fixup);
+        } else if self.ends_with('.') {
+            self.nbsp();
         }
         self.word(match expr.limits {
             RangeLimits::HalfOpen(_) => "..",
             RangeLimits::Closed(_) => "..=",
         });
         if let Some(end) = &expr.end {
-            self.expr(end);
+            let right_fixup = fixup.rightmost_subexpression_fixup(false, true, Precedence::Range);
+            let right_prec = right_fixup.rightmost_subexpression_precedence(end);
+            self.subexpr(end, right_prec <= Precedence::Range, right_fixup);
         }
     }
 
-    fn expr_raw_addr(&mut self, expr: &ExprRawAddr) {
+    fn expr_raw_addr(&mut self, expr: &ExprRawAddr, fixup: FixupContext) {
+        let (right_prec, right_fixup) =
+            fixup.rightmost_subexpression(&expr.expr, Precedence::Prefix);
+
         self.outer_attrs(&expr.attrs);
         self.word("&raw ");
         self.pointer_mutability(&expr.mutability);
         self.nbsp();
-        self.expr(&expr.expr);
+        self.subexpr(&expr.expr, right_prec < Precedence::Prefix, right_fixup);
     }
 
-    fn expr_reference(&mut self, expr: &ExprReference) {
+    fn expr_reference(&mut self, expr: &ExprReference, fixup: FixupContext) {
+        let (right_prec, right_fixup) =
+            fixup.rightmost_subexpression(&expr.expr, Precedence::Prefix);
+
         self.outer_attrs(&expr.attrs);
         self.word("&");
         if expr.mutability.is_some() {
             self.word("mut ");
         }
-        self.expr(&expr.expr);
+        self.subexpr(&expr.expr, right_prec < Precedence::Prefix, right_fixup);
     }
 
     fn expr_repeat(&mut self, expr: &ExprRepeat) {
         self.outer_attrs(&expr.attrs);
         self.word("[");
-        self.expr(&expr.expr);
+        self.expr(&expr.expr, FixupContext::NONE);
         self.word("; ");
-        self.expr(&expr.len);
+        self.expr(&expr.len, FixupContext::NONE);
         self.word("]");
     }
 
-    fn expr_return(&mut self, expr: &ExprReturn) {
+    fn expr_return(&mut self, expr: &ExprReturn, fixup: FixupContext) {
         self.outer_attrs(&expr.attrs);
         self.word("return");
         if let Some(value) = &expr.expr {
             self.nbsp();
-            self.expr(value);
+            self.expr(
+                value,
+                fixup.rightmost_subexpression_fixup(true, false, Precedence::Jump),
+            );
         }
     }
 
@@ -614,7 +832,7 @@
         }
         if let Some(rest) = &expr.rest {
             self.word("..");
-            self.expr(rest);
+            self.expr(rest, FixupContext::NONE);
             self.space();
         }
         self.offset(-INDENT);
@@ -622,14 +840,28 @@
         self.word("}");
     }
 
-    fn expr_try(&mut self, expr: &ExprTry, beginning_of_line: bool) {
+    fn expr_try(&mut self, expr: &ExprTry, beginning_of_line: bool, fixup: FixupContext) {
+        let (left_prec, left_fixup) = fixup.leftmost_subexpression_with_dot(&expr.expr);
+
         self.outer_attrs(&expr.attrs);
-        self.expr_beginning_of_line(&expr.expr, beginning_of_line);
+        self.expr_beginning_of_line(
+            &expr.expr,
+            left_prec < Precedence::Unambiguous,
+            beginning_of_line,
+            left_fixup,
+        );
         self.word("?");
     }
 
-    fn subexpr_try(&mut self, expr: &ExprTry, beginning_of_line: bool) {
-        self.subexpr(&expr.expr, beginning_of_line);
+    fn prefix_subexpr_try(&mut self, expr: &ExprTry, beginning_of_line: bool, fixup: FixupContext) {
+        let (left_prec, left_fixup) = fixup.leftmost_subexpression_with_dot(&expr.expr);
+
+        self.prefix_subexpr(
+            &expr.expr,
+            left_prec < Precedence::Unambiguous,
+            beginning_of_line,
+            left_fixup,
+        );
         self.word("?");
     }
 
@@ -647,7 +879,7 @@
         self.cbox(INDENT);
         self.zerobreak();
         for elem in expr.elems.iter().delimited() {
-            self.expr(&elem);
+            self.expr(&elem, FixupContext::NONE);
             if expr.elems.len() == 1 {
                 self.word(",");
                 self.zerobreak();
@@ -660,10 +892,13 @@
         self.word(")");
     }
 
-    fn expr_unary(&mut self, expr: &ExprUnary) {
+    fn expr_unary(&mut self, expr: &ExprUnary, fixup: FixupContext) {
+        let (right_prec, right_fixup) =
+            fixup.rightmost_subexpression(&expr.expr, Precedence::Prefix);
+
         self.outer_attrs(&expr.attrs);
         self.unary_operator(&expr.op);
-        self.expr(&expr.expr);
+        self.subexpr(&expr.expr, right_prec < Precedence::Prefix, right_fixup);
     }
 
     fn expr_unsafe(&mut self, expr: &ExprUnsafe) {
@@ -675,14 +910,14 @@
     }
 
     #[cfg(not(feature = "verbatim"))]
-    fn expr_verbatim(&mut self, expr: &TokenStream) {
+    fn expr_verbatim(&mut self, expr: &TokenStream, _fixup: FixupContext) {
         if !expr.is_empty() {
             unimplemented!("Expr::Verbatim `{}`", expr);
         }
     }
 
     #[cfg(feature = "verbatim")]
-    fn expr_verbatim(&mut self, tokens: &TokenStream) {
+    fn expr_verbatim(&mut self, tokens: &TokenStream, fixup: FixupContext) {
         use syn::parse::discouraged::Speculative;
         use syn::parse::{Parse, ParseStream, Result};
         use syn::{parenthesized, Ident};
@@ -754,7 +989,10 @@
                 self.outer_attrs(&expr.attrs);
                 self.word("become");
                 self.nbsp();
-                self.expr(&expr.tail_call);
+                self.expr(
+                    &expr.tail_call,
+                    fixup.rightmost_subexpression_fixup(true, false, Precedence::Jump),
+                );
             }
             ExprVerbatim::Builtin(expr) => {
                 self.outer_attrs(&expr.attrs);
@@ -782,26 +1020,29 @@
             self.label(label);
         }
         self.word("while ");
-        self.wrap_exterior_struct(&expr.cond);
+        self.expr_condition(&expr.cond);
         self.word("{");
         self.neverbreak();
         self.cbox(INDENT);
         self.hardbreak_if_nonempty();
         self.inner_attrs(&expr.attrs);
-        for stmt in &expr.body.stmts {
-            self.stmt(stmt);
+        for stmt in expr.body.stmts.iter().delimited() {
+            self.stmt(&stmt, stmt.is_last);
         }
         self.offset(-INDENT);
         self.end();
         self.word("}");
     }
 
-    fn expr_yield(&mut self, expr: &ExprYield) {
+    fn expr_yield(&mut self, expr: &ExprYield, fixup: FixupContext) {
         self.outer_attrs(&expr.attrs);
         self.word("yield");
         if let Some(value) = &expr.expr {
             self.nbsp();
-            self.expr(value);
+            self.expr(
+                value,
+                fixup.rightmost_subexpression_fixup(true, false, Precedence::Jump),
+            );
         }
     }
 
@@ -816,7 +1057,7 @@
         if field_value.colon_token.is_some() {
             self.word(": ");
             self.ibox(0);
-            self.expr(&field_value.expr);
+            self.expr(&field_value.expr, FixupContext::NONE);
             self.end();
         }
     }
@@ -827,9 +1068,9 @@
         self.pat(&arm.pat);
         if let Some((_if_token, guard)) = &arm.guard {
             self.word(" if ");
-            self.expr(guard);
+            self.expr(guard, FixupContext::NONE);
         }
-        self.word(" =>");
+        self.word(" => ");
         let empty_block;
         let mut body = &*arm.body;
         while let Expr::Block(expr) = body {
@@ -856,7 +1097,6 @@
             }
         }
         if let Expr::Block(body) = body {
-            self.nbsp();
             if let Some(label) = &body.label {
                 self.label(label);
             }
@@ -865,45 +1105,44 @@
             self.cbox(INDENT);
             self.hardbreak_if_nonempty();
             self.inner_attrs(&body.attrs);
-            for stmt in &body.block.stmts {
-                self.stmt(stmt);
+            for stmt in body.block.stmts.iter().delimited() {
+                self.stmt(&stmt, stmt.is_last);
             }
             self.offset(-INDENT);
             self.end();
             self.word("}");
-            self.end();
         } else {
-            self.nbsp();
             self.neverbreak();
             self.cbox(INDENT);
+            let okay_to_brace = parseable_as_stmt(body);
             self.scan_break(BreakToken {
-                pre_break: Some('{'),
+                pre_break: Some(if okay_to_brace { '{' } else { '(' }),
                 ..BreakToken::default()
             });
-            self.expr_beginning_of_line(body, true);
+            self.expr_beginning_of_line(body, false, true, FixupContext::new_match_arm());
             self.scan_break(BreakToken {
                 offset: -INDENT,
-                pre_break: stmt::add_semi(body).then(|| ';'),
-                post_break: Some('}'),
-                no_break: requires_terminator(body).then(|| ','),
+                pre_break: (okay_to_brace && stmt::add_semi(body)).then_some(';'),
+                post_break: if okay_to_brace { "}" } else { ")," },
+                no_break: classify::requires_comma_to_be_match_arm(body).then_some(','),
                 ..BreakToken::default()
             });
             self.end();
-            self.end();
         }
+        self.end();
     }
 
     fn call_args(&mut self, args: &Punctuated<Expr, Token![,]>) {
         let mut iter = args.iter();
         match (iter.next(), iter.next()) {
             (Some(expr), None) if is_blocklike(expr) => {
-                self.expr(expr);
+                self.expr(expr, FixupContext::NONE);
             }
             _ => {
                 self.cbox(INDENT);
                 self.zerobreak();
                 for arg in args.iter().delimited() {
-                    self.expr(&arg);
+                    self.expr(&arg, FixupContext::NONE);
                     self.trailing_comma(arg.is_last);
                 }
                 self.offset(-INDENT);
@@ -920,13 +1159,13 @@
             match block.stmts.as_slice() {
                 [Stmt::Expr(expr, None)] if stmt::break_after(expr) => {
                     self.ibox(0);
-                    self.expr_beginning_of_line(expr, true);
+                    self.expr_beginning_of_line(expr, false, true, FixupContext::new_stmt());
                     self.end();
                     self.space();
                 }
                 _ => {
-                    for stmt in &block.stmts {
-                        self.stmt(stmt);
+                    for stmt in block.stmts.iter().delimited() {
+                        self.stmt(&stmt, stmt.is_last);
                     }
                 }
             }
@@ -935,6 +1174,17 @@
         self.word("}");
     }
 
+    pub fn expr_as_small_block(&mut self, expr: &Expr, indent: isize) {
+        self.word("{");
+        self.space();
+        self.ibox(indent);
+        self.expr_beginning_of_line(expr, false, true, FixupContext::new_stmt());
+        self.end();
+        self.space();
+        self.offset(-INDENT);
+        self.word("}");
+    }
+
     pub fn member(&mut self, member: &Member) {
         match member {
             Member::Named(ident) => self.ident(ident),
@@ -1001,123 +1251,6 @@
             PointerMutability::Mut(_) => self.word("mut"),
         }
     }
-
-    fn zerobreak_unless_short_ident(&mut self, beginning_of_line: bool, expr: &Expr) {
-        if beginning_of_line && is_short_ident(expr) {
-            return;
-        }
-        self.zerobreak();
-    }
-}
-
-fn requires_terminator(expr: &Expr) -> bool {
-    // see https://github.com/rust-lang/rust/blob/a266f1199/compiler/rustc_ast/src/util/classify.rs#L7-L26
-    match expr {
-        #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
-        Expr::If(_)
-        | Expr::Match(_)
-        | Expr::Block(_) | Expr::Unsafe(_) // both under ExprKind::Block in rustc
-        | Expr::While(_)
-        | Expr::Loop(_)
-        | Expr::ForLoop(_)
-        | Expr::TryBlock(_)
-        | Expr::Const(_) => false,
-
-        Expr::Array(_)
-        | Expr::Assign(_)
-        | Expr::Async(_)
-        | Expr::Await(_)
-        | Expr::Binary(_)
-        | Expr::Break(_)
-        | Expr::Call(_)
-        | Expr::Cast(_)
-        | Expr::Closure(_)
-        | Expr::Continue(_)
-        | Expr::Field(_)
-        | Expr::Group(_)
-        | Expr::Index(_)
-        | Expr::Infer(_)
-        | Expr::Let(_)
-        | Expr::Lit(_)
-        | Expr::Macro(_)
-        | Expr::MethodCall(_)
-        | Expr::Paren(_)
-        | Expr::Path(_)
-        | Expr::Range(_)
-        | Expr::RawAddr(_)
-        | Expr::Reference(_)
-        | Expr::Repeat(_)
-        | Expr::Return(_)
-        | Expr::Struct(_)
-        | Expr::Try(_)
-        | Expr::Tuple(_)
-        | Expr::Unary(_)
-        | Expr::Verbatim(_)
-        | Expr::Yield(_) => true,
-
-        _ => true,
-    }
-}
-
-// Expressions that syntactically contain an "exterior" struct literal i.e. not
-// surrounded by any parens or other delimiters. For example `X { y: 1 }`, `X {
-// y: 1 }.method()`, `foo == X { y: 1 }` and `X { y: 1 } == foo` all do, but `(X
-// { y: 1 }) == foo` does not.
-fn contains_exterior_struct_lit(expr: &Expr) -> bool {
-    match expr {
-        #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
-        Expr::Struct(_) => true,
-
-        Expr::Assign(ExprAssign { left, right, .. })
-        | Expr::Binary(ExprBinary { left, right, .. }) => {
-            // X { y: 1 } + X { y: 2 }
-            contains_exterior_struct_lit(left) || contains_exterior_struct_lit(right)
-        }
-
-        Expr::Await(ExprAwait { base: e, .. })
-        | Expr::Cast(ExprCast { expr: e, .. })
-        | Expr::Field(ExprField { base: e, .. })
-        | Expr::Group(ExprGroup { expr: e, .. })
-        | Expr::Index(ExprIndex { expr: e, .. })
-        | Expr::MethodCall(ExprMethodCall { receiver: e, .. })
-        | Expr::RawAddr(ExprRawAddr { expr: e, .. })
-        | Expr::Reference(ExprReference { expr: e, .. })
-        | Expr::Unary(ExprUnary { expr: e, .. }) => {
-            // &X { y: 1 }, X { y: 1 }.y
-            contains_exterior_struct_lit(e)
-        }
-
-        Expr::Array(_)
-        | Expr::Async(_)
-        | Expr::Block(_)
-        | Expr::Break(_)
-        | Expr::Call(_)
-        | Expr::Closure(_)
-        | Expr::Const(_)
-        | Expr::Continue(_)
-        | Expr::ForLoop(_)
-        | Expr::If(_)
-        | Expr::Infer(_)
-        | Expr::Let(_)
-        | Expr::Lit(_)
-        | Expr::Loop(_)
-        | Expr::Macro(_)
-        | Expr::Match(_)
-        | Expr::Paren(_)
-        | Expr::Path(_)
-        | Expr::Range(_)
-        | Expr::Repeat(_)
-        | Expr::Return(_)
-        | Expr::Try(_)
-        | Expr::TryBlock(_)
-        | Expr::Tuple(_)
-        | Expr::Unsafe(_)
-        | Expr::Verbatim(_)
-        | Expr::While(_)
-        | Expr::Yield(_) => false,
-
-        _ => false,
-    }
 }
 
 fn needs_newline_if_wrap(expr: &Expr) -> bool {
@@ -1208,7 +1341,6 @@
         | Expr::Continue(_)
         | Expr::Field(_)
         | Expr::ForLoop(_)
-        | Expr::Group(_)
         | Expr::If(_)
         | Expr::Index(_)
         | Expr::Infer(_)
@@ -1231,62 +1363,106 @@
         | Expr::While(_)
         | Expr::Yield(_) => false,
 
+        Expr::Group(e) => is_blocklike(&e.expr),
+
         _ => false,
     }
 }
 
+pub fn simple_block(expr: &Expr) -> Option<&ExprBlock> {
+    if let Expr::Block(expr) = expr {
+        if expr.attrs.is_empty() && expr.label.is_none() {
+            return Some(expr);
+        }
+    }
+    None
+}
+
 // Expressions for which `$expr` and `{ $expr }` mean the same thing.
 //
 // This is not the case for all expressions. For example `{} | x | x` has some
 // bitwise OR operators while `{ {} |x| x }` has a block followed by a closure.
-fn parseable_as_stmt(expr: &Expr) -> bool {
-    match expr {
-        #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
-        Expr::Array(_)
-        | Expr::Async(_)
-        | Expr::Block(_)
-        | Expr::Break(_)
-        | Expr::Closure(_)
-        | Expr::Const(_)
-        | Expr::Continue(_)
-        | Expr::ForLoop(_)
-        | Expr::If(_)
-        | Expr::Infer(_)
-        | Expr::Let(_)
-        | Expr::Lit(_)
-        | Expr::Loop(_)
-        | Expr::Macro(_)
-        | Expr::Match(_)
-        | Expr::Paren(_)
-        | Expr::Path(_)
-        | Expr::RawAddr(_)
-        | Expr::Reference(_)
-        | Expr::Repeat(_)
-        | Expr::Return(_)
-        | Expr::Struct(_)
-        | Expr::TryBlock(_)
-        | Expr::Tuple(_)
-        | Expr::Unary(_)
-        | Expr::Unsafe(_)
-        | Expr::Verbatim(_)
-        | Expr::While(_)
-        | Expr::Yield(_) => true,
+fn parseable_as_stmt(mut expr: &Expr) -> bool {
+    loop {
+        match expr {
+            #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
+            Expr::Array(_)
+            | Expr::Async(_)
+            | Expr::Block(_)
+            | Expr::Break(_)
+            | Expr::Closure(_)
+            | Expr::Const(_)
+            | Expr::Continue(_)
+            | Expr::ForLoop(_)
+            | Expr::If(_)
+            | Expr::Infer(_)
+            | Expr::Lit(_)
+            | Expr::Loop(_)
+            | Expr::Macro(_)
+            | Expr::Match(_)
+            | Expr::Paren(_)
+            | Expr::Path(_)
+            | Expr::RawAddr(_)
+            | Expr::Reference(_)
+            | Expr::Repeat(_)
+            | Expr::Return(_)
+            | Expr::Struct(_)
+            | Expr::TryBlock(_)
+            | Expr::Tuple(_)
+            | Expr::Unary(_)
+            | Expr::Unsafe(_)
+            | Expr::Verbatim(_)
+            | Expr::While(_)
+            | Expr::Yield(_) => return true,
 
-        Expr::Assign(expr) => parseable_as_stmt(&expr.left),
-        Expr::Await(expr) => parseable_as_stmt(&expr.base),
-        Expr::Binary(expr) => requires_terminator(&expr.left) && parseable_as_stmt(&expr.left),
-        Expr::Call(expr) => requires_terminator(&expr.func) && parseable_as_stmt(&expr.func),
-        Expr::Cast(expr) => requires_terminator(&expr.expr) && parseable_as_stmt(&expr.expr),
-        Expr::Field(expr) => parseable_as_stmt(&expr.base),
-        Expr::Group(expr) => parseable_as_stmt(&expr.expr),
-        Expr::Index(expr) => requires_terminator(&expr.expr) && parseable_as_stmt(&expr.expr),
-        Expr::MethodCall(expr) => parseable_as_stmt(&expr.receiver),
-        Expr::Range(expr) => match &expr.start {
-            None => true,
-            Some(start) => requires_terminator(start) && parseable_as_stmt(start),
-        },
-        Expr::Try(expr) => parseable_as_stmt(&expr.expr),
+            Expr::Let(_) => return false,
 
-        _ => false,
+            Expr::Assign(e) => {
+                if !classify::requires_semi_to_be_stmt(&e.left) {
+                    return false;
+                }
+                expr = &e.left;
+            }
+            Expr::Await(e) => expr = &e.base,
+            Expr::Binary(e) => {
+                if !classify::requires_semi_to_be_stmt(&e.left) {
+                    return false;
+                }
+                expr = &e.left;
+            }
+            Expr::Call(e) => {
+                if !classify::requires_semi_to_be_stmt(&e.func) {
+                    return false;
+                }
+                expr = &e.func;
+            }
+            Expr::Cast(e) => {
+                if !classify::requires_semi_to_be_stmt(&e.expr) {
+                    return false;
+                }
+                expr = &e.expr;
+            }
+            Expr::Field(e) => expr = &e.base,
+            Expr::Group(e) => expr = &e.expr,
+            Expr::Index(e) => {
+                if !classify::requires_semi_to_be_stmt(&e.expr) {
+                    return false;
+                }
+                expr = &e.expr;
+            }
+            Expr::MethodCall(e) => expr = &e.receiver,
+            Expr::Range(e) => match &e.start {
+                None => return true,
+                Some(start) => {
+                    if !classify::requires_semi_to_be_stmt(start) {
+                        return false;
+                    }
+                    expr = start;
+                }
+            },
+            Expr::Try(e) => expr = &e.expr,
+
+            _ => return false,
+        }
     }
 }
diff --git a/crates/prettyplease/src/fixup.rs b/crates/prettyplease/src/fixup.rs
new file mode 100644
index 0000000..55d3623
--- /dev/null
+++ b/crates/prettyplease/src/fixup.rs
@@ -0,0 +1,673 @@
+use crate::classify;
+use crate::precedence::Precedence;
+use syn::{
+    Expr, ExprBreak, ExprRange, ExprRawAddr, ExprReference, ExprReturn, ExprUnary, ExprYield,
+};
+
+#[derive(Copy, Clone)]
+pub struct FixupContext {
+    previous_operator: Precedence,
+    next_operator: Precedence,
+
+    // 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
+    //     }
+    //
+    condition: bool,
+
+    // This is the difference between:
+    //
+    //     if break Struct {} == (break) {}  // needs parens
+    //
+    //     if break break == Struct {} {}  // no parens
+    //
+    rightmost_subexpression_in_condition: bool,
+
+    // This is the difference between:
+    //
+    //     if break ({ x }).field + 1 {}  needs parens
+    //
+    //     if break 1 + { x }.field {}  // no parens
+    //
+    leftmost_subexpression_in_optional_operand: bool,
+
+    // This is the difference between:
+    //
+    //     let _ = (return) - 1;  // without paren, this would return -1
+    //
+    //     let _ = return + 1;  // no paren because '+' cannot begin expr
+    //
+    next_operator_can_begin_expr: bool,
+
+    // This is the difference between:
+    //
+    //     let _ = 1 + return 1;  // no parens if rightmost subexpression
+    //
+    //     let _ = 1 + (return 1) + 1;  // needs parens
+    //
+    next_operator_can_continue_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 {
+    /// The default amount of fixing is minimal fixing. Fixups should be turned
+    /// on in a targeted fashion where needed.
+    pub const NONE: Self = FixupContext {
+        previous_operator: Precedence::MIN,
+        next_operator: Precedence::MIN,
+        stmt: false,
+        leftmost_subexpression_in_stmt: false,
+        match_arm: false,
+        leftmost_subexpression_in_match_arm: false,
+        condition: false,
+        rightmost_subexpression_in_condition: false,
+        leftmost_subexpression_in_optional_operand: false,
+        next_operator_can_begin_expr: false,
+        next_operator_can_continue_expr: false,
+        next_operator_can_begin_generics: false,
+    };
+
+    /// Create the initial fixup for printing an expression in statement
+    /// position.
+    pub fn new_stmt() -> Self {
+        FixupContext {
+            stmt: true,
+            ..FixupContext::NONE
+        }
+    }
+
+    /// 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::NONE
+        }
+    }
+
+    /// 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 {
+            condition: true,
+            rightmost_subexpression_in_condition: true,
+            ..FixupContext::NONE
+        }
+    }
+
+    /// 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_with_operator(
+        self,
+        expr: &Expr,
+        next_operator_can_begin_expr: bool,
+        next_operator_can_begin_generics: bool,
+        precedence: Precedence,
+    ) -> (Precedence, Self) {
+        let fixup = FixupContext {
+            next_operator: precedence,
+            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,
+            rightmost_subexpression_in_condition: false,
+            next_operator_can_begin_expr,
+            next_operator_can_continue_expr: true,
+            next_operator_can_begin_generics,
+            ..self
+        };
+
+        (fixup.leftmost_subexpression_precedence(expr), fixup)
+    }
+
+    /// 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, expr: &Expr) -> (Precedence, Self) {
+        let fixup = FixupContext {
+            next_operator: Precedence::Unambiguous,
+            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,
+            rightmost_subexpression_in_condition: false,
+            next_operator_can_begin_expr: false,
+            next_operator_can_continue_expr: true,
+            next_operator_can_begin_generics: false,
+            ..self
+        };
+
+        (fixup.leftmost_subexpression_precedence(expr), fixup)
+    }
+
+    fn leftmost_subexpression_precedence(self, expr: &Expr) -> Precedence {
+        if !self.next_operator_can_begin_expr || self.next_operator == Precedence::Range {
+            if let Scan::Bailout = scan_right(expr, self, Precedence::MIN, 0, 0) {
+                if scan_left(expr, self) {
+                    return Precedence::Unambiguous;
+                }
+            }
+        }
+
+        self.precedence(expr)
+    }
+
+    /// Transform this fixup into the one that should apply when printing the
+    /// rightmost subexpression of the current expression.
+    ///
+    /// The rightmost subexpression is any subexpression that has a different
+    /// first token than the current expression, but has the same last token.
+    ///
+    /// For example in `$a + $b` and `-$b`, the subexpression `$b` is a
+    /// rightmost subexpression.
+    ///
+    /// Not every expression has a rightmost subexpression. For example neither
+    /// `[$b]` nor `$a.f($b)` have one.
+    pub fn rightmost_subexpression(
+        self,
+        expr: &Expr,
+        precedence: Precedence,
+    ) -> (Precedence, Self) {
+        let fixup = self.rightmost_subexpression_fixup(false, false, precedence);
+        (fixup.rightmost_subexpression_precedence(expr), fixup)
+    }
+
+    pub fn rightmost_subexpression_fixup(
+        self,
+        reset_allow_struct: bool,
+        optional_operand: bool,
+        precedence: Precedence,
+    ) -> Self {
+        FixupContext {
+            previous_operator: precedence,
+            stmt: false,
+            leftmost_subexpression_in_stmt: false,
+            match_arm: false,
+            leftmost_subexpression_in_match_arm: false,
+            condition: self.condition && !reset_allow_struct,
+            leftmost_subexpression_in_optional_operand: self.condition && optional_operand,
+            ..self
+        }
+    }
+
+    pub fn rightmost_subexpression_precedence(self, expr: &Expr) -> Precedence {
+        let default_prec = self.precedence(expr);
+
+        if match self.previous_operator {
+            Precedence::Assign | Precedence::Let | Precedence::Prefix => {
+                default_prec < self.previous_operator
+            }
+            _ => default_prec <= self.previous_operator,
+        } && match self.next_operator {
+            Precedence::Range | Precedence::Or | Precedence::And => true,
+            _ => !self.next_operator_can_begin_expr,
+        } {
+            if let Scan::Bailout | Scan::Fail = scan_right(expr, self, self.previous_operator, 1, 0)
+            {
+                if scan_left(expr, self) {
+                    return Precedence::Prefix;
+                }
+            }
+        }
+
+        default_prec
+    }
+
+    /// Determine whether parentheses are needed around the given expression to
+    /// head off the early termination of a statement or condition.
+    pub fn parenthesize(self, expr: &Expr) -> bool {
+        (self.leftmost_subexpression_in_stmt && !classify::requires_semi_to_be_stmt(expr))
+            || ((self.stmt || self.leftmost_subexpression_in_stmt) && matches!(expr, Expr::Let(_)))
+            || (self.leftmost_subexpression_in_match_arm
+                && !classify::requires_comma_to_be_match_arm(expr))
+            || (self.condition && matches!(expr, Expr::Struct(_)))
+            || (self.rightmost_subexpression_in_condition
+                && matches!(
+                    expr,
+                    Expr::Return(ExprReturn { expr: None, .. })
+                        | Expr::Yield(ExprYield { expr: None, .. })
+                ))
+            || (self.rightmost_subexpression_in_condition
+                && !self.condition
+                && matches!(
+                    expr,
+                    Expr::Break(ExprBreak { expr: None, .. })
+                        | Expr::Path(_)
+                        | Expr::Range(ExprRange { end: None, .. })
+                ))
+            || (self.leftmost_subexpression_in_optional_operand
+                && matches!(expr, Expr::Block(expr) if expr.attrs.is_empty() && expr.label.is_none()))
+    }
+
+    /// Determines the effective precedence of a subexpression. Some expressions
+    /// have higher or lower precedence when adjacent to particular operators.
+    fn precedence(self, expr: &Expr) -> Precedence {
+        if self.next_operator_can_begin_expr {
+            // 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(ExprBreak { expr: None, .. })
+            | Expr::Return(ExprReturn { expr: None, .. })
+            | Expr::Yield(ExprYield { expr: None, .. }) = expr
+            {
+                return Precedence::Jump;
+            }
+        }
+
+        if !self.next_operator_can_continue_expr {
+            match expr {
+                // Increase precedence of expressions that extend to the end of
+                // current statement or group.
+                Expr::Break(_)
+                | Expr::Closure(_)
+                | Expr::Let(_)
+                | Expr::Return(_)
+                | Expr::Yield(_) => {
+                    return Precedence::Prefix;
+                }
+                Expr::Range(e) if e.start.is_none() => return Precedence::Prefix,
+                _ => {}
+            }
+        }
+
+        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)
+    }
+}
+
+#[derive(Copy, Clone, PartialEq)]
+enum Scan {
+    Fail,
+    Bailout,
+    Consume,
+}
+
+fn scan_left(expr: &Expr, fixup: FixupContext) -> bool {
+    match expr {
+        Expr::Assign(_) => fixup.previous_operator <= Precedence::Assign,
+        Expr::Binary(e) => match Precedence::of_binop(&e.op) {
+            Precedence::Assign => fixup.previous_operator <= Precedence::Assign,
+            binop_prec => fixup.previous_operator < binop_prec,
+        },
+        Expr::Cast(_) => fixup.previous_operator < Precedence::Cast,
+        Expr::Range(e) => e.start.is_none() || fixup.previous_operator < Precedence::Assign,
+        _ => true,
+    }
+}
+
+fn scan_right(
+    expr: &Expr,
+    fixup: FixupContext,
+    precedence: Precedence,
+    fail_offset: u8,
+    bailout_offset: u8,
+) -> Scan {
+    let consume_by_precedence = if match precedence {
+        Precedence::Assign | Precedence::Compare => precedence <= fixup.next_operator,
+        _ => precedence < fixup.next_operator,
+    } || fixup.next_operator == Precedence::MIN
+    {
+        Scan::Consume
+    } else {
+        Scan::Bailout
+    };
+    if fixup.parenthesize(expr) {
+        return consume_by_precedence;
+    }
+    match expr {
+        #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
+        Expr::Assign(e) => {
+            if match fixup.next_operator {
+                Precedence::Unambiguous => fail_offset >= 2,
+                _ => bailout_offset >= 1,
+            } {
+                return Scan::Consume;
+            }
+            let right_fixup = fixup.rightmost_subexpression_fixup(false, false, Precedence::Assign);
+            let scan = scan_right(
+                &e.right,
+                right_fixup,
+                Precedence::Assign,
+                match fixup.next_operator {
+                    Precedence::Unambiguous => fail_offset,
+                    _ => 1,
+                },
+                1,
+            );
+            if let Scan::Bailout | Scan::Consume = scan {
+                Scan::Consume
+            } else if let Precedence::Unambiguous = fixup.next_operator {
+                Scan::Fail
+            } else {
+                Scan::Bailout
+            }
+        }
+        Expr::Binary(e) => {
+            if match fixup.next_operator {
+                Precedence::Unambiguous => {
+                    fail_offset >= 2
+                        && (consume_by_precedence == Scan::Consume || bailout_offset >= 1)
+                }
+                _ => bailout_offset >= 1,
+            } {
+                return Scan::Consume;
+            }
+            let binop_prec = Precedence::of_binop(&e.op);
+            if binop_prec == Precedence::Compare && fixup.next_operator == Precedence::Compare {
+                return Scan::Consume;
+            }
+            let right_fixup = fixup.rightmost_subexpression_fixup(false, false, binop_prec);
+            let scan = scan_right(
+                &e.right,
+                right_fixup,
+                binop_prec,
+                match fixup.next_operator {
+                    Precedence::Unambiguous => fail_offset,
+                    _ => 1,
+                },
+                consume_by_precedence as u8 - Scan::Bailout as u8,
+            );
+            match scan {
+                Scan::Fail => {}
+                Scan::Bailout => return consume_by_precedence,
+                Scan::Consume => return Scan::Consume,
+            }
+            let right_needs_group = binop_prec != Precedence::Assign
+                && right_fixup.rightmost_subexpression_precedence(&e.right) <= binop_prec;
+            if right_needs_group {
+                consume_by_precedence
+            } else if let (Scan::Fail, Precedence::Unambiguous) = (scan, fixup.next_operator) {
+                Scan::Fail
+            } else {
+                Scan::Bailout
+            }
+        }
+        Expr::RawAddr(ExprRawAddr { expr, .. })
+        | Expr::Reference(ExprReference { expr, .. })
+        | Expr::Unary(ExprUnary { expr, .. }) => {
+            if match fixup.next_operator {
+                Precedence::Unambiguous => {
+                    fail_offset >= 2
+                        && (consume_by_precedence == Scan::Consume || bailout_offset >= 1)
+                }
+                _ => bailout_offset >= 1,
+            } {
+                return Scan::Consume;
+            }
+            let right_fixup = fixup.rightmost_subexpression_fixup(false, false, Precedence::Prefix);
+            let scan = scan_right(
+                expr,
+                right_fixup,
+                precedence,
+                match fixup.next_operator {
+                    Precedence::Unambiguous => fail_offset,
+                    _ => 1,
+                },
+                consume_by_precedence as u8 - Scan::Bailout as u8,
+            );
+            match scan {
+                Scan::Fail => {}
+                Scan::Bailout => return consume_by_precedence,
+                Scan::Consume => return Scan::Consume,
+            }
+            if right_fixup.rightmost_subexpression_precedence(expr) < Precedence::Prefix {
+                consume_by_precedence
+            } else if let (Scan::Fail, Precedence::Unambiguous) = (scan, fixup.next_operator) {
+                Scan::Fail
+            } else {
+                Scan::Bailout
+            }
+        }
+        Expr::Range(e) => match &e.end {
+            Some(end) => {
+                if fail_offset >= 2 {
+                    return Scan::Consume;
+                }
+                let right_fixup =
+                    fixup.rightmost_subexpression_fixup(false, true, Precedence::Range);
+                let scan = scan_right(
+                    end,
+                    right_fixup,
+                    Precedence::Range,
+                    fail_offset,
+                    match fixup.next_operator {
+                        Precedence::Assign | Precedence::Range => 0,
+                        _ => 1,
+                    },
+                );
+                if match (scan, fixup.next_operator) {
+                    (Scan::Fail, _) => false,
+                    (Scan::Bailout, Precedence::Assign | Precedence::Range) => false,
+                    (Scan::Bailout | Scan::Consume, _) => true,
+                } {
+                    return Scan::Consume;
+                }
+                if right_fixup.rightmost_subexpression_precedence(end) <= Precedence::Range {
+                    Scan::Consume
+                } else {
+                    Scan::Fail
+                }
+            }
+            None => {
+                if fixup.next_operator_can_begin_expr {
+                    Scan::Consume
+                } else {
+                    Scan::Fail
+                }
+            }
+        },
+        Expr::Break(e) => match &e.expr {
+            Some(value) => {
+                if bailout_offset >= 1 || e.label.is_none() && classify::expr_leading_label(value) {
+                    return Scan::Consume;
+                }
+                let right_fixup = fixup.rightmost_subexpression_fixup(true, true, Precedence::Jump);
+                match scan_right(value, right_fixup, Precedence::Jump, 1, 1) {
+                    Scan::Fail => Scan::Bailout,
+                    Scan::Bailout | Scan::Consume => Scan::Consume,
+                }
+            }
+            None => match fixup.next_operator {
+                Precedence::Assign if precedence > Precedence::Assign => Scan::Fail,
+                _ => Scan::Consume,
+            },
+        },
+        Expr::Return(ExprReturn { expr, .. }) | Expr::Yield(ExprYield { expr, .. }) => match expr {
+            Some(e) => {
+                if bailout_offset >= 1 {
+                    return Scan::Consume;
+                }
+                let right_fixup =
+                    fixup.rightmost_subexpression_fixup(true, false, Precedence::Jump);
+                match scan_right(e, right_fixup, Precedence::Jump, 1, 1) {
+                    Scan::Fail => Scan::Bailout,
+                    Scan::Bailout | Scan::Consume => Scan::Consume,
+                }
+            }
+            None => match fixup.next_operator {
+                Precedence::Assign if precedence > Precedence::Assign => Scan::Fail,
+                _ => Scan::Consume,
+            },
+        },
+        Expr::Closure(_) => Scan::Consume,
+        Expr::Let(e) => {
+            if bailout_offset >= 1 {
+                return Scan::Consume;
+            }
+            let right_fixup = fixup.rightmost_subexpression_fixup(false, false, Precedence::Let);
+            let scan = scan_right(
+                &e.expr,
+                right_fixup,
+                Precedence::Let,
+                1,
+                if fixup.next_operator < Precedence::Let {
+                    0
+                } else {
+                    1
+                },
+            );
+            match scan {
+                Scan::Fail | Scan::Bailout if fixup.next_operator < Precedence::Let => {
+                    return Scan::Bailout;
+                }
+                Scan::Consume => return Scan::Consume,
+                _ => {}
+            }
+            if right_fixup.rightmost_subexpression_precedence(&e.expr) < Precedence::Let {
+                Scan::Consume
+            } else if let Scan::Fail = scan {
+                Scan::Bailout
+            } else {
+                Scan::Consume
+            }
+        }
+        Expr::Group(e) => scan_right(&e.expr, fixup, precedence, fail_offset, bailout_offset),
+        Expr::Array(_)
+        | Expr::Async(_)
+        | Expr::Await(_)
+        | Expr::Block(_)
+        | Expr::Call(_)
+        | Expr::Cast(_)
+        | Expr::Const(_)
+        | Expr::Continue(_)
+        | Expr::Field(_)
+        | Expr::ForLoop(_)
+        | Expr::If(_)
+        | Expr::Index(_)
+        | Expr::Infer(_)
+        | Expr::Lit(_)
+        | Expr::Loop(_)
+        | Expr::Macro(_)
+        | Expr::Match(_)
+        | Expr::MethodCall(_)
+        | Expr::Paren(_)
+        | Expr::Path(_)
+        | Expr::Repeat(_)
+        | Expr::Struct(_)
+        | Expr::Try(_)
+        | Expr::TryBlock(_)
+        | Expr::Tuple(_)
+        | Expr::Unsafe(_)
+        | Expr::Verbatim(_)
+        | Expr::While(_) => match fixup.next_operator {
+            Precedence::Assign | Precedence::Range if precedence == Precedence::Range => Scan::Fail,
+            _ if precedence == Precedence::Let && fixup.next_operator < Precedence::Let => {
+                Scan::Fail
+            }
+            _ => consume_by_precedence,
+        },
+
+        _ => match fixup.next_operator {
+            Precedence::Assign | Precedence::Range if precedence == Precedence::Range => Scan::Fail,
+            _ if precedence == Precedence::Let && fixup.next_operator < Precedence::Let => {
+                Scan::Fail
+            }
+            _ => consume_by_precedence,
+        },
+    }
+}
diff --git a/crates/prettyplease/src/generics.rs b/crates/prettyplease/src/generics.rs
index 6145345..3f225b8 100644
--- a/crates/prettyplease/src/generics.rs
+++ b/crates/prettyplease/src/generics.rs
@@ -5,7 +5,7 @@
 use proc_macro2::TokenStream;
 use std::ptr;
 use syn::{
-    BoundLifetimes, CapturedParam, ConstParam, GenericParam, Generics, LifetimeParam,
+    BoundLifetimes, CapturedParam, ConstParam, Expr, GenericParam, Generics, LifetimeParam,
     PreciseCapture, PredicateLifetime, PredicateType, TraitBound, TraitBoundModifier, TypeParam,
     TypeParamBound, WhereClause, WherePredicate,
 };
@@ -209,7 +209,7 @@
         self.ty(&const_param.ty);
         if let Some(default) = &const_param.default {
             self.word(" = ");
-            self.expr(default);
+            self.const_argument(default);
         }
     }
 
@@ -357,4 +357,27 @@
             _ => unimplemented!("unknown CapturedParam"),
         }
     }
+
+    pub fn const_argument(&mut self, expr: &Expr) {
+        match expr {
+            #![cfg_attr(all(test, exhaustive), allow(non_exhaustive_omitted_patterns))]
+            Expr::Lit(expr) => self.expr_lit(expr),
+
+            Expr::Path(expr)
+                if expr.attrs.is_empty()
+                    && expr.qself.is_none()
+                    && expr.path.get_ident().is_some() =>
+            {
+                self.expr_path(expr);
+            }
+
+            Expr::Block(expr) => self.expr_block(expr),
+
+            _ => {
+                self.cbox(INDENT);
+                self.expr_as_small_block(expr, 0);
+                self.end();
+            }
+        }
+    }
 }
diff --git a/crates/prettyplease/src/item.rs b/crates/prettyplease/src/item.rs
index 8254429..4e9d95d 100644
--- a/crates/prettyplease/src/item.rs
+++ b/crates/prettyplease/src/item.rs
@@ -1,5 +1,7 @@
 use crate::algorithm::Printer;
+use crate::fixup::FixupContext;
 use crate::iter::IterDelimited;
+use crate::mac;
 use crate::path::PathKind;
 use crate::INDENT;
 use proc_macro2::TokenStream;
@@ -47,7 +49,7 @@
         self.ty(&item.ty);
         self.word(" = ");
         self.neverbreak();
-        self.expr(&item.expr);
+        self.expr(&item.expr, FixupContext::NONE);
         self.word(";");
         self.end();
         self.hardbreak();
@@ -100,8 +102,8 @@
         self.word("{");
         self.hardbreak_if_nonempty();
         self.inner_attrs(&item.attrs);
-        for stmt in &item.block.stmts {
-            self.stmt(stmt);
+        for stmt in item.block.stmts.iter().delimited() {
+            self.stmt(&stmt, stmt.is_last);
         }
         self.offset(-INDENT);
         self.end();
@@ -168,7 +170,7 @@
 
     fn item_macro(&mut self, item: &ItemMacro) {
         self.outer_attrs(&item.attrs);
-        let semicolon = true;
+        let semicolon = mac::requires_semi(&item.mac.delimiter);
         self.mac(&item.mac, item.ident.as_ref(), semicolon);
         self.hardbreak();
     }
@@ -210,7 +212,7 @@
         self.ty(&item.ty);
         self.word(" = ");
         self.neverbreak();
-        self.expr(&item.expr);
+        self.expr(&item.expr, FixupContext::NONE);
         self.word(";");
         self.end();
         self.hardbreak();
@@ -836,7 +838,7 @@
 
     fn foreign_item_macro(&mut self, foreign_item: &ForeignItemMacro) {
         self.outer_attrs(&foreign_item.attrs);
-        let semicolon = true;
+        let semicolon = mac::requires_semi(&foreign_item.mac.delimiter);
         self.mac(&foreign_item.mac, None, semicolon);
         self.hardbreak();
     }
@@ -961,7 +963,7 @@
         if let Some((_eq_token, default)) = &trait_item.default {
             self.word(" = ");
             self.neverbreak();
-            self.expr(default);
+            self.expr(default, FixupContext::NONE);
         }
         self.word(";");
         self.end();
@@ -981,8 +983,8 @@
             self.word("{");
             self.hardbreak_if_nonempty();
             self.inner_attrs(&trait_item.attrs);
-            for stmt in &block.stmts {
-                self.stmt(stmt);
+            for stmt in block.stmts.iter().delimited() {
+                self.stmt(&stmt, stmt.is_last);
             }
             self.offset(-INDENT);
             self.end();
@@ -1023,7 +1025,7 @@
 
     fn trait_item_macro(&mut self, trait_item: &TraitItemMacro) {
         self.outer_attrs(&trait_item.attrs);
-        let semicolon = true;
+        let semicolon = mac::requires_semi(&trait_item.mac.delimiter);
         self.mac(&trait_item.mac, None, semicolon);
         self.hardbreak();
     }
@@ -1158,7 +1160,7 @@
         self.ty(&impl_item.ty);
         self.word(" = ");
         self.neverbreak();
-        self.expr(&impl_item.expr);
+        self.expr(&impl_item.expr, FixupContext::NONE);
         self.word(";");
         self.end();
         self.hardbreak();
@@ -1180,8 +1182,8 @@
         self.word("{");
         self.hardbreak_if_nonempty();
         self.inner_attrs(&impl_item.attrs);
-        for stmt in &impl_item.block.stmts {
-            self.stmt(stmt);
+        for stmt in impl_item.block.stmts.iter().delimited() {
+            self.stmt(&stmt, stmt.is_last);
         }
         self.offset(-INDENT);
         self.end();
@@ -1211,7 +1213,7 @@
 
     fn impl_item_macro(&mut self, impl_item: &ImplItemMacro) {
         self.outer_attrs(&impl_item.attrs);
-        let semicolon = true;
+        let semicolon = mac::requires_semi(&impl_item.mac.delimiter);
         self.mac(&impl_item.mac, None, semicolon);
         self.hardbreak();
     }
@@ -1421,6 +1423,7 @@
 #[cfg(feature = "verbatim")]
 mod verbatim {
     use crate::algorithm::Printer;
+    use crate::fixup::FixupContext;
     use crate::iter::IterDelimited;
     use crate::INDENT;
     use syn::ext::IdentExt;
@@ -1707,7 +1710,7 @@
                 self.word(" = ");
                 self.neverbreak();
                 self.ibox(-INDENT);
-                self.expr(value);
+                self.expr(value, FixupContext::NONE);
                 self.end();
             }
             self.where_clause_oneline_semi(&item.generics.where_clause);
@@ -1728,8 +1731,8 @@
                 self.word("{");
                 self.hardbreak_if_nonempty();
                 self.inner_attrs(&item.attrs);
-                for stmt in body {
-                    self.stmt(stmt);
+                for stmt in body.iter().delimited() {
+                    self.stmt(&stmt, stmt.is_last);
                 }
                 self.offset(-INDENT);
                 self.end();
@@ -1756,7 +1759,7 @@
             if let Some(expr) = &item.expr {
                 self.word(" = ");
                 self.neverbreak();
-                self.expr(expr);
+                self.expr(expr, FixupContext::NONE);
             }
             self.word(";");
             self.end();
diff --git a/crates/prettyplease/src/lib.rs b/crates/prettyplease/src/lib.rs
index b970d8f..25f0250 100644
--- a/crates/prettyplease/src/lib.rs
+++ b/crates/prettyplease/src/lib.rs
@@ -320,8 +320,9 @@
 //! these situations with conditional punctuation tokens whose selection can be
 //! deferred and populated after it's known that the group is or is not broken.
 
-#![doc(html_root_url = "https://docs.rs/prettyplease/0.2.25")]
+#![doc(html_root_url = "https://docs.rs/prettyplease/0.2.29")]
 #![allow(
+    clippy::bool_to_int_with_if,
     clippy::cast_possible_wrap,
     clippy::cast_sign_loss,
     clippy::derive_partial_eq_without_eq,
@@ -336,6 +337,7 @@
     clippy::needless_pass_by_value,
     clippy::ref_option,
     clippy::similar_names,
+    clippy::struct_excessive_bools,
     clippy::too_many_lines,
     clippy::unused_self,
     clippy::vec_init_then_push
@@ -344,10 +346,12 @@
 
 mod algorithm;
 mod attr;
+mod classify;
 mod convenience;
 mod data;
 mod expr;
 mod file;
+mod fixup;
 mod generics;
 mod item;
 mod iter;
@@ -356,6 +360,7 @@
 mod mac;
 mod pat;
 mod path;
+mod precedence;
 mod ring;
 mod stmt;
 mod token;
diff --git a/crates/prettyplease/src/mac.rs b/crates/prettyplease/src/mac.rs
index ebc56c7..230a8d4 100644
--- a/crates/prettyplease/src/mac.rs
+++ b/crates/prettyplease/src/mac.rs
@@ -41,10 +41,7 @@
         }
         self.word(close);
         if semicolon {
-            match mac.delimiter {
-                MacroDelimiter::Paren(_) | MacroDelimiter::Bracket(_) => self.word(";"),
-                MacroDelimiter::Brace(_) => {}
-            }
+            self.word(";");
         }
     }
 
@@ -179,6 +176,7 @@
                 (_, Token::Ident(ident)) if !is_keyword(ident) => {
                     (state != Dot && state != Colon2, Ident)
                 }
+                (_, Token::Literal(lit)) if lit.to_string().ends_with('.') => (state != Dot, Other),
                 (_, Token::Literal(_)) => (state != Dot, Ident),
                 (_, Token::Punct(',' | ';', _)) => (false, Other),
                 (_, Token::Punct('.', _)) if !matcher => (state != Ident && state != Delim, Dot),
@@ -211,6 +209,13 @@
     }
 }
 
+pub(crate) fn requires_semi(delimiter: &MacroDelimiter) -> bool {
+    match delimiter {
+        MacroDelimiter::Paren(_) | MacroDelimiter::Bracket(_) => true,
+        MacroDelimiter::Brace(_) => false,
+    }
+}
+
 fn is_keyword(ident: &Ident) -> bool {
     match ident.to_string().as_str() {
         "as" | "async" | "await" | "box" | "break" | "const" | "continue" | "crate" | "dyn"
@@ -224,6 +229,7 @@
 #[cfg(feature = "verbatim")]
 mod standard_library {
     use crate::algorithm::Printer;
+    use crate::fixup::FixupContext;
     use crate::iter::IterDelimited;
     use crate::path::PathKind;
     use crate::INDENT;
@@ -543,7 +549,7 @@
                     self.word("(");
                     self.cbox(INDENT);
                     self.zerobreak();
-                    self.expr(expr);
+                    self.expr(expr, FixupContext::NONE);
                     self.zerobreak();
                     self.offset(-INDENT);
                     self.end();
@@ -554,7 +560,7 @@
                     self.cbox(INDENT);
                     self.zerobreak();
                     for elem in exprs.iter().delimited() {
-                        self.expr(&elem);
+                        self.expr(&elem, FixupContext::NONE);
                         self.trailing_comma(elem.is_last);
                     }
                     self.offset(-INDENT);
@@ -570,14 +576,14 @@
                     self.word("(");
                     self.cbox(INDENT);
                     self.zerobreak();
-                    self.expr(&matches.expression);
+                    self.expr(&matches.expression, FixupContext::NONE);
                     self.word(",");
                     self.space();
                     self.pat(&matches.pattern);
                     if let Some(guard) = &matches.guard {
                         self.space();
                         self.word("if ");
-                        self.expr(guard);
+                        self.expr(guard, FixupContext::NONE);
                     }
                     self.zerobreak();
                     self.offset(-INDENT);
@@ -598,7 +604,7 @@
                         self.ty(&item.ty);
                         self.word(" = ");
                         self.neverbreak();
-                        self.expr(&item.init);
+                        self.expr(&item.init, FixupContext::NONE);
                         self.word(";");
                         self.end();
                         self.hardbreak();
@@ -613,7 +619,7 @@
                     self.cbox(INDENT);
                     self.zerobreak();
                     for elem in vec.iter().delimited() {
-                        self.expr(&elem);
+                        self.expr(&elem, FixupContext::NONE);
                         self.trailing_comma(elem.is_last);
                     }
                     self.offset(-INDENT);
@@ -624,10 +630,10 @@
                     self.word("[");
                     self.cbox(INDENT);
                     self.zerobreak();
-                    self.expr(elem);
+                    self.expr(elem, FixupContext::NONE);
                     self.word(";");
                     self.space();
-                    self.expr(n);
+                    self.expr(n, FixupContext::NONE);
                     self.zerobreak();
                     self.offset(-INDENT);
                     self.end();
diff --git a/crates/prettyplease/src/pat.rs b/crates/prettyplease/src/pat.rs
index aa1806e..23a38cb 100644
--- a/crates/prettyplease/src/pat.rs
+++ b/crates/prettyplease/src/pat.rs
@@ -1,4 +1,5 @@
 use crate::algorithm::Printer;
+use crate::fixup::FixupContext;
 use crate::iter::IterDelimited;
 use crate::path::PathKind;
 use crate::INDENT;
@@ -19,7 +20,7 @@
             Pat::Or(pat) => self.pat_or(pat),
             Pat::Paren(pat) => self.pat_paren(pat),
             Pat::Path(pat) => self.expr_path(pat),
-            Pat::Range(pat) => self.expr_range(pat),
+            Pat::Range(pat) => self.expr_range(pat, FixupContext::NONE),
             Pat::Reference(pat) => self.pat_reference(pat),
             Pat::Rest(pat) => self.pat_rest(pat),
             Pat::Slice(pat) => self.pat_slice(pat),
diff --git a/crates/prettyplease/src/path.rs b/crates/prettyplease/src/path.rs
index 2ae94b3..44428cc 100644
--- a/crates/prettyplease/src/path.rs
+++ b/crates/prettyplease/src/path.rs
@@ -3,7 +3,7 @@
 use crate::INDENT;
 use std::ptr;
 use syn::{
-    AngleBracketedGenericArguments, AssocConst, AssocType, Constraint, Expr, GenericArgument,
+    AngleBracketedGenericArguments, AssocConst, AssocType, Constraint, GenericArgument,
     ParenthesizedGenericArguments, Path, PathArguments, PathSegment, QSelf,
 };
 
@@ -50,20 +50,7 @@
             #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
             GenericArgument::Lifetime(lifetime) => self.lifetime(lifetime),
             GenericArgument::Type(ty) => self.ty(ty),
-            GenericArgument::Const(expr) => {
-                match expr {
-                    #![cfg_attr(all(test, exhaustive), allow(non_exhaustive_omitted_patterns))]
-                    Expr::Lit(expr) => self.expr_lit(expr),
-                    Expr::Block(expr) => self.expr_block(expr),
-                    // ERROR CORRECTION: Add braces to make sure that the
-                    // generated code is valid.
-                    _ => {
-                        self.word("{");
-                        self.expr(expr);
-                        self.word("}");
-                    }
-                }
-            }
+            GenericArgument::Const(expr) => self.const_argument(expr),
             GenericArgument::AssocType(assoc) => self.assoc_type(assoc),
             GenericArgument::AssocConst(assoc) => self.assoc_const(assoc),
             GenericArgument::Constraint(constraint) => self.constraint(constraint),
@@ -136,7 +123,7 @@
             self.angle_bracketed_generic_arguments(generics, PathKind::Type);
         }
         self.word(" = ");
-        self.expr(&assoc.value);
+        self.const_argument(&assoc.value);
     }
 
     fn constraint(&mut self, constraint: &Constraint) {
diff --git a/crates/prettyplease/src/precedence.rs b/crates/prettyplease/src/precedence.rs
new file mode 100644
index 0000000..03117d5
--- /dev/null
+++ b/crates/prettyplease/src/precedence.rs
@@ -0,0 +1,148 @@
+use syn::{
+    AttrStyle, Attribute, BinOp, Expr, ExprArray, ExprAsync, ExprAwait, ExprBlock, ExprBreak,
+    ExprCall, ExprConst, ExprContinue, ExprField, ExprForLoop, ExprIf, ExprIndex, ExprInfer,
+    ExprLit, ExprLoop, ExprMacro, ExprMatch, ExprMethodCall, ExprParen, ExprPath, ExprRepeat,
+    ExprReturn, ExprStruct, ExprTry, ExprTryBlock, ExprTuple, ExprUnsafe, ExprWhile, ExprYield,
+    ReturnType,
+};
+
+// Reference: https://doc.rust-lang.org/reference/expressions.html#expression-precedence
+#[derive(Copy, Clone, PartialEq, PartialOrd)]
+pub enum Precedence {
+    // return, break, closures
+    Jump,
+    // = += -= *= /= %= &= |= ^= <<= >>=
+    Assign,
+    // .. ..=
+    Range,
+    // ||
+    Or,
+    // &&
+    And,
+    // let
+    Let,
+    // == != < > <= >=
+    Compare,
+    // |
+    BitOr,
+    // ^
+    BitXor,
+    // &
+    BitAnd,
+    // << >>
+    Shift,
+    // + -
+    Sum,
+    // * / %
+    Product,
+    // as
+    Cast,
+    // unary - * ! & &mut
+    Prefix,
+    // paths, loops, function calls, array indexing, field expressions, method calls
+    Unambiguous,
+}
+
+impl Precedence {
+    pub(crate) const MIN: Self = Precedence::Jump;
+
+    pub(crate) fn of_binop(op: &BinOp) -> Self {
+        match op {
+            #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
+            BinOp::Add(_) | BinOp::Sub(_) => Precedence::Sum,
+            BinOp::Mul(_) | BinOp::Div(_) | BinOp::Rem(_) => Precedence::Product,
+            BinOp::And(_) => Precedence::And,
+            BinOp::Or(_) => Precedence::Or,
+            BinOp::BitXor(_) => Precedence::BitXor,
+            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(_)
+            | BinOp::DivAssign(_)
+            | BinOp::RemAssign(_)
+            | BinOp::BitXorAssign(_)
+            | BinOp::BitAndAssign(_)
+            | BinOp::BitOrAssign(_)
+            | BinOp::ShlAssign(_)
+            | BinOp::ShrAssign(_) => Precedence::Assign,
+
+            _ => Precedence::MIN,
+        }
+    }
+
+    pub(crate) fn of(e: &Expr) -> Self {
+        fn prefix_attrs(attrs: &[Attribute]) -> Precedence {
+            for attr in attrs {
+                if let AttrStyle::Outer = attr.style {
+                    return Precedence::Prefix;
+                }
+            }
+            Precedence::Unambiguous
+        }
+
+        match e {
+            #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
+            Expr::Closure(e) => match e.output {
+                ReturnType::Default => Precedence::Jump,
+                ReturnType::Type(..) => prefix_attrs(&e.attrs),
+            },
+
+            Expr::Break(ExprBreak { expr, .. })
+            | Expr::Return(ExprReturn { expr, .. })
+            | Expr::Yield(ExprYield { expr, .. }) => match expr {
+                Some(_) => Precedence::Jump,
+                None => Precedence::Unambiguous,
+            },
+
+            Expr::Assign(_) => Precedence::Assign,
+            Expr::Range(_) => Precedence::Range,
+            Expr::Binary(e) => Precedence::of_binop(&e.op),
+            Expr::Let(_) => Precedence::Let,
+            Expr::Cast(_) => Precedence::Cast,
+            Expr::RawAddr(_) | Expr::Reference(_) | Expr::Unary(_) => Precedence::Prefix,
+
+            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::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),
+
+            Expr::Group(e) => Precedence::of(&e.expr),
+
+            Expr::Verbatim(_) => Precedence::Unambiguous,
+
+            _ => Precedence::Unambiguous,
+        }
+    }
+}
diff --git a/crates/prettyplease/src/ring.rs b/crates/prettyplease/src/ring.rs
index aff9258..882a988 100644
--- a/crates/prettyplease/src/ring.rs
+++ b/crates/prettyplease/src/ring.rs
@@ -1,5 +1,5 @@
 use std::collections::VecDeque;
-use std::ops::{Index, IndexMut};
+use std::ops::{Index, IndexMut, Range};
 
 pub struct RingBuffer<T> {
     data: VecDeque<T>,
@@ -33,8 +33,8 @@
         self.data.clear();
     }
 
-    pub fn index_of_first(&self) -> usize {
-        self.offset
+    pub fn index_range(&self) -> Range<usize> {
+        self.offset..self.offset + self.data.len()
     }
 
     pub fn first(&self) -> &T {
diff --git a/crates/prettyplease/src/stmt.rs b/crates/prettyplease/src/stmt.rs
index 2b97da6..ce58200 100644
--- a/crates/prettyplease/src/stmt.rs
+++ b/crates/prettyplease/src/stmt.rs
@@ -1,9 +1,13 @@
 use crate::algorithm::Printer;
+use crate::classify;
+use crate::expr;
+use crate::fixup::FixupContext;
+use crate::mac;
 use crate::INDENT;
 use syn::{BinOp, Expr, Stmt};
 
 impl Printer {
-    pub fn stmt(&mut self, stmt: &Stmt) {
+    pub fn stmt(&mut self, stmt: &Stmt, is_last: bool) {
         match stmt {
             Stmt::Local(local) => {
                 self.outer_attrs(&local.attrs);
@@ -13,32 +17,26 @@
                 if let Some(local_init) = &local.init {
                     self.word(" = ");
                     self.neverbreak();
-                    self.expr(&local_init.expr);
+                    self.subexpr(
+                        &local_init.expr,
+                        local_init.diverge.is_some()
+                            && classify::expr_trailing_brace(&local_init.expr),
+                        FixupContext::NONE,
+                    );
                     if let Some((_else, diverge)) = &local_init.diverge {
                         self.space();
                         self.word("else ");
                         self.end();
                         self.neverbreak();
-                        if let Expr::Block(expr) = diverge.as_ref() {
-                            self.cbox(INDENT);
+                        self.cbox(INDENT);
+                        if let Some(expr) = expr::simple_block(diverge) {
                             self.small_block(&expr.block, &[]);
-                            self.end();
                         } else {
-                            self.word("{");
-                            self.space();
-                            self.ibox(INDENT);
-                            self.expr(diverge);
-                            self.end();
-                            self.space();
-                            self.offset(-INDENT);
-                            self.word("}");
+                            self.expr_as_small_block(diverge, INDENT);
                         }
-                    } else {
-                        self.end();
                     }
-                } else {
-                    self.end();
                 }
+                self.end();
                 self.word(";");
                 self.hardbreak();
             }
@@ -46,14 +44,14 @@
             Stmt::Expr(expr, None) => {
                 if break_after(expr) {
                     self.ibox(0);
-                    self.expr_beginning_of_line(expr, true);
+                    self.expr_beginning_of_line(expr, false, true, FixupContext::new_stmt());
                     if add_semi(expr) {
                         self.word(";");
                     }
                     self.end();
                     self.hardbreak();
                 } else {
-                    self.expr_beginning_of_line(expr, true);
+                    self.expr_beginning_of_line(expr, false, true, FixupContext::new_stmt());
                 }
             }
             Stmt::Expr(expr, Some(_semi)) => {
@@ -63,7 +61,7 @@
                     }
                 }
                 self.ibox(0);
-                self.expr_beginning_of_line(expr, true);
+                self.expr_beginning_of_line(expr, false, true, FixupContext::new_stmt());
                 if !remove_semi(expr) {
                     self.word(";");
                 }
@@ -72,7 +70,8 @@
             }
             Stmt::Macro(stmt) => {
                 self.outer_attrs(&stmt.attrs);
-                let semicolon = true;
+                let semicolon = stmt.semi_token.is_some()
+                    || !is_last && mac::requires_semi(&stmt.mac.delimiter);
                 self.mac(&stmt.mac, None, semicolon);
                 self.hardbreak();
             }
diff --git a/crates/prettyplease/src/ty.rs b/crates/prettyplease/src/ty.rs
index ec2cd0a..7f92821 100644
--- a/crates/prettyplease/src/ty.rs
+++ b/crates/prettyplease/src/ty.rs
@@ -1,4 +1,5 @@
 use crate::algorithm::Printer;
+use crate::fixup::FixupContext;
 use crate::iter::IterDelimited;
 use crate::path::PathKind;
 use crate::INDENT;
@@ -36,7 +37,7 @@
         self.word("[");
         self.ty(&ty.elem);
         self.word("; ");
-        self.expr(&ty.len);
+        self.expr(&ty.len, FixupContext::NONE);
         self.word("]");
     }
 
diff --git a/crates/prettyplease/tests/test.rs b/crates/prettyplease/tests/test.rs
index 04004a7..aa6b849 100644
--- a/crates/prettyplease/tests/test.rs
+++ b/crates/prettyplease/tests/test.rs
@@ -20,7 +20,7 @@
         },
         indoc! {"
             fn main() {
-                if (Struct {} == Struct {}) {}
+                if (Struct {}) == (Struct {}) {}
             }
         "},
     );
@@ -39,17 +39,11 @@
                 }
             }
         },
-        // FIXME: no parens around `Struct {}` because anything until the `=>`
-        // is considered part of the match guard expression. Parsing of the
-        // expression is not terminated by `{` in that position.
-        //
-        // FIXME: the `true && false` needs parens. Otherwise the precedence is
-        // `(let _ = true) && false` which means something different.
         indoc! {"
             fn main() {
                 match () {
-                    () if let _ = (Struct {}) => {}
-                    () if let _ = true && false => {}
+                    () if let _ = Struct {} => {}
+                    () if let _ = (true && false) => {}
                 }
             }
         "},
diff --git a/crates/prettyplease/tests/test_precedence.rs b/crates/prettyplease/tests/test_precedence.rs
new file mode 100644
index 0000000..f1eec23
--- /dev/null
+++ b/crates/prettyplease/tests/test_precedence.rs
@@ -0,0 +1,900 @@
+use proc_macro2::{Ident, Span, TokenStream};
+use quote::ToTokens as _;
+use std::mem;
+use std::process::ExitCode;
+use syn::punctuated::Punctuated;
+use syn::visit_mut::{self, VisitMut};
+use syn::{
+    token, AngleBracketedGenericArguments, Arm, BinOp, Block, Expr, ExprArray, ExprAssign,
+    ExprAsync, ExprAwait, ExprBinary, ExprBlock, ExprBreak, ExprCall, ExprCast, ExprClosure,
+    ExprConst, ExprContinue, ExprField, ExprForLoop, ExprIf, ExprIndex, ExprLet, ExprLit, ExprLoop,
+    ExprMacro, ExprMatch, ExprMethodCall, ExprPath, ExprRange, ExprRawAddr, ExprReference,
+    ExprReturn, ExprStruct, ExprTry, ExprTryBlock, ExprUnary, ExprUnsafe, ExprWhile, ExprYield,
+    File, GenericArgument, Generics, Item, ItemConst, Label, Lifetime, LifetimeParam, Lit, LitInt,
+    Macro, MacroDelimiter, Member, Pat, PatWild, Path, PathArguments, PathSegment,
+    PointerMutability, QSelf, RangeLimits, ReturnType, Stmt, StmtMacro, Token, Type, TypeInfer,
+    TypeParam, TypePath, UnOp, Visibility,
+};
+
+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);
+    }
+}
+
+struct AsIfPrinted;
+
+impl VisitMut for AsIfPrinted {
+    fn visit_generics_mut(&mut self, generics: &mut Generics) {
+        if generics.params.is_empty() {
+            generics.lt_token = None;
+            generics.gt_token = None;
+        }
+        if let Some(where_clause) = &generics.where_clause {
+            if where_clause.predicates.is_empty() {
+                generics.where_clause = None;
+            }
+        }
+        visit_mut::visit_generics_mut(self, generics);
+    }
+
+    fn visit_lifetime_param_mut(&mut self, param: &mut LifetimeParam) {
+        if param.bounds.is_empty() {
+            param.colon_token = None;
+        }
+        visit_mut::visit_lifetime_param_mut(self, param);
+    }
+
+    fn visit_stmt_mut(&mut self, stmt: &mut Stmt) {
+        if let Stmt::Expr(expr, semi) = stmt {
+            if let Expr::Macro(e) = expr {
+                if match e.mac.delimiter {
+                    MacroDelimiter::Brace(_) => true,
+                    MacroDelimiter::Paren(_) | MacroDelimiter::Bracket(_) => semi.is_some(),
+                } {
+                    let expr = match mem::replace(expr, Expr::PLACEHOLDER) {
+                        Expr::Macro(expr) => expr,
+                        _ => unreachable!(),
+                    };
+                    *stmt = Stmt::Macro(StmtMacro {
+                        attrs: expr.attrs,
+                        mac: expr.mac,
+                        semi_token: *semi,
+                    });
+                }
+            }
+        }
+        visit_mut::visit_stmt_mut(self, stmt);
+    }
+
+    fn visit_type_param_mut(&mut self, param: &mut TypeParam) {
+        if param.bounds.is_empty() {
+            param.colon_token = None;
+        }
+        visit_mut::visit_type_param_mut(self, param);
+    }
+}
+
+#[test]
+fn test_permutations() -> ExitCode {
+    fn iter(depth: usize, f: &mut dyn FnMut(Expr)) {
+        let span = Span::call_site();
+
+        // Expr::Path
+        f(Expr::Path(ExprPath {
+            // `x`
+            attrs: Vec::new(),
+            qself: None,
+            path: Path::from(Ident::new("x", span)),
+        }));
+        if false {
+            f(Expr::Path(ExprPath {
+                // `x::<T>`
+                attrs: Vec::new(),
+                qself: None,
+                path: Path {
+                    leading_colon: None,
+                    segments: Punctuated::from_iter([PathSegment {
+                        ident: Ident::new("x", span),
+                        arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
+                            colon2_token: Some(Token![::](span)),
+                            lt_token: Token![<](span),
+                            args: Punctuated::from_iter([GenericArgument::Type(Type::Path(
+                                TypePath {
+                                    qself: None,
+                                    path: Path::from(Ident::new("T", span)),
+                                },
+                            ))]),
+                            gt_token: Token![>](span),
+                        }),
+                    }]),
+                },
+            }));
+            f(Expr::Path(ExprPath {
+                // `<T as Trait>::CONST`
+                attrs: Vec::new(),
+                qself: Some(QSelf {
+                    lt_token: Token![<](span),
+                    ty: Box::new(Type::Path(TypePath {
+                        qself: None,
+                        path: Path::from(Ident::new("T", span)),
+                    })),
+                    position: 1,
+                    as_token: Some(Token![as](span)),
+                    gt_token: Token![>](span),
+                }),
+                path: Path {
+                    leading_colon: None,
+                    segments: Punctuated::from_iter([
+                        PathSegment::from(Ident::new("Trait", span)),
+                        PathSegment::from(Ident::new("CONST", span)),
+                    ]),
+                },
+            }));
+        }
+
+        let depth = match depth.checked_sub(1) {
+            Some(depth) => depth,
+            None => return,
+        };
+
+        // Expr::Assign
+        iter(depth, &mut |expr| {
+            iter(0, &mut |simple| {
+                f(Expr::Assign(ExprAssign {
+                    // `x = $expr`
+                    attrs: Vec::new(),
+                    left: Box::new(simple.clone()),
+                    eq_token: Token![=](span),
+                    right: Box::new(expr.clone()),
+                }));
+                f(Expr::Assign(ExprAssign {
+                    // `$expr = x`
+                    attrs: Vec::new(),
+                    left: Box::new(expr.clone()),
+                    eq_token: Token![=](span),
+                    right: Box::new(simple),
+                }));
+            });
+        });
+
+        // Expr::Binary
+        iter(depth, &mut |expr| {
+            iter(0, &mut |simple| {
+                for op in [
+                    BinOp::Add(Token![+](span)),
+                    //BinOp::Sub(Token![-](span)),
+                    //BinOp::Mul(Token![*](span)),
+                    //BinOp::Div(Token![/](span)),
+                    //BinOp::Rem(Token![%](span)),
+                    //BinOp::And(Token![&&](span)),
+                    //BinOp::Or(Token![||](span)),
+                    //BinOp::BitXor(Token![^](span)),
+                    //BinOp::BitAnd(Token![&](span)),
+                    //BinOp::BitOr(Token![|](span)),
+                    //BinOp::Shl(Token![<<](span)),
+                    //BinOp::Shr(Token![>>](span)),
+                    //BinOp::Eq(Token![==](span)),
+                    BinOp::Lt(Token![<](span)),
+                    //BinOp::Le(Token![<=](span)),
+                    //BinOp::Ne(Token![!=](span)),
+                    //BinOp::Ge(Token![>=](span)),
+                    //BinOp::Gt(Token![>](span)),
+                    BinOp::ShlAssign(Token![<<=](span)),
+                ] {
+                    f(Expr::Binary(ExprBinary {
+                        // `x + $expr`
+                        attrs: Vec::new(),
+                        left: Box::new(simple.clone()),
+                        op,
+                        right: Box::new(expr.clone()),
+                    }));
+                    f(Expr::Binary(ExprBinary {
+                        // `$expr + x`
+                        attrs: Vec::new(),
+                        left: Box::new(expr.clone()),
+                        op,
+                        right: Box::new(simple.clone()),
+                    }));
+                }
+            });
+        });
+
+        // Expr::Block
+        f(Expr::Block(ExprBlock {
+            // `{}`
+            attrs: Vec::new(),
+            label: None,
+            block: Block {
+                brace_token: token::Brace(span),
+                stmts: Vec::new(),
+            },
+        }));
+
+        // Expr::Break
+        f(Expr::Break(ExprBreak {
+            // `break`
+            attrs: Vec::new(),
+            break_token: Token![break](span),
+            label: None,
+            expr: None,
+        }));
+        iter(depth, &mut |expr| {
+            f(Expr::Break(ExprBreak {
+                // `break $expr`
+                attrs: Vec::new(),
+                break_token: Token![break](span),
+                label: None,
+                expr: Some(Box::new(expr)),
+            }));
+        });
+
+        // Expr::Call
+        iter(depth, &mut |expr| {
+            f(Expr::Call(ExprCall {
+                // `$expr()`
+                attrs: Vec::new(),
+                func: Box::new(expr),
+                paren_token: token::Paren(span),
+                args: Punctuated::new(),
+            }));
+        });
+
+        // Expr::Cast
+        iter(depth, &mut |expr| {
+            f(Expr::Cast(ExprCast {
+                // `$expr as T`
+                attrs: Vec::new(),
+                expr: Box::new(expr),
+                as_token: Token![as](span),
+                ty: Box::new(Type::Path(TypePath {
+                    qself: None,
+                    path: Path::from(Ident::new("T", span)),
+                })),
+            }));
+        });
+
+        // Expr::Closure
+        iter(depth, &mut |expr| {
+            f(Expr::Closure(ExprClosure {
+                // `|| $expr`
+                attrs: Vec::new(),
+                lifetimes: None,
+                constness: None,
+                movability: None,
+                asyncness: None,
+                capture: None,
+                or1_token: Token![|](span),
+                inputs: Punctuated::new(),
+                or2_token: Token![|](span),
+                output: ReturnType::Default,
+                body: Box::new(expr),
+            }));
+        });
+
+        // Expr::Field
+        iter(depth, &mut |expr| {
+            f(Expr::Field(ExprField {
+                // `$expr.field`
+                attrs: Vec::new(),
+                base: Box::new(expr),
+                dot_token: Token![.](span),
+                member: Member::Named(Ident::new("field", span)),
+            }));
+        });
+
+        // Expr::If
+        iter(depth, &mut |expr| {
+            f(Expr::If(ExprIf {
+                // `if $expr {}`
+                attrs: Vec::new(),
+                if_token: Token![if](span),
+                cond: Box::new(expr),
+                then_branch: Block {
+                    brace_token: token::Brace(span),
+                    stmts: Vec::new(),
+                },
+                else_branch: None,
+            }));
+        });
+
+        // Expr::Let
+        iter(depth, &mut |expr| {
+            f(Expr::Let(ExprLet {
+                attrs: Vec::new(),
+                let_token: Token![let](span),
+                pat: Box::new(Pat::Wild(PatWild {
+                    attrs: Vec::new(),
+                    underscore_token: Token![_](span),
+                })),
+                eq_token: Token![=](span),
+                expr: Box::new(expr),
+            }));
+        });
+
+        // Expr::Range
+        f(Expr::Range(ExprRange {
+            // `..`
+            attrs: Vec::new(),
+            start: None,
+            limits: RangeLimits::HalfOpen(Token![..](span)),
+            end: None,
+        }));
+        iter(depth, &mut |expr| {
+            f(Expr::Range(ExprRange {
+                // `..$expr`
+                attrs: Vec::new(),
+                start: None,
+                limits: RangeLimits::HalfOpen(Token![..](span)),
+                end: Some(Box::new(expr.clone())),
+            }));
+            f(Expr::Range(ExprRange {
+                // `$expr..`
+                attrs: Vec::new(),
+                start: Some(Box::new(expr)),
+                limits: RangeLimits::HalfOpen(Token![..](span)),
+                end: None,
+            }));
+        });
+
+        // Expr::Reference
+        iter(depth, &mut |expr| {
+            f(Expr::Reference(ExprReference {
+                // `&$expr`
+                attrs: Vec::new(),
+                and_token: Token![&](span),
+                mutability: None,
+                expr: Box::new(expr),
+            }));
+        });
+
+        // Expr::Return
+        f(Expr::Return(ExprReturn {
+            // `return`
+            attrs: Vec::new(),
+            return_token: Token![return](span),
+            expr: None,
+        }));
+        iter(depth, &mut |expr| {
+            f(Expr::Return(ExprReturn {
+                // `return $expr`
+                attrs: Vec::new(),
+                return_token: Token![return](span),
+                expr: Some(Box::new(expr)),
+            }));
+        });
+
+        // Expr::Try
+        iter(depth, &mut |expr| {
+            f(Expr::Try(ExprTry {
+                // `$expr?`
+                attrs: Vec::new(),
+                expr: Box::new(expr),
+                question_token: Token![?](span),
+            }));
+        });
+
+        // Expr::Unary
+        iter(depth, &mut |expr| {
+            for op in [
+                UnOp::Deref(Token![*](span)),
+                //UnOp::Not(Token![!](span)),
+                //UnOp::Neg(Token![-](span)),
+            ] {
+                f(Expr::Unary(ExprUnary {
+                    // `*$expr`
+                    attrs: Vec::new(),
+                    op,
+                    expr: Box::new(expr.clone()),
+                }));
+            }
+        });
+
+        if false {
+            // Expr::Array
+            f(Expr::Array(ExprArray {
+                // `[]`
+                attrs: Vec::new(),
+                bracket_token: token::Bracket(span),
+                elems: Punctuated::new(),
+            }));
+
+            // Expr::Async
+            f(Expr::Async(ExprAsync {
+                // `async {}`
+                attrs: Vec::new(),
+                async_token: Token![async](span),
+                capture: None,
+                block: Block {
+                    brace_token: token::Brace(span),
+                    stmts: Vec::new(),
+                },
+            }));
+
+            // Expr::Await
+            iter(depth, &mut |expr| {
+                f(Expr::Await(ExprAwait {
+                    // `$expr.await`
+                    attrs: Vec::new(),
+                    base: Box::new(expr),
+                    dot_token: Token![.](span),
+                    await_token: Token![await](span),
+                }));
+            });
+
+            // Expr::Block
+            f(Expr::Block(ExprBlock {
+                // `'a: {}`
+                attrs: Vec::new(),
+                label: Some(Label {
+                    name: Lifetime::new("'a", span),
+                    colon_token: Token![:](span),
+                }),
+                block: Block {
+                    brace_token: token::Brace(span),
+                    stmts: Vec::new(),
+                },
+            }));
+            iter(depth, &mut |expr| {
+                f(Expr::Block(ExprBlock {
+                    // `{ $expr }`
+                    attrs: Vec::new(),
+                    label: None,
+                    block: Block {
+                        brace_token: token::Brace(span),
+                        stmts: Vec::from([Stmt::Expr(expr.clone(), None)]),
+                    },
+                }));
+                f(Expr::Block(ExprBlock {
+                    // `{ $expr; }`
+                    attrs: Vec::new(),
+                    label: None,
+                    block: Block {
+                        brace_token: token::Brace(span),
+                        stmts: Vec::from([Stmt::Expr(expr, Some(Token![;](span)))]),
+                    },
+                }));
+            });
+
+            // Expr::Break
+            f(Expr::Break(ExprBreak {
+                // `break 'a`
+                attrs: Vec::new(),
+                break_token: Token![break](span),
+                label: Some(Lifetime::new("'a", span)),
+                expr: None,
+            }));
+            iter(depth, &mut |expr| {
+                f(Expr::Break(ExprBreak {
+                    // `break 'a $expr`
+                    attrs: Vec::new(),
+                    break_token: Token![break](span),
+                    label: Some(Lifetime::new("'a", span)),
+                    expr: Some(Box::new(expr)),
+                }));
+            });
+
+            // Expr::Closure
+            f(Expr::Closure(ExprClosure {
+                // `|| -> T {}`
+                attrs: Vec::new(),
+                lifetimes: None,
+                constness: None,
+                movability: None,
+                asyncness: None,
+                capture: None,
+                or1_token: Token![|](span),
+                inputs: Punctuated::new(),
+                or2_token: Token![|](span),
+                output: ReturnType::Type(
+                    Token![->](span),
+                    Box::new(Type::Path(TypePath {
+                        qself: None,
+                        path: Path::from(Ident::new("T", span)),
+                    })),
+                ),
+                body: Box::new(Expr::Block(ExprBlock {
+                    attrs: Vec::new(),
+                    label: None,
+                    block: Block {
+                        brace_token: token::Brace(span),
+                        stmts: Vec::new(),
+                    },
+                })),
+            }));
+
+            // Expr::Const
+            f(Expr::Const(ExprConst {
+                // `const {}`
+                attrs: Vec::new(),
+                const_token: Token![const](span),
+                block: Block {
+                    brace_token: token::Brace(span),
+                    stmts: Vec::new(),
+                },
+            }));
+
+            // Expr::Continue
+            f(Expr::Continue(ExprContinue {
+                // `continue`
+                attrs: Vec::new(),
+                continue_token: Token![continue](span),
+                label: None,
+            }));
+            f(Expr::Continue(ExprContinue {
+                // `continue 'a`
+                attrs: Vec::new(),
+                continue_token: Token![continue](span),
+                label: Some(Lifetime::new("'a", span)),
+            }));
+
+            // Expr::ForLoop
+            iter(depth, &mut |expr| {
+                f(Expr::ForLoop(ExprForLoop {
+                    // `for _ in $expr {}`
+                    attrs: Vec::new(),
+                    label: None,
+                    for_token: Token![for](span),
+                    pat: Box::new(Pat::Wild(PatWild {
+                        attrs: Vec::new(),
+                        underscore_token: Token![_](span),
+                    })),
+                    in_token: Token![in](span),
+                    expr: Box::new(expr.clone()),
+                    body: Block {
+                        brace_token: token::Brace(span),
+                        stmts: Vec::new(),
+                    },
+                }));
+                f(Expr::ForLoop(ExprForLoop {
+                    // `'a: for _ in $expr {}`
+                    attrs: Vec::new(),
+                    label: Some(Label {
+                        name: Lifetime::new("'a", span),
+                        colon_token: Token![:](span),
+                    }),
+                    for_token: Token![for](span),
+                    pat: Box::new(Pat::Wild(PatWild {
+                        attrs: Vec::new(),
+                        underscore_token: Token![_](span),
+                    })),
+                    in_token: Token![in](span),
+                    expr: Box::new(expr),
+                    body: Block {
+                        brace_token: token::Brace(span),
+                        stmts: Vec::new(),
+                    },
+                }));
+            });
+
+            // Expr::Index
+            iter(depth, &mut |expr| {
+                f(Expr::Index(ExprIndex {
+                    // `$expr[0]`
+                    attrs: Vec::new(),
+                    expr: Box::new(expr),
+                    bracket_token: token::Bracket(span),
+                    index: Box::new(Expr::Lit(ExprLit {
+                        attrs: Vec::new(),
+                        lit: Lit::Int(LitInt::new("0", span)),
+                    })),
+                }));
+            });
+
+            // Expr::Loop
+            f(Expr::Loop(ExprLoop {
+                // `loop {}`
+                attrs: Vec::new(),
+                label: None,
+                loop_token: Token![loop](span),
+                body: Block {
+                    brace_token: token::Brace(span),
+                    stmts: Vec::new(),
+                },
+            }));
+            f(Expr::Loop(ExprLoop {
+                // `'a: loop {}`
+                attrs: Vec::new(),
+                label: Some(Label {
+                    name: Lifetime::new("'a", span),
+                    colon_token: Token![:](span),
+                }),
+                loop_token: Token![loop](span),
+                body: Block {
+                    brace_token: token::Brace(span),
+                    stmts: Vec::new(),
+                },
+            }));
+
+            // Expr::Macro
+            f(Expr::Macro(ExprMacro {
+                // `m!()`
+                attrs: Vec::new(),
+                mac: Macro {
+                    path: Path::from(Ident::new("m", span)),
+                    bang_token: Token![!](span),
+                    delimiter: MacroDelimiter::Paren(token::Paren(span)),
+                    tokens: TokenStream::new(),
+                },
+            }));
+            f(Expr::Macro(ExprMacro {
+                // `m! {}`
+                attrs: Vec::new(),
+                mac: Macro {
+                    path: Path::from(Ident::new("m", span)),
+                    bang_token: Token![!](span),
+                    delimiter: MacroDelimiter::Brace(token::Brace(span)),
+                    tokens: TokenStream::new(),
+                },
+            }));
+
+            // Expr::Match
+            iter(depth, &mut |expr| {
+                f(Expr::Match(ExprMatch {
+                    // `match $expr {}`
+                    attrs: Vec::new(),
+                    match_token: Token![match](span),
+                    expr: Box::new(expr.clone()),
+                    brace_token: token::Brace(span),
+                    arms: Vec::new(),
+                }));
+                f(Expr::Match(ExprMatch {
+                    // `match x { _ => $expr }`
+                    attrs: Vec::new(),
+                    match_token: Token![match](span),
+                    expr: Box::new(Expr::Path(ExprPath {
+                        attrs: Vec::new(),
+                        qself: None,
+                        path: Path::from(Ident::new("x", span)),
+                    })),
+                    brace_token: token::Brace(span),
+                    arms: Vec::from([Arm {
+                        attrs: Vec::new(),
+                        pat: Pat::Wild(PatWild {
+                            attrs: Vec::new(),
+                            underscore_token: Token![_](span),
+                        }),
+                        guard: None,
+                        fat_arrow_token: Token![=>](span),
+                        body: Box::new(expr.clone()),
+                        comma: None,
+                    }]),
+                }));
+                f(Expr::Match(ExprMatch {
+                    // `match x { _ if $expr => {} }`
+                    attrs: Vec::new(),
+                    match_token: Token![match](span),
+                    expr: Box::new(Expr::Path(ExprPath {
+                        attrs: Vec::new(),
+                        qself: None,
+                        path: Path::from(Ident::new("x", span)),
+                    })),
+                    brace_token: token::Brace(span),
+                    arms: Vec::from([Arm {
+                        attrs: Vec::new(),
+                        pat: Pat::Wild(PatWild {
+                            attrs: Vec::new(),
+                            underscore_token: Token![_](span),
+                        }),
+                        guard: Some((Token![if](span), Box::new(expr))),
+                        fat_arrow_token: Token![=>](span),
+                        body: Box::new(Expr::Block(ExprBlock {
+                            attrs: Vec::new(),
+                            label: None,
+                            block: Block {
+                                brace_token: token::Brace(span),
+                                stmts: Vec::new(),
+                            },
+                        })),
+                        comma: None,
+                    }]),
+                }));
+            });
+
+            // Expr::MethodCall
+            iter(depth, &mut |expr| {
+                f(Expr::MethodCall(ExprMethodCall {
+                    // `$expr.method()`
+                    attrs: Vec::new(),
+                    receiver: Box::new(expr.clone()),
+                    dot_token: Token![.](span),
+                    method: Ident::new("method", span),
+                    turbofish: None,
+                    paren_token: token::Paren(span),
+                    args: Punctuated::new(),
+                }));
+                f(Expr::MethodCall(ExprMethodCall {
+                    // `$expr.method::<T>()`
+                    attrs: Vec::new(),
+                    receiver: Box::new(expr),
+                    dot_token: Token![.](span),
+                    method: Ident::new("method", span),
+                    turbofish: Some(AngleBracketedGenericArguments {
+                        colon2_token: Some(Token![::](span)),
+                        lt_token: Token![<](span),
+                        args: Punctuated::from_iter([GenericArgument::Type(Type::Path(
+                            TypePath {
+                                qself: None,
+                                path: Path::from(Ident::new("T", span)),
+                            },
+                        ))]),
+                        gt_token: Token![>](span),
+                    }),
+                    paren_token: token::Paren(span),
+                    args: Punctuated::new(),
+                }));
+            });
+
+            // Expr::RawAddr
+            iter(depth, &mut |expr| {
+                f(Expr::RawAddr(ExprRawAddr {
+                    // `&raw const $expr`
+                    attrs: Vec::new(),
+                    and_token: Token![&](span),
+                    raw: Token![raw](span),
+                    mutability: PointerMutability::Const(Token![const](span)),
+                    expr: Box::new(expr),
+                }));
+            });
+
+            // Expr::Struct
+            f(Expr::Struct(ExprStruct {
+                // `Struct {}`
+                attrs: Vec::new(),
+                qself: None,
+                path: Path::from(Ident::new("Struct", span)),
+                brace_token: token::Brace(span),
+                fields: Punctuated::new(),
+                dot2_token: None,
+                rest: None,
+            }));
+
+            // Expr::TryBlock
+            f(Expr::TryBlock(ExprTryBlock {
+                // `try {}`
+                attrs: Vec::new(),
+                try_token: Token![try](span),
+                block: Block {
+                    brace_token: token::Brace(span),
+                    stmts: Vec::new(),
+                },
+            }));
+
+            // Expr::Unsafe
+            f(Expr::Unsafe(ExprUnsafe {
+                // `unsafe {}`
+                attrs: Vec::new(),
+                unsafe_token: Token![unsafe](span),
+                block: Block {
+                    brace_token: token::Brace(span),
+                    stmts: Vec::new(),
+                },
+            }));
+
+            // Expr::While
+            iter(depth, &mut |expr| {
+                f(Expr::While(ExprWhile {
+                    // `while $expr {}`
+                    attrs: Vec::new(),
+                    label: None,
+                    while_token: Token![while](span),
+                    cond: Box::new(expr.clone()),
+                    body: Block {
+                        brace_token: token::Brace(span),
+                        stmts: Vec::new(),
+                    },
+                }));
+                f(Expr::While(ExprWhile {
+                    // `'a: while $expr {}`
+                    attrs: Vec::new(),
+                    label: Some(Label {
+                        name: Lifetime::new("'a", span),
+                        colon_token: Token![:](span),
+                    }),
+                    while_token: Token![while](span),
+                    cond: Box::new(expr),
+                    body: Block {
+                        brace_token: token::Brace(span),
+                        stmts: Vec::new(),
+                    },
+                }));
+            });
+
+            // Expr::Yield
+            f(Expr::Yield(ExprYield {
+                // `yield`
+                attrs: Vec::new(),
+                yield_token: Token![yield](span),
+                expr: None,
+            }));
+            iter(depth, &mut |expr| {
+                f(Expr::Yield(ExprYield {
+                    // `yield $expr`
+                    attrs: Vec::new(),
+                    yield_token: Token![yield](span),
+                    expr: Some(Box::new(expr)),
+                }));
+            });
+        }
+    }
+
+    let mut failures = 0;
+    macro_rules! fail {
+        ($($message:tt)*) => {{
+            eprintln!($($message)*);
+            failures += 1;
+            return;
+        }};
+    }
+    let mut assert = |mut original: Expr| {
+        let span = Span::call_site();
+        // `const _: () = $expr;`
+        let pretty = prettyplease::unparse(&File {
+            shebang: None,
+            attrs: Vec::new(),
+            items: Vec::from([Item::Const(ItemConst {
+                attrs: Vec::new(),
+                vis: Visibility::Inherited,
+                const_token: Token![const](span),
+                ident: Ident::from(Token![_](span)),
+                generics: Generics::default(),
+                colon_token: Token![:](span),
+                ty: Box::new(Type::Infer(TypeInfer {
+                    underscore_token: Token![_](span),
+                })),
+                eq_token: Token![=](span),
+                expr: Box::new(original.clone()),
+                semi_token: Token![;](span),
+            })]),
+        });
+        let mut parsed = match syn::parse_file(&pretty) {
+            Ok(parsed) => parsed,
+            _ => fail!("failed to parse: {pretty}{original:#?}"),
+        };
+        let item = match parsed.items.as_mut_slice() {
+            [Item::Const(item)] => item,
+            _ => unreachable!(),
+        };
+        let mut parsed = mem::replace(&mut *item.expr, Expr::PLACEHOLDER);
+        AsIfPrinted.visit_expr_mut(&mut original);
+        FlattenParens.visit_expr_mut(&mut parsed);
+        if original != parsed {
+            fail!(
+                "before: {}\n{:#?}\nafter: {}\n{:#?}",
+                original.to_token_stream(),
+                original,
+                parsed.to_token_stream(),
+                parsed,
+            );
+        }
+        if pretty.contains("(||") {
+            // https://github.com/dtolnay/prettyplease/issues/99
+            return;
+        }
+        let no_paren = pretty.replace(['(', ')'], "");
+        if pretty != no_paren {
+            if let Ok(mut parsed2) = syn::parse_file(&no_paren) {
+                let item = match parsed2.items.as_mut_slice() {
+                    [Item::Const(item)] => item,
+                    _ => unreachable!(),
+                };
+                if original == *item.expr {
+                    fail!("redundant parens: {}", pretty);
+                }
+            }
+        }
+    };
+
+    iter(if cfg!(debug_assertions) { 3 } else { 4 }, &mut assert);
+    if failures > 0 {
+        eprintln!("FAILURES: {failures}");
+        ExitCode::FAILURE
+    } else {
+        ExitCode::SUCCESS
+    }
+}
diff --git a/pseudo_crate/Cargo.lock b/pseudo_crate/Cargo.lock
index 984e54a..e9cf7c7 100644
--- a/pseudo_crate/Cargo.lock
+++ b/pseudo_crate/Cargo.lock
@@ -338,7 +338,7 @@
  "predicates",
  "predicates-core",
  "predicates-tree",
- "prettyplease 0.2.25",
+ "prettyplease 0.2.29",
  "proc-macro2 1.0.93",
  "protobuf 3.2.0",
  "protobuf-codegen",
@@ -890,7 +890,7 @@
  "lazy_static",
  "lazycell",
  "log",
- "prettyplease 0.2.25",
+ "prettyplease 0.2.29",
  "proc-macro2 1.0.93",
  "quote 1.0.38",
  "regex",
@@ -3001,20 +3001,11 @@
 
 [[package]]
 name = "litrs"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b487d13a3f4b465df87895a37b24e364907019afa12d943528df5b7abe0836f1"
-dependencies = [
- "proc-macro2 1.0.93",
-]
-
-[[package]]
-name = "litrs"
 version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
 dependencies = [
- "proc-macro2 1.0.92",
+ "proc-macro2 1.0.93",
 ]
 
 [[package]]
@@ -3634,7 +3625,7 @@
 dependencies = [
  "proc-macro2 1.0.93",
  "quote 1.0.38",
- "syn 2.0.90",
+ "syn 2.0.96",
 ]
 
 [[package]]
@@ -3784,7 +3775,7 @@
  "heck",
  "pest",
  "pest_derive",
- "prettyplease 0.2.25",
+ "prettyplease 0.2.29",
  "proc-macro2 1.0.93",
  "quote 1.0.38",
  "serde",
@@ -4084,9 +4075,9 @@
 
 [[package]]
 name = "prettyplease"
-version = "0.2.25"
+version = "0.2.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033"
+checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac"
 dependencies = [
  "proc-macro2 1.0.93",
  "syn 2.0.96",
@@ -4371,7 +4362,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.7.0",
 ]
 
 [[package]]
@@ -5229,9 +5220,9 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c"
 dependencies = [
- "proc-macro2 1.0.92",
+ "proc-macro2 1.0.93",
  "quote 1.0.38",
- "syn 2.0.90",
+ "syn 2.0.96",
 ]
 
 [[package]]
diff --git a/pseudo_crate/Cargo.toml b/pseudo_crate/Cargo.toml
index e24e8f0..625543b 100644
--- a/pseudo_crate/Cargo.toml
+++ b/pseudo_crate/Cargo.toml
@@ -253,7 +253,7 @@
 predicates = "=3.1.3"
 predicates-core = "=1.0.9"
 predicates-tree = "=1.0.11"
-prettyplease = "=0.2.25"
+prettyplease = "=0.2.29"
 proc-macro2 = "=1.0.93"
 protobuf = "=3.2.0"
 protobuf-codegen = "=3.2.0"