about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorCaleb Cartwright <caleb.cartwright@outlook.com>2023-10-22 12:45:06 -0500
committerCaleb Cartwright <caleb.cartwright@outlook.com>2023-10-22 12:45:06 -0500
commitf35f25287fea7d45cb05764e5d8b0a3a4e4ab2cd (patch)
treebd7f33be19074b7fe3bfe4431645078cb73b1951 /src
parent4b5ef37e217d98de6ea3cef71aea9f47c2e9ce23 (diff)
parent547577fa5d309d90292ca3a58fef1bf0d9325cc0 (diff)
downloadrust-f35f25287fea7d45cb05764e5d8b0a3a4e4ab2cd.tar.gz
rust-f35f25287fea7d45cb05764e5d8b0a3a4e4ab2cd.zip
Merge remote-tracking branch 'upstream/master' into subtree-sync-2023-10-22
Diffstat (limited to 'src')
-rw-r--r--src/attr.rs8
-rw-r--r--src/attr/doc_comment.rs6
-rw-r--r--src/bin/main.rs26
-rw-r--r--src/cargo-fmt/main.rs30
-rw-r--r--src/chains.rs91
-rw-r--r--src/closures.rs11
-rw-r--r--src/comment.rs65
-rw-r--r--src/config/config_type.rs10
-rw-r--r--src/config/file_lines.rs2
-rw-r--r--src/config/macro_names.rs16
-rw-r--r--src/config/mod.rs15
-rw-r--r--src/config/options.rs2
-rw-r--r--src/emitter.rs2
-rw-r--r--src/emitter/checkstyle.rs4
-rw-r--r--src/emitter/checkstyle/xml.rs2
-rw-r--r--src/emitter/diff.rs6
-rw-r--r--src/emitter/json.rs6
-rw-r--r--src/emitter/stdout.rs4
-rw-r--r--src/expr.rs133
-rw-r--r--src/format-diff/main.rs15
-rw-r--r--src/formatting.rs2
-rw-r--r--src/git-rustfmt/main.rs11
-rw-r--r--src/imports.rs26
-rw-r--r--src/items.rs377
-rw-r--r--src/lib.rs12
-rw-r--r--src/lists.rs2
-rw-r--r--src/macros.rs84
-rw-r--r--src/matches.rs25
-rw-r--r--src/pairs.rs45
-rw-r--r--src/parse/session.rs3
-rw-r--r--src/patterns.rs8
-rw-r--r--src/rustfmt_diff.rs17
-rw-r--r--src/skip.rs2
-rw-r--r--src/source_file.rs2
-rw-r--r--src/stmt.rs34
-rw-r--r--src/string.rs4
-rw-r--r--src/test/configuration_snippet.rs10
-rw-r--r--src/test/mod.rs24
-rw-r--r--src/types.rs46
-rw-r--r--src/utils.rs41
40 files changed, 725 insertions, 504 deletions
diff --git a/src/attr.rs b/src/attr.rs
index 22e45082a9f..4d83547d664 100644
--- a/src/attr.rs
+++ b/src/attr.rs
@@ -308,7 +308,7 @@ impl Rewrite for ast::MetaItem {
                 // See #2479 for example.
                 let value = rewrite_literal(context, lit.as_token_lit(), lit.span, lit_shape)
                     .unwrap_or_else(|| context.snippet(lit.span).to_owned());
-                format!("{} = {}", path, value)
+                format!("{path} = {value}")
             }
         })
     }
@@ -342,7 +342,7 @@ impl Rewrite for ast::Attribute {
                         let literal_str = literal.as_str();
                         let doc_comment_formatter =
                             DocCommentFormatter::new(literal_str, comment_style);
-                        let doc_comment = format!("{}", doc_comment_formatter);
+                        let doc_comment = format!("{doc_comment_formatter}");
                         return rewrite_doc_comment(
                             &doc_comment,
                             shape.comment(context.config),
@@ -406,9 +406,9 @@ impl Rewrite for [ast::Attribute] {
                         0,
                     )?;
                     let comment = if comment.is_empty() {
-                        format!("\n{}", mlb)
+                        format!("\n{mlb}")
                     } else {
-                        format!("{}{}\n{}", mla, comment, mlb)
+                        format!("{mla}{comment}\n{mlb}")
                     };
                     result.push_str(&comment);
                     result.push_str(&shape.indent.to_string(context.config));
diff --git a/src/attr/doc_comment.rs b/src/attr/doc_comment.rs
index 25c8158df8c..f55201839b5 100644
--- a/src/attr/doc_comment.rs
+++ b/src/attr/doc_comment.rs
@@ -20,15 +20,15 @@ impl Display for DocCommentFormatter<'_> {
 
         // Handle `#[doc = ""]`.
         if lines.peek().is_none() {
-            return write!(formatter, "{}", opener);
+            return write!(formatter, "{opener}");
         }
 
         while let Some(line) = lines.next() {
             let is_last_line = lines.peek().is_none();
             if is_last_line {
-                write!(formatter, "{}{}", opener, line)?;
+                write!(formatter, "{opener}{line}")?;
             } else {
-                writeln!(formatter, "{}{}", opener, line)?;
+                writeln!(formatter, "{opener}{line}")?;
             }
         }
         Ok(())
diff --git a/src/bin/main.rs b/src/bin/main.rs
index 03b75c1b041..6f564083656 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -6,6 +6,7 @@ use io::Error as IoError;
 use thiserror::Error;
 
 use rustfmt_nightly as rustfmt;
+use tracing_subscriber::EnvFilter;
 
 use std::collections::HashMap;
 use std::env;
@@ -29,13 +30,15 @@ extern crate rustc_driver;
 fn main() {
     rustc_driver::install_ice_hook(BUG_REPORT_URL, |_| ());
 
-    env_logger::Builder::from_env("RUSTFMT_LOG").init();
+    tracing_subscriber::fmt()
+        .with_env_filter(EnvFilter::from_env("RUSTFMT_LOG"))
+        .init();
     let opts = make_opts();
 
     let exit_code = match execute(&opts) {
         Ok(code) => code,
         Err(e) => {
-            eprintln!("{:#}", e);
+            eprintln!("{e:#}");
             1
         }
     };
@@ -281,7 +284,7 @@ fn format_string(input: String, options: GetOptsOptions) -> Result<i32> {
     for f in config.file_lines().files() {
         match *f {
             FileName::Stdin => {}
-            _ => eprintln!("Warning: Extra file listed in file_lines option '{}'", f),
+            _ => eprintln!("Warning: Extra file listed in file_lines option '{f}'"),
         }
     }
 
@@ -377,7 +380,7 @@ fn format_and_emit_report<T: Write>(session: &mut Session<'_, T>, input: Input)
             }
         }
         Err(msg) => {
-            eprintln!("Error writing files: {}", msg);
+            eprintln!("Error writing files: {msg}");
             session.add_operational_error();
         }
     }
@@ -400,12 +403,9 @@ fn print_usage_to_stdout(opts: &Options, reason: &str) {
     let sep = if reason.is_empty() {
         String::new()
     } else {
-        format!("{}\n\n", reason)
+        format!("{reason}\n\n")
     };
-    let msg = format!(
-        "{}Format Rust code\n\nusage: rustfmt [options] <file>...",
-        sep
-    );
+    let msg = format!("{sep}Format Rust code\n\nusage: rustfmt [options] <file>...");
     println!("{}", opts.usage(&msg));
 }
 
@@ -419,7 +419,7 @@ are 1-based and inclusive of both end points. Specifying an empty array
 will result in no files being formatted. For example,
 
 ```
-rustfmt --file-lines '[
+rustfmt src/lib.rs src/foo.rs --file-lines '[
     {{\"file\":\"src/lib.rs\",\"range\":[7,13]}},
     {{\"file\":\"src/lib.rs\",\"range\":[21,29]}},
     {{\"file\":\"src/foo.rs\",\"range\":[10,11]}},
@@ -439,7 +439,7 @@ fn print_version() {
         include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt"))
     );
 
-    println!("rustfmt {}", version_info);
+    println!("rustfmt {version_info}");
 }
 
 fn determine_operation(matches: &Matches) -> Result<Operation, OperationError> {
@@ -644,9 +644,9 @@ impl GetOptsOptions {
             match *f {
                 FileName::Real(ref f) if files.contains(f) => {}
                 FileName::Real(_) => {
-                    eprintln!("Warning: Extra file listed in file_lines option '{}'", f)
+                    eprintln!("Warning: Extra file listed in file_lines option '{f}'")
                 }
-                FileName::Stdin => eprintln!("Warning: Not a file '{}'", f),
+                FileName::Stdin => eprintln!("Warning: Not a file '{f}'"),
             }
         }
     }
diff --git a/src/cargo-fmt/main.rs b/src/cargo-fmt/main.rs
index bc9745275f2..a1ad1aafac4 100644
--- a/src/cargo-fmt/main.rs
+++ b/src/cargo-fmt/main.rs
@@ -22,27 +22,28 @@ use clap::{CommandFactory, Parser};
 mod cargo_fmt_tests;
 
 #[derive(Parser)]
-#[clap(
+#[command(
     disable_version_flag = true,
     bin_name = "cargo fmt",
     about = "This utility formats all bin and lib files of \
              the current crate using rustfmt."
 )]
+#[command(styles = clap_cargo::style::CLAP_STYLING)]
 pub struct Opts {
     /// No output printed to stdout
-    #[clap(short = 'q', long = "quiet")]
+    #[arg(short = 'q', long = "quiet")]
     quiet: bool,
 
     /// Use verbose output
-    #[clap(short = 'v', long = "verbose")]
+    #[arg(short = 'v', long = "verbose")]
     verbose: bool,
 
     /// Print rustfmt version and exit
-    #[clap(long = "version")]
+    #[arg(long = "version")]
     version: bool,
 
     /// Specify package to format
-    #[clap(
+    #[arg(
         short = 'p',
         long = "package",
         value_name = "package",
@@ -51,24 +52,24 @@ pub struct Opts {
     packages: Vec<String>,
 
     /// Specify path to Cargo.toml
-    #[clap(long = "manifest-path", value_name = "manifest-path")]
+    #[arg(long = "manifest-path", value_name = "manifest-path")]
     manifest_path: Option<String>,
 
     /// Specify message-format: short|json|human
-    #[clap(long = "message-format", value_name = "message-format")]
+    #[arg(long = "message-format", value_name = "message-format")]
     message_format: Option<String>,
 
     /// Options passed to rustfmt
     // 'raw = true' to make `--` explicit.
-    #[clap(name = "rustfmt_options", raw(true))]
+    #[arg(name = "rustfmt_options", raw = true)]
     rustfmt_options: Vec<String>,
 
     /// Format all packages, and also their local path-based dependencies
-    #[clap(long = "all")]
+    #[arg(long = "all")]
     format_all: bool,
 
     /// Run rustfmt in check mode
-    #[clap(long = "check")]
+    #[arg(long = "check")]
     check: bool,
 }
 
@@ -200,14 +201,13 @@ fn convert_message_format_to_rustfmt_args(
         }
         "human" => Ok(()),
         _ => Err(format!(
-            "invalid --message-format value: {}. Allowed values are: short|json|human",
-            message_format
+            "invalid --message-format value: {message_format}. Allowed values are: short|json|human"
         )),
     }
 }
 
 fn print_usage_to_stderr(reason: &str) {
-    eprintln!("{}", reason);
+    eprintln!("{reason}");
     let app = Opts::command();
     app.after_help("")
         .write_help(&mut io::stderr())
@@ -460,7 +460,7 @@ fn get_targets_with_hitlist(
         let package = workspace_hitlist.iter().next().unwrap();
         Err(io::Error::new(
             io::ErrorKind::InvalidInput,
-            format!("package `{}` is not a member of the workspace", package),
+            format!("package `{package}` is not a member of the workspace"),
         ))
     }
 }
@@ -498,7 +498,7 @@ fn run_rustfmt(
 
         if verbosity == Verbosity::Verbose {
             print!("rustfmt");
-            print!(" --edition {}", edition);
+            print!(" --edition {edition}");
             fmt_args.iter().for_each(|f| print!(" {}", f));
             files.iter().for_each(|f| print!(" {}", f.display()));
             println!();
diff --git a/src/chains.rs b/src/chains.rs
index 0afce7cf659..ea23690caed 100644
--- a/src/chains.rs
+++ b/src/chains.rs
@@ -153,7 +153,13 @@ enum CommentPosition {
     Top,
 }
 
-// An expression plus trailing `?`s to be formatted together.
+/// Information about an expression in a chain.
+struct SubExpr {
+    expr: ast::Expr,
+    is_method_call_receiver: bool,
+}
+
+/// An expression plus trailing `?`s to be formatted together.
 #[derive(Debug)]
 struct ChainItem {
     kind: ChainItemKind,
@@ -166,7 +172,10 @@ struct ChainItem {
 // would remove a lot of cloning.
 #[derive(Debug)]
 enum ChainItemKind {
-    Parent(ast::Expr),
+    Parent {
+        expr: ast::Expr,
+        parens: bool,
+    },
     MethodCall(
         ast::PathSegment,
         Vec<ast::GenericArg>,
@@ -181,7 +190,7 @@ enum ChainItemKind {
 impl ChainItemKind {
     fn is_block_like(&self, context: &RewriteContext<'_>, reps: &str) -> bool {
         match self {
-            ChainItemKind::Parent(ref expr) => utils::is_block_expr(context, expr, reps),
+            ChainItemKind::Parent { expr, .. } => utils::is_block_expr(context, expr, reps),
             ChainItemKind::MethodCall(..)
             | ChainItemKind::StructField(..)
             | ChainItemKind::TupleField(..)
@@ -199,7 +208,11 @@ impl ChainItemKind {
         }
     }
 
-    fn from_ast(context: &RewriteContext<'_>, expr: &ast::Expr) -> (ChainItemKind, Span) {
+    fn from_ast(
+        context: &RewriteContext<'_>,
+        expr: &ast::Expr,
+        is_method_call_receiver: bool,
+    ) -> (ChainItemKind, Span) {
         let (kind, span) = match expr.kind {
             ast::ExprKind::MethodCall(ref call) => {
                 let types = if let Some(ref generic_args) = call.seg.args {
@@ -236,7 +249,15 @@ impl ChainItemKind {
                 let span = mk_sp(nested.span.hi(), expr.span.hi());
                 (ChainItemKind::Await, span)
             }
-            _ => return (ChainItemKind::Parent(expr.clone()), expr.span),
+            _ => {
+                return (
+                    ChainItemKind::Parent {
+                        expr: expr.clone(),
+                        parens: is_method_call_receiver && should_add_parens(expr),
+                    },
+                    expr.span,
+                );
+            }
         };
 
         // Remove comments from the span.
@@ -249,7 +270,14 @@ impl Rewrite for ChainItem {
     fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
         let shape = shape.sub_width(self.tries)?;
         let rewrite = match self.kind {
-            ChainItemKind::Parent(ref expr) => expr.rewrite(context, shape)?,
+            ChainItemKind::Parent {
+                ref expr,
+                parens: true,
+            } => crate::expr::rewrite_paren(context, &expr, shape, expr.span)?,
+            ChainItemKind::Parent {
+                ref expr,
+                parens: false,
+            } => expr.rewrite(context, shape)?,
             ChainItemKind::MethodCall(ref segment, ref types, ref exprs) => {
                 Self::rewrite_method_call(segment.ident, types, exprs, self.span, context, shape)?
             }
@@ -268,13 +296,14 @@ impl Rewrite for ChainItem {
                 rewrite_comment(comment, false, shape, context.config)?
             }
         };
-        Some(format!("{}{}", rewrite, "?".repeat(self.tries)))
+        Some(format!("{rewrite}{}", "?".repeat(self.tries)))
     }
 }
 
 impl ChainItem {
-    fn new(context: &RewriteContext<'_>, expr: &ast::Expr, tries: usize) -> ChainItem {
-        let (kind, span) = ChainItemKind::from_ast(context, expr);
+    fn new(context: &RewriteContext<'_>, expr: &SubExpr, tries: usize) -> ChainItem {
+        let (kind, span) =
+            ChainItemKind::from_ast(context, &expr.expr, expr.is_method_call_receiver);
         ChainItem { kind, tries, span }
     }
 
@@ -327,7 +356,7 @@ impl Chain {
         let mut rev_children = vec![];
         let mut sub_tries = 0;
         for subexpr in &subexpr_list {
-            match subexpr.kind {
+            match subexpr.expr.kind {
                 ast::ExprKind::Try(_) => sub_tries += 1,
                 _ => {
                     rev_children.push(ChainItem::new(context, subexpr, sub_tries));
@@ -442,11 +471,14 @@ impl Chain {
 
     // Returns a Vec of the prefixes of the chain.
     // E.g., for input `a.b.c` we return [`a.b.c`, `a.b`, 'a']
-    fn make_subexpr_list(expr: &ast::Expr, context: &RewriteContext<'_>) -> Vec<ast::Expr> {
-        let mut subexpr_list = vec![expr.clone()];
+    fn make_subexpr_list(expr: &ast::Expr, context: &RewriteContext<'_>) -> Vec<SubExpr> {
+        let mut subexpr_list = vec![SubExpr {
+            expr: expr.clone(),
+            is_method_call_receiver: false,
+        }];
 
         while let Some(subexpr) = Self::pop_expr_chain(subexpr_list.last().unwrap(), context) {
-            subexpr_list.push(subexpr.clone());
+            subexpr_list.push(subexpr);
         }
 
         subexpr_list
@@ -454,12 +486,18 @@ impl Chain {
 
     // Returns the expression's subexpression, if it exists. When the subexpr
     // is a try! macro, we'll convert it to shorthand when the option is set.
-    fn pop_expr_chain(expr: &ast::Expr, context: &RewriteContext<'_>) -> Option<ast::Expr> {
-        match expr.kind {
-            ast::ExprKind::MethodCall(ref call) => Some(Self::convert_try(&call.receiver, context)),
+    fn pop_expr_chain(expr: &SubExpr, context: &RewriteContext<'_>) -> Option<SubExpr> {
+        match expr.expr.kind {
+            ast::ExprKind::MethodCall(ref call) => Some(SubExpr {
+                expr: Self::convert_try(&call.receiver, context),
+                is_method_call_receiver: true,
+            }),
             ast::ExprKind::Field(ref subexpr, _)
             | ast::ExprKind::Try(ref subexpr)
-            | ast::ExprKind::Await(ref subexpr, _) => Some(Self::convert_try(subexpr, context)),
+            | ast::ExprKind::Await(ref subexpr, _) => Some(SubExpr {
+                expr: Self::convert_try(subexpr, context),
+                is_method_call_receiver: false,
+            }),
             _ => None,
         }
     }
@@ -940,3 +978,22 @@ fn trim_tries(s: &str) -> String {
     }
     result
 }
+
+/// Whether a method call's receiver needs parenthesis, like
+/// ```rust,ignore
+/// || .. .method();
+/// || 1.. .method();
+/// 1. .method();
+/// ```
+/// Which all need parenthesis or a space before `.method()`.
+fn should_add_parens(expr: &ast::Expr) -> bool {
+    match expr.kind {
+        ast::ExprKind::Lit(ref lit) => crate::expr::lit_ends_in_dot(lit),
+        ast::ExprKind::Closure(ref cl) => match cl.body.kind {
+            ast::ExprKind::Range(_, _, ast::RangeLimits::HalfOpen) => true,
+            ast::ExprKind::Lit(ref lit) => crate::expr::lit_ends_in_dot(lit),
+            _ => false,
+        },
+        _ => false,
+    }
+}
diff --git a/src/closures.rs b/src/closures.rs
index c95e9a97b43..a09146e9592 100644
--- a/src/closures.rs
+++ b/src/closures.rs
@@ -12,7 +12,7 @@ use crate::overflow::OverflowableItem;
 use crate::rewrite::{Rewrite, RewriteContext};
 use crate::shape::Shape;
 use crate::source_map::SpanUtils;
-use crate::types::rewrite_lifetime_param;
+use crate::types::rewrite_bound_params;
 use crate::utils::{last_line_width, left_most_sub_expr, stmt_expr, NodeIdExt};
 
 // This module is pretty messy because of the rules around closures and blocks:
@@ -175,7 +175,7 @@ fn rewrite_closure_with_block(
         shape,
         false,
     )?;
-    Some(format!("{} {}", prefix, block))
+    Some(format!("{prefix} {block}"))
 }
 
 // Rewrite closure with a single expression without wrapping its body with block.
@@ -246,7 +246,7 @@ fn rewrite_closure_fn_decl(
             "for<> ".to_owned()
         }
         ast::ClosureBinder::For { generic_params, .. } => {
-            let lifetime_str = rewrite_lifetime_param(context, shape, generic_params)?;
+            let lifetime_str = rewrite_bound_params(context, shape, generic_params)?;
             format!("for<{lifetime_str}> ")
         }
         ast::ClosureBinder::NotPresent => "".to_owned(),
@@ -310,10 +310,7 @@ fn rewrite_closure_fn_decl(
         .tactic(tactic)
         .preserve_newline(true);
     let list_str = write_list(&item_vec, &fmt)?;
-    let mut prefix = format!(
-        "{}{}{}{}{}|{}|",
-        binder, const_, immovable, is_async, mover, list_str
-    );
+    let mut prefix = format!("{binder}{const_}{immovable}{is_async}{mover}|{list_str}|");
 
     if !ret_str.is_empty() {
         if prefix.contains('\n') {
diff --git a/src/comment.rs b/src/comment.rs
index 85918ecc116..f7957321464 100644
--- a/src/comment.rs
+++ b/src/comment.rs
@@ -58,25 +58,23 @@ fn custom_opener(s: &str) -> &str {
 }
 
 impl<'a> CommentStyle<'a> {
-    /// Returns `true` if the commenting style covers a line only.
+    /// Returns `true` if the commenting style cannot span multiple lines.
     pub(crate) fn is_line_comment(&self) -> bool {
-        match *self {
+        matches!(
+            self,
             CommentStyle::DoubleSlash
-            | CommentStyle::TripleSlash
-            | CommentStyle::Doc
-            | CommentStyle::Custom(_) => true,
-            _ => false,
-        }
+                | CommentStyle::TripleSlash
+                | CommentStyle::Doc
+                | CommentStyle::Custom(_)
+        )
     }
 
-    /// Returns `true` if the commenting style can span over multiple lines.
+    /// Returns `true` if the commenting style can span multiple lines.
     pub(crate) fn is_block_comment(&self) -> bool {
-        match *self {
-            CommentStyle::SingleBullet | CommentStyle::DoubleBullet | CommentStyle::Exclamation => {
-                true
-            }
-            _ => false,
-        }
+        matches!(
+            self,
+            CommentStyle::SingleBullet | CommentStyle::DoubleBullet | CommentStyle::Exclamation
+        )
     }
 
     /// Returns `true` if the commenting style is for documentation.
@@ -367,7 +365,11 @@ fn identify_comment(
             trim_left_preserve_layout(first_group, shape.indent, config)?
         } else if !config.normalize_comments()
             && !config.wrap_comments()
-            && !config.format_code_in_doc_comments()
+            && !(
+                // `format_code_in_doc_comments` should only take effect on doc comments,
+                // so we only consider it when this comment block is a doc comment block.
+                is_doc_comment && config.format_code_in_doc_comments()
+            )
         {
             light_rewrite_comment(first_group, shape.indent, config, is_doc_comment)
         } else {
@@ -484,7 +486,9 @@ impl ItemizedBlock {
         // allowed.
         for suffix in [". ", ") "] {
             if let Some((prefix, _)) = trimmed.split_once(suffix) {
-                if prefix.len() <= 2 && prefix.chars().all(|c| char::is_ascii_digit(&c)) {
+                let has_leading_digits = (1..=2).contains(&prefix.len())
+                    && prefix.chars().all(|c| char::is_ascii_digit(&c));
+                if has_leading_digits {
                     return Some(prefix.len() + suffix.len());
                 }
             }
@@ -623,7 +627,7 @@ impl<'a> CommentRewrite<'a> {
             is_prev_line_multi_line: false,
             code_block_attr: None,
             item_block: None,
-            comment_line_separator: format!("{}{}", indent_str, line_start),
+            comment_line_separator: format!("{indent_str}{line_start}"),
             max_width,
             indent_str,
             fmt_indent: shape.indent,
@@ -953,7 +957,7 @@ const RUSTFMT_CUSTOM_COMMENT_PREFIX: &str = "//#### ";
 fn hide_sharp_behind_comment(s: &str) -> Cow<'_, str> {
     let s_trimmed = s.trim();
     if s_trimmed.starts_with("# ") || s_trimmed == "#" {
-        Cow::from(format!("{}{}", RUSTFMT_CUSTOM_COMMENT_PREFIX, s))
+        Cow::from(format!("{RUSTFMT_CUSTOM_COMMENT_PREFIX}{s}"))
     } else {
         Cow::from(s)
     }
@@ -1037,7 +1041,7 @@ pub(crate) fn recover_missing_comment_in_span(
         } else {
             Cow::from(" ")
         };
-        Some(format!("{}{}", sep, missing_comment))
+        Some(format!("{sep}{missing_comment}"))
     }
 }
 
@@ -1834,8 +1838,7 @@ fn remove_comment_header(comment: &str) -> &str {
     } else {
         assert!(
             comment.starts_with("/*"),
-            "string '{}' is not a comment",
-            comment
+            "string '{comment}' is not a comment"
         );
         &comment[2..comment.len() - 2]
     }
@@ -2071,6 +2074,7 @@ fn main() {
             expected_line_start: &str,
         ) {
             let block = ItemizedBlock::new(test_input).unwrap();
+<<<<<<< HEAD
             assert_eq!(1, block.lines.len(), "test_input: {:?}", test_input);
             assert_eq!(
                 expected_line, &block.lines[0],
@@ -2091,6 +2095,15 @@ fn main() {
                 expected_line_start, &block.line_start,
                 "test_input: {:?}",
                 test_input
+=======
+            assert_eq!(1, block.lines.len(), "test_input: {test_input:?}");
+            assert_eq!(expected_line, &block.lines[0], "test_input: {test_input:?}");
+            assert_eq!(expected_indent, block.indent, "test_input: {test_input:?}");
+            assert_eq!(expected_opener, &block.opener, "test_input: {test_input:?}");
+            assert_eq!(
+                expected_line_start, &block.line_start,
+                "test_input: {test_input:?}"
+>>>>>>> upstream/master
             );
         }
 
@@ -2142,13 +2155,23 @@ fn main() {
             // https://spec.commonmark.org/0.30 says: "A start number may not be negative":
             "-1. Not a list item.",
             "-1 Not a list item.",
+<<<<<<< HEAD
+=======
+            // Marker without prefix are not recognized as item markers:
+            ".   Not a list item.",
+            ")   Not a list item.",
+>>>>>>> upstream/master
         ];
         for line in test_inputs.iter() {
             let maybe_block = ItemizedBlock::new(line);
             assert!(
                 maybe_block.is_none(),
+<<<<<<< HEAD
                 "The following line shouldn't be classified as a list item: {}",
                 line
+=======
+                "The following line shouldn't be classified as a list item: {line}"
+>>>>>>> upstream/master
             );
         }
     }
diff --git a/src/config/config_type.rs b/src/config/config_type.rs
index c836b4bbb78..feb452d7235 100644
--- a/src/config/config_type.rs
+++ b/src/config/config_type.rs
@@ -500,18 +500,16 @@ where
         // Stable with an unstable option
         (false, false, _) => {
             eprintln!(
-                "Warning: can't set `{} = {:?}`, unstable features are only \
-                       available in nightly channel.",
-                option_name, option_value
+                "Warning: can't set `{option_name} = {option_value:?}`, unstable features are only \
+                       available in nightly channel."
             );
             false
         }
         // Stable with a stable option, but an unstable variant
         (false, true, false) => {
             eprintln!(
-                "Warning: can't set `{} = {:?}`, unstable variants are only \
-                       available in nightly channel.",
-                option_name, option_value
+                "Warning: can't set `{option_name} = {option_value:?}`, unstable variants are only \
+                       available in nightly channel."
             );
             false
         }
diff --git a/src/config/file_lines.rs b/src/config/file_lines.rs
index e4e51a3f3b4..e33fe9bb283 100644
--- a/src/config/file_lines.rs
+++ b/src/config/file_lines.rs
@@ -162,7 +162,7 @@ impl fmt::Display for FileLines {
             None => write!(f, "None")?,
             Some(map) => {
                 for (file_name, ranges) in map.iter() {
-                    write!(f, "{}: ", file_name)?;
+                    write!(f, "{file_name}: ")?;
                     write!(f, "{}\n", ranges.iter().format(", "))?;
                 }
             }
diff --git a/src/config/macro_names.rs b/src/config/macro_names.rs
index 26ad78d6dca..edfe925c2b3 100644
--- a/src/config/macro_names.rs
+++ b/src/config/macro_names.rs
@@ -3,7 +3,7 @@
 use itertools::Itertools;
 use std::{fmt, str};
 
-use serde::{Deserialize, Serialize};
+use serde::{Deserialize, Deserializer, Serialize};
 use serde_json as json;
 use thiserror::Error;
 
@@ -30,12 +30,22 @@ impl From<MacroName> for String {
 }
 
 /// Defines a selector to match against a macro.
-#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
+#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize)]
 pub enum MacroSelector {
     Name(MacroName),
     All,
 }
 
+impl<'de> Deserialize<'de> for MacroSelector {
+    fn deserialize<D>(de: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let s = String::deserialize(de)?;
+        std::str::FromStr::from_str(&s).map_err(serde::de::Error::custom)
+    }
+}
+
 impl fmt::Display for MacroSelector {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
@@ -113,6 +123,6 @@ mod test {
     #[test]
     fn macro_names_display() {
         let macro_names = MacroSelectors::from_str(r#"["foo", "*", "bar"]"#).unwrap();
-        assert_eq!(format!("{}", macro_names), "foo, *, bar");
+        assert_eq!(format!("{macro_names}"), "foo, *, bar");
     }
 }
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 6f41b299e87..7538b26522d 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -216,8 +216,8 @@ impl Config {
             let required_version = self.required_version();
             if version != required_version {
                 println!(
-                    "Error: rustfmt version ({}) doesn't match the required version ({})",
-                    version, required_version,
+                    "Error: rustfmt version ({version}) doesn't match the required version \
+({required_version})"
                 );
                 return false;
             }
@@ -310,20 +310,20 @@ impl Config {
             .ok_or_else(|| String::from("Parsed config was not table"))?;
         for key in table.keys() {
             if !Config::is_valid_name(key) {
-                let msg = &format!("Warning: Unknown configuration option `{}`\n", key);
+                let msg = &format!("Warning: Unknown configuration option `{key}`\n");
                 err.push_str(msg)
             }
         }
         match parsed.try_into() {
             Ok(parsed_config) => {
                 if !err.is_empty() {
-                    eprint!("{}", err);
+                    eprint!("{err}");
                 }
                 Ok(Config::default().fill_from_parsed_config(parsed_config, dir))
             }
             Err(e) => {
                 err.push_str("Error: Decoding config file failed:\n");
-                err.push_str(format!("{}\n", e).as_str());
+                err.push_str(format!("{e}\n").as_str());
                 err.push_str("Please check your config file.");
                 Err(err)
             }
@@ -563,10 +563,7 @@ mod test {
         let toml = used_options.to_toml().unwrap();
         assert_eq!(
             toml,
-            format!(
-                "merge_derives = {}\nskip_children = {}\n",
-                merge_derives, skip_children,
-            )
+            format!("merge_derives = {merge_derives}\nskip_children = {skip_children}\n",)
         );
     }
 
diff --git a/src/config/options.rs b/src/config/options.rs
index 3aa1a4de99d..e37f4027e4a 100644
--- a/src/config/options.rs
+++ b/src/config/options.rs
@@ -243,7 +243,7 @@ pub struct WidthHeuristics {
 
 impl fmt::Display for WidthHeuristics {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{:?}", self)
+        write!(f, "{self:?}")
     }
 }
 
diff --git a/src/emitter.rs b/src/emitter.rs
index dc2c99a301e..9c335314d75 100644
--- a/src/emitter.rs
+++ b/src/emitter.rs
@@ -47,6 +47,6 @@ pub(crate) trait Emitter {
 fn ensure_real_path(filename: &FileName) -> &Path {
     match *filename {
         FileName::Real(ref path) => path,
-        _ => panic!("cannot format `{}` and emit to files", filename),
+        _ => panic!("cannot format `{filename}` and emit to files"),
     }
 }
diff --git a/src/emitter/checkstyle.rs b/src/emitter/checkstyle.rs
index 545b259979d..56d6a0ed681 100644
--- a/src/emitter/checkstyle.rs
+++ b/src/emitter/checkstyle.rs
@@ -43,7 +43,7 @@ pub(crate) fn output_checkstyle_file<T>(
 where
     T: Write,
 {
-    write!(writer, r#"<file name="{}">"#, filename)?;
+    write!(writer, r#"<file name="{filename}">"#)?;
     for mismatch in diff {
         let begin_line = mismatch.line_number;
         let mut current_line;
@@ -82,7 +82,7 @@ mod tests {
         );
         assert_eq!(
             &writer[..],
-            format!(r#"<file name="{}"></file>"#, file_name).as_bytes()
+            format!(r#"<file name="{file_name}"></file>"#).as_bytes()
         );
     }
 
diff --git a/src/emitter/checkstyle/xml.rs b/src/emitter/checkstyle/xml.rs
index f251aabe878..d1d9af70857 100644
--- a/src/emitter/checkstyle/xml.rs
+++ b/src/emitter/checkstyle/xml.rs
@@ -13,7 +13,7 @@ impl<'a> Display for XmlEscaped<'a> {
                 '"' => write!(formatter, "&quot;"),
                 '\'' => write!(formatter, "&apos;"),
                 '&' => write!(formatter, "&amp;"),
-                _ => write!(formatter, "{}", char),
+                _ => write!(formatter, "{char}"),
             }?;
         }
 
diff --git a/src/emitter/diff.rs b/src/emitter/diff.rs
index 5e1f1344656..764cd136e01 100644
--- a/src/emitter/diff.rs
+++ b/src/emitter/diff.rs
@@ -28,7 +28,7 @@ impl Emitter for DiffEmitter {
 
         if has_diff {
             if self.config.print_misformatted_file_names() {
-                writeln!(output, "{}", filename)?;
+                writeln!(output, "{filename}")?;
             } else {
                 print_diff(
                     mismatch,
@@ -40,7 +40,7 @@ impl Emitter for DiffEmitter {
             // This occurs when the only difference between the original and formatted values
             // is the newline style. This happens because The make_diff function compares the
             // original and formatted values line by line, independent of line endings.
-            writeln!(output, "Incorrect newline style in {}", filename)?;
+            writeln!(output, "Incorrect newline style in {filename}")?;
             return Ok(EmitterResult { has_diff: true });
         }
 
@@ -110,7 +110,7 @@ mod tests {
 
         assert_eq!(
             String::from_utf8(writer).unwrap(),
-            format!("{}\n{}\n", bin_file, lib_file),
+            format!("{bin_file}\n{lib_file}\n"),
         )
     }
 
diff --git a/src/emitter/json.rs b/src/emitter/json.rs
index c7f68d4675a..5594196bed9 100644
--- a/src/emitter/json.rs
+++ b/src/emitter/json.rs
@@ -96,7 +96,7 @@ impl JsonEmitter {
             });
         }
         self.mismatched_files.push(MismatchedFile {
-            name: format!("{}", filename),
+            name: format!("{filename}"),
             mismatches,
         });
         Ok(())
@@ -281,7 +281,7 @@ mod tests {
         }])
         .unwrap();
         assert_eq!(result.has_diff, true);
-        assert_eq!(&writer[..], format!("{}\n", exp_json).as_bytes());
+        assert_eq!(&writer[..], format!("{exp_json}\n").as_bytes());
     }
 
     #[test]
@@ -341,6 +341,6 @@ mod tests {
         };
 
         let exp_json = to_json_string(&vec![exp_bin, exp_lib]).unwrap();
-        assert_eq!(&writer[..], format!("{}\n", exp_json).as_bytes());
+        assert_eq!(&writer[..], format!("{exp_json}\n").as_bytes());
     }
 }
diff --git a/src/emitter/stdout.rs b/src/emitter/stdout.rs
index 9fddd515e49..0bbc7332dfe 100644
--- a/src/emitter/stdout.rs
+++ b/src/emitter/stdout.rs
@@ -24,9 +24,9 @@ impl Emitter for StdoutEmitter {
         }: FormattedFile<'_>,
     ) -> Result<EmitterResult, io::Error> {
         if self.verbosity != Verbosity::Quiet {
-            writeln!(output, "{}:\n", filename)?;
+            writeln!(output, "{filename}:\n")?;
         }
-        write!(output, "{}", formatted_text)?;
+        write!(output, "{formatted_text}")?;
         Ok(EmitterResult::default())
     }
 }
diff --git a/src/expr.rs b/src/expr.rs
index 03cdddc4140..878e2b7d6d0 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -2,7 +2,7 @@ use std::borrow::Cow;
 use std::cmp::min;
 
 use itertools::Itertools;
-use rustc_ast::token::{Delimiter, LitKind};
+use rustc_ast::token::{Delimiter, Lit, LitKind};
 use rustc_ast::{ast, ptr, token};
 use rustc_span::{BytePos, Span};
 
@@ -26,6 +26,7 @@ use crate::rewrite::{Rewrite, RewriteContext};
 use crate::shape::{Indent, Shape};
 use crate::source_map::{LineRangeUtils, SpanUtils};
 use crate::spanned::Spanned;
+use crate::stmt;
 use crate::string::{rewrite_string, StringFormat};
 use crate::types::{rewrite_path, PathContext};
 use crate::utils::{
@@ -48,6 +49,10 @@ pub(crate) enum ExprType {
     SubExpression,
 }
 
+pub(crate) fn lit_ends_in_dot(lit: &Lit) -> bool {
+    matches!(lit, Lit { kind: LitKind::Float, suffix: None, symbol } if symbol.as_str().ends_with('.'))
+}
+
 pub(crate) fn format_expr(
     expr: &ast::Expr,
     expr_type: ExprType,
@@ -127,7 +132,7 @@ pub(crate) fn format_expr(
         ast::ExprKind::Tup(ref items) => {
             rewrite_tuple(context, items.iter(), expr.span, shape, items.len() == 1)
         }
-        ast::ExprKind::Let(..) => None,
+        ast::ExprKind::Let(ref pat, ref expr, _span) => rewrite_let(context, shape, pat, expr),
         ast::ExprKind::If(..)
         | ast::ExprKind::ForLoop(..)
         | ast::ExprKind::Loop(..)
@@ -182,7 +187,7 @@ pub(crate) fn format_expr(
                 Some(label) => format!(" {}", label.ident),
                 None => String::new(),
             };
-            Some(format!("continue{}", id_str))
+            Some(format!("continue{id_str}"))
         }
         ast::ExprKind::Break(ref opt_label, ref opt_expr) => {
             let id_str = match *opt_label {
@@ -191,9 +196,9 @@ pub(crate) fn format_expr(
             };
 
             if let Some(ref expr) = *opt_expr {
-                rewrite_unary_prefix(context, &format!("break{} ", id_str), &**expr, shape)
+                rewrite_unary_prefix(context, &format!("break{id_str} "), &**expr, shape)
             } else {
-                Some(format!("break{}", id_str))
+                Some(format!("break{id_str}"))
             }
         }
         ast::ExprKind::Yield(ref opt_expr) => {
@@ -275,12 +280,7 @@ pub(crate) fn format_expr(
 
             fn needs_space_before_range(context: &RewriteContext<'_>, lhs: &ast::Expr) -> bool {
                 match lhs.kind {
-                    ast::ExprKind::Lit(token_lit) => match token_lit.kind {
-                        token::LitKind::Float if token_lit.suffix.is_none() => {
-                            context.snippet(lhs.span).ends_with('.')
-                        }
-                        _ => false,
-                    },
+                    ast::ExprKind::Lit(token_lit) => lit_ends_in_dot(&token_lit),
                     ast::ExprKind::Unary(_, ref expr) => needs_space_before_range(context, expr),
                     _ => false,
                 }
@@ -309,7 +309,7 @@ pub(crate) fn format_expr(
             match (lhs.as_ref().map(|x| &**x), rhs.as_ref().map(|x| &**x)) {
                 (Some(lhs), Some(rhs)) => {
                     let sp_delim = if context.config.spaces_around_ranges() {
-                        format!(" {} ", delim)
+                        format!(" {delim} ")
                     } else {
                         default_sp_delim(Some(lhs), Some(rhs))
                     };
@@ -324,7 +324,7 @@ pub(crate) fn format_expr(
                 }
                 (None, Some(rhs)) => {
                     let sp_delim = if context.config.spaces_around_ranges() {
-                        format!("{} ", delim)
+                        format!("{delim} ")
                     } else {
                         default_sp_delim(None, Some(rhs))
                     };
@@ -332,7 +332,7 @@ pub(crate) fn format_expr(
                 }
                 (Some(lhs), None) => {
                     let sp_delim = if context.config.spaces_around_ranges() {
-                        format!(" {}", delim)
+                        format!(" {delim}")
                     } else {
                         default_sp_delim(Some(lhs), None)
                     };
@@ -375,7 +375,7 @@ pub(crate) fn format_expr(
             };
             if let rw @ Some(_) = rewrite_single_line_block(
                 context,
-                format!("{}{}", "async ", mover).as_str(),
+                format!("async {mover}").as_str(),
                 block,
                 Some(&expr.attrs),
                 None,
@@ -386,9 +386,7 @@ pub(crate) fn format_expr(
                 // 6 = `async `
                 let budget = shape.width.saturating_sub(6);
                 Some(format!(
-                    "{}{}{}",
-                    "async ",
-                    mover,
+                    "async {mover}{}",
                     rewrite_block(
                         block,
                         Some(&expr.attrs),
@@ -460,7 +458,7 @@ fn rewrite_empty_block(
     }
 
     if !block_contains_comment(context, block) && shape.width >= 2 {
-        return Some(format!("{}{}{{}}", prefix, label_str));
+        return Some(format!("{prefix}{label_str}{{}}"));
     }
 
     // If a block contains only a single-line comment, then leave it on one line.
@@ -473,7 +471,7 @@ fn rewrite_empty_block(
             && !comment_str.starts_with("//")
             && comment_str.len() + 4 <= shape.width
         {
-            return Some(format!("{}{}{{ {} }}", prefix, label_str, comment_str));
+            return Some(format!("{prefix}{label_str}{{ {comment_str} }}"));
         }
     }
 
@@ -516,11 +514,11 @@ fn rewrite_single_line_block(
     label: Option<ast::Label>,
     shape: Shape,
 ) -> Option<String> {
-    if is_simple_block(context, block, attrs) {
+    if let Some(block_expr) = stmt::Stmt::from_simple_block(context, block, attrs) {
         let expr_shape = shape.offset_left(last_line_width(prefix))?;
-        let expr_str = block.stmts[0].rewrite(context, expr_shape)?;
+        let expr_str = block_expr.rewrite(context, expr_shape)?;
         let label_str = rewrite_label(label);
-        let result = format!("{}{}{{ {} }}", prefix, label_str, expr_str);
+        let result = format!("{prefix}{label_str}{{ {expr_str} }}");
         if result.len() <= shape.width && !result.contains('\n') {
             return Some(result);
         }
@@ -800,19 +798,19 @@ impl<'a> ControlFlow<'a> {
         let fixed_cost = self.keyword.len() + "  {  } else {  }".len();
 
         if let ast::ExprKind::Block(ref else_node, _) = else_block.kind {
-            if !is_simple_block(context, self.block, None)
-                || !is_simple_block(context, else_node, None)
-                || pat_expr_str.contains('\n')
-            {
-                return None;
-            }
+            let (if_expr, else_expr) = match (
+                stmt::Stmt::from_simple_block(context, self.block, None),
+                stmt::Stmt::from_simple_block(context, else_node, None),
+                pat_expr_str.contains('\n'),
+            ) {
+                (Some(if_expr), Some(else_expr), false) => (if_expr, else_expr),
+                _ => return None,
+            };
 
             let new_width = width.checked_sub(pat_expr_str.len() + fixed_cost)?;
-            let expr = &self.block.stmts[0];
-            let if_str = expr.rewrite(context, Shape::legacy(new_width, Indent::empty()))?;
+            let if_str = if_expr.rewrite(context, Shape::legacy(new_width, Indent::empty()))?;
 
             let new_width = new_width.checked_sub(if_str.len())?;
-            let else_expr = &else_node.stmts[0];
             let else_str = else_expr.rewrite(context, Shape::legacy(new_width, Indent::empty()))?;
 
             if if_str.contains('\n') || else_str.contains('\n') {
@@ -1100,7 +1098,7 @@ impl<'a> Rewrite for ControlFlow<'a> {
             result?
         };
 
-        let mut result = format!("{}{}", cond_str, block_str);
+        let mut result = format!("{cond_str}{block_str}");
 
         if let Some(else_block) = self.else_block {
             let shape = Shape::indented(shape.indent, context.config);
@@ -1160,8 +1158,7 @@ fn rewrite_label(opt_label: Option<ast::Label>) -> Cow<'static, str> {
 fn extract_comment(span: Span, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
     match rewrite_missing_comment(span, shape, context) {
         Some(ref comment) if !comment.is_empty() => Some(format!(
-            "{indent}{}{indent}",
-            comment,
+            "{indent}{comment}{indent}",
             indent = shape.indent.to_string_with_newline(context.config)
         )),
         _ => None,
@@ -1436,7 +1433,7 @@ pub(crate) fn span_ends_with_comma(context: &RewriteContext<'_>, span: Span) ->
     result
 }
 
-fn rewrite_paren(
+pub(crate) fn rewrite_paren(
     context: &RewriteContext<'_>,
     mut subexpr: &ast::Expr,
     shape: Shape,
@@ -1452,7 +1449,7 @@ fn rewrite_paren(
     let remove_nested_parens = context.config.remove_nested_parens();
     loop {
         // 1 = "(" or ")"
-        pre_span = mk_sp(span.lo() + BytePos(1), subexpr.span.lo());
+        pre_span = mk_sp(span.lo() + BytePos(1), subexpr.span().lo());
         post_span = mk_sp(subexpr.span.hi(), span.hi() - BytePos(1));
         pre_comment = rewrite_missing_comment(pre_span, shape, context)?;
         post_comment = rewrite_missing_comment(post_span, shape, context)?;
@@ -1474,7 +1471,7 @@ fn rewrite_paren(
     let subexpr_str = subexpr.rewrite(context, sub_shape)?;
     let fits_single_line = !pre_comment.contains("//") && !post_comment.contains("//");
     if fits_single_line {
-        Some(format!("({}{}{})", pre_comment, subexpr_str, post_comment))
+        Some(format!("({pre_comment}{subexpr_str}{post_comment})"))
     } else {
         rewrite_paren_in_multi_line(context, subexpr, shape, pre_span, post_span)
     }
@@ -1538,7 +1535,7 @@ fn rewrite_index(
     // Return if index fits in a single line.
     match orig_index_rw {
         Some(ref index_str) if !index_str.contains('\n') => {
-            return Some(format!("{}[{}]", expr_str, index_str));
+            return Some(format!("{expr_str}[{index_str}]"));
         }
         _ => (),
     }
@@ -1561,7 +1558,7 @@ fn rewrite_index(
             indent.to_string_with_newline(context.config),
             new_index_str,
         )),
-        (Some(ref index_str), _) => Some(format!("{}[{}]", expr_str, index_str)),
+        (Some(ref index_str), _) => Some(format!("{expr_str}[{index_str}]")),
         _ => None,
     }
 }
@@ -1593,9 +1590,9 @@ fn rewrite_struct_lit<'a>(
     let path_str = rewrite_path(context, PathContext::Expr, qself, path, path_shape)?;
 
     let has_base_or_rest = match struct_rest {
-        ast::StructRest::None if fields.is_empty() => return Some(format!("{} {{}}", path_str)),
+        ast::StructRest::None if fields.is_empty() => return Some(format!("{path_str} {{}}")),
         ast::StructRest::Rest(_) if fields.is_empty() => {
-            return Some(format!("{} {{ .. }}", path_str));
+            return Some(format!("{path_str} {{ .. }}"));
         }
         ast::StructRest::Rest(_) | ast::StructRest::Base(_) => true,
         _ => false,
@@ -1686,7 +1683,7 @@ fn rewrite_struct_lit<'a>(
 
     let fields_str =
         wrap_struct_field(context, attrs, &fields_str, shape, v_shape, one_line_width)?;
-    Some(format!("{} {{{}}}", path_str, fields_str))
+    Some(format!("{path_str} {{{fields_str}}}"))
 
     // FIXME if context.config.indent_style() == Visual, but we run out
     // of space, we should fall back to BlockIndent.
@@ -1716,7 +1713,7 @@ pub(crate) fn wrap_struct_field(
             ))
         } else {
             // One liner or visual indent.
-            Some(format!(" {} ", fields_str))
+            Some(format!(" {fields_str} "))
         }
     } else {
         Some(format!(
@@ -1765,7 +1762,7 @@ pub(crate) fn rewrite_field(
             {
                 Some(attrs_str + name)
             }
-            Some(e) => Some(format!("{}{}{}{}", attrs_str, name, separator, e)),
+            Some(e) => Some(format!("{attrs_str}{name}{separator}{e}")),
             None => {
                 let expr_offset = shape.indent.block_indent(context.config);
                 let expr = field
@@ -1830,7 +1827,41 @@ fn rewrite_tuple_in_visual_indent_style<'a, T: 'a + IntoOverflowableItem<'a>>(
         .ends_with_newline(false);
     let list_str = write_list(&item_vec, &fmt)?;
 
-    Some(format!("({})", list_str))
+    Some(format!("({list_str})"))
+}
+
+fn rewrite_let(
+    context: &RewriteContext<'_>,
+    shape: Shape,
+    pat: &ast::Pat,
+    expr: &ast::Expr,
+) -> Option<String> {
+    let mut result = "let ".to_owned();
+
+    // TODO(ytmimi) comments could appear between `let` and the `pat`
+
+    // 4 = "let ".len()
+    let pat_shape = shape.offset_left(4)?;
+    let pat_str = pat.rewrite(context, pat_shape)?;
+    result.push_str(&pat_str);
+
+    // TODO(ytmimi) comments could appear between `pat` and `=`
+    result.push_str(" =");
+
+    let comments_lo = context
+        .snippet_provider
+        .span_after(expr.span.with_lo(pat.span.hi()), "=");
+    let comments_span = mk_sp(comments_lo, expr.span.lo());
+    rewrite_assign_rhs_with_comments(
+        context,
+        result,
+        expr,
+        shape,
+        &RhsAssignKind::Expr(&expr.kind, expr.span),
+        RhsTactics::Default,
+        comments_span,
+        true,
+    )
 }
 
 pub(crate) fn rewrite_tuple<'a, T: 'a + IntoOverflowableItem<'a>>(
@@ -2072,7 +2103,7 @@ fn choose_rhs<R: Rewrite>(
         Some(ref new_str)
             if !new_str.contains('\n') && unicode_str_width(new_str) <= shape.width =>
         {
-            Some(format!(" {}", new_str))
+            Some(format!(" {new_str}"))
         }
         _ => {
             // Expression did not fit on the same line as the identifier.
@@ -2089,21 +2120,21 @@ fn choose_rhs<R: Rewrite>(
                 (Some(ref orig_rhs), Some(ref new_rhs))
                     if !filtered_str_fits(&new_rhs, context.config.max_width(), new_shape) =>
                 {
-                    Some(format!("{}{}", before_space_str, orig_rhs))
+                    Some(format!("{before_space_str}{orig_rhs}"))
                 }
                 (Some(ref orig_rhs), Some(ref new_rhs))
                     if prefer_next_line(orig_rhs, new_rhs, rhs_tactics) =>
                 {
-                    Some(format!("{}{}", new_indent_str, new_rhs))
+                    Some(format!("{new_indent_str}{new_rhs}"))
                 }
-                (None, Some(ref new_rhs)) => Some(format!("{}{}", new_indent_str, new_rhs)),
+                (None, Some(ref new_rhs)) => Some(format!("{new_indent_str}{new_rhs}")),
                 (None, None) if rhs_tactics == RhsTactics::AllowOverflow => {
                     let shape = shape.infinite_width();
                     expr.rewrite(context, shape)
                         .map(|s| format!("{}{}", before_space_str, s))
                 }
                 (None, None) => None,
-                (Some(orig_rhs), _) => Some(format!("{}{}", before_space_str, orig_rhs)),
+                (Some(orig_rhs), _) => Some(format!("{before_space_str}{orig_rhs}")),
             }
         }
     }
diff --git a/src/format-diff/main.rs b/src/format-diff/main.rs
index f6b739e1c2a..61e2cb711a5 100644
--- a/src/format-diff/main.rs
+++ b/src/format-diff/main.rs
@@ -5,11 +5,12 @@
 #![deny(warnings)]
 
 #[macro_use]
-extern crate log;
+extern crate tracing;
 
 use serde::{Deserialize, Serialize};
 use serde_json as json;
 use thiserror::Error;
+use tracing_subscriber::EnvFilter;
 
 use std::collections::HashSet;
 use std::env;
@@ -63,10 +64,12 @@ pub struct Opts {
 }
 
 fn main() {
-    env_logger::Builder::from_env("RUSTFMT_LOG").init();
+    tracing_subscriber::fmt()
+        .with_env_filter(EnvFilter::from_env("RUSTFMT_LOG"))
+        .init();
     let opts = Opts::parse();
     if let Err(e) = run(opts) {
-        println!("{}", e);
+        println!("{e}");
         Opts::command()
             .print_help()
             .expect("cannot write to stdout");
@@ -110,7 +113,7 @@ fn run_rustfmt(files: &HashSet<String>, ranges: &[Range]) -> Result<(), FormatDi
     if !exit_status.success() {
         return Err(FormatDiffError::IoError(io::Error::new(
             io::ErrorKind::Other,
-            format!("rustfmt failed with {}", exit_status),
+            format!("rustfmt failed with {exit_status}"),
         )));
     }
     Ok(())
@@ -126,12 +129,12 @@ fn scan_diff<R>(
 where
     R: io::Read,
 {
-    let diff_pattern = format!(r"^\+\+\+\s(?:.*?/){{{}}}(\S*)", skip_prefix);
+    let diff_pattern = format!(r"^\+\+\+\s(?:.*?/){{{skip_prefix}}}(\S*)");
     let diff_pattern = Regex::new(&diff_pattern).unwrap();
 
     let lines_pattern = Regex::new(r"^@@.*\+(\d+)(,(\d+))?").unwrap();
 
-    let file_filter = Regex::new(&format!("^{}$", file_filter))?;
+    let file_filter = Regex::new(&format!("^{file_filter}$"))?;
 
     let mut current_file = None;
 
diff --git a/src/formatting.rs b/src/formatting.rs
index 1f4ad6960e2..cd57a025b67 100644
--- a/src/formatting.rs
+++ b/src/formatting.rs
@@ -296,7 +296,7 @@ impl<'b, T: Write + 'b> FormatHandler for Session<'b, T> {
                 Ok(ref result) if result.has_diff => report.add_diff(),
                 Err(e) => {
                     // Create a new error with path_str to help users see which files failed
-                    let err_msg = format!("{}: {}", path, e);
+                    let err_msg = format!("{path}: {e}");
                     return Err(io::Error::new(e.kind(), err_msg).into());
                 }
                 _ => {}
diff --git a/src/git-rustfmt/main.rs b/src/git-rustfmt/main.rs
index 579778edbe7..3059d917c6b 100644
--- a/src/git-rustfmt/main.rs
+++ b/src/git-rustfmt/main.rs
@@ -1,5 +1,5 @@
 #[macro_use]
-extern crate log;
+extern crate tracing;
 
 use std::env;
 use std::io::stdout;
@@ -9,6 +9,7 @@ use std::str::FromStr;
 
 use getopts::{Matches, Options};
 use rustfmt_nightly as rustfmt;
+use tracing_subscriber::EnvFilter;
 
 use crate::rustfmt::{load_config, CliOptions, FormatReportFormatterBuilder, Input, Session};
 
@@ -42,7 +43,7 @@ fn git_diff(commits: &str) -> String {
     let mut cmd = Command::new("git");
     cmd.arg("diff");
     if commits != "0" {
-        cmd.arg(format!("HEAD~{}", commits));
+        cmd.arg(format!("HEAD~{commits}"));
     }
     let output = cmd.output().expect("Couldn't execute `git diff`");
     String::from_utf8_lossy(&output.stdout).into_owned()
@@ -107,7 +108,7 @@ fn check_uncommitted() {
     if !uncommitted.is_empty() {
         println!("Found untracked changes:");
         for f in &uncommitted {
-            println!("  {}", f);
+            println!("  {f}");
         }
         println!("Commit your work, or run with `-u`.");
         println!("Exiting.");
@@ -170,7 +171,9 @@ impl Config {
 }
 
 fn main() {
-    env_logger::Builder::from_env("RUSTFMT_LOG").init();
+    tracing_subscriber::fmt()
+        .with_env_filter(EnvFilter::from_env("RUSTFMT_LOG"))
+        .init();
 
     let opts = make_opts();
     let matches = opts
diff --git a/src/imports.rs b/src/imports.rs
index 339e5cef5af..f8e7fa62890 100644
--- a/src/imports.rs
+++ b/src/imports.rs
@@ -191,7 +191,7 @@ impl UseSegment {
             "crate" => UseSegmentKind::Crate(None),
             _ => {
                 let mod_sep = if modsep { "::" } else { "" };
-                UseSegmentKind::Ident(format!("{}{}", mod_sep, name), None)
+                UseSegmentKind::Ident(format!("{mod_sep}{name}"), None)
             }
         };
 
@@ -295,8 +295,8 @@ impl fmt::Display for UseSegmentKind {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match *self {
             UseSegmentKind::Glob => write!(f, "*"),
-            UseSegmentKind::Ident(ref s, Some(ref alias)) => write!(f, "{} as {}", s, alias),
-            UseSegmentKind::Ident(ref s, None) => write!(f, "{}", s),
+            UseSegmentKind::Ident(ref s, Some(ref alias)) => write!(f, "{s} as {alias}"),
+            UseSegmentKind::Ident(ref s, None) => write!(f, "{s}"),
             UseSegmentKind::Slf(..) => write!(f, "self"),
             UseSegmentKind::Super(..) => write!(f, "super"),
             UseSegmentKind::Crate(..) => write!(f, "crate"),
@@ -306,7 +306,7 @@ impl fmt::Display for UseSegmentKind {
                     if i != 0 {
                         write!(f, ", ")?;
                     }
-                    write!(f, "{}", item)?;
+                    write!(f, "{item}")?;
                 }
                 write!(f, "}}")
             }
@@ -319,7 +319,7 @@ impl fmt::Display for UseTree {
             if i != 0 {
                 write!(f, "::")?;
             }
-            write!(f, "{}", segment)?;
+            write!(f, "{segment}")?;
         }
         Ok(())
     }
@@ -589,7 +589,7 @@ impl UseTree {
 
         // Normalise foo::{bar} -> foo::bar
         if let UseSegmentKind::List(ref list) = last.kind {
-            if list.len() == 1 && list[0].to_string() != "self" {
+            if list.len() == 1 && list[0].to_string() != "self" && !list[0].has_comment() {
                 normalize_sole_list = true;
             }
         }
@@ -1032,7 +1032,9 @@ fn rewrite_nested_use_tree(
 
     let list_str = write_list(&list_items, &fmt)?;
 
-    let result = if (list_str.contains('\n') || list_str.len() > remaining_width)
+    let result = if (list_str.contains('\n')
+        || list_str.len() > remaining_width
+        || tactic == DefinitiveListTactic::Vertical)
         && context.config.imports_indent() == IndentStyle::Block
     {
         format!(
@@ -1042,7 +1044,7 @@ fn rewrite_nested_use_tree(
             shape.indent.to_string(context.config)
         )
     } else {
-        format!("{{{}}}", list_str)
+        format!("{{{list_str}}}")
     };
 
     Some(result)
@@ -1052,14 +1054,14 @@ impl Rewrite for UseSegment {
     fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
         Some(match self.kind {
             UseSegmentKind::Ident(ref ident, Some(ref rename)) => {
-                format!("{} as {}", ident, rename)
+                format!("{ident} as {rename}")
             }
             UseSegmentKind::Ident(ref ident, None) => ident.clone(),
-            UseSegmentKind::Slf(Some(ref rename)) => format!("self as {}", rename),
+            UseSegmentKind::Slf(Some(ref rename)) => format!("self as {rename}"),
             UseSegmentKind::Slf(None) => "self".to_owned(),
-            UseSegmentKind::Super(Some(ref rename)) => format!("super as {}", rename),
+            UseSegmentKind::Super(Some(ref rename)) => format!("super as {rename}"),
             UseSegmentKind::Super(None) => "super".to_owned(),
-            UseSegmentKind::Crate(Some(ref rename)) => format!("crate as {}", rename),
+            UseSegmentKind::Crate(Some(ref rename)) => format!("crate as {rename}"),
             UseSegmentKind::Crate(None) => "crate".to_owned(),
             UseSegmentKind::Glob => "*".to_owned(),
             UseSegmentKind::List(ref use_tree_list) => rewrite_nested_use_tree(
diff --git a/src/items.rs b/src/items.rs
index 002cffa9b0c..edb5a5b629a 100644
--- a/src/items.rs
+++ b/src/items.rs
@@ -75,6 +75,7 @@ impl Rewrite for ast::Local {
                 false,
             )?
         };
+        let let_kw_offset = result.len() - "let ".len();
 
         // 4 = "let ".len()
         let pat_shape = shape.offset_left(4)?;
@@ -127,8 +128,15 @@ impl Rewrite for ast::Local {
 
             if let Some(block) = else_block {
                 let else_kw_span = init.span.between(block.span);
+                // Strip attributes and comments to check if newline is needed before the else
+                // keyword from the initializer part. (#5901)
+                let init_str = if context.config.version() == Version::Two {
+                    &result[let_kw_offset..]
+                } else {
+                    result.as_str()
+                };
                 let force_newline_else = pat_str.contains('\n')
-                    || !same_line_else_kw_and_brace(&result, context, else_kw_span, nested_shape);
+                    || !same_line_else_kw_and_brace(init_str, context, else_kw_span, nested_shape);
                 let else_kw = rewrite_else_kw_with_comments(
                     force_newline_else,
                     true,
@@ -146,11 +154,16 @@ impl Rewrite for ast::Local {
                     std::cmp::min(shape.width, context.config.single_line_let_else_max_width());
 
                 // If available_space hits zero we know for sure this will be a multi-lined block
-                let available_space = max_width.saturating_sub(result.len());
+                let assign_str_with_else_kw = if context.config.version() == Version::Two {
+                    &result[let_kw_offset..]
+                } else {
+                    result.as_str()
+                };
+                let available_space = max_width.saturating_sub(assign_str_with_else_kw.len());
 
                 let allow_single_line = !force_newline_else
                     && available_space > 0
-                    && allow_single_line_let_else_block(&result, block);
+                    && allow_single_line_let_else_block(assign_str_with_else_kw, block);
 
                 let mut rw_else_block =
                     rewrite_let_else_block(block, allow_single_line, context, shape)?;
@@ -248,7 +261,6 @@ impl<'a> Item<'a> {
             abi: format_extern(
                 ast::Extern::from_abi(fm.abi, DUMMY_SP),
                 config.force_explicit_abi(),
-                true,
             ),
             vis: None,
             body: fm
@@ -306,22 +318,20 @@ impl<'a> FnSig<'a> {
         defaultness: ast::Defaultness,
     ) -> FnSig<'a> {
         match *fn_kind {
-            visit::FnKind::Fn(fn_ctxt, _, fn_sig, vis, generics, _) => match fn_ctxt {
-                visit::FnCtxt::Assoc(..) => {
-                    let mut fn_sig = FnSig::from_method_sig(fn_sig, generics, vis);
-                    fn_sig.defaultness = defaultness;
-                    fn_sig
-                }
-                _ => FnSig {
-                    decl,
-                    generics,
-                    ext: fn_sig.header.ext,
-                    constness: fn_sig.header.constness,
-                    is_async: Cow::Borrowed(&fn_sig.header.asyncness),
-                    defaultness,
-                    unsafety: fn_sig.header.unsafety,
-                    visibility: vis,
-                },
+            visit::FnKind::Fn(visit::FnCtxt::Assoc(..), _, fn_sig, vis, generics, _) => {
+                let mut fn_sig = FnSig::from_method_sig(fn_sig, generics, vis);
+                fn_sig.defaultness = defaultness;
+                fn_sig
+            }
+            visit::FnKind::Fn(_, _, fn_sig, vis, generics, _) => FnSig {
+                decl,
+                generics,
+                ext: fn_sig.header.ext,
+                constness: fn_sig.header.constness,
+                is_async: Cow::Borrowed(&fn_sig.header.asyncness),
+                defaultness,
+                unsafety: fn_sig.header.unsafety,
+                visibility: vis,
             },
             _ => unreachable!(),
         }
@@ -338,7 +348,6 @@ impl<'a> FnSig<'a> {
         result.push_str(&format_extern(
             self.ext,
             context.config.force_explicit_abi(),
-            false,
         ));
         result
     }
@@ -472,7 +481,7 @@ impl<'a> FmtVisitor<'a> {
             && self.block_indent.width() + fn_str.len() + 3 <= self.config.max_width()
             && !last_line_contains_single_line_comment(fn_str)
         {
-            return Some(format!("{} {{}}", fn_str));
+            return Some(format!("{fn_str} {{}}"));
         }
 
         if !self.config.fn_single_line() || !is_simple_block_stmt(&context, block, None) {
@@ -484,7 +493,7 @@ impl<'a> FmtVisitor<'a> {
 
         let width = self.block_indent.width() + fn_str.len() + res.len() + 5;
         if !res.contains('\n') && width <= self.config.max_width() {
-            Some(format!("{} {{ {} }}", fn_str, res))
+            Some(format!("{fn_str} {{ {res} }}"))
         } else {
             None
         }
@@ -666,7 +675,7 @@ impl<'a> FmtVisitor<'a> {
         };
 
         let variant_body = if let Some(ref expr) = field.disr_expr {
-            let lhs = format!("{:1$} =", variant_body, pad_discrim_ident_to);
+            let lhs = format!("{variant_body:pad_discrim_ident_to$} =");
             let ex = &*expr.value;
             rewrite_assign_rhs_with(
                 &context,
@@ -829,7 +838,7 @@ pub(crate) fn format_impl(
             if generics.where_clause.predicates.len() == 1 {
                 result.push(',');
             }
-            result.push_str(&format!("{}{{{}}}", sep, sep));
+            result.push_str(&format!("{sep}{{{sep}}}"));
         } else {
             result.push_str(" {}");
         }
@@ -1020,7 +1029,7 @@ fn rewrite_trait_ref(
     let shape = Shape::indented(offset + used_space, context.config);
     if let Some(trait_ref_str) = trait_ref.rewrite(context, shape) {
         if !trait_ref_str.contains('\n') {
-            return Some(format!(" {}{}", polarity_str, trait_ref_str));
+            return Some(format!(" {polarity_str}{trait_ref_str}"));
         }
     }
     // We could not make enough space for trait_ref, so put it on new line.
@@ -1118,172 +1127,172 @@ pub(crate) fn format_trait(
     item: &ast::Item,
     offset: Indent,
 ) -> Option<String> {
-    if let ast::ItemKind::Trait(trait_kind) = &item.kind {
-        let ast::Trait {
-            is_auto,
-            unsafety,
-            ref generics,
-            ref bounds,
-            ref items,
-        } = **trait_kind;
-        let mut result = String::with_capacity(128);
-        let header = format!(
-            "{}{}{}trait ",
-            format_visibility(context, &item.vis),
-            format_unsafety(unsafety),
-            format_auto(is_auto),
-        );
-        result.push_str(&header);
+    let ast::ItemKind::Trait(trait_kind) = &item.kind else {
+        unreachable!();
+    };
+    let ast::Trait {
+        is_auto,
+        unsafety,
+        ref generics,
+        ref bounds,
+        ref items,
+    } = **trait_kind;
 
-        let body_lo = context.snippet_provider.span_after(item.span, "{");
+    let mut result = String::with_capacity(128);
+    let header = format!(
+        "{}{}{}trait ",
+        format_visibility(context, &item.vis),
+        format_unsafety(unsafety),
+        format_auto(is_auto),
+    );
+    result.push_str(&header);
 
-        let shape = Shape::indented(offset, context.config).offset_left(result.len())?;
-        let generics_str =
-            rewrite_generics(context, rewrite_ident(context, item.ident), generics, shape)?;
-        result.push_str(&generics_str);
+    let body_lo = context.snippet_provider.span_after(item.span, "{");
 
-        // FIXME(#2055): rustfmt fails to format when there are comments between trait bounds.
-        if !bounds.is_empty() {
-            let ident_hi = context
-                .snippet_provider
-                .span_after(item.span, item.ident.as_str());
-            let bound_hi = bounds.last().unwrap().span().hi();
-            let snippet = context.snippet(mk_sp(ident_hi, bound_hi));
-            if contains_comment(snippet) {
-                return None;
-            }
+    let shape = Shape::indented(offset, context.config).offset_left(result.len())?;
+    let generics_str =
+        rewrite_generics(context, rewrite_ident(context, item.ident), generics, shape)?;
+    result.push_str(&generics_str);
 
-            result = rewrite_assign_rhs_with(
-                context,
-                result + ":",
-                bounds,
-                shape,
-                &RhsAssignKind::Bounds,
-                RhsTactics::ForceNextLineWithoutIndent,
-            )?;
+    // FIXME(#2055): rustfmt fails to format when there are comments between trait bounds.
+    if !bounds.is_empty() {
+        let ident_hi = context
+            .snippet_provider
+            .span_after(item.span, item.ident.as_str());
+        let bound_hi = bounds.last().unwrap().span().hi();
+        let snippet = context.snippet(mk_sp(ident_hi, bound_hi));
+        if contains_comment(snippet) {
+            return None;
         }
 
-        // Rewrite where-clause.
-        if !generics.where_clause.predicates.is_empty() {
-            let where_on_new_line = context.config.indent_style() != IndentStyle::Block;
+        result = rewrite_assign_rhs_with(
+            context,
+            result + ":",
+            bounds,
+            shape,
+            &RhsAssignKind::Bounds,
+            RhsTactics::ForceNextLineWithoutIndent,
+        )?;
+    }
+
+    // Rewrite where-clause.
+    if !generics.where_clause.predicates.is_empty() {
+        let where_on_new_line = context.config.indent_style() != IndentStyle::Block;
 
-            let where_budget = context.budget(last_line_width(&result));
-            let pos_before_where = if bounds.is_empty() {
-                generics.where_clause.span.lo()
+        let where_budget = context.budget(last_line_width(&result));
+        let pos_before_where = if bounds.is_empty() {
+            generics.where_clause.span.lo()
+        } else {
+            bounds[bounds.len() - 1].span().hi()
+        };
+        let option = WhereClauseOption::snuggled(&generics_str);
+        let where_clause_str = rewrite_where_clause(
+            context,
+            &generics.where_clause.predicates,
+            generics.where_clause.span,
+            context.config.brace_style(),
+            Shape::legacy(where_budget, offset.block_only()),
+            where_on_new_line,
+            "{",
+            None,
+            pos_before_where,
+            option,
+        )?;
+        // If the where-clause cannot fit on the same line,
+        // put the where-clause on a new line
+        if !where_clause_str.contains('\n')
+            && last_line_width(&result) + where_clause_str.len() + offset.width()
+                > context.config.comment_width()
+        {
+            let width = offset.block_indent + context.config.tab_spaces() - 1;
+            let where_indent = Indent::new(0, width);
+            result.push_str(&where_indent.to_string_with_newline(context.config));
+        }
+        result.push_str(&where_clause_str);
+    } else {
+        let item_snippet = context.snippet(item.span);
+        if let Some(lo) = item_snippet.find('/') {
+            // 1 = `{`
+            let comment_hi = if generics.params.len() > 0 {
+                generics.span.lo() - BytePos(1)
             } else {
-                bounds[bounds.len() - 1].span().hi()
+                body_lo - BytePos(1)
             };
-            let option = WhereClauseOption::snuggled(&generics_str);
-            let where_clause_str = rewrite_where_clause(
-                context,
-                &generics.where_clause.predicates,
-                generics.where_clause.span,
-                context.config.brace_style(),
-                Shape::legacy(where_budget, offset.block_only()),
-                where_on_new_line,
-                "{",
-                None,
-                pos_before_where,
-                option,
-            )?;
-            // If the where-clause cannot fit on the same line,
-            // put the where-clause on a new line
-            if !where_clause_str.contains('\n')
-                && last_line_width(&result) + where_clause_str.len() + offset.width()
-                    > context.config.comment_width()
-            {
-                let width = offset.block_indent + context.config.tab_spaces() - 1;
-                let where_indent = Indent::new(0, width);
-                result.push_str(&where_indent.to_string_with_newline(context.config));
-            }
-            result.push_str(&where_clause_str);
-        } else {
-            let item_snippet = context.snippet(item.span);
-            if let Some(lo) = item_snippet.find('/') {
-                // 1 = `{`
-                let comment_hi = if generics.params.len() > 0 {
-                    generics.span.lo() - BytePos(1)
-                } else {
-                    body_lo - BytePos(1)
-                };
-                let comment_lo = item.span.lo() + BytePos(lo as u32);
-                if comment_lo < comment_hi {
-                    match recover_missing_comment_in_span(
-                        mk_sp(comment_lo, comment_hi),
-                        Shape::indented(offset, context.config),
-                        context,
-                        last_line_width(&result),
-                    ) {
-                        Some(ref missing_comment) if !missing_comment.is_empty() => {
-                            result.push_str(missing_comment);
-                        }
-                        _ => (),
+            let comment_lo = item.span.lo() + BytePos(lo as u32);
+            if comment_lo < comment_hi {
+                match recover_missing_comment_in_span(
+                    mk_sp(comment_lo, comment_hi),
+                    Shape::indented(offset, context.config),
+                    context,
+                    last_line_width(&result),
+                ) {
+                    Some(ref missing_comment) if !missing_comment.is_empty() => {
+                        result.push_str(missing_comment);
                     }
+                    _ => (),
                 }
             }
         }
+    }
 
-        let block_span = mk_sp(generics.where_clause.span.hi(), item.span.hi());
-        let snippet = context.snippet(block_span);
-        let open_pos = snippet.find_uncommented("{")? + 1;
+    let block_span = mk_sp(generics.where_clause.span.hi(), item.span.hi());
+    let snippet = context.snippet(block_span);
+    let open_pos = snippet.find_uncommented("{")? + 1;
 
-        match context.config.brace_style() {
-            _ if last_line_contains_single_line_comment(&result)
-                || last_line_width(&result) + 2 > context.budget(offset.width()) =>
-            {
-                result.push_str(&offset.to_string_with_newline(context.config));
-            }
-            _ if context.config.empty_item_single_line()
-                && items.is_empty()
-                && !result.contains('\n')
-                && !contains_comment(&snippet[open_pos..]) =>
+    match context.config.brace_style() {
+        _ if last_line_contains_single_line_comment(&result)
+            || last_line_width(&result) + 2 > context.budget(offset.width()) =>
+        {
+            result.push_str(&offset.to_string_with_newline(context.config));
+        }
+        _ if context.config.empty_item_single_line()
+            && items.is_empty()
+            && !result.contains('\n')
+            && !contains_comment(&snippet[open_pos..]) =>
+        {
+            result.push_str(" {}");
+            return Some(result);
+        }
+        BraceStyle::AlwaysNextLine => {
+            result.push_str(&offset.to_string_with_newline(context.config));
+        }
+        BraceStyle::PreferSameLine => result.push(' '),
+        BraceStyle::SameLineWhere => {
+            if result.contains('\n')
+                || (!generics.where_clause.predicates.is_empty() && !items.is_empty())
             {
-                result.push_str(" {}");
-                return Some(result);
-            }
-            BraceStyle::AlwaysNextLine => {
                 result.push_str(&offset.to_string_with_newline(context.config));
-            }
-            BraceStyle::PreferSameLine => result.push(' '),
-            BraceStyle::SameLineWhere => {
-                if result.contains('\n')
-                    || (!generics.where_clause.predicates.is_empty() && !items.is_empty())
-                {
-                    result.push_str(&offset.to_string_with_newline(context.config));
-                } else {
-                    result.push(' ');
-                }
+            } else {
+                result.push(' ');
             }
         }
-        result.push('{');
-
-        let outer_indent_str = offset.block_only().to_string_with_newline(context.config);
+    }
+    result.push('{');
 
-        if !items.is_empty() || contains_comment(&snippet[open_pos..]) {
-            let mut visitor = FmtVisitor::from_context(context);
-            visitor.block_indent = offset.block_only().block_indent(context.config);
-            visitor.last_pos = block_span.lo() + BytePos(open_pos as u32);
+    let outer_indent_str = offset.block_only().to_string_with_newline(context.config);
 
-            for item in items {
-                visitor.visit_trait_item(item);
-            }
+    if !items.is_empty() || contains_comment(&snippet[open_pos..]) {
+        let mut visitor = FmtVisitor::from_context(context);
+        visitor.block_indent = offset.block_only().block_indent(context.config);
+        visitor.last_pos = block_span.lo() + BytePos(open_pos as u32);
 
-            visitor.format_missing(item.span.hi() - BytePos(1));
+        for item in items {
+            visitor.visit_trait_item(item);
+        }
 
-            let inner_indent_str = visitor.block_indent.to_string_with_newline(context.config);
+        visitor.format_missing(item.span.hi() - BytePos(1));
 
-            result.push_str(&inner_indent_str);
-            result.push_str(visitor.buffer.trim());
-            result.push_str(&outer_indent_str);
-        } else if result.contains('\n') {
-            result.push_str(&outer_indent_str);
-        }
+        let inner_indent_str = visitor.block_indent.to_string_with_newline(context.config);
 
-        result.push('}');
-        Some(result)
-    } else {
-        unreachable!();
+        result.push_str(&inner_indent_str);
+        result.push_str(visitor.buffer.trim());
+        result.push_str(&outer_indent_str);
+    } else if result.contains('\n') {
+        result.push_str(&outer_indent_str);
     }
+
+    result.push('}');
+    Some(result)
 }
 
 pub(crate) struct TraitAliasBounds<'a> {
@@ -1322,7 +1331,7 @@ impl<'a> Rewrite for TraitAliasBounds<'a> {
             shape.indent.to_string_with_newline(context.config)
         };
 
-        Some(format!("{}{}{}", generic_bounds_str, space, where_str))
+        Some(format!("{generic_bounds_str}{space}{where_str}"))
     }
 }
 
@@ -1339,7 +1348,7 @@ pub(crate) fn format_trait_alias(
     let g_shape = shape.offset_left(6)?.sub_width(2)?;
     let generics_str = rewrite_generics(context, alias, generics, g_shape)?;
     let vis_str = format_visibility(context, vis);
-    let lhs = format!("{}trait {} =", vis_str, generics_str);
+    let lhs = format!("{vis_str}trait {generics_str} =");
     // 1 = ";"
     let trait_alias_bounds = TraitAliasBounds {
         generic_bounds,
@@ -1376,7 +1385,7 @@ fn format_unit_struct(
     } else {
         String::new()
     };
-    Some(format!("{}{};", header_str, generics_str))
+    Some(format!("{header_str}{generics_str};"))
 }
 
 pub(crate) fn format_struct_struct(
@@ -1466,7 +1475,7 @@ pub(crate) fn format_struct_struct(
         && items_str.len() <= one_line_budget
         && !last_line_contains_single_line_comment(&items_str)
     {
-        Some(format!("{} {} }}", result, items_str))
+        Some(format!("{result} {items_str} }}"))
     } else {
         Some(format!(
             "{}\n{}{}\n{}}}",
@@ -1696,7 +1705,7 @@ pub(crate) fn rewrite_type_alias<'a, 'b>(
                 rewrite_ty(rw_info, Some(bounds), ty_opt, vis)
             }?;
             match defaultness {
-                ast::Defaultness::Default(..) => Some(format!("default {}", result)),
+                ast::Defaultness::Default(..) => Some(format!("default {result}")),
                 _ => Some(result),
             }
         }
@@ -1803,14 +1812,14 @@ fn rewrite_ty<R: Rewrite>(
                     true,
                 )?
             }
-            _ => format!("{}=", result),
+            _ => format!("{result}="),
         };
 
         // 1 = `;`
         let shape = Shape::indented(indent, context.config).sub_width(1)?;
         rewrite_assign_rhs(context, lhs, &*ty, &RhsAssignKind::Ty, shape).map(|s| s + ";")
     } else {
-        Some(format!("{};", result))
+        Some(format!("{result};"))
     }
 }
 
@@ -2019,7 +2028,7 @@ fn rewrite_static(
         let expr_lo = expr.span.lo();
         let comments_span = mk_sp(comments_lo, expr_lo);
 
-        let lhs = format!("{}{} =", prefix, ty_str);
+        let lhs = format!("{prefix}{ty_str} =");
 
         // 1 = ;
         let remaining_width = context.budget(offset.block_indent + 1);
@@ -2036,7 +2045,7 @@ fn rewrite_static(
         .and_then(|res| recover_comment_removed(res, static_parts.span, context))
         .map(|s| if s.ends_with(';') { s } else { s + ";" })
     } else {
-        Some(format!("{}{};", prefix, ty_str))
+        Some(format!("{prefix}{ty_str};"))
     }
 }
 
@@ -2229,7 +2238,7 @@ fn rewrite_explicit_self(
                     Some(combine_strs_with_missing_comments(
                         context,
                         param_attrs,
-                        &format!("&{} {}self", lifetime_str, mut_str),
+                        &format!("&{lifetime_str} {mut_str}self"),
                         span,
                         shape,
                         !has_multiple_attr_lines,
@@ -2238,7 +2247,7 @@ fn rewrite_explicit_self(
                 None => Some(combine_strs_with_missing_comments(
                     context,
                     param_attrs,
-                    &format!("&{}self", mut_str),
+                    &format!("&{mut_str}self"),
                     span,
                     shape,
                     !has_multiple_attr_lines,
@@ -2909,7 +2918,7 @@ fn rewrite_where_clause_rfc_style(
             clause_shape.indent.to_string_with_newline(context.config)
         };
 
-    Some(format!("{}{}{}", where_keyword, clause_sep, preds_str))
+    Some(format!("{where_keyword}{clause_sep}{preds_str}"))
 }
 
 /// Rewrite `where` and comment around it.
@@ -2949,8 +2958,8 @@ fn rewrite_where_keyword(
     let newline_before_where = comment_separator(&comment_before, shape);
     let newline_after_where = comment_separator(&comment_after, clause_shape);
     let result = format!(
-        "{}{}{}where{}{}",
-        starting_newline, comment_before, newline_before_where, newline_after_where, comment_after
+        "{starting_newline}{comment_before}{newline_before_where}where\
+{newline_after_where}{comment_after}"
     );
     let allow_single_line = where_clause_option.allow_single_line
         && comment_before.is_empty()
@@ -3001,10 +3010,12 @@ fn rewrite_bounds_on_where_clause(
         DefinitiveListTactic::Vertical
     };
 
+    let preserve_newline = context.config.version() == Version::One;
+
     let fmt = ListFormatting::new(shape, context.config)
         .tactic(shape_tactic)
         .trailing_separator(comma_tactic)
-        .preserve_newline(true);
+        .preserve_newline(preserve_newline);
     write_list(&items.collect::<Vec<_>>(), &fmt)
 }
 
@@ -3105,7 +3116,7 @@ fn rewrite_where_clause(
             preds_str
         ))
     } else {
-        Some(format!(" where {}", preds_str))
+        Some(format!(" where {preds_str}"))
     }
 }
 
@@ -3234,7 +3245,7 @@ fn format_generics(
                     if brace_pos == BracePos::None {
                         span.hi()
                     } else {
-                        context.snippet_provider.span_before(span, "{")
+                        context.snippet_provider.span_before_last(span, "{")
                     },
                 ),
                 shape,
diff --git a/src/lib.rs b/src/lib.rs
index 567628f63bd..8800e200fa4 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -3,13 +3,12 @@
 #![warn(unreachable_pub)]
 #![recursion_limit = "256"]
 #![allow(clippy::match_like_matches_macro)]
-#![allow(unreachable_pub)]
 
 #[cfg(test)]
 #[macro_use]
 extern crate lazy_static;
 #[macro_use]
-extern crate log;
+extern crate tracing;
 
 // N.B. these crates are loaded from the sysroot, so they need extern crate.
 extern crate rustc_ast;
@@ -29,6 +28,7 @@ extern crate thin_vec;
 extern crate rustc_driver;
 
 use std::cell::RefCell;
+use std::cmp::min;
 use std::collections::HashMap;
 use std::fmt;
 use std::io::{self, Write};
@@ -386,9 +386,15 @@ fn format_code_block(
         .snippet
         .rfind('}')
         .unwrap_or_else(|| formatted.snippet.len());
+
+    // It's possible that `block_len < FN_MAIN_PREFIX.len()`. This can happen if the code block was
+    // formatted into the empty string, leading to the enclosing `fn main() {\n}` being formatted
+    // into `fn main() {}`. In this case no unindentation is done.
+    let block_start = min(FN_MAIN_PREFIX.len(), block_len);
+
     let mut is_indented = true;
     let indent_str = Indent::from_width(config, config.tab_spaces()).to_string(config);
-    for (kind, ref line) in LineClasses::new(&formatted.snippet[FN_MAIN_PREFIX.len()..block_len]) {
+    for (kind, ref line) in LineClasses::new(&formatted.snippet[block_start..block_len]) {
         if !is_first {
             result.push('\n');
         } else {
diff --git a/src/lists.rs b/src/lists.rs
index a878e6cf9b2..41afef279e9 100644
--- a/src/lists.rs
+++ b/src/lists.rs
@@ -637,7 +637,7 @@ pub(crate) fn extract_post_comment(
         post_snippet.trim_matches(white_space)
     }
     // not comment or over two lines
-    else if post_snippet.ends_with(',')
+    else if post_snippet.ends_with(separator)
         && (!post_snippet.trim().starts_with("//") || post_snippet.trim().contains('\n'))
     {
         post_snippet[..(post_snippet.len() - 1)].trim_matches(white_space)
diff --git a/src/macros.rs b/src/macros.rs
index 4f45d0c7402..76553466e48 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -103,7 +103,7 @@ fn rewrite_macro_name(
         format!("{}!", pprust::path_to_string(path))
     };
     match extra_ident {
-        Some(ident) if ident.name != kw::Empty => format!("{} {}", name, ident),
+        Some(ident) if ident.name != kw::Empty => format!("{name} {ident}"),
         _ => name,
     }
 }
@@ -214,14 +214,14 @@ fn rewrite_macro_inner(
     if ts.is_empty() && !has_comment {
         return match style {
             Delimiter::Parenthesis if position == MacroPosition::Item => {
-                Some(format!("{}();", macro_name))
+                Some(format!("{macro_name}();"))
             }
             Delimiter::Bracket if position == MacroPosition::Item => {
-                Some(format!("{}[];", macro_name))
+                Some(format!("{macro_name}[];"))
             }
-            Delimiter::Parenthesis => Some(format!("{}()", macro_name)),
-            Delimiter::Bracket => Some(format!("{}[]", macro_name)),
-            Delimiter::Brace => Some(format!("{} {{}}", macro_name)),
+            Delimiter::Parenthesis => Some(format!("{macro_name}()")),
+            Delimiter::Bracket => Some(format!("{macro_name}[]")),
+            Delimiter::Brace => Some(format!("{macro_name} {{}}")),
             _ => unreachable!(),
         };
     }
@@ -255,6 +255,7 @@ fn rewrite_macro_inner(
             &macro_name,
             shape,
             style,
+            original_style,
             position,
             mac.span(),
         );
@@ -295,20 +296,19 @@ fn rewrite_macro_inner(
                 // If we are rewriting `vec!` macro or other special macros,
                 // then we can rewrite this as a usual array literal.
                 // Otherwise, we must preserve the original existence of trailing comma.
-                let macro_name = &macro_name.as_str();
                 let mut force_trailing_comma = if trailing_comma {
                     Some(SeparatorTactic::Always)
                 } else {
                     Some(SeparatorTactic::Never)
                 };
-                if FORCED_BRACKET_MACROS.contains(macro_name) && !is_nested_macro {
+                if is_forced_bracket && !is_nested_macro {
                     context.leave_macro();
                     if context.use_block_indent() {
                         force_trailing_comma = Some(SeparatorTactic::Vertical);
                     };
                 }
                 let rewrite = rewrite_array(
-                    macro_name,
+                    &macro_name,
                     arg_vec.iter(),
                     mac.span(),
                     context,
@@ -321,7 +321,7 @@ fn rewrite_macro_inner(
                     _ => "",
                 };
 
-                Some(format!("{}{}", rewrite, comma))
+                Some(format!("{rewrite}{comma}"))
             }
         }
         Delimiter::Brace => {
@@ -330,8 +330,8 @@ fn rewrite_macro_inner(
             // anything in between the braces (for now).
             let snippet = context.snippet(mac.span()).trim_start_matches(|c| c != '{');
             match trim_left_preserve_layout(snippet, shape.indent, context.config) {
-                Some(macro_body) => Some(format!("{} {}", macro_name, macro_body)),
-                None => Some(format!("{} {}", macro_name, snippet)),
+                Some(macro_body) => Some(format!("{macro_name} {macro_body}")),
+                None => Some(format!("{macro_name} {snippet}")),
             }
         }
         _ => unreachable!(),
@@ -362,7 +362,7 @@ fn handle_vec_semi(
         && lhs.len() + rhs.len() + total_overhead <= shape.width
     {
         // macro_name(lhs; rhs) or macro_name[lhs; rhs]
-        Some(format!("{}{}{}; {}{}", macro_name, left, lhs, rhs, right))
+        Some(format!("{macro_name}{left}{lhs}; {rhs}{right}"))
     } else {
         // macro_name(\nlhs;\nrhs\n) or macro_name[\nlhs;\nrhs\n]
         Some(format!(
@@ -379,6 +379,23 @@ fn handle_vec_semi(
     }
 }
 
+fn rewrite_empty_macro_def_body(
+    context: &RewriteContext<'_>,
+    span: Span,
+    shape: Shape,
+) -> Option<String> {
+    // Create an empty, dummy `ast::Block` representing an empty macro body
+    let block = ast::Block {
+        stmts: vec![].into(),
+        id: rustc_ast::node_id::DUMMY_NODE_ID,
+        rules: ast::BlockCheckMode::Default,
+        span: span,
+        tokens: None,
+        could_be_bare_literal: false,
+    };
+    block.rewrite(context, shape)
+}
+
 pub(crate) fn rewrite_macro_def(
     context: &RewriteContext<'_>,
     shape: Shape,
@@ -419,6 +436,13 @@ pub(crate) fn rewrite_macro_def(
         shape
     };
 
+    if parsed_def.branches.len() == 0 {
+        let lo = context.snippet_provider.span_before(span, "{");
+        result += " ";
+        result += &rewrite_empty_macro_def_body(context, span.with_lo(lo), shape)?;
+        return Some(result);
+    }
+
     let branch_items = itemize_list(
         context.snippet_provider,
         parsed_def.branches.iter(),
@@ -572,8 +596,8 @@ fn delim_token_to_str(
             .block_indent(context.config)
             .to_string_with_newline(context.config);
         (
-            format!("{}{}", lhs, nested_indent_str),
-            format!("{}{}", indent_str, rhs),
+            format!("{lhs}{nested_indent_str}"),
+            format!("{indent_str}{rhs}"),
         )
     } else {
         (lhs.to_owned(), rhs.to_owned())
@@ -630,7 +654,7 @@ impl MacroArgKind {
         };
 
         match *self {
-            MacroArgKind::MetaVariable(ty, ref name) => Some(format!("${}:{}", name, ty)),
+            MacroArgKind::MetaVariable(ty, ref name) => Some(format!("${name}:{ty}")),
             MacroArgKind::Repeat(delim_tok, ref args, ref another, ref tok) => {
                 let (lhs, inner, rhs) = rewrite_delimited_inner(delim_tok, args)?;
                 let another = another
@@ -639,14 +663,14 @@ impl MacroArgKind {
                     .unwrap_or_else(|| "".to_owned());
                 let repeat_tok = pprust::token_to_string(tok);
 
-                Some(format!("${}{}{}{}{}", lhs, inner, rhs, another, repeat_tok))
+                Some(format!("${lhs}{inner}{rhs}{another}{repeat_tok}"))
             }
             MacroArgKind::Delimited(delim_tok, ref args) => {
                 rewrite_delimited_inner(delim_tok, args)
                     .map(|(lhs, inner, rhs)| format!("{}{}{}", lhs, inner, rhs))
             }
-            MacroArgKind::Separator(ref sep, ref prefix) => Some(format!("{}{} ", prefix, sep)),
-            MacroArgKind::Other(ref inner, ref prefix) => Some(format!("{}{}", prefix, inner)),
+            MacroArgKind::Separator(ref sep, ref prefix) => Some(format!("{prefix}{sep} ")),
+            MacroArgKind::Other(ref inner, ref prefix) => Some(format!("{prefix}{inner}")),
         }
     }
 }
@@ -1378,15 +1402,19 @@ fn rewrite_macro_with_items(
     macro_name: &str,
     shape: Shape,
     style: Delimiter,
+    original_style: Delimiter,
     position: MacroPosition,
     span: Span,
 ) -> Option<String> {
-    let (opener, closer) = match style {
-        Delimiter::Parenthesis => ("(", ")"),
-        Delimiter::Bracket => ("[", "]"),
-        Delimiter::Brace => (" {", "}"),
-        _ => return None,
+    let style_to_delims = |style| match style {
+        Delimiter::Parenthesis => Some(("(", ")")),
+        Delimiter::Bracket => Some(("[", "]")),
+        Delimiter::Brace => Some((" {", "}")),
+        _ => None,
     };
+
+    let (opener, closer) = style_to_delims(style)?;
+    let (original_opener, _) = style_to_delims(original_style)?;
     let trailing_semicolon = match style {
         Delimiter::Parenthesis | Delimiter::Bracket if position == MacroPosition::Item => ";",
         _ => "",
@@ -1394,7 +1422,13 @@ fn rewrite_macro_with_items(
 
     let mut visitor = FmtVisitor::from_context(context);
     visitor.block_indent = shape.indent.block_indent(context.config);
-    visitor.last_pos = context.snippet_provider.span_after(span, opener.trim());
+
+    // The current opener may be different from the original opener. This can happen
+    // if our macro is a forced bracket macro originally written with non-bracket
+    // delimiters. We need to use the original opener to locate the span after it.
+    visitor.last_pos = context
+        .snippet_provider
+        .span_after(span, original_opener.trim());
     for item in items {
         let item = match item {
             MacroArg::Item(item) => item,
diff --git a/src/matches.rs b/src/matches.rs
index 4c37116f120..95b0ed16db8 100644
--- a/src/matches.rs
+++ b/src/matches.rs
@@ -124,7 +124,7 @@ pub(crate) fn rewrite_match(
     if arms.is_empty() {
         let snippet = context.snippet(mk_sp(open_brace_pos, span.hi() - BytePos(1)));
         if snippet.trim().is_empty() {
-            Some(format!("match {} {{}}", cond_str))
+            Some(format!("match {cond_str} {{}}"))
         } else {
             // Empty match with comments or inner attributes? We are not going to bother, sorry ;)
             Some(context.snippet(span).to_owned())
@@ -246,8 +246,18 @@ fn rewrite_match_arm(
     };
 
     // Patterns
-    // 5 = ` => {`
-    let pat_shape = shape.sub_width(5)?.offset_left(pipe_offset)?;
+    let pat_shape = match &arm.body.kind {
+        ast::ExprKind::Block(_, Some(label)) => {
+            // Some block with a label ` => 'label: {`
+            // 7 = ` => : {`
+            let label_len = label.ident.as_str().len();
+            shape.sub_width(7 + label_len)?.offset_left(pipe_offset)?
+        }
+        _ => {
+            // 5 = ` => {`
+            shape.sub_width(5)?.offset_left(pipe_offset)?
+        }
+    };
     let pats_str = arm.pat.rewrite(context, pat_shape)?;
 
     // Guard
@@ -264,7 +274,7 @@ fn rewrite_match_arm(
     let lhs_str = combine_strs_with_missing_comments(
         context,
         &attrs_str,
-        &format!("{}{}{}", pipe_str, pats_str, guard_str),
+        &format!("{pipe_str}{pats_str}{guard_str}"),
         missing_span,
         shape,
         false,
@@ -296,8 +306,9 @@ fn block_can_be_flattened<'a>(
     expr: &'a ast::Expr,
 ) -> Option<&'a ast::Block> {
     match expr.kind {
-        ast::ExprKind::Block(ref block, _)
-            if !is_unsafe_block(block)
+        ast::ExprKind::Block(ref block, label)
+            if label.is_none()
+                && !is_unsafe_block(block)
                 && !context.inside_macro()
                 && is_simple_block(context, block, Some(&expr.attrs))
                 && !stmt_is_expr_mac(&block.stmts[0]) =>
@@ -532,7 +543,7 @@ fn rewrite_guard(
             if let Some(cond_shape) = cond_shape {
                 if let Some(cond_str) = guard.rewrite(context, cond_shape) {
                     if !cond_str.contains('\n') || pattern_width <= context.config.tab_spaces() {
-                        return Some(format!(" if {}", cond_str));
+                        return Some(format!(" if {cond_str}"));
                     }
                 }
             }
diff --git a/src/pairs.rs b/src/pairs.rs
index d135da7e359..9dac20d3699 100644
--- a/src/pairs.rs
+++ b/src/pairs.rs
@@ -42,9 +42,13 @@ pub(crate) fn rewrite_all_pairs(
     context: &RewriteContext<'_>,
 ) -> Option<String> {
     expr.flatten(context, shape).and_then(|list| {
-        // First we try formatting on one line.
-        rewrite_pairs_one_line(&list, shape, context)
-            .or_else(|| rewrite_pairs_multiline(&list, shape, context))
+        if list.let_chain_count() > 0 && !list.can_rewrite_let_chain_single_line() {
+            rewrite_pairs_multiline(&list, shape, context)
+        } else {
+            // First we try formatting on one line.
+            rewrite_pairs_one_line(&list, shape, context)
+                .or_else(|| rewrite_pairs_multiline(&list, shape, context))
+        }
     })
 }
 
@@ -234,8 +238,8 @@ where
     let rhs_result = rhs.rewrite(context, rhs_shape)?;
     let indent_str = rhs_shape.indent.to_string_with_newline(context.config);
     let infix_with_sep = match separator_place {
-        SeparatorPlace::Back => format!("{}{}", infix, indent_str),
-        SeparatorPlace::Front => format!("{}{}", indent_str, infix),
+        SeparatorPlace::Back => format!("{infix}{indent_str}"),
+        SeparatorPlace::Front => format!("{indent_str}{infix}"),
     };
     Some(format!(
         "{}{}{}{}",
@@ -255,6 +259,37 @@ struct PairList<'a, 'b, T: Rewrite> {
     separators: Vec<&'a str>,
 }
 
+fn is_ident(expr: &ast::Expr) -> bool {
+    match &expr.kind {
+        ast::ExprKind::Path(None, path) if path.segments.len() == 1 => true,
+        ast::ExprKind::Unary(_, expr)
+        | ast::ExprKind::AddrOf(_, _, expr)
+        | ast::ExprKind::Paren(expr)
+        | ast::ExprKind::Try(expr) => is_ident(expr),
+        _ => false,
+    }
+}
+
+impl<'a, 'b> PairList<'a, 'b, ast::Expr> {
+    fn let_chain_count(&self) -> usize {
+        self.list
+            .iter()
+            .filter(|(expr, _)| matches!(expr.kind, ast::ExprKind::Let(_, _, _)))
+            .count()
+    }
+
+    fn can_rewrite_let_chain_single_line(&self) -> bool {
+        if self.list.len() != 2 {
+            return false;
+        }
+
+        let fist_item_is_ident = is_ident(self.list[0].0);
+        let second_item_is_let_chain = matches!(self.list[1].0.kind, ast::ExprKind::Let(_, _, _));
+
+        fist_item_is_ident && second_item_is_let_chain
+    }
+}
+
 impl FlattenPair for ast::Expr {
     fn flatten(
         &self,
diff --git a/src/parse/session.rs b/src/parse/session.rs
index 3f94bb29933..0573df9de2f 100644
--- a/src/parse/session.rs
+++ b/src/parse/session.rs
@@ -316,8 +316,7 @@ impl LineRangeUtils for ParseSess {
 
         debug_assert_eq!(
             lo.sf.name, hi.sf.name,
-            "span crossed file boundary: lo: {:?}, hi: {:?}",
-            lo, hi
+            "span crossed file boundary: lo: {lo:?}, hi: {hi:?}"
         );
 
         // in case the span starts with a newline, the line range is off by 1 without the
diff --git a/src/patterns.rs b/src/patterns.rs
index 3f335172590..33f3b4b8a21 100644
--- a/src/patterns.rs
+++ b/src/patterns.rs
@@ -208,7 +208,7 @@ impl Rewrite for Pat {
                         None => "",
                         Some(_) => " ",
                     };
-                    format!("{}{}{}", lhs_spacing, infix, rhs_spacing)
+                    format!("{lhs_spacing}{infix}{rhs_spacing}")
                 } else {
                     infix.to_owned()
                 };
@@ -283,7 +283,7 @@ fn rewrite_struct_pat(
     let path_str = rewrite_path(context, PathContext::Expr, qself, path, path_shape)?;
 
     if fields.is_empty() && !ellipsis {
-        return Some(format!("{} {{}}", path_str));
+        return Some(format!("{path_str} {{}}"));
     }
 
     let (ellipsis_str, terminator) = if ellipsis { (", ..", "..") } else { ("", "}") };
@@ -344,7 +344,7 @@ fn rewrite_struct_pat(
 
     // ast::Pat doesn't have attrs so use &[]
     let fields_str = wrap_struct_field(context, &[], &fields_str, shape, v_shape, one_line_width)?;
-    Some(format!("{} {{{}}}", path_str, fields_str))
+    Some(format!("{path_str} {{{fields_str}}}"))
 }
 
 impl Rewrite for PatField {
@@ -376,7 +376,7 @@ impl Rewrite for PatField {
             let id_str = rewrite_ident(context, self.ident);
             let one_line_width = id_str.len() + 2 + pat_str.len();
             let pat_and_id_str = if one_line_width <= shape.width {
-                format!("{}: {}", id_str, pat_str)
+                format!("{id_str}: {pat_str}")
             } else {
                 format!(
                     "{}:\n{}{}",
diff --git a/src/rustfmt_diff.rs b/src/rustfmt_diff.rs
index 1724a0f87bf..c9883452185 100644
--- a/src/rustfmt_diff.rs
+++ b/src/rustfmt_diff.rs
@@ -95,7 +95,7 @@ impl fmt::Display for ModifiedLines {
             )?;
 
             for line in &chunk.lines {
-                writeln!(f, "{}", line)?;
+                writeln!(f, "{line}")?;
             }
         }
 
@@ -166,12 +166,12 @@ impl OutputWriter {
                 if let Some(color) = color {
                     t.fg(color).unwrap();
                 }
-                writeln!(t, "{}", msg).unwrap();
+                writeln!(t, "{msg}").unwrap();
                 if color.is_some() {
                     t.reset().unwrap();
                 }
             }
-            None => println!("{}", msg),
+            None => println!("{msg}"),
         }
     }
 }
@@ -265,16 +265,15 @@ where
         for line in mismatch.lines {
             match line {
                 DiffLine::Context(ref str) => {
-                    writer.writeln(&format!(" {}{}", str, line_terminator), None)
+                    writer.writeln(&format!(" {str}{line_terminator}"), None)
                 }
                 DiffLine::Expected(ref str) => writer.writeln(
-                    &format!("+{}{}", str, line_terminator),
+                    &format!("+{str}{line_terminator}"),
                     Some(term::color::GREEN),
                 ),
-                DiffLine::Resulting(ref str) => writer.writeln(
-                    &format!("-{}{}", str, line_terminator),
-                    Some(term::color::RED),
-                ),
+                DiffLine::Resulting(ref str) => {
+                    writer.writeln(&format!("-{str}{line_terminator}"), Some(term::color::RED))
+                }
             }
         }
     }
diff --git a/src/skip.rs b/src/skip.rs
index 68f85b2ade4..d733f7068fd 100644
--- a/src/skip.rs
+++ b/src/skip.rs
@@ -105,7 +105,7 @@ pub(crate) fn is_skip_attr(segments: &[ast::PathSegment]) -> bool {
 
 fn get_skip_names(kind: &str, attrs: &[ast::Attribute]) -> Vec<String> {
     let mut skip_names = vec![];
-    let path = format!("{}::{}::{}", RUSTFMT, SKIP, kind);
+    let path = format!("{RUSTFMT}::{SKIP}::{kind}");
     for attr in attrs {
         // rustc_ast::ast::Path is implemented partialEq
         // but it is designed for segments.len() == 1
diff --git a/src/source_file.rs b/src/source_file.rs
index 56d4ab40038..958f9b0154f 100644
--- a/src/source_file.rs
+++ b/src/source_file.rs
@@ -62,7 +62,7 @@ where
     fn ensure_real_path(filename: &FileName) -> &Path {
         match *filename {
             FileName::Real(ref path) => path,
-            _ => panic!("cannot format `{}` and emit to files", filename),
+            _ => panic!("cannot format `{filename}` and emit to files"),
         }
     }
 
diff --git a/src/stmt.rs b/src/stmt.rs
index 0b3854425ea..e3fe4ebca11 100644
--- a/src/stmt.rs
+++ b/src/stmt.rs
@@ -3,7 +3,7 @@ use rustc_span::Span;
 
 use crate::comment::recover_comment_removed;
 use crate::config::Version;
-use crate::expr::{format_expr, ExprType};
+use crate::expr::{format_expr, is_simple_block, ExprType};
 use crate::rewrite::{Rewrite, RewriteContext};
 use crate::shape::Shape;
 use crate::source_map::LineRangeUtils;
@@ -33,6 +33,21 @@ impl<'a> Stmt<'a> {
         }
     }
 
+    pub(crate) fn from_simple_block(
+        context: &RewriteContext<'_>,
+        block: &'a ast::Block,
+        attrs: Option<&[ast::Attribute]>,
+    ) -> Option<Self> {
+        if is_simple_block(context, block, attrs) {
+            let inner = &block.stmts[0];
+            // Simple blocks only contain one expr and no stmts
+            let is_last = true;
+            Some(Stmt { inner, is_last })
+        } else {
+            None
+        }
+    }
+
     pub(crate) fn from_ast_node(inner: &'a ast::Stmt, is_last: bool) -> Self {
         Stmt { inner, is_last }
     }
@@ -80,13 +95,13 @@ impl<'a> Rewrite for Stmt<'a> {
         } else {
             ExprType::Statement
         };
-        format_stmt(context, shape, self.as_ast_node(), expr_type)
-    }
-}
-
-impl Rewrite for ast::Stmt {
-    fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
-        format_stmt(context, shape, self, ExprType::Statement)
+        format_stmt(
+            context,
+            shape,
+            self.as_ast_node(),
+            expr_type,
+            self.is_last_expr(),
+        )
     }
 }
 
@@ -95,13 +110,14 @@ fn format_stmt(
     shape: Shape,
     stmt: &ast::Stmt,
     expr_type: ExprType,
+    is_last_expr: bool,
 ) -> Option<String> {
     skip_out_of_file_lines_range!(context, stmt.span());
 
     let result = match stmt.kind {
         ast::StmtKind::Local(ref local) => local.rewrite(context, shape),
         ast::StmtKind::Expr(ref ex) | ast::StmtKind::Semi(ref ex) => {
-            let suffix = if semicolon_for_stmt(context, stmt) {
+            let suffix = if semicolon_for_stmt(context, stmt, is_last_expr) {
                 ";"
             } else {
                 ""
diff --git a/src/string.rs b/src/string.rs
index 78b72a50cb2..cb666fff695 100644
--- a/src/string.rs
+++ b/src/string.rs
@@ -1,7 +1,7 @@
 // Format string literals.
 
 use regex::Regex;
-use unicode_categories::UnicodeCategories;
+use unicode_properties::{GeneralCategory, UnicodeGeneralCategory};
 use unicode_segmentation::UnicodeSegmentation;
 
 use crate::config::Config;
@@ -366,7 +366,7 @@ fn is_whitespace(grapheme: &str) -> bool {
 fn is_punctuation(grapheme: &str) -> bool {
     grapheme
         .chars()
-        .all(UnicodeCategories::is_punctuation_other)
+        .all(|c| c.general_category() == GeneralCategory::OtherPunctuation)
 }
 
 fn graphemes_width(graphemes: &[&str]) -> usize {
diff --git a/src/test/configuration_snippet.rs b/src/test/configuration_snippet.rs
index c70b3c5facd..80b61c88a00 100644
--- a/src/test/configuration_snippet.rs
+++ b/src/test/configuration_snippet.rs
@@ -233,13 +233,11 @@ impl ConfigCodeBlock {
                 Some(ConfigurationSection::ConfigName(name)) => {
                     assert!(
                         Config::is_valid_name(&name),
-                        "an unknown configuration option was found: {}",
-                        name
+                        "an unknown configuration option was found: {name}"
                     );
                     assert!(
                         hash_set.remove(&name),
-                        "multiple configuration guides found for option {}",
-                        name
+                        "multiple configuration guides found for option {name}"
                     );
                     code_block.set_config_name(Some(name));
                 }
@@ -266,7 +264,7 @@ fn configuration_snippet_tests() {
 
     // Display results.
     println!("Ran {} configurations tests.", blocks.len());
-    assert_eq!(failures, 0, "{} configurations tests failed", failures);
+    assert_eq!(failures, 0, "{failures} configurations tests failed");
 }
 
 // Read Configurations.md and build a `Vec` of `ConfigCodeBlock` structs with one
@@ -289,7 +287,7 @@ fn get_code_blocks() -> Vec<ConfigCodeBlock> {
 
     for name in hash_set {
         if !Config::is_hidden_option(&name) {
-            panic!("{} does not have a configuration guide", name);
+            panic!("{name} does not have a configuration guide");
         }
     }
 
diff --git a/src/test/mod.rs b/src/test/mod.rs
index 37854ead28b..47f89c1871a 100644
--- a/src/test/mod.rs
+++ b/src/test/mod.rs
@@ -47,7 +47,7 @@ const FILE_SKIP_LIST: &[&str] = &[
 ];
 
 fn init_log() {
-    let _ = env_logger::builder().is_test(true).try_init();
+    let _ = tracing_subscriber::fmt().with_test_writer().try_init();
 }
 
 struct TestSetting {
@@ -203,8 +203,8 @@ fn coverage_tests() {
     let files = get_test_files(Path::new("tests/coverage/source"), true);
     let (_reports, count, fails) = check_files(files, &None);
 
-    println!("Ran {} tests in coverage mode.", count);
-    assert_eq!(fails, 0, "{} tests failed", fails);
+    println!("Ran {count} tests in coverage mode.");
+    assert_eq!(fails, 0, "{fails} tests failed");
 }
 
 #[test]
@@ -396,8 +396,8 @@ fn self_tests() {
     let mut warnings = 0;
 
     // Display results.
-    println!("Ran {} self tests.", count);
-    assert_eq!(fails, 0, "{} self tests failed", fails);
+    println!("Ran {count} self tests.");
+    assert_eq!(fails, 0, "{fails} self tests failed");
 
     for format_report in reports {
         println!(
@@ -407,11 +407,7 @@ fn self_tests() {
         warnings += format_report.warning_count();
     }
 
-    assert_eq!(
-        warnings, 0,
-        "Rustfmt's code generated {} warnings",
-        warnings
-    );
+    assert_eq!(warnings, 0, "Rustfmt's code generated {warnings} warnings");
 }
 
 #[test]
@@ -606,7 +602,7 @@ fn stdin_handles_mod_inner_ignore_attr() {
 fn format_lines_errors_are_reported() {
     init_log();
     let long_identifier = String::from_utf8(vec![b'a'; 239]).unwrap();
-    let input = Input::Text(format!("fn {}() {{}}", long_identifier));
+    let input = Input::Text(format!("fn {long_identifier}() {{}}"));
     let mut config = Config::default();
     config.set().error_on_line_overflow(true);
     let mut session = Session::<io::Stdout>::new(config, None);
@@ -618,7 +614,7 @@ fn format_lines_errors_are_reported() {
 fn format_lines_errors_are_reported_with_tabs() {
     init_log();
     let long_identifier = String::from_utf8(vec![b'a'; 97]).unwrap();
-    let input = Input::Text(format!("fn a() {{\n\t{}\n}}", long_identifier));
+    let input = Input::Text(format!("fn a() {{\n\t{long_identifier}\n}}"));
     let mut config = Config::default();
     config.set().error_on_line_overflow(true);
     config.set().hard_tabs(true);
@@ -829,11 +825,11 @@ fn handle_result(
     for (file_name, fmt_text) in result {
         // If file is in tests/source, compare to file with same name in tests/target.
         let target = get_target(&file_name, target);
-        let open_error = format!("couldn't open target {:?}", target);
+        let open_error = format!("couldn't open target {target:?}");
         let mut f = fs::File::open(&target).expect(&open_error);
 
         let mut text = String::new();
-        let read_error = format!("failed reading target {:?}", target);
+        let read_error = format!("failed reading target {target:?}");
         f.read_to_string(&mut text).expect(&read_error);
 
         // Ignore LF and CRLF difference for Windows.
diff --git a/src/types.rs b/src/types.rs
index 5e8edd8f8bf..127aff913e3 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -301,7 +301,7 @@ where
     let output = match *output {
         FnRetTy::Ty(ref ty) => {
             let type_str = ty.rewrite(context, ty_shape)?;
-            format!(" -> {}", type_str)
+            format!(" -> {type_str}")
         }
         FnRetTy::Default(..) => String::new(),
     };
@@ -373,7 +373,7 @@ where
         || !context.use_block_indent()
         || is_inputs_empty
     {
-        format!("({})", list_str)
+        format!("({list_str})")
     } else {
         format!(
             "({}{}{})",
@@ -383,7 +383,7 @@ where
         )
     };
     if output.is_empty() || last_line_width(&args) + first_line_width(&output) <= shape.width {
-        Some(format!("{}{}", args, output))
+        Some(format!("{args}{output}"))
     } else {
         Some(format!(
             "{}\n{}{}",
@@ -426,12 +426,12 @@ impl Rewrite for ast::WherePredicate {
             }) => {
                 let type_str = bounded_ty.rewrite(context, shape)?;
                 let colon = type_bound_colon(context).trim_end();
-                let lhs = if let Some(lifetime_str) =
-                    rewrite_lifetime_param(context, shape, bound_generic_params)
+                let lhs = if let Some(binder_str) =
+                    rewrite_bound_params(context, shape, bound_generic_params)
                 {
-                    format!("for<{}> {}{}", lifetime_str, type_str, colon)
+                    format!("for<{binder_str}> {type_str}{colon}")
                 } else {
-                    format!("{}{}", type_str, colon)
+                    format!("{type_str}{colon}")
                 };
 
                 rewrite_assign_rhs(context, lhs, bounds, &RhsAssignKind::Bounds, shape)?
@@ -657,8 +657,7 @@ impl Rewrite for ast::GenericParam {
 
 impl Rewrite for ast::PolyTraitRef {
     fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
-        if let Some(lifetime_str) =
-            rewrite_lifetime_param(context, shape, &self.bound_generic_params)
+        if let Some(lifetime_str) = rewrite_bound_params(context, shape, &self.bound_generic_params)
         {
             // 6 is "for<> ".len()
             let extra_offset = lifetime_str.len() + 6;
@@ -666,7 +665,7 @@ impl Rewrite for ast::PolyTraitRef {
                 .trait_ref
                 .rewrite(context, shape.offset_left(extra_offset)?)?;
 
-            Some(format!("for<{}> {}", lifetime_str, path_str))
+            Some(format!("for<{lifetime_str}> {path_str}"))
         } else {
             self.trait_ref.rewrite(context, shape)
         }
@@ -684,9 +683,11 @@ impl Rewrite for ast::Ty {
         match self.kind {
             ast::TyKind::TraitObject(ref bounds, tobj_syntax) => {
                 // we have to consider 'dyn' keyword is used or not!!!
-                let is_dyn = tobj_syntax == ast::TraitObjectSyntax::Dyn;
-                // 4 is length of 'dyn '
-                let shape = if is_dyn { shape.offset_left(4)? } else { shape };
+                let (shape, prefix) = match tobj_syntax {
+                    ast::TraitObjectSyntax::Dyn => (shape.offset_left(4)?, "dyn "),
+                    ast::TraitObjectSyntax::DynStar => (shape.offset_left(5)?, "dyn* "),
+                    ast::TraitObjectSyntax::None => (shape, ""),
+                };
                 let mut res = bounds.rewrite(context, shape)?;
                 // We may have falsely removed a trailing `+` inside macro call.
                 if context.inside_macro() && bounds.len() == 1 {
@@ -694,11 +695,7 @@ impl Rewrite for ast::Ty {
                         res.push('+');
                     }
                 }
-                if is_dyn {
-                    Some(format!("dyn {}", res))
-                } else {
-                    Some(res)
-                }
+                Some(format!("{prefix}{res}"))
             }
             ast::TyKind::Ptr(ref mt) => {
                 let prefix = match mt.mutbl {
@@ -794,7 +791,7 @@ impl Rewrite for ast::Ty {
                 if let Some(sh) = shape.sub_width(2) {
                     if let Some(ref s) = ty.rewrite(context, sh) {
                         if !s.contains('\n') {
-                            return Some(format!("({})", s));
+                            return Some(format!("({s})"));
                         }
                     }
                 }
@@ -883,8 +880,7 @@ fn rewrite_bare_fn(
 
     let mut result = String::with_capacity(128);
 
-    if let Some(ref lifetime_str) = rewrite_lifetime_param(context, shape, &bare_fn.generic_params)
-    {
+    if let Some(ref lifetime_str) = rewrite_bound_params(context, shape, &bare_fn.generic_params) {
         result.push_str("for<");
         // 6 = "for<> ".len(), 4 = "for<".
         // This doesn't work out so nicely for multiline situation with lots of
@@ -898,7 +894,6 @@ fn rewrite_bare_fn(
     result.push_str(&format_extern(
         bare_fn.ext,
         context.config.force_explicit_abi(),
-        false,
     ));
 
     result.push_str("fn");
@@ -1124,16 +1119,15 @@ pub(crate) fn can_be_overflowed_type(
     }
 }
 
-/// Returns `None` if there is no `LifetimeDef` in the given generic parameters.
-pub(crate) fn rewrite_lifetime_param(
+/// Returns `None` if there is no `GenericParam` in the list
+pub(crate) fn rewrite_bound_params(
     context: &RewriteContext<'_>,
     shape: Shape,
     generic_params: &[ast::GenericParam],
 ) -> Option<String> {
     let result = generic_params
         .iter()
-        .filter(|p| matches!(p.kind, ast::GenericParamKind::Lifetime))
-        .map(|lt| lt.rewrite(context, shape))
+        .map(|param| param.rewrite(context, shape))
         .collect::<Option<Vec<_>>>()?
         .join(", ");
     if result.is_empty() {
diff --git a/src/utils.rs b/src/utils.rs
index 4fc5a9b6896..79a759d68ce 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -69,7 +69,7 @@ pub(crate) fn format_visibility(
             let path = segments_iter.collect::<Vec<_>>().join("::");
             let in_str = if is_keyword(&path) { "" } else { "in " };
 
-            Cow::from(format!("pub({}{}) ", in_str, path))
+            Cow::from(format!("pub({in_str}{path}) "))
         }
     }
 }
@@ -131,23 +131,18 @@ pub(crate) fn format_mutability(mutability: ast::Mutability) -> &'static str {
 }
 
 #[inline]
-pub(crate) fn format_extern(
-    ext: ast::Extern,
-    explicit_abi: bool,
-    is_mod: bool,
-) -> Cow<'static, str> {
-    let abi = match ext {
-        ast::Extern::None => "Rust".to_owned(),
-        ast::Extern::Implicit(_) => "C".to_owned(),
-        ast::Extern::Explicit(abi, _) => abi.symbol_unescaped.to_string(),
-    };
-
-    if abi == "Rust" && !is_mod {
-        Cow::from("")
-    } else if abi == "C" && !explicit_abi {
-        Cow::from("extern ")
-    } else {
-        Cow::from(format!(r#"extern "{}" "#, abi))
+pub(crate) fn format_extern(ext: ast::Extern, explicit_abi: bool) -> Cow<'static, str> {
+    match ext {
+        ast::Extern::None => Cow::from(""),
+        ast::Extern::Implicit(_) if explicit_abi => Cow::from("extern \"C\" "),
+        ast::Extern::Implicit(_) => Cow::from("extern "),
+        // turn `extern "C"` into `extern` when `explicit_abi` is set to false
+        ast::Extern::Explicit(abi, _) if abi.symbol_unescaped == sym::C && !explicit_abi => {
+            Cow::from("extern ")
+        }
+        ast::Extern::Explicit(abi, _) => {
+            Cow::from(format!(r#"extern "{}" "#, abi.symbol_unescaped))
+        }
     }
 }
 
@@ -292,14 +287,20 @@ pub(crate) fn semicolon_for_expr(context: &RewriteContext<'_>, expr: &ast::Expr)
 }
 
 #[inline]
-pub(crate) fn semicolon_for_stmt(context: &RewriteContext<'_>, stmt: &ast::Stmt) -> bool {
+pub(crate) fn semicolon_for_stmt(
+    context: &RewriteContext<'_>,
+    stmt: &ast::Stmt,
+    is_last_expr: bool,
+) -> bool {
     match stmt.kind {
         ast::StmtKind::Semi(ref expr) => match expr.kind {
             ast::ExprKind::While(..) | ast::ExprKind::Loop(..) | ast::ExprKind::ForLoop(..) => {
                 false
             }
             ast::ExprKind::Break(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Ret(..) => {
-                context.config.trailing_semicolon()
+                // The only time we can skip the semi-colon is if the config option is set to false
+                // **and** this is the last expr (even though any following exprs are unreachable)
+                context.config.trailing_semicolon() || !is_last_expr
             }
             _ => true,
         },