diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs
index b5b628a3f55bd..9bf672312781e 100644
--- a/compiler/rustc_parse/src/parser/diagnostics.rs
+++ b/compiler/rustc_parse/src/parser/diagnostics.rs
@@ -17,7 +17,7 @@ use rustc_ast::{
 };
 use rustc_ast_pretty::pprust;
 use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::{pluralize, struct_span_err, Diagnostic, ErrorGuaranteed};
+use rustc_errors::{pluralize, struct_span_err, Diagnostic, EmissionGuarantee, ErrorGuaranteed};
 use rustc_errors::{Applicability, DiagnosticBuilder, Handler, PResult};
 use rustc_span::source_map::Spanned;
 use rustc_span::symbol::{kw, Ident};
@@ -156,6 +156,89 @@ impl AttemptLocalParseRecovery {
     }
 }
 
+/// Information for emitting suggestions and recovering from
+/// C-style `i++`, `--i`, etc.
+#[derive(Debug, Copy, Clone)]
+struct IncDecRecovery {
+    /// Is this increment/decrement its own statement?
+    standalone: IsStandalone,
+    /// Is this an increment or decrement?
+    op: IncOrDec,
+    /// Is this pre- or postfix?
+    fixity: UnaryFixity,
+}
+
+/// Is an increment or decrement expression its own statement?
+#[derive(Debug, Copy, Clone)]
+enum IsStandalone {
+    /// It's standalone, i.e., its own statement.
+    Standalone,
+    /// It's a subexpression, i.e., *not* standalone.
+    Subexpr,
+    /// It's maybe standalone; we're not sure.
+    Maybe,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+enum IncOrDec {
+    Inc,
+    // FIXME: `i--` recovery isn't implemented yet
+    #[allow(dead_code)]
+    Dec,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+enum UnaryFixity {
+    Pre,
+    Post,
+}
+
+impl IncOrDec {
+    fn chr(&self) -> char {
+        match self {
+            Self::Inc => '+',
+            Self::Dec => '-',
+        }
+    }
+
+    fn name(&self) -> &'static str {
+        match self {
+            Self::Inc => "increment",
+            Self::Dec => "decrement",
+        }
+    }
+}
+
+impl std::fmt::Display for UnaryFixity {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Pre => write!(f, "prefix"),
+            Self::Post => write!(f, "postfix"),
+        }
+    }
+}
+
+struct MultiSugg {
+    msg: String,
+    patches: Vec<(Span, String)>,
+    applicability: Applicability,
+}
+
+impl MultiSugg {
+    fn emit<G: EmissionGuarantee>(self, err: &mut DiagnosticBuilder<'_, G>) {
+        err.multipart_suggestion(&self.msg, self.patches, self.applicability);
+    }
+
+    /// Overrides individual messages and applicabilities.
+    fn emit_many<G: EmissionGuarantee>(
+        err: &mut DiagnosticBuilder<'_, G>,
+        msg: &str,
+        applicability: Applicability,
+        suggestions: impl Iterator<Item = Self>,
+    ) {
+        err.multipart_suggestions(msg, suggestions.map(|s| s.patches), applicability);
+    }
+}
 // SnapshotParser is used to create a snapshot of the parser
 // without causing duplicate errors being emitted when the `Parser`
 // is dropped.
@@ -1167,6 +1250,135 @@ impl<'a> Parser<'a> {
         Ok(())
     }
 
+    pub(super) fn recover_from_prefix_increment(
+        &mut self,
+        operand_expr: P<Expr>,
+        op_span: Span,
+        prev_is_semi: bool,
+    ) -> PResult<'a, P<Expr>> {
+        let standalone =
+            if prev_is_semi { IsStandalone::Standalone } else { IsStandalone::Subexpr };
+        let kind = IncDecRecovery { standalone, op: IncOrDec::Inc, fixity: UnaryFixity::Pre };
+
+        self.recover_from_inc_dec(operand_expr, kind, op_span)
+    }
+
+    pub(super) fn recover_from_postfix_increment(
+        &mut self,
+        operand_expr: P<Expr>,
+        op_span: Span,
+    ) -> PResult<'a, P<Expr>> {
+        let kind = IncDecRecovery {
+            standalone: IsStandalone::Maybe,
+            op: IncOrDec::Inc,
+            fixity: UnaryFixity::Post,
+        };
+
+        self.recover_from_inc_dec(operand_expr, kind, op_span)
+    }
+
+    fn recover_from_inc_dec(
+        &mut self,
+        base: P<Expr>,
+        kind: IncDecRecovery,
+        op_span: Span,
+    ) -> PResult<'a, P<Expr>> {
+        let mut err = self.struct_span_err(
+            op_span,
+            &format!("Rust has no {} {} operator", kind.fixity, kind.op.name()),
+        );
+        err.span_label(op_span, &format!("not a valid {} operator", kind.fixity));
+
+        let help_base_case = |mut err: DiagnosticBuilder<'_, _>, base| {
+            err.help(&format!("use `{}= 1` instead", kind.op.chr()));
+            err.emit();
+            Ok(base)
+        };
+
+        // (pre, post)
+        let spans = match kind.fixity {
+            UnaryFixity::Pre => (op_span, base.span.shrink_to_hi()),
+            UnaryFixity::Post => (base.span.shrink_to_lo(), op_span),
+        };
+
+        match kind.standalone {
+            IsStandalone::Standalone => self.inc_dec_standalone_suggest(kind, spans).emit(&mut err),
+            IsStandalone::Subexpr => {
+                let Ok(base_src) = self.span_to_snippet(base.span)
+                    else { return help_base_case(err, base) };
+                match kind.fixity {
+                    UnaryFixity::Pre => {
+                        self.prefix_inc_dec_suggest(base_src, kind, spans).emit(&mut err)
+                    }
+                    UnaryFixity::Post => {
+                        self.postfix_inc_dec_suggest(base_src, kind, spans).emit(&mut err)
+                    }
+                }
+            }
+            IsStandalone::Maybe => {
+                let Ok(base_src) = self.span_to_snippet(base.span)
+                    else { return help_base_case(err, base) };
+                let sugg1 = match kind.fixity {
+                    UnaryFixity::Pre => self.prefix_inc_dec_suggest(base_src, kind, spans),
+                    UnaryFixity::Post => self.postfix_inc_dec_suggest(base_src, kind, spans),
+                };
+                let sugg2 = self.inc_dec_standalone_suggest(kind, spans);
+                MultiSugg::emit_many(
+                    &mut err,
+                    "use `+= 1` instead",
+                    Applicability::Unspecified,
+                    [sugg1, sugg2].into_iter(),
+                )
+            }
+        }
+        Err(err)
+    }
+
+    fn prefix_inc_dec_suggest(
+        &mut self,
+        base_src: String,
+        kind: IncDecRecovery,
+        (pre_span, post_span): (Span, Span),
+    ) -> MultiSugg {
+        MultiSugg {
+            msg: format!("use `{}= 1` instead", kind.op.chr()),
+            patches: vec![
+                (pre_span, "{ ".to_string()),
+                (post_span, format!(" {}= 1; {} }}", kind.op.chr(), base_src)),
+            ],
+            applicability: Applicability::MachineApplicable,
+        }
+    }
+
+    fn postfix_inc_dec_suggest(
+        &mut self,
+        base_src: String,
+        kind: IncDecRecovery,
+        (pre_span, post_span): (Span, Span),
+    ) -> MultiSugg {
+        let tmp_var = if base_src.trim() == "tmp" { "tmp_" } else { "tmp" };
+        MultiSugg {
+            msg: format!("use `{}= 1` instead", kind.op.chr()),
+            patches: vec![
+                (pre_span, format!("{{ let {} = ", tmp_var)),
+                (post_span, format!("; {} {}= 1; {} }}", base_src, kind.op.chr(), tmp_var)),
+            ],
+            applicability: Applicability::HasPlaceholders,
+        }
+    }
+
+    fn inc_dec_standalone_suggest(
+        &mut self,
+        kind: IncDecRecovery,
+        (pre_span, post_span): (Span, Span),
+    ) -> MultiSugg {
+        MultiSugg {
+            msg: format!("use `{}= 1` instead", kind.op.chr()),
+            patches: vec![(pre_span, String::new()), (post_span, format!(" {}= 1", kind.op.chr()))],
+            applicability: Applicability::MachineApplicable,
+        }
+    }
+
     /// Tries to recover from associated item paths like `[T]::AssocItem` / `(T, U)::AssocItem`.
     /// Attempts to convert the base expression/pattern/type into a type, parses the `::AssocItem`
     /// tail, and combines them into a `<Ty>::AssocItem` expression/pattern/type.
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index 148e0a24ec304..8c4ec8dc6e362 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -267,6 +267,17 @@ impl<'a> Parser<'a> {
                 self.bump();
             }
 
+            if self.prev_token == token::BinOp(token::Plus)
+                && self.token == token::BinOp(token::Plus)
+                && self.prev_token.span.between(self.token.span).is_empty()
+            {
+                let op_span = self.prev_token.span.to(self.token.span);
+                // Eat the second `+`
+                self.bump();
+                lhs = self.recover_from_postfix_increment(lhs, op_span)?;
+                continue;
+            }
+
             let op = op.node;
             // Special cases:
             if op == AssocOp::As {
@@ -580,6 +591,19 @@ impl<'a> Parser<'a> {
                 this.bump();
                 this.parse_prefix_expr(None)
             } // `+expr`
+            // Recover from `++x`:
+            token::BinOp(token::Plus)
+                if this.look_ahead(1, |t| *t == token::BinOp(token::Plus)) =>
+            {
+                let prev_is_semi = this.prev_token == token::Semi;
+                let pre_span = this.token.span.to(this.look_ahead(1, |t| t.span));
+                // Eat both `+`s.
+                this.bump();
+                this.bump();
+
+                let operand_expr = this.parse_dot_or_call_expr(Default::default())?;
+                this.recover_from_prefix_increment(operand_expr, pre_span, prev_is_semi)
+            }
             token::Ident(..) if this.token.is_keyword(kw::Box) => {
                 make_it!(this, attrs, |this, _| this.parse_box_expr(lo))
             }
diff --git a/src/test/ui/parser/increment-autofix.fixed b/src/test/ui/parser/increment-autofix.fixed
new file mode 100644
index 0000000000000..7a426badfc2d3
--- /dev/null
+++ b/src/test/ui/parser/increment-autofix.fixed
@@ -0,0 +1,31 @@
+// run-rustfix
+
+pub fn pre_regular() {
+    let mut i = 0;
+    i += 1; //~ ERROR Rust has no prefix increment operator
+    println!("{}", i);
+}
+
+pub fn pre_while() {
+    let mut i = 0;
+    while { i += 1; i } < 5 {
+        //~^ ERROR Rust has no prefix increment operator
+        println!("{}", i);
+    }
+}
+
+pub fn pre_regular_tmp() {
+    let mut tmp = 0;
+    tmp += 1; //~ ERROR Rust has no prefix increment operator
+    println!("{}", tmp);
+}
+
+pub fn pre_while_tmp() {
+    let mut tmp = 0;
+    while { tmp += 1; tmp } < 5 {
+        //~^ ERROR Rust has no prefix increment operator
+        println!("{}", tmp);
+    }
+}
+
+fn main() {}
diff --git a/src/test/ui/parser/increment-autofix.rs b/src/test/ui/parser/increment-autofix.rs
new file mode 100644
index 0000000000000..d38603697a7a6
--- /dev/null
+++ b/src/test/ui/parser/increment-autofix.rs
@@ -0,0 +1,31 @@
+// run-rustfix
+
+pub fn pre_regular() {
+    let mut i = 0;
+    ++i; //~ ERROR Rust has no prefix increment operator
+    println!("{}", i);
+}
+
+pub fn pre_while() {
+    let mut i = 0;
+    while ++i < 5 {
+        //~^ ERROR Rust has no prefix increment operator
+        println!("{}", i);
+    }
+}
+
+pub fn pre_regular_tmp() {
+    let mut tmp = 0;
+    ++tmp; //~ ERROR Rust has no prefix increment operator
+    println!("{}", tmp);
+}
+
+pub fn pre_while_tmp() {
+    let mut tmp = 0;
+    while ++tmp < 5 {
+        //~^ ERROR Rust has no prefix increment operator
+        println!("{}", tmp);
+    }
+}
+
+fn main() {}
diff --git a/src/test/ui/parser/increment-autofix.stderr b/src/test/ui/parser/increment-autofix.stderr
new file mode 100644
index 0000000000000..593592ba4ab74
--- /dev/null
+++ b/src/test/ui/parser/increment-autofix.stderr
@@ -0,0 +1,52 @@
+error: Rust has no prefix increment operator
+  --> $DIR/increment-autofix.rs:5:5
+   |
+LL |     ++i;
+   |     ^^ not a valid prefix operator
+   |
+help: use `+= 1` instead
+   |
+LL -     ++i;
+LL +     i += 1;
+   | 
+
+error: Rust has no prefix increment operator
+  --> $DIR/increment-autofix.rs:11:11
+   |
+LL |     while ++i < 5 {
+   |     ----- ^^ not a valid prefix operator
+   |     |
+   |     while parsing the condition of this `while` expression
+   |
+help: use `+= 1` instead
+   |
+LL |     while { i += 1; i } < 5 {
+   |           ~   +++++++++
+
+error: Rust has no prefix increment operator
+  --> $DIR/increment-autofix.rs:19:5
+   |
+LL |     ++tmp;
+   |     ^^ not a valid prefix operator
+   |
+help: use `+= 1` instead
+   |
+LL -     ++tmp;
+LL +     tmp += 1;
+   | 
+
+error: Rust has no prefix increment operator
+  --> $DIR/increment-autofix.rs:25:11
+   |
+LL |     while ++tmp < 5 {
+   |     ----- ^^ not a valid prefix operator
+   |     |
+   |     while parsing the condition of this `while` expression
+   |
+help: use `+= 1` instead
+   |
+LL |     while { tmp += 1; tmp } < 5 {
+   |           ~     +++++++++++
+
+error: aborting due to 4 previous errors
+
diff --git a/src/test/ui/parser/increment-notfixed.rs b/src/test/ui/parser/increment-notfixed.rs
new file mode 100644
index 0000000000000..15f159e53d294
--- /dev/null
+++ b/src/test/ui/parser/increment-notfixed.rs
@@ -0,0 +1,61 @@
+struct Foo {
+    bar: Bar,
+}
+
+struct Bar {
+    qux: i32,
+}
+
+pub fn post_regular() {
+    let mut i = 0;
+    i++; //~ ERROR Rust has no postfix increment operator
+    println!("{}", i);
+}
+
+pub fn post_while() {
+    let mut i = 0;
+    while i++ < 5 {
+        //~^ ERROR Rust has no postfix increment operator
+        println!("{}", i);
+    }
+}
+
+pub fn post_regular_tmp() {
+    let mut tmp = 0;
+    tmp++; //~ ERROR Rust has no postfix increment operator
+    println!("{}", tmp);
+}
+
+pub fn post_while_tmp() {
+    let mut tmp = 0;
+    while tmp++ < 5 {
+        //~^ ERROR Rust has no postfix increment operator
+        println!("{}", tmp);
+    }
+}
+
+pub fn post_field() {
+    let foo = Foo { bar: Bar { qux: 0 } };
+    foo.bar.qux++;
+    //~^ ERROR Rust has no postfix increment operator
+    println!("{}", foo.bar.qux);
+}
+
+pub fn post_field_tmp() {
+    struct S {
+        tmp: i32
+    }
+    let s = S { tmp: 0 };
+    s.tmp++;
+    //~^ ERROR Rust has no postfix increment operator
+    println!("{}", s.tmp);
+}
+
+pub fn pre_field() {
+    let foo = Foo { bar: Bar { qux: 0 } };
+    ++foo.bar.qux;
+    //~^ ERROR Rust has no prefix increment operator
+    println!("{}", foo.bar.qux);
+}
+
+fn main() {}
diff --git a/src/test/ui/parser/increment-notfixed.stderr b/src/test/ui/parser/increment-notfixed.stderr
new file mode 100644
index 0000000000000..f23595da32ac8
--- /dev/null
+++ b/src/test/ui/parser/increment-notfixed.stderr
@@ -0,0 +1,102 @@
+error: Rust has no postfix increment operator
+  --> $DIR/increment-notfixed.rs:11:6
+   |
+LL |     i++;
+   |      ^^ not a valid postfix operator
+   |
+help: use `+= 1` instead
+   |
+LL |     { let tmp = i; i += 1; tmp };
+   |     +++++++++++  ~~~~~~~~~~~~~~~
+LL -     i++;
+LL +     i += 1;
+   | 
+
+error: Rust has no postfix increment operator
+  --> $DIR/increment-notfixed.rs:17:12
+   |
+LL |     while i++ < 5 {
+   |     -----  ^^ not a valid postfix operator
+   |     |
+   |     while parsing the condition of this `while` expression
+   |
+help: use `+= 1` instead
+   |
+LL |     while { let tmp = i; i += 1; tmp } < 5 {
+   |           +++++++++++  ~~~~~~~~~~~~~~~
+LL -     while i++ < 5 {
+LL +     while i += 1 < 5 {
+   | 
+
+error: Rust has no postfix increment operator
+  --> $DIR/increment-notfixed.rs:25:8
+   |
+LL |     tmp++;
+   |        ^^ not a valid postfix operator
+   |
+help: use `+= 1` instead
+   |
+LL |     { let tmp_ = tmp; tmp += 1; tmp_ };
+   |     ++++++++++++    ~~~~~~~~~~~~~~~~~~
+LL -     tmp++;
+LL +     tmp += 1;
+   | 
+
+error: Rust has no postfix increment operator
+  --> $DIR/increment-notfixed.rs:31:14
+   |
+LL |     while tmp++ < 5 {
+   |     -----    ^^ not a valid postfix operator
+   |     |
+   |     while parsing the condition of this `while` expression
+   |
+help: use `+= 1` instead
+   |
+LL |     while { let tmp_ = tmp; tmp += 1; tmp_ } < 5 {
+   |           ++++++++++++    ~~~~~~~~~~~~~~~~~~
+LL -     while tmp++ < 5 {
+LL +     while tmp += 1 < 5 {
+   | 
+
+error: Rust has no postfix increment operator
+  --> $DIR/increment-notfixed.rs:39:16
+   |
+LL |     foo.bar.qux++;
+   |                ^^ not a valid postfix operator
+   |
+help: use `+= 1` instead
+   |
+LL |     { let tmp = foo.bar.qux; foo.bar.qux += 1; tmp };
+   |     +++++++++++            ~~~~~~~~~~~~~~~~~~~~~~~~~
+LL -     foo.bar.qux++;
+LL +     foo.bar.qux += 1;
+   | 
+
+error: Rust has no postfix increment operator
+  --> $DIR/increment-notfixed.rs:49:10
+   |
+LL |     s.tmp++;
+   |          ^^ not a valid postfix operator
+   |
+help: use `+= 1` instead
+   |
+LL |     { let tmp = s.tmp; s.tmp += 1; tmp };
+   |     +++++++++++      ~~~~~~~~~~~~~~~~~~~
+LL -     s.tmp++;
+LL +     s.tmp += 1;
+   | 
+
+error: Rust has no prefix increment operator
+  --> $DIR/increment-notfixed.rs:56:5
+   |
+LL |     ++foo.bar.qux;
+   |     ^^ not a valid prefix operator
+   |
+help: use `+= 1` instead
+   |
+LL -     ++foo.bar.qux;
+LL +     foo.bar.qux += 1;
+   | 
+
+error: aborting due to 7 previous errors
+