about summary refs log tree commit diff
path: root/compiler/rustc_parse/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-07-16 05:46:31 +0000
committerbors <bors@rust-lang.org>2024-07-16 05:46:31 +0000
commit451035fff364e81cc63d15bd475da8ba32e01470 (patch)
treeb24a14bb4f49de11c1ed351b24910d00a32f4a95 /compiler/rustc_parse/src
parente90f04731a382a27962a30113f8283a59a6ab87e (diff)
parent547ade539c578c8a39b02841c3ef4ae980f5c658 (diff)
downloadrust-451035fff364e81cc63d15bd475da8ba32e01470.tar.gz
rust-451035fff364e81cc63d15bd475da8ba32e01470.zip
Auto merge of #3751 - rust-lang:rustup-2024-07-16, r=RalfJung
Automatic Rustup
Diffstat (limited to 'compiler/rustc_parse/src')
-rw-r--r--compiler/rustc_parse/src/errors.rs436
-rw-r--r--compiler/rustc_parse/src/lib.rs6
-rw-r--r--compiler/rustc_parse/src/parser/attr_wrapper.rs122
-rw-r--r--compiler/rustc_parse/src/parser/diagnostics.rs51
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs11
-rw-r--r--compiler/rustc_parse/src/parser/item.rs120
-rw-r--r--compiler/rustc_parse/src/parser/mod.rs76
-rw-r--r--compiler/rustc_parse/src/parser/pat.rs50
-rw-r--r--compiler/rustc_parse/src/parser/path.rs2
-rw-r--r--compiler/rustc_parse/src/parser/stmt.rs8
-rw-r--r--compiler/rustc_parse/src/parser/tests.rs359
-rw-r--r--compiler/rustc_parse/src/parser/ty.rs23
12 files changed, 902 insertions, 362 deletions
diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs
index 6894f470d88..092a2a10ab7 100644
--- a/compiler/rustc_parse/src/errors.rs
+++ b/compiler/rustc_parse/src/errors.rs
@@ -18,10 +18,10 @@ use crate::parser::{ForbiddenLetReason, TokenDescription};
 #[derive(Diagnostic)]
 #[diag(parse_maybe_report_ambiguous_plus)]
 pub(crate) struct AmbiguousPlus {
-    pub sum_ty: String,
     #[primary_span]
-    #[suggestion(code = "({sum_ty})")]
     pub span: Span,
+    #[subdiagnostic]
+    pub suggestion: AddParen,
 }
 
 #[derive(Diagnostic)]
@@ -35,16 +35,19 @@ pub(crate) struct BadTypePlus {
 }
 
 #[derive(Subdiagnostic)]
+#[multipart_suggestion(parse_add_paren, applicability = "machine-applicable")]
+pub(crate) struct AddParen {
+    #[suggestion_part(code = "(")]
+    pub lo: Span,
+    #[suggestion_part(code = ")")]
+    pub hi: Span,
+}
+
+#[derive(Subdiagnostic)]
 pub(crate) enum BadTypePlusSub {
-    #[suggestion(
-        parse_add_paren,
-        code = "{sum_with_parens}",
-        applicability = "machine-applicable"
-    )]
     AddParen {
-        sum_with_parens: String,
-        #[primary_span]
-        span: Span,
+        #[subdiagnostic]
+        suggestion: AddParen,
     },
     #[label(parse_forgot_paren)]
     ForgotParen {
@@ -80,7 +83,7 @@ pub(crate) struct WrapType {
 #[diag(parse_incorrect_semicolon)]
 pub(crate) struct IncorrectSemicolon<'a> {
     #[primary_span]
-    #[suggestion(style = "short", code = "", applicability = "machine-applicable")]
+    #[suggestion(style = "verbose", code = "", applicability = "machine-applicable")]
     pub span: Span,
     #[help]
     pub show_help: bool,
@@ -91,19 +94,35 @@ pub(crate) struct IncorrectSemicolon<'a> {
 #[diag(parse_incorrect_use_of_await)]
 pub(crate) struct IncorrectUseOfAwait {
     #[primary_span]
-    #[suggestion(parse_parentheses_suggestion, code = "", applicability = "machine-applicable")]
+    #[suggestion(
+        parse_parentheses_suggestion,
+        style = "verbose",
+        code = "",
+        applicability = "machine-applicable"
+    )]
     pub span: Span,
 }
 
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(
+    parse_incorrect_use_of_await_postfix_suggestion,
+    applicability = "machine-applicable"
+)]
+pub(crate) struct AwaitSuggestion {
+    #[suggestion_part(code = "")]
+    pub removal: Span,
+    #[suggestion_part(code = ".await{question_mark}")]
+    pub dot_await: Span,
+    pub question_mark: &'static str,
+}
+
 #[derive(Diagnostic)]
 #[diag(parse_incorrect_use_of_await)]
 pub(crate) struct IncorrectAwait {
     #[primary_span]
     pub span: Span,
-    #[suggestion(parse_postfix_suggestion, code = "{expr}.await{question_mark}")]
-    pub sugg_span: (Span, Applicability),
-    pub expr: String,
-    pub question_mark: &'static str,
+    #[subdiagnostic]
+    pub suggestion: AwaitSuggestion,
 }
 
 #[derive(Diagnostic)]
@@ -111,7 +130,7 @@ pub(crate) struct IncorrectAwait {
 pub(crate) struct InInTypo {
     #[primary_span]
     pub span: Span,
-    #[suggestion(code = "", applicability = "machine-applicable")]
+    #[suggestion(code = "", style = "verbose", applicability = "machine-applicable")]
     pub sugg_span: Span,
 }
 
@@ -126,17 +145,33 @@ pub(crate) struct InvalidVariableDeclaration {
 
 #[derive(Subdiagnostic)]
 pub(crate) enum InvalidVariableDeclarationSub {
-    #[suggestion(parse_switch_mut_let_order, applicability = "maybe-incorrect", code = "let mut")]
+    #[suggestion(
+        parse_switch_mut_let_order,
+        style = "verbose",
+        applicability = "maybe-incorrect",
+        code = "let mut"
+    )]
     SwitchMutLetOrder(#[primary_span] Span),
     #[suggestion(
         parse_missing_let_before_mut,
         applicability = "machine-applicable",
+        style = "verbose",
         code = "let mut"
     )]
     MissingLet(#[primary_span] Span),
-    #[suggestion(parse_use_let_not_auto, applicability = "machine-applicable", code = "let")]
+    #[suggestion(
+        parse_use_let_not_auto,
+        style = "verbose",
+        applicability = "machine-applicable",
+        code = "let"
+    )]
     UseLetNotAuto(#[primary_span] Span),
-    #[suggestion(parse_use_let_not_var, applicability = "machine-applicable", code = "let")]
+    #[suggestion(
+        parse_use_let_not_var,
+        style = "verbose",
+        applicability = "machine-applicable",
+        code = "let"
+    )]
     UseLetNotVar(#[primary_span] Span),
 }
 
@@ -144,7 +179,7 @@ pub(crate) enum InvalidVariableDeclarationSub {
 #[diag(parse_switch_ref_box_order)]
 pub(crate) struct SwitchRefBoxOrder {
     #[primary_span]
-    #[suggestion(applicability = "machine-applicable", code = "box ref")]
+    #[suggestion(applicability = "machine-applicable", style = "verbose", code = "box ref")]
     pub span: Span,
 }
 
@@ -162,7 +197,7 @@ pub(crate) struct InvalidComparisonOperator {
 pub(crate) enum InvalidComparisonOperatorSub {
     #[suggestion(
         parse_use_instead,
-        style = "short",
+        style = "verbose",
         applicability = "machine-applicable",
         code = "{correct}"
     )]
@@ -191,14 +226,14 @@ pub(crate) struct InvalidLogicalOperator {
 pub(crate) enum InvalidLogicalOperatorSub {
     #[suggestion(
         parse_use_amp_amp_for_conjunction,
-        style = "short",
+        style = "verbose",
         applicability = "machine-applicable",
         code = "&&"
     )]
     Conjunction(#[primary_span] Span),
     #[suggestion(
         parse_use_pipe_pipe_for_disjunction,
-        style = "short",
+        style = "verbose",
         applicability = "machine-applicable",
         code = "||"
     )]
@@ -209,7 +244,7 @@ pub(crate) enum InvalidLogicalOperatorSub {
 #[diag(parse_tilde_is_not_unary_operator)]
 pub(crate) struct TildeAsUnaryOperator(
     #[primary_span]
-    #[suggestion(style = "short", applicability = "machine-applicable", code = "!")]
+    #[suggestion(style = "verbose", applicability = "machine-applicable", code = "!")]
     pub Span,
 );
 
@@ -227,7 +262,7 @@ pub(crate) struct NotAsNegationOperator {
 pub enum NotAsNegationOperatorSub {
     #[suggestion(
         parse_unexpected_token_after_not_default,
-        style = "short",
+        style = "verbose",
         applicability = "machine-applicable",
         code = "!"
     )]
@@ -235,7 +270,7 @@ pub enum NotAsNegationOperatorSub {
 
     #[suggestion(
         parse_unexpected_token_after_not_bitwise,
-        style = "short",
+        style = "verbose",
         applicability = "machine-applicable",
         code = "!"
     )]
@@ -243,7 +278,7 @@ pub enum NotAsNegationOperatorSub {
 
     #[suggestion(
         parse_unexpected_token_after_not_logical,
-        style = "short",
+        style = "verbose",
         applicability = "machine-applicable",
         code = "!"
     )]
@@ -254,9 +289,9 @@ pub enum NotAsNegationOperatorSub {
 #[diag(parse_malformed_loop_label)]
 pub(crate) struct MalformedLoopLabel {
     #[primary_span]
-    #[suggestion(applicability = "machine-applicable", code = "{correct_label}")]
     pub span: Span,
-    pub correct_label: Ident,
+    #[suggestion(applicability = "machine-applicable", code = "'", style = "verbose")]
+    pub suggestion: Span,
 }
 
 #[derive(Diagnostic)]
@@ -264,7 +299,7 @@ pub(crate) struct MalformedLoopLabel {
 pub(crate) struct LifetimeInBorrowExpression {
     #[primary_span]
     pub span: Span,
-    #[suggestion(applicability = "machine-applicable", code = "")]
+    #[suggestion(applicability = "machine-applicable", code = "", style = "verbose")]
     #[label]
     pub lifetime_span: Span,
 }
@@ -306,7 +341,7 @@ pub(crate) struct RequireColonAfterLabeledExpression {
     pub span: Span,
     #[label]
     pub label: Span,
-    #[suggestion(style = "short", applicability = "machine-applicable", code = ": ")]
+    #[suggestion(style = "verbose", applicability = "machine-applicable", code = ": ")]
     pub label_end: Span,
 }
 
@@ -315,7 +350,7 @@ pub(crate) struct RequireColonAfterLabeledExpression {
 #[note]
 pub(crate) struct DoCatchSyntaxRemoved {
     #[primary_span]
-    #[suggestion(applicability = "machine-applicable", code = "try")]
+    #[suggestion(applicability = "machine-applicable", code = "try", style = "verbose")]
     pub span: Span,
 }
 
@@ -323,9 +358,9 @@ pub(crate) struct DoCatchSyntaxRemoved {
 #[diag(parse_float_literal_requires_integer_part)]
 pub(crate) struct FloatLiteralRequiresIntegerPart {
     #[primary_span]
-    #[suggestion(applicability = "machine-applicable", code = "{correct}")]
     pub span: Span,
-    pub correct: String,
+    #[suggestion(applicability = "machine-applicable", code = "0", style = "verbose")]
+    pub suggestion: Span,
 }
 
 #[derive(Diagnostic)]
@@ -394,7 +429,12 @@ pub struct TernaryOperator {
 }
 
 #[derive(Subdiagnostic)]
-#[suggestion(parse_extra_if_in_let_else, applicability = "maybe-incorrect", code = "")]
+#[suggestion(
+    parse_extra_if_in_let_else,
+    applicability = "maybe-incorrect",
+    code = "",
+    style = "verbose"
+)]
 pub(crate) struct IfExpressionLetSomeSub {
     #[primary_span]
     pub if_span: Span,
@@ -463,7 +503,7 @@ pub(crate) struct ExpectedElseBlock {
     pub first_tok: String,
     #[label]
     pub else_span: Span,
-    #[suggestion(applicability = "maybe-incorrect", code = "if ")]
+    #[suggestion(applicability = "maybe-incorrect", code = "if ", style = "verbose")]
     pub condition_start: Span,
 }
 
@@ -491,7 +531,7 @@ pub(crate) struct OuterAttributeNotAllowedOnIfElse {
     pub ctx_span: Span,
     pub ctx: String,
 
-    #[suggestion(applicability = "machine-applicable", code = "")]
+    #[suggestion(applicability = "machine-applicable", code = "", style = "verbose")]
     pub attributes: Span,
 }
 
@@ -509,12 +549,17 @@ pub(crate) enum MissingInInForLoopSub {
     // Has been misleading, at least in the past (closed Issue #48492), thus maybe-incorrect
     #[suggestion(
         parse_use_in_not_of,
-        style = "short",
+        style = "verbose",
         applicability = "maybe-incorrect",
         code = "in"
     )]
     InNotOf(#[primary_span] Span),
-    #[suggestion(parse_add_in, style = "short", applicability = "maybe-incorrect", code = " in ")]
+    #[suggestion(
+        parse_add_in,
+        style = "verbose",
+        applicability = "maybe-incorrect",
+        code = " in "
+    )]
     AddIn(#[primary_span] Span),
 }
 
@@ -545,7 +590,7 @@ pub(crate) struct LoopElseNotSupported {
 #[diag(parse_missing_comma_after_match_arm)]
 pub(crate) struct MissingCommaAfterMatchArm {
     #[primary_span]
-    #[suggestion(applicability = "machine-applicable", code = ",")]
+    #[suggestion(applicability = "machine-applicable", code = ",", style = "verbose")]
     pub span: Span,
 }
 
@@ -563,7 +608,7 @@ pub(crate) struct CatchAfterTry {
 pub(crate) struct CommaAfterBaseStruct {
     #[primary_span]
     pub span: Span,
-    #[suggestion(style = "short", applicability = "machine-applicable", code = "")]
+    #[suggestion(style = "verbose", applicability = "machine-applicable", code = "")]
     pub comma: Span,
 }
 
@@ -572,7 +617,7 @@ pub(crate) struct CommaAfterBaseStruct {
 pub(crate) struct EqFieldInit {
     #[primary_span]
     pub span: Span,
-    #[suggestion(applicability = "machine-applicable", code = ":")]
+    #[suggestion(applicability = "machine-applicable", code = ":", style = "verbose")]
     pub eq: Span,
 }
 
@@ -580,8 +625,18 @@ pub(crate) struct EqFieldInit {
 #[diag(parse_dotdotdot)]
 pub(crate) struct DotDotDot {
     #[primary_span]
-    #[suggestion(parse_suggest_exclusive_range, applicability = "maybe-incorrect", code = "..")]
-    #[suggestion(parse_suggest_inclusive_range, applicability = "maybe-incorrect", code = "..=")]
+    #[suggestion(
+        parse_suggest_exclusive_range,
+        applicability = "maybe-incorrect",
+        code = "..",
+        style = "verbose"
+    )]
+    #[suggestion(
+        parse_suggest_inclusive_range,
+        applicability = "maybe-incorrect",
+        code = "..=",
+        style = "verbose"
+    )]
     pub span: Span,
 }
 
@@ -589,7 +644,7 @@ pub(crate) struct DotDotDot {
 #[diag(parse_left_arrow_operator)]
 pub(crate) struct LeftArrowOperator {
     #[primary_span]
-    #[suggestion(applicability = "maybe-incorrect", code = "< -")]
+    #[suggestion(applicability = "maybe-incorrect", code = "< -", style = "verbose")]
     pub span: Span,
 }
 
@@ -597,7 +652,7 @@ pub(crate) struct LeftArrowOperator {
 #[diag(parse_remove_let)]
 pub(crate) struct RemoveLet {
     #[primary_span]
-    #[suggestion(applicability = "machine-applicable", code = "")]
+    #[suggestion(applicability = "machine-applicable", code = "", style = "verbose")]
     pub span: Span,
 }
 
@@ -605,8 +660,9 @@ pub(crate) struct RemoveLet {
 #[diag(parse_use_eq_instead)]
 pub(crate) struct UseEqInstead {
     #[primary_span]
-    #[suggestion(style = "short", applicability = "machine-applicable", code = "=")]
     pub span: Span,
+    #[suggestion(style = "verbose", applicability = "machine-applicable", code = "")]
+    pub suggestion: Span,
 }
 
 #[derive(Diagnostic)]
@@ -780,7 +836,7 @@ pub(crate) struct InclusiveRangeExtraEquals {
     #[primary_span]
     #[suggestion(
         parse_suggestion_remove_eq,
-        style = "short",
+        style = "verbose",
         code = "..=",
         applicability = "maybe-incorrect"
     )]
@@ -803,13 +859,14 @@ pub(crate) struct InclusiveRangeMatchArrow {
 #[note]
 pub(crate) struct InclusiveRangeNoEnd {
     #[primary_span]
+    pub span: Span,
     #[suggestion(
         parse_suggestion_open_range,
-        code = "..",
+        code = "",
         applicability = "machine-applicable",
-        style = "short"
+        style = "verbose"
     )]
-    pub span: Span,
+    pub suggestion: Span,
 }
 
 #[derive(Subdiagnostic)]
@@ -824,7 +881,8 @@ pub(crate) enum MatchArmBodyWithoutBracesSugg {
     #[suggestion(
         parse_suggestion_use_comma_not_semicolon,
         code = ",",
-        applicability = "machine-applicable"
+        applicability = "machine-applicable",
+        style = "verbose"
     )]
     UseComma {
         #[primary_span]
@@ -867,7 +925,7 @@ pub(crate) struct InvalidLiteralSuffixOnTupleIndex {
 #[diag(parse_non_string_abi_literal)]
 pub(crate) struct NonStringAbiLiteral {
     #[primary_span]
-    #[suggestion(code = "\"C\"", applicability = "maybe-incorrect")]
+    #[suggestion(code = "\"C\"", applicability = "maybe-incorrect", style = "verbose")]
     pub span: Span,
 }
 
@@ -890,7 +948,7 @@ pub(crate) struct MismatchedClosingDelimiter {
 #[help]
 pub(crate) struct IncorrectVisibilityRestriction {
     #[primary_span]
-    #[suggestion(code = "in {inner_str}", applicability = "machine-applicable")]
+    #[suggestion(code = "in {inner_str}", applicability = "machine-applicable", style = "verbose")]
     pub span: Span,
     pub inner_str: String,
 }
@@ -915,7 +973,7 @@ pub(crate) struct ExpectedStatementAfterOuterAttr {
 pub(crate) struct DocCommentDoesNotDocumentAnything {
     #[primary_span]
     pub span: Span,
-    #[suggestion(code = ",", applicability = "machine-applicable")]
+    #[suggestion(code = ",", applicability = "machine-applicable", style = "verbose")]
     pub missing_comma: Option<Span>,
 }
 
@@ -923,7 +981,7 @@ pub(crate) struct DocCommentDoesNotDocumentAnything {
 #[diag(parse_const_let_mutually_exclusive)]
 pub(crate) struct ConstLetMutuallyExclusive {
     #[primary_span]
-    #[suggestion(code = "const", applicability = "maybe-incorrect")]
+    #[suggestion(code = "const", applicability = "maybe-incorrect", style = "verbose")]
     pub span: Span,
 }
 
@@ -951,8 +1009,9 @@ pub(crate) struct InvalidCurlyInLetElse {
 #[help]
 pub(crate) struct CompoundAssignmentExpressionInLet {
     #[primary_span]
-    #[suggestion(style = "short", code = "=", applicability = "maybe-incorrect")]
     pub span: Span,
+    #[suggestion(style = "verbose", code = "", applicability = "maybe-incorrect")]
+    pub suggestion: Span,
 }
 
 #[derive(Diagnostic)]
@@ -996,7 +1055,12 @@ pub(crate) struct SuggEscapeIdentifier {
 }
 
 #[derive(Subdiagnostic)]
-#[suggestion(parse_sugg_remove_comma, applicability = "machine-applicable", code = "")]
+#[suggestion(
+    parse_sugg_remove_comma,
+    applicability = "machine-applicable",
+    code = "",
+    style = "verbose"
+)]
 pub(crate) struct SuggRemoveComma {
     #[primary_span]
     pub span: Span,
@@ -1147,13 +1211,18 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for ExpectedSemi {
 
 #[derive(Subdiagnostic)]
 pub(crate) enum ExpectedSemiSugg {
-    #[suggestion(parse_sugg_change_this_to_semi, code = ";", applicability = "machine-applicable")]
+    #[suggestion(
+        parse_sugg_change_this_to_semi,
+        code = ";",
+        applicability = "machine-applicable",
+        style = "short"
+    )]
     ChangeToSemi(#[primary_span] Span),
     #[suggestion(
         parse_sugg_add_semi,
-        style = "short",
         code = ";",
-        applicability = "machine-applicable"
+        applicability = "machine-applicable",
+        style = "short"
     )]
     AddSemi(#[primary_span] Span),
 }
@@ -1198,7 +1267,7 @@ pub(crate) struct StructLiteralNeedingParensSugg {
 #[diag(parse_unmatched_angle_brackets)]
 pub(crate) struct UnmatchedAngleBrackets {
     #[primary_span]
-    #[suggestion(code = "", applicability = "machine-applicable")]
+    #[suggestion(code = "", applicability = "machine-applicable", style = "verbose")]
     pub span: Span,
     pub num_extra_brackets: usize,
 }
@@ -1337,7 +1406,7 @@ pub(crate) struct AttributeOnParamType {
 #[diag(parse_pattern_method_param_without_body, code = E0642)]
 pub(crate) struct PatternMethodParamWithoutBody {
     #[primary_span]
-    #[suggestion(code = "_", applicability = "machine-applicable")]
+    #[suggestion(code = "_", applicability = "machine-applicable", style = "verbose")]
     pub span: Span,
 }
 
@@ -1421,7 +1490,7 @@ pub(crate) struct AsyncMoveOrderIncorrect {
 pub(crate) struct DoubleColonInBound {
     #[primary_span]
     pub span: Span,
-    #[suggestion(code = ": ", applicability = "machine-applicable")]
+    #[suggestion(code = ": ", applicability = "machine-applicable", style = "verbose")]
     pub between: Span,
 }
 
@@ -1500,9 +1569,11 @@ pub(crate) struct ExpectedFnPathFoundFnKeyword {
 #[diag(parse_path_single_colon)]
 pub(crate) struct PathSingleColon {
     #[primary_span]
-    #[suggestion(applicability = "machine-applicable", code = "::")]
     pub span: Span,
 
+    #[suggestion(applicability = "machine-applicable", code = ":", style = "verbose")]
+    pub suggestion: Span,
+
     #[note(parse_type_ascription_removed)]
     pub type_ascription: Option<()>,
 }
@@ -1511,7 +1582,7 @@ pub(crate) struct PathSingleColon {
 #[diag(parse_colon_as_semi)]
 pub(crate) struct ColonAsSemi {
     #[primary_span]
-    #[suggestion(applicability = "machine-applicable", code = ";")]
+    #[suggestion(applicability = "machine-applicable", code = ";", style = "verbose")]
     pub span: Span,
 
     #[note(parse_type_ascription_removed)]
@@ -1612,28 +1683,44 @@ pub(crate) struct DefaultNotFollowedByItem {
 
 #[derive(Diagnostic)]
 pub(crate) enum MissingKeywordForItemDefinition {
+    #[diag(parse_missing_enum_for_enum_definition)]
+    Enum {
+        #[primary_span]
+        span: Span,
+        #[suggestion(style = "verbose", applicability = "maybe-incorrect", code = "enum ")]
+        insert_span: Span,
+        ident: Ident,
+    },
+    #[diag(parse_missing_enum_or_struct_for_item_definition)]
+    EnumOrStruct {
+        #[primary_span]
+        span: Span,
+    },
     #[diag(parse_missing_struct_for_struct_definition)]
     Struct {
         #[primary_span]
-        #[suggestion(style = "short", applicability = "maybe-incorrect", code = " struct ")]
         span: Span,
+        #[suggestion(style = "verbose", applicability = "maybe-incorrect", code = "struct ")]
+        insert_span: Span,
         ident: Ident,
     },
     #[diag(parse_missing_fn_for_function_definition)]
     Function {
         #[primary_span]
-        #[suggestion(style = "short", applicability = "maybe-incorrect", code = " fn ")]
         span: Span,
+        #[suggestion(style = "verbose", applicability = "maybe-incorrect", code = "fn ")]
+        insert_span: Span,
         ident: Ident,
     },
     #[diag(parse_missing_fn_for_method_definition)]
     Method {
         #[primary_span]
-        #[suggestion(style = "short", applicability = "maybe-incorrect", code = " fn ")]
         span: Span,
+        #[suggestion(style = "verbose", applicability = "maybe-incorrect", code = "fn ")]
+        insert_span: Span,
         ident: Ident,
     },
-    #[diag(parse_ambiguous_missing_keyword_for_item_definition)]
+    #[diag(parse_missing_fn_or_struct_for_item_definition)]
     Ambiguous {
         #[primary_span]
         span: Span,
@@ -1644,7 +1731,12 @@ pub(crate) enum MissingKeywordForItemDefinition {
 
 #[derive(Subdiagnostic)]
 pub(crate) enum AmbiguousMissingKwForItemSub {
-    #[suggestion(parse_suggestion, applicability = "maybe-incorrect", code = "{snippet}!")]
+    #[suggestion(
+        parse_suggestion,
+        applicability = "maybe-incorrect",
+        code = "{snippet}!",
+        style = "verbose"
+    )]
     SuggestMacro {
         #[primary_span]
         span: Span,
@@ -1658,7 +1750,7 @@ pub(crate) enum AmbiguousMissingKwForItemSub {
 #[diag(parse_missing_fn_params)]
 pub(crate) struct MissingFnParams {
     #[primary_span]
-    #[suggestion(code = "()", applicability = "machine-applicable", style = "short")]
+    #[suggestion(code = "()", applicability = "machine-applicable", style = "verbose")]
     pub span: Span,
 }
 
@@ -1666,9 +1758,19 @@ pub(crate) struct MissingFnParams {
 #[diag(parse_missing_trait_in_trait_impl)]
 pub(crate) struct MissingTraitInTraitImpl {
     #[primary_span]
-    #[suggestion(parse_suggestion_add_trait, code = " Trait ", applicability = "has-placeholders")]
+    #[suggestion(
+        parse_suggestion_add_trait,
+        code = " Trait ",
+        applicability = "has-placeholders",
+        style = "verbose"
+    )]
     pub span: Span,
-    #[suggestion(parse_suggestion_remove_for, code = "", applicability = "maybe-incorrect")]
+    #[suggestion(
+        parse_suggestion_remove_for,
+        code = "",
+        applicability = "maybe-incorrect",
+        style = "verbose"
+    )]
     pub for_span: Span,
 }
 
@@ -1676,7 +1778,7 @@ pub(crate) struct MissingTraitInTraitImpl {
 #[diag(parse_missing_for_in_trait_impl)]
 pub(crate) struct MissingForInTraitImpl {
     #[primary_span]
-    #[suggestion(style = "short", code = " for ", applicability = "machine-applicable")]
+    #[suggestion(style = "verbose", code = " for ", applicability = "machine-applicable")]
     pub span: Span,
 }
 
@@ -1691,7 +1793,7 @@ pub(crate) struct ExpectedTraitInTraitImplFoundType {
 #[diag(parse_extra_impl_keyword_in_trait_impl)]
 pub(crate) struct ExtraImplKeywordInTraitImpl {
     #[primary_span]
-    #[suggestion(code = "", applicability = "maybe-incorrect")]
+    #[suggestion(code = "", applicability = "maybe-incorrect", style = "short")]
     pub extra_impl_kw: Span,
     #[note]
     pub impl_trait_span: Span,
@@ -1750,7 +1852,7 @@ pub(crate) struct ExternCrateNameWithDashesSugg {
 pub(crate) struct ExternItemCannotBeConst {
     #[primary_span]
     pub ident_span: Span,
-    #[suggestion(code = "static ", applicability = "machine-applicable")]
+    #[suggestion(code = "static ", applicability = "machine-applicable", style = "verbose")]
     pub const_span: Option<Span>,
 }
 
@@ -1760,7 +1862,7 @@ pub(crate) struct ConstGlobalCannotBeMutable {
     #[primary_span]
     #[label]
     pub ident_span: Span,
-    #[suggestion(code = "static", applicability = "maybe-incorrect")]
+    #[suggestion(code = "static", style = "verbose", applicability = "maybe-incorrect")]
     pub const_span: Span,
 }
 
@@ -1768,7 +1870,7 @@ pub(crate) struct ConstGlobalCannotBeMutable {
 #[diag(parse_missing_const_type)]
 pub(crate) struct MissingConstType {
     #[primary_span]
-    #[suggestion(code = "{colon} <type>", applicability = "has-placeholders")]
+    #[suggestion(code = "{colon} <type>", style = "verbose", applicability = "has-placeholders")]
     pub span: Span,
 
     pub kind: &'static str,
@@ -1779,7 +1881,7 @@ pub(crate) struct MissingConstType {
 #[diag(parse_enum_struct_mutually_exclusive)]
 pub(crate) struct EnumStructMutuallyExclusive {
     #[primary_span]
-    #[suggestion(code = "enum", applicability = "machine-applicable")]
+    #[suggestion(code = "enum", style = "verbose", applicability = "machine-applicable")]
     pub span: Span,
 }
 
@@ -2016,7 +2118,12 @@ pub struct UnknownTokenStart {
 
 #[derive(Subdiagnostic)]
 pub enum TokenSubstitution {
-    #[suggestion(parse_sugg_quotes, code = "{suggestion}", applicability = "maybe-incorrect")]
+    #[suggestion(
+        parse_sugg_quotes,
+        code = "{suggestion}",
+        applicability = "maybe-incorrect",
+        style = "verbose"
+    )]
     DirectedQuotes {
         #[primary_span]
         span: Span,
@@ -2024,7 +2131,12 @@ pub enum TokenSubstitution {
         ascii_str: &'static str,
         ascii_name: &'static str,
     },
-    #[suggestion(parse_sugg_other, code = "{suggestion}", applicability = "maybe-incorrect")]
+    #[suggestion(
+        parse_sugg_other,
+        code = "{suggestion}",
+        applicability = "maybe-incorrect",
+        style = "verbose"
+    )]
     Other {
         #[primary_span]
         span: Span,
@@ -2060,7 +2172,12 @@ pub enum UnescapeError {
     EscapeOnlyChar {
         #[primary_span]
         span: Span,
-        #[suggestion(parse_escape, applicability = "machine-applicable", code = "{escaped_sugg}")]
+        #[suggestion(
+            parse_escape,
+            applicability = "machine-applicable",
+            code = "{escaped_sugg}",
+            style = "verbose"
+        )]
         char_span: Span,
         escaped_sugg: String,
         escaped_msg: String,
@@ -2069,7 +2186,12 @@ pub enum UnescapeError {
     #[diag(parse_bare_cr)]
     BareCr {
         #[primary_span]
-        #[suggestion(parse_escape, applicability = "machine-applicable", code = "\\r")]
+        #[suggestion(
+            parse_escape,
+            applicability = "machine-applicable",
+            code = "\\r",
+            style = "verbose"
+        )]
         span: Span,
         double_quotes: bool,
     },
@@ -2186,7 +2308,8 @@ pub enum MoreThanOneCharSugg {
     #[suggestion(
         parse_consider_normalized,
         code = "{normalized}",
-        applicability = "machine-applicable"
+        applicability = "machine-applicable",
+        style = "verbose"
     )]
     NormalizedForm {
         #[primary_span]
@@ -2194,13 +2317,23 @@ pub enum MoreThanOneCharSugg {
         ch: String,
         normalized: String,
     },
-    #[suggestion(parse_remove_non, code = "{ch}", applicability = "maybe-incorrect")]
+    #[suggestion(
+        parse_remove_non,
+        code = "{ch}",
+        applicability = "maybe-incorrect",
+        style = "verbose"
+    )]
     RemoveNonPrinting {
         #[primary_span]
         span: Span,
         ch: String,
     },
-    #[suggestion(parse_use_double_quotes, code = "{sugg}", applicability = "machine-applicable")]
+    #[suggestion(
+        parse_use_double_quotes,
+        code = "{sugg}",
+        applicability = "machine-applicable",
+        style = "verbose"
+    )]
     QuotesFull {
         #[primary_span]
         span: Span,
@@ -2238,7 +2371,12 @@ pub enum MoreThanOneCharNote {
 
 #[derive(Subdiagnostic)]
 pub enum NoBraceUnicodeSub {
-    #[suggestion(parse_use_braces, code = "{suggestion}", applicability = "maybe-incorrect")]
+    #[suggestion(
+        parse_use_braces,
+        code = "{suggestion}",
+        applicability = "maybe-incorrect",
+        style = "verbose"
+    )]
     Suggestion {
         #[primary_span]
         span: Span,
@@ -2249,26 +2387,31 @@ pub enum NoBraceUnicodeSub {
 }
 
 #[derive(Subdiagnostic)]
+#[multipart_suggestion(parse_sugg_wrap_pattern_in_parens, applicability = "machine-applicable")]
+pub(crate) struct WrapInParens {
+    #[suggestion_part(code = "(")]
+    pub(crate) lo: Span,
+    #[suggestion_part(code = ")")]
+    pub(crate) hi: Span,
+}
+
+#[derive(Subdiagnostic)]
 pub(crate) enum TopLevelOrPatternNotAllowedSugg {
     #[suggestion(
         parse_sugg_remove_leading_vert_in_pattern,
-        code = "{pat}",
-        applicability = "machine-applicable"
+        code = "",
+        applicability = "machine-applicable",
+        style = "verbose"
     )]
     RemoveLeadingVert {
         #[primary_span]
         span: Span,
-        pat: String,
     },
-    #[suggestion(
-        parse_sugg_wrap_pattern_in_parens,
-        code = "({pat})",
-        applicability = "machine-applicable"
-    )]
     WrapInParens {
         #[primary_span]
         span: Span,
-        pat: String,
+        #[subdiagnostic]
+        suggestion: WrapInParens,
     },
 }
 
@@ -2277,7 +2420,7 @@ pub(crate) enum TopLevelOrPatternNotAllowedSugg {
 #[note(parse_note_pattern_alternatives_use_single_vert)]
 pub(crate) struct UnexpectedVertVertBeforeFunctionParam {
     #[primary_span]
-    #[suggestion(code = "", applicability = "machine-applicable")]
+    #[suggestion(code = "", applicability = "machine-applicable", style = "verbose")]
     pub span: Span,
 }
 
@@ -2285,7 +2428,7 @@ pub(crate) struct UnexpectedVertVertBeforeFunctionParam {
 #[diag(parse_unexpected_vert_vert_in_pattern)]
 pub(crate) struct UnexpectedVertVertInPattern {
     #[primary_span]
-    #[suggestion(code = "|", applicability = "machine-applicable")]
+    #[suggestion(code = "|", applicability = "machine-applicable", style = "verbose")]
     pub span: Span,
     #[label(parse_label_while_parsing_or_pattern_here)]
     pub start: Option<Span>,
@@ -2295,7 +2438,7 @@ pub(crate) struct UnexpectedVertVertInPattern {
 #[diag(parse_trailing_vert_not_allowed)]
 pub(crate) struct TrailingVertNotAllowed {
     #[primary_span]
-    #[suggestion(code = "", applicability = "machine-applicable")]
+    #[suggestion(code = "", applicability = "machine-applicable", style = "verbose")]
     pub span: Span,
     #[label(parse_label_while_parsing_or_pattern_here)]
     pub start: Option<Span>,
@@ -2308,16 +2451,17 @@ pub(crate) struct TrailingVertNotAllowed {
 #[diag(parse_dotdotdot_rest_pattern)]
 pub(crate) struct DotDotDotRestPattern {
     #[primary_span]
-    #[suggestion(style = "short", code = "..", applicability = "machine-applicable")]
     #[label]
     pub span: Span,
+    #[suggestion(style = "verbose", code = "", applicability = "machine-applicable")]
+    pub suggestion: Span,
 }
 
 #[derive(Diagnostic)]
 #[diag(parse_pattern_on_wrong_side_of_at)]
 pub(crate) struct PatternOnWrongSideOfAt {
     #[primary_span]
-    #[suggestion(code = "{whole_pat}", applicability = "machine-applicable")]
+    #[suggestion(code = "{whole_pat}", applicability = "machine-applicable", style = "verbose")]
     pub whole_span: Span,
     pub whole_pat: String,
     #[label(parse_label_pattern)]
@@ -2338,22 +2482,35 @@ pub(crate) struct ExpectedBindingLeftOfAt {
     pub rhs: Span,
 }
 
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(
+    parse_ambiguous_range_pattern_suggestion,
+    applicability = "machine-applicable"
+)]
+pub(crate) struct ParenRangeSuggestion {
+    #[suggestion_part(code = "(")]
+    pub lo: Span,
+    #[suggestion_part(code = ")")]
+    pub hi: Span,
+}
+
 #[derive(Diagnostic)]
 #[diag(parse_ambiguous_range_pattern)]
 pub(crate) struct AmbiguousRangePattern {
     #[primary_span]
-    #[suggestion(code = "({pat})", applicability = "maybe-incorrect")]
     pub span: Span,
-    pub pat: String,
+    #[subdiagnostic]
+    pub suggestion: ParenRangeSuggestion,
 }
 
 #[derive(Diagnostic)]
 #[diag(parse_unexpected_lifetime_in_pattern)]
 pub(crate) struct UnexpectedLifetimeInPattern {
     #[primary_span]
-    #[suggestion(code = "", applicability = "machine-applicable")]
     pub span: Span,
     pub symbol: Symbol,
+    #[suggestion(code = "", applicability = "machine-applicable", style = "verbose")]
+    pub suggestion: Span,
 }
 
 #[derive(Diagnostic)]
@@ -2362,7 +2519,7 @@ pub(crate) enum InvalidMutInPattern {
     #[note(parse_note_mut_pattern_usage)]
     NestedIdent {
         #[primary_span]
-        #[suggestion(code = "{pat}", applicability = "machine-applicable")]
+        #[suggestion(code = "{pat}", applicability = "machine-applicable", style = "verbose")]
         span: Span,
         pat: String,
     },
@@ -2370,7 +2527,7 @@ pub(crate) enum InvalidMutInPattern {
     #[note(parse_note_mut_pattern_usage)]
     NonIdent {
         #[primary_span]
-        #[suggestion(code = "", applicability = "machine-applicable")]
+        #[suggestion(code = "", applicability = "machine-applicable", style = "verbose")]
         span: Span,
     },
 }
@@ -2379,7 +2536,7 @@ pub(crate) enum InvalidMutInPattern {
 #[diag(parse_repeated_mut_in_pattern)]
 pub(crate) struct RepeatedMutInPattern {
     #[primary_span]
-    #[suggestion(code = "", applicability = "machine-applicable")]
+    #[suggestion(code = "", applicability = "machine-applicable", style = "verbose")]
     pub span: Span,
 }
 
@@ -2387,7 +2544,7 @@ pub(crate) struct RepeatedMutInPattern {
 #[diag(parse_dot_dot_dot_range_to_pattern_not_allowed)]
 pub(crate) struct DotDotDotRangeToPatternNotAllowed {
     #[primary_span]
-    #[suggestion(style = "short", code = "..=", applicability = "machine-applicable")]
+    #[suggestion(style = "verbose", code = "..=", applicability = "machine-applicable")]
     pub span: Span,
 }
 
@@ -2451,8 +2608,9 @@ pub(crate) struct UnexpectedParenInRangePatSugg {
 #[diag(parse_return_types_use_thin_arrow)]
 pub(crate) struct ReturnTypesUseThinArrow {
     #[primary_span]
-    #[suggestion(style = "short", code = "->", applicability = "machine-applicable")]
     pub span: Span,
+    #[suggestion(style = "verbose", code = " -> ", applicability = "machine-applicable")]
+    pub suggestion: Span,
 }
 
 #[derive(Diagnostic)]
@@ -2467,7 +2625,7 @@ pub(crate) struct NeedPlusAfterTraitObjectLifetime {
 pub(crate) struct ExpectedMutOrConstInRawPointerType {
     #[primary_span]
     pub span: Span,
-    #[suggestion(code("mut ", "const "), applicability = "has-placeholders")]
+    #[suggestion(code("mut ", "const "), applicability = "has-placeholders", style = "verbose")]
     pub after_asterisk: Span,
 }
 
@@ -2476,7 +2634,7 @@ pub(crate) struct ExpectedMutOrConstInRawPointerType {
 pub(crate) struct LifetimeAfterMut {
     #[primary_span]
     pub span: Span,
-    #[suggestion(code = "&{snippet} mut", applicability = "maybe-incorrect")]
+    #[suggestion(code = "&{snippet} mut", applicability = "maybe-incorrect", style = "verbose")]
     pub suggest_lifetime: Option<Span>,
     pub snippet: String,
 }
@@ -2485,7 +2643,7 @@ pub(crate) struct LifetimeAfterMut {
 #[diag(parse_dyn_after_mut)]
 pub(crate) struct DynAfterMut {
     #[primary_span]
-    #[suggestion(code = "&mut dyn", applicability = "machine-applicable")]
+    #[suggestion(code = "&mut dyn", applicability = "machine-applicable", style = "verbose")]
     pub span: Span,
 }
 
@@ -2494,7 +2652,7 @@ pub(crate) struct DynAfterMut {
 pub(crate) struct FnPointerCannotBeConst {
     #[primary_span]
     pub span: Span,
-    #[suggestion(code = "", applicability = "maybe-incorrect")]
+    #[suggestion(code = "", applicability = "maybe-incorrect", style = "verbose")]
     #[label]
     pub qualifier: Span,
 }
@@ -2504,7 +2662,7 @@ pub(crate) struct FnPointerCannotBeConst {
 pub(crate) struct FnPointerCannotBeAsync {
     #[primary_span]
     pub span: Span,
-    #[suggestion(code = "", applicability = "maybe-incorrect")]
+    #[suggestion(code = "", applicability = "maybe-incorrect", style = "verbose")]
     #[label]
     pub qualifier: Span,
 }
@@ -2521,8 +2679,9 @@ pub(crate) struct NestedCVariadicType {
 #[help]
 pub(crate) struct InvalidDynKeyword {
     #[primary_span]
-    #[suggestion(code = "", applicability = "machine-applicable")]
     pub span: Span,
+    #[suggestion(code = "", applicability = "machine-applicable", style = "verbose")]
+    pub suggestion: Span,
 }
 
 #[derive(Subdiagnostic)]
@@ -2563,7 +2722,7 @@ pub struct BoxSyntaxRemoved<'a> {
 #[diag(parse_bad_return_type_notation_output)]
 pub(crate) struct BadReturnTypeNotationOutput {
     #[primary_span]
-    #[suggestion(code = "", applicability = "maybe-incorrect")]
+    #[suggestion(code = "", applicability = "maybe-incorrect", style = "verbose")]
     pub span: Span,
 }
 
@@ -2634,14 +2793,25 @@ pub(crate) struct ModifierLifetime {
     pub modifier: &'static str,
 }
 
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(
+    parse_parenthesized_lifetime_suggestion,
+    applicability = "machine-applicable"
+)]
+pub(crate) struct RemoveParens {
+    #[suggestion_part(code = "")]
+    pub lo: Span,
+    #[suggestion_part(code = "")]
+    pub hi: Span,
+}
+
 #[derive(Diagnostic)]
 #[diag(parse_parenthesized_lifetime)]
 pub(crate) struct ParenthesizedLifetime {
     #[primary_span]
     pub span: Span,
-    #[suggestion(style = "short", applicability = "machine-applicable", code = "{snippet}")]
-    pub sugg: Option<Span>,
-    pub snippet: String,
+    #[subdiagnostic]
+    pub sugg: RemoveParens,
 }
 
 #[derive(Diagnostic)]
@@ -2656,7 +2826,7 @@ pub(crate) struct UnderscoreLiteralSuffix {
 pub(crate) struct ExpectedLabelFoundIdent {
     #[primary_span]
     pub span: Span,
-    #[suggestion(code = "'", applicability = "machine-applicable", style = "short")]
+    #[suggestion(code = "'", applicability = "machine-applicable", style = "verbose")]
     pub start: Span,
 }
 
@@ -2675,7 +2845,7 @@ pub(crate) struct InappropriateDefault {
 #[diag(parse_recover_import_as_use)]
 pub(crate) struct RecoverImportAsUse {
     #[primary_span]
-    #[suggestion(code = "use", applicability = "machine-applicable", style = "short")]
+    #[suggestion(code = "use", applicability = "machine-applicable", style = "verbose")]
     pub span: Span,
     pub token_name: String,
 }
@@ -2685,7 +2855,7 @@ pub(crate) struct RecoverImportAsUse {
 #[note]
 pub(crate) struct SingleColonImportPath {
     #[primary_span]
-    #[suggestion(code = "::", applicability = "machine-applicable", style = "short")]
+    #[suggestion(code = "::", applicability = "machine-applicable", style = "verbose")]
     pub span: Span,
 }
 
@@ -2712,7 +2882,7 @@ pub(crate) struct SingleColonStructType {
 #[diag(parse_equals_struct_default)]
 pub(crate) struct EqualsStructDefault {
     #[primary_span]
-    #[suggestion(code = "", applicability = "machine-applicable")]
+    #[suggestion(code = "", applicability = "machine-applicable", style = "verbose")]
     pub span: Span,
 }
 
@@ -2729,7 +2899,7 @@ pub(crate) struct MacroRulesMissingBang {
 #[diag(parse_macro_name_remove_bang)]
 pub(crate) struct MacroNameRemoveBang {
     #[primary_span]
-    #[suggestion(code = "", applicability = "machine-applicable")]
+    #[suggestion(code = "", applicability = "machine-applicable", style = "short")]
     pub span: Span,
 }
 
@@ -2737,7 +2907,7 @@ pub(crate) struct MacroNameRemoveBang {
 #[diag(parse_macro_rules_visibility)]
 pub(crate) struct MacroRulesVisibility<'a> {
     #[primary_span]
-    #[suggestion(code = "#[macro_export]", applicability = "maybe-incorrect")]
+    #[suggestion(code = "#[macro_export]", applicability = "maybe-incorrect", style = "verbose")]
     pub span: Span,
     pub vis: &'a str,
 }
@@ -2747,7 +2917,7 @@ pub(crate) struct MacroRulesVisibility<'a> {
 #[help]
 pub(crate) struct MacroInvocationVisibility<'a> {
     #[primary_span]
-    #[suggestion(code = "", applicability = "machine-applicable")]
+    #[suggestion(code = "", applicability = "machine-applicable", style = "verbose")]
     pub span: Span,
     pub vis: &'a str,
 }
@@ -2757,7 +2927,7 @@ pub(crate) struct MacroInvocationVisibility<'a> {
 pub(crate) struct NestedAdt<'a> {
     #[primary_span]
     pub span: Span,
-    #[suggestion(code = "", applicability = "maybe-incorrect")]
+    #[suggestion(code = "", applicability = "maybe-incorrect", style = "verbose")]
     pub item: Span,
     pub keyword: &'a str,
     pub kw_str: Cow<'a, str>,
@@ -2797,7 +2967,7 @@ pub(crate) struct BoxNotPat {
 #[diag(parse_unmatched_angle)]
 pub(crate) struct UnmatchedAngle {
     #[primary_span]
-    #[suggestion(code = "", applicability = "machine-applicable")]
+    #[suggestion(code = "", applicability = "machine-applicable", style = "verbose")]
     pub span: Span,
     pub plural: bool,
 }
@@ -2837,7 +3007,7 @@ pub(crate) struct IncorrectParensTraitBoundsSugg {
 #[diag(parse_kw_bad_case)]
 pub(crate) struct KwBadCase<'a> {
     #[primary_span]
-    #[suggestion(code = "{kw}", applicability = "machine-applicable")]
+    #[suggestion(code = "{kw}", style = "verbose", applicability = "machine-applicable")]
     pub span: Span,
     pub kw: &'a str,
 }
@@ -2874,7 +3044,7 @@ pub(crate) struct MetaBadDelimSugg {
 #[note]
 pub(crate) struct MalformedCfgAttr {
     #[primary_span]
-    #[suggestion(code = "{sugg}")]
+    #[suggestion(style = "verbose", code = "{sugg}")]
     pub span: Span,
     pub sugg: &'static str,
 }
@@ -2979,7 +3149,7 @@ pub(crate) struct AsyncImpl {
 #[help]
 pub(crate) struct ExprRArrowCall {
     #[primary_span]
-    #[suggestion(style = "short", applicability = "machine-applicable", code = ".")]
+    #[suggestion(style = "verbose", applicability = "machine-applicable", code = ".")]
     pub span: Span,
 }
 
diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs
index 5522127be83..4454747ea02 100644
--- a/compiler/rustc_parse/src/lib.rs
+++ b/compiler/rustc_parse/src/lib.rs
@@ -157,14 +157,14 @@ pub fn fake_token_stream_for_crate(psess: &ParseSess, krate: &ast::Crate) -> Tok
 }
 
 pub fn parse_cfg_attr(
-    attr: &Attribute,
+    cfg_attr: &Attribute,
     psess: &ParseSess,
 ) -> Option<(MetaItem, Vec<(AttrItem, Span)>)> {
     const CFG_ATTR_GRAMMAR_HELP: &str = "#[cfg_attr(condition, attribute, other_attribute, ...)]";
     const CFG_ATTR_NOTE_REF: &str = "for more information, visit \
         <https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute>";
 
-    match attr.get_normal_item().args {
+    match cfg_attr.get_normal_item().args {
         ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, ref tokens })
             if !tokens.is_empty() =>
         {
@@ -180,7 +180,7 @@ pub fn parse_cfg_attr(
         }
         _ => {
             psess.dcx().emit_err(errors::MalformedCfgAttr {
-                span: attr.span,
+                span: cfg_attr.span,
                 sugg: CFG_ATTR_GRAMMAR_HELP,
             });
         }
diff --git a/compiler/rustc_parse/src/parser/attr_wrapper.rs b/compiler/rustc_parse/src/parser/attr_wrapper.rs
index 38f18022e3c..1123c31f551 100644
--- a/compiler/rustc_parse/src/parser/attr_wrapper.rs
+++ b/compiler/rustc_parse/src/parser/attr_wrapper.rs
@@ -103,11 +103,8 @@ impl ToAttrTokenStream for LazyAttrTokenStreamImpl {
         // produce an empty `TokenStream` if no calls were made, and omit the
         // final token otherwise.
         let mut cursor_snapshot = self.cursor_snapshot.clone();
-        let tokens = iter::once((FlatToken::Token(self.start_token.0.clone()), self.start_token.1))
-            .chain(iter::repeat_with(|| {
-                let token = cursor_snapshot.next();
-                (FlatToken::Token(token.0), token.1)
-            }))
+        let tokens = iter::once(FlatToken::Token(self.start_token.clone()))
+            .chain(iter::repeat_with(|| FlatToken::Token(cursor_snapshot.next())))
             .take(self.num_calls as usize);
 
         if self.replace_ranges.is_empty() {
@@ -156,11 +153,8 @@ impl ToAttrTokenStream for LazyAttrTokenStreamImpl {
                     (range.start as usize)..(range.end as usize),
                     target
                         .into_iter()
-                        .map(|target| (FlatToken::AttrsTarget(target), Spacing::Alone))
-                        .chain(
-                            iter::repeat((FlatToken::Empty, Spacing::Alone))
-                                .take(range.len() - target_len),
-                        ),
+                        .map(|target| FlatToken::AttrsTarget(target))
+                        .chain(iter::repeat(FlatToken::Empty).take(range.len() - target_len)),
                 );
             }
             make_attr_token_stream(tokens.into_iter(), self.break_last_token)
@@ -301,21 +295,22 @@ impl<'a> Parser<'a> {
 
         let num_calls = end_pos - start_pos;
 
-        // If we have no attributes, then we will never need to
-        // use any replace ranges.
-        let replace_ranges: Box<[ReplaceRange]> = if ret.attrs().is_empty() && !self.capture_cfg {
-            Box::new([])
-        } else {
-            // Grab any replace ranges that occur *inside* the current AST node.
-            // We will perform the actual replacement when we convert the `LazyAttrTokenStream`
-            // to an `AttrTokenStream`.
-            self.capture_state.replace_ranges[replace_ranges_start..replace_ranges_end]
-                .iter()
-                .cloned()
-                .chain(inner_attr_replace_ranges.iter().cloned())
-                .map(|(range, data)| ((range.start - start_pos)..(range.end - start_pos), data))
-                .collect()
-        };
+        // This is hot enough for `deep-vector` that checking the conditions for an empty iterator
+        // is measurably faster than actually executing the iterator.
+        let replace_ranges: Box<[ReplaceRange]> =
+            if replace_ranges_start == replace_ranges_end && inner_attr_replace_ranges.is_empty() {
+                Box::new([])
+            } else {
+                // Grab any replace ranges that occur *inside* the current AST node.
+                // We will perform the actual replacement when we convert the `LazyAttrTokenStream`
+                // to an `AttrTokenStream`.
+                self.capture_state.replace_ranges[replace_ranges_start..replace_ranges_end]
+                    .iter()
+                    .cloned()
+                    .chain(inner_attr_replace_ranges.iter().cloned())
+                    .map(|(range, data)| ((range.start - start_pos)..(range.end - start_pos), data))
+                    .collect()
+            };
 
         let tokens = LazyAttrTokenStream::new(LazyAttrTokenStreamImpl {
             start_token,
@@ -325,12 +320,9 @@ impl<'a> Parser<'a> {
             replace_ranges,
         });
 
-        // If we support tokens at all
-        if let Some(target_tokens) = ret.tokens_mut() {
-            if target_tokens.is_none() {
-                // Store our newly captured tokens into the AST node.
-                *target_tokens = Some(tokens.clone());
-            }
+        // If we support tokens and don't already have them, store the newly captured tokens.
+        if let Some(target_tokens @ None) = ret.tokens_mut() {
+            *target_tokens = Some(tokens.clone());
         }
 
         let final_attrs = ret.attrs();
@@ -352,15 +344,10 @@ impl<'a> Parser<'a> {
             let target = AttrsTarget { attrs: final_attrs.iter().cloned().collect(), tokens };
             self.capture_state.replace_ranges.push((start_pos..end_pos, Some(target)));
             self.capture_state.replace_ranges.extend(inner_attr_replace_ranges);
-        }
-
-        // Only clear our `replace_ranges` when we're finished capturing entirely.
-        if matches!(self.capture_state.capturing, Capturing::No) {
+        } else if matches!(self.capture_state.capturing, Capturing::No) {
+            // Only clear the ranges once we've finished capturing entirely.
             self.capture_state.replace_ranges.clear();
-            // We don't clear `inner_attr_ranges`, as doing so repeatedly
-            // had a measurable performance impact. Most inner attributes that
-            // we insert will get removed - when we drop the parser, we'll free
-            // up the memory used by any attributes that we didn't remove from the map.
+            self.capture_state.inner_attr_ranges.clear();
         }
         Ok(ret)
     }
@@ -370,7 +357,7 @@ impl<'a> Parser<'a> {
 /// `AttrTokenStream`, creating an `AttrTokenTree::Delimited` for each matching pair of open and
 /// close delims.
 fn make_attr_token_stream(
-    mut iter: impl Iterator<Item = (FlatToken, Spacing)>,
+    iter: impl Iterator<Item = FlatToken>,
     break_last_token: bool,
 ) -> AttrTokenStream {
     #[derive(Debug)]
@@ -379,19 +366,19 @@ fn make_attr_token_stream(
         open_delim_sp: Option<(Delimiter, Span, Spacing)>,
         inner: Vec<AttrTokenTree>,
     }
-    let mut stack = vec![FrameData { open_delim_sp: None, inner: vec![] }];
-    let mut token_and_spacing = iter.next();
-    while let Some((token, spacing)) = token_and_spacing {
-        match token {
-            FlatToken::Token(Token { kind: TokenKind::OpenDelim(delim), span }) => {
-                stack
-                    .push(FrameData { open_delim_sp: Some((delim, span, spacing)), inner: vec![] });
+    // The stack always has at least one element. Storing it separately makes for shorter code.
+    let mut stack_top = FrameData { open_delim_sp: None, inner: vec![] };
+    let mut stack_rest = vec![];
+    for flat_token in iter {
+        match flat_token {
+            FlatToken::Token((Token { kind: TokenKind::OpenDelim(delim), span }, spacing)) => {
+                stack_rest.push(mem::replace(
+                    &mut stack_top,
+                    FrameData { open_delim_sp: Some((delim, span, spacing)), inner: vec![] },
+                ));
             }
-            FlatToken::Token(Token { kind: TokenKind::CloseDelim(delim), span }) => {
-                let frame_data = stack
-                    .pop()
-                    .unwrap_or_else(|| panic!("Token stack was empty for token: {token:?}"));
-
+            FlatToken::Token((Token { kind: TokenKind::CloseDelim(delim), span }, spacing)) => {
+                let frame_data = mem::replace(&mut stack_top, stack_rest.pop().unwrap());
                 let (open_delim, open_sp, open_spacing) = frame_data.open_delim_sp.unwrap();
                 assert_eq!(
                     open_delim, delim,
@@ -401,29 +388,20 @@ fn make_attr_token_stream(
                 let dspacing = DelimSpacing::new(open_spacing, spacing);
                 let stream = AttrTokenStream::new(frame_data.inner);
                 let delimited = AttrTokenTree::Delimited(dspan, dspacing, delim, stream);
-                stack
-                    .last_mut()
-                    .unwrap_or_else(|| panic!("Bottom token frame is missing for token: {token:?}"))
-                    .inner
-                    .push(delimited);
+                stack_top.inner.push(delimited);
+            }
+            FlatToken::Token((token, spacing)) => {
+                stack_top.inner.push(AttrTokenTree::Token(token, spacing))
+            }
+            FlatToken::AttrsTarget(target) => {
+                stack_top.inner.push(AttrTokenTree::AttrsTarget(target))
             }
-            FlatToken::Token(token) => stack
-                .last_mut()
-                .expect("Bottom token frame is missing!")
-                .inner
-                .push(AttrTokenTree::Token(token, spacing)),
-            FlatToken::AttrsTarget(target) => stack
-                .last_mut()
-                .expect("Bottom token frame is missing!")
-                .inner
-                .push(AttrTokenTree::AttrsTarget(target)),
             FlatToken::Empty => {}
         }
-        token_and_spacing = iter.next();
     }
-    let mut final_buf = stack.pop().expect("Missing final buf!");
+
     if break_last_token {
-        let last_token = final_buf.inner.pop().unwrap();
+        let last_token = stack_top.inner.pop().unwrap();
         if let AttrTokenTree::Token(last_token, spacing) = last_token {
             let unglued_first = last_token.kind.break_two_token_op().unwrap().0;
 
@@ -431,14 +409,14 @@ fn make_attr_token_stream(
             let mut first_span = last_token.span.shrink_to_lo();
             first_span = first_span.with_hi(first_span.lo() + rustc_span::BytePos(1));
 
-            final_buf
+            stack_top
                 .inner
                 .push(AttrTokenTree::Token(Token::new(unglued_first, first_span), spacing));
         } else {
             panic!("Unexpected last token {last_token:?}")
         }
     }
-    AttrTokenStream::new(final_buf.inner)
+    AttrTokenStream::new(stack_top.inner)
 }
 
 // Some types are used a lot. Make sure they don't unintentionally get bigger.
diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs
index 81d5f0fca0e..0da7fefe6ed 100644
--- a/compiler/rustc_parse/src/parser/diagnostics.rs
+++ b/compiler/rustc_parse/src/parser/diagnostics.rs
@@ -3,8 +3,8 @@ use super::{
     BlockMode, CommaRecoveryMode, Parser, PathStyle, Restrictions, SemiColonMode, SeqSep, TokenType,
 };
 use crate::errors::{
-    AmbiguousPlus, AsyncMoveBlockIn2015, AttributeOnParamType, BadQPathStage2, BadTypePlus,
-    BadTypePlusSub, ColonAsSemi, ComparisonOperatorsCannotBeChained,
+    AddParen, AmbiguousPlus, AsyncMoveBlockIn2015, AttributeOnParamType, AwaitSuggestion,
+    BadQPathStage2, BadTypePlus, BadTypePlusSub, ColonAsSemi, ComparisonOperatorsCannotBeChained,
     ComparisonOperatorsCannotBeChainedSugg, ConstGenericWithoutBraces,
     ConstGenericWithoutBracesSugg, DocCommentDoesNotDocumentAnything, DocCommentOnParamType,
     DoubleColonInBound, ExpectedIdentifier, ExpectedSemi, ExpectedSemiSugg,
@@ -566,7 +566,10 @@ impl<'a> Parser<'a> {
             && expected.iter().any(|tok| matches!(tok, TokenType::Token(TokenKind::Eq)))
         {
             // Likely typo: `=` → `==` in let expr or enum item
-            return Err(self.dcx().create_err(UseEqInstead { span: self.token.span }));
+            return Err(self.dcx().create_err(UseEqInstead {
+                span: self.token.span,
+                suggestion: self.token.span.with_lo(self.token.span.lo() + BytePos(1)),
+            }));
         }
 
         if self.token.is_keyword(kw::Move) && self.prev_token.is_keyword(kw::Async) {
@@ -1151,7 +1154,7 @@ impl<'a> Parser<'a> {
             // Eat from where we started until the end token so that parsing can continue
             // as if we didn't have those extra angle brackets.
             self.eat_to_tokens(end);
-            let span = lo.until(self.token.span);
+            let span = lo.to(self.prev_token.span);
 
             let num_extra_brackets = number_of_gt + number_of_shr * 2;
             return Some(self.dcx().emit_err(UnmatchedAngleBrackets { span, num_extra_brackets }));
@@ -1539,7 +1542,10 @@ impl<'a> Parser<'a> {
 
     pub(super) fn maybe_report_ambiguous_plus(&mut self, impl_dyn_multi: bool, ty: &Ty) {
         if impl_dyn_multi {
-            self.dcx().emit_err(AmbiguousPlus { sum_ty: pprust::ty_to_string(ty), span: ty.span });
+            self.dcx().emit_err(AmbiguousPlus {
+                span: ty.span,
+                suggestion: AddParen { lo: ty.span.shrink_to_lo(), hi: ty.span.shrink_to_hi() },
+            });
         }
     }
 
@@ -1604,25 +1610,14 @@ impl<'a> Parser<'a> {
         }
 
         self.bump(); // `+`
-        let bounds = self.parse_generic_bounds()?;
+        let _bounds = self.parse_generic_bounds()?;
         let sum_span = ty.span.to(self.prev_token.span);
 
         let sub = match &ty.kind {
-            TyKind::Ref(lifetime, mut_ty) => {
-                let sum_with_parens = pprust::to_string(|s| {
-                    s.s.word("&");
-                    s.print_opt_lifetime(lifetime);
-                    s.print_mutability(mut_ty.mutbl, false);
-                    s.popen();
-                    s.print_type(&mut_ty.ty);
-                    if !bounds.is_empty() {
-                        s.word(" + ");
-                        s.print_type_bounds(&bounds);
-                    }
-                    s.pclose()
-                });
-
-                BadTypePlusSub::AddParen { sum_with_parens, span: sum_span }
+            TyKind::Ref(_lifetime, mut_ty) => {
+                let lo = mut_ty.ty.span.shrink_to_lo();
+                let hi = self.prev_token.span.shrink_to_hi();
+                BadTypePlusSub::AddParen { suggestion: AddParen { lo, hi } }
             }
             TyKind::Ptr(..) | TyKind::BareFn(..) => BadTypePlusSub::ForgotParen { span: sum_span },
             _ => BadTypePlusSub::ExpectPath { span: sum_span },
@@ -1964,18 +1959,14 @@ impl<'a> Parser<'a> {
         is_question: bool,
     ) -> (Span, ErrorGuaranteed) {
         let span = lo.to(hi);
-        let applicability = match expr.kind {
-            ExprKind::Try(_) => Applicability::MaybeIncorrect, // `await <expr>?`
-            _ => Applicability::MachineApplicable,
-        };
-
         let guar = self.dcx().emit_err(IncorrectAwait {
             span,
-            sugg_span: (span, applicability),
-            expr: self.span_to_snippet(expr.span).unwrap_or_else(|_| pprust::expr_to_string(expr)),
-            question_mark: if is_question { "?" } else { "" },
+            suggestion: AwaitSuggestion {
+                removal: lo.until(expr.span),
+                dot_await: expr.span.shrink_to_hi(),
+                question_mark: if is_question { "?" } else { "" },
+            },
         });
-
         (span, guar)
     }
 
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index b2df9a14eb0..4bd20be4171 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -714,7 +714,7 @@ impl<'a> Parser<'a> {
                                 type_err.cancel();
                                 self.dcx().emit_err(errors::MalformedLoopLabel {
                                     span: label.ident.span,
-                                    correct_label: label.ident,
+                                    suggestion: label.ident.span.shrink_to_lo(),
                                 });
                                 return Ok(expr);
                             }
@@ -856,7 +856,7 @@ impl<'a> Parser<'a> {
         let hi = self.interpolated_or_expr_span(&expr);
         let span = lo.to(hi);
         if let Some(lt) = lifetime {
-            self.error_remove_borrow_lifetime(span, lt.ident.span);
+            self.error_remove_borrow_lifetime(span, lt.ident.span.until(expr.span));
         }
         Ok((span, ExprKind::AddrOf(borrow_kind, mutbl, expr)))
     }
@@ -1653,6 +1653,7 @@ impl<'a> Parser<'a> {
         let lo = label_.ident.span;
         let label = Some(label_);
         let ate_colon = self.eat(&token::Colon);
+        let tok_sp = self.token.span;
         let expr = if self.eat_keyword(kw::While) {
             self.parse_expr_while(label, lo)
         } else if self.eat_keyword(kw::For) {
@@ -1747,7 +1748,7 @@ impl<'a> Parser<'a> {
             self.dcx().emit_err(errors::RequireColonAfterLabeledExpression {
                 span: expr.span,
                 label: lo,
-                label_end: lo.shrink_to_hi(),
+                label_end: lo.between(tok_sp),
             });
         }
 
@@ -2106,7 +2107,7 @@ impl<'a> Parser<'a> {
                 self.bump();
                 self.dcx().emit_err(errors::FloatLiteralRequiresIntegerPart {
                     span: token.span,
-                    correct: pprust::token_to_string(token).into_owned(),
+                    suggestion: token.span.shrink_to_lo(),
                 });
             }
         }
@@ -2741,7 +2742,7 @@ impl<'a> Parser<'a> {
         if !attrs.is_empty()
             && let [x0 @ xn] | [x0, .., xn] = &*attrs.take_for_recovery(self.psess)
         {
-            let attributes = x0.span.to(xn.span);
+            let attributes = x0.span.until(branch_span);
             let last = xn.span;
             let ctx = if is_ctx_else { "else" } else { "if" };
             self.dcx().emit_err(errors::OuterAttributeNotAllowedOnIfElse {
diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs
index abb6b51cebd..76857cb8504 100644
--- a/compiler/rustc_parse/src/parser/item.rs
+++ b/compiler/rustc_parse/src/parser/item.rs
@@ -239,6 +239,7 @@ impl<'a> Parser<'a> {
                 self.recover_const_impl(const_span, attrs, def_())?
             } else {
                 self.recover_const_mut(const_span);
+                self.recover_missing_kw_before_item()?;
                 let (ident, generics, ty, expr) = self.parse_const_item()?;
                 (
                     ident,
@@ -311,6 +312,9 @@ impl<'a> Parser<'a> {
                 Case::Insensitive,
             );
         } else if macros_allowed && self.check_path() {
+            if self.isnt_macro_invocation() {
+                self.recover_missing_kw_before_item()?;
+            }
             // MACRO INVOCATION ITEM
             (Ident::empty(), ItemKind::MacCall(P(self.parse_item_macro(vis)?)))
         } else {
@@ -374,25 +378,25 @@ impl<'a> Parser<'a> {
         self.check_ident() && self.look_ahead(1, |t| *t != token::Not && *t != token::PathSep)
     }
 
-    /// Recover on encountering a struct or method definition where the user
-    /// forgot to add the `struct` or `fn` keyword after writing `pub`: `pub S {}`.
+    /// Recover on encountering a struct, enum, or method definition where the user
+    /// forgot to add the `struct`, `enum`, or `fn` keyword
     fn recover_missing_kw_before_item(&mut self) -> PResult<'a, ()> {
-        // Space between `pub` keyword and the identifier
-        //
-        //     pub   S {}
-        //        ^^^ `sp` points here
-        let sp = self.prev_token.span.between(self.token.span);
-        let full_sp = self.prev_token.span.to(self.token.span);
-        let ident_sp = self.token.span;
-
-        let ident = if self.look_ahead(1, |t| {
-            [
-                token::Lt,
-                token::OpenDelim(Delimiter::Brace),
-                token::OpenDelim(Delimiter::Parenthesis),
-            ]
-            .contains(&t.kind)
-        }) {
+        let is_pub = self.prev_token.is_keyword(kw::Pub);
+        let is_const = self.prev_token.is_keyword(kw::Const);
+        let ident_span = self.token.span;
+        let span = if is_pub { self.prev_token.span.to(ident_span) } else { ident_span };
+        let insert_span = ident_span.shrink_to_lo();
+
+        let ident = if self.token.is_ident()
+            && (!is_const || self.look_ahead(1, |t| *t == token::OpenDelim(Delimiter::Parenthesis)))
+            && self.look_ahead(1, |t| {
+                [
+                    token::Lt,
+                    token::OpenDelim(Delimiter::Brace),
+                    token::OpenDelim(Delimiter::Parenthesis),
+                ]
+                .contains(&t.kind)
+            }) {
             self.parse_ident().unwrap()
         } else {
             return Ok(());
@@ -406,46 +410,56 @@ impl<'a> Parser<'a> {
         }
 
         let err = if self.check(&token::OpenDelim(Delimiter::Brace)) {
-            // possible public struct definition where `struct` was forgotten
-            Some(errors::MissingKeywordForItemDefinition::Struct { span: sp, ident })
+            // possible struct or enum definition where `struct` or `enum` was forgotten
+            if self.look_ahead(1, |t| *t == token::CloseDelim(Delimiter::Brace)) {
+                // `S {}` could be unit enum or struct
+                Some(errors::MissingKeywordForItemDefinition::EnumOrStruct { span })
+            } else if self.look_ahead(2, |t| *t == token::Colon)
+                || self.look_ahead(3, |t| *t == token::Colon)
+            {
+                // `S { f:` or `S { pub f:`
+                Some(errors::MissingKeywordForItemDefinition::Struct { span, insert_span, ident })
+            } else {
+                Some(errors::MissingKeywordForItemDefinition::Enum { span, insert_span, ident })
+            }
         } else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) {
-            // possible public function or tuple struct definition where `fn`/`struct` was
-            // forgotten
+            // possible function or tuple struct definition where `fn` or `struct` was forgotten
             self.bump(); // `(`
             let is_method = self.recover_self_param();
 
             self.consume_block(Delimiter::Parenthesis, ConsumeClosingDelim::Yes);
 
-            let err =
-                if self.check(&token::RArrow) || self.check(&token::OpenDelim(Delimiter::Brace)) {
-                    self.eat_to_tokens(&[&token::OpenDelim(Delimiter::Brace)]);
-                    self.bump(); // `{`
-                    self.consume_block(Delimiter::Brace, ConsumeClosingDelim::Yes);
-                    if is_method {
-                        errors::MissingKeywordForItemDefinition::Method { span: sp, ident }
-                    } else {
-                        errors::MissingKeywordForItemDefinition::Function { span: sp, ident }
-                    }
-                } else if self.check(&token::Semi) {
-                    errors::MissingKeywordForItemDefinition::Struct { span: sp, ident }
+            let err = if self.check(&token::RArrow)
+                || self.check(&token::OpenDelim(Delimiter::Brace))
+            {
+                self.eat_to_tokens(&[&token::OpenDelim(Delimiter::Brace)]);
+                self.bump(); // `{`
+                self.consume_block(Delimiter::Brace, ConsumeClosingDelim::Yes);
+                if is_method {
+                    errors::MissingKeywordForItemDefinition::Method { span, insert_span, ident }
                 } else {
-                    errors::MissingKeywordForItemDefinition::Ambiguous {
-                        span: sp,
-                        subdiag: if found_generics {
-                            None
-                        } else if let Ok(snippet) = self.span_to_snippet(ident_sp) {
-                            Some(errors::AmbiguousMissingKwForItemSub::SuggestMacro {
-                                span: full_sp,
-                                snippet,
-                            })
-                        } else {
-                            Some(errors::AmbiguousMissingKwForItemSub::HelpMacro)
-                        },
-                    }
-                };
+                    errors::MissingKeywordForItemDefinition::Function { span, insert_span, ident }
+                }
+            } else if is_pub && self.check(&token::Semi) {
+                errors::MissingKeywordForItemDefinition::Struct { span, insert_span, ident }
+            } else {
+                errors::MissingKeywordForItemDefinition::Ambiguous {
+                    span,
+                    subdiag: if found_generics {
+                        None
+                    } else if let Ok(snippet) = self.span_to_snippet(ident_span) {
+                        Some(errors::AmbiguousMissingKwForItemSub::SuggestMacro {
+                            span: ident_span,
+                            snippet,
+                        })
+                    } else {
+                        Some(errors::AmbiguousMissingKwForItemSub::HelpMacro)
+                    },
+                }
+            };
             Some(err)
         } else if found_generics {
-            Some(errors::MissingKeywordForItemDefinition::Ambiguous { span: sp, subdiag: None })
+            Some(errors::MissingKeywordForItemDefinition::Ambiguous { span, subdiag: None })
         } else {
             None
         };
@@ -796,7 +810,7 @@ impl<'a> Parser<'a> {
                         self.dcx().struct_span_err(non_item_span, "non-item in item list");
                     self.consume_block(Delimiter::Brace, ConsumeClosingDelim::Yes);
                     if is_let {
-                        err.span_suggestion(
+                        err.span_suggestion_verbose(
                             non_item_span,
                             "consider using `const` instead of `let` for associated const",
                             "const",
@@ -2227,9 +2241,13 @@ impl<'a> Parser<'a> {
             let kw_token = self.token.clone();
             let kw_str = pprust::token_to_string(&kw_token);
             let item = self.parse_item(ForceCollect::No)?;
+            let mut item = item.unwrap().span;
+            if self.token == token::Comma {
+                item = item.to(self.token.span);
+            }
             self.dcx().emit_err(errors::NestedAdt {
                 span: kw_token.span,
-                item: item.unwrap().span,
+                item,
                 kw_str,
                 keyword: keyword.as_str(),
             });
diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs
index 45ca267fe5d..958458eda9a 100644
--- a/compiler/rustc_parse/src/parser/mod.rs
+++ b/compiler/rustc_parse/src/parser/mod.rs
@@ -1118,41 +1118,37 @@ impl<'a> Parser<'a> {
             return looker(&self.token);
         }
 
-        if let Some(&(_, span, _, delim)) = self.token_cursor.stack.last()
-            && delim != Delimiter::Invisible
-        {
-            // We are not in the outermost token stream, and the token stream
-            // we are in has non-skipped delimiters. Look for skipped
-            // delimiters in the lookahead range.
-            let tree_cursor = &self.token_cursor.tree_cursor;
-            let all_normal = (0..dist).all(|i| {
-                let token = tree_cursor.look_ahead(i);
-                !matches!(token, Some(TokenTree::Delimited(.., Delimiter::Invisible, _)))
-            });
-            if all_normal {
-                // There were no skipped delimiters. Do lookahead by plain indexing.
-                return match tree_cursor.look_ahead(dist - 1) {
-                    Some(tree) => {
-                        // Indexing stayed within the current token stream.
-                        match tree {
-                            TokenTree::Token(token, _) => looker(token),
-                            TokenTree::Delimited(dspan, _, delim, _) => {
-                                looker(&Token::new(token::OpenDelim(*delim), dspan.open))
-                            }
+        // Typically around 98% of the `dist > 0` cases have `dist == 1`, so we
+        // have a fast special case for that.
+        if dist == 1 {
+            // The index is zero because the tree cursor's index always points
+            // to the next token to be gotten.
+            match self.token_cursor.tree_cursor.look_ahead(0) {
+                Some(tree) => {
+                    // Indexing stayed within the current token tree.
+                    return match tree {
+                        TokenTree::Token(token, _) => looker(token),
+                        TokenTree::Delimited(dspan, _, delim, _) => {
+                            looker(&Token::new(token::OpenDelim(*delim), dspan.open))
                         }
+                    };
+                }
+                None => {
+                    // The tree cursor lookahead went (one) past the end of the
+                    // current token tree. Try to return a close delimiter.
+                    if let Some(&(_, span, _, delim)) = self.token_cursor.stack.last()
+                        && delim != Delimiter::Invisible
+                    {
+                        // We are not in the outermost token stream, so we have
+                        // delimiters. Also, those delimiters are not skipped.
+                        return looker(&Token::new(token::CloseDelim(delim), span.close));
                     }
-                    None => {
-                        // Indexing went past the end of the current token
-                        // stream. Use the close delimiter, no matter how far
-                        // ahead `dist` went.
-                        looker(&Token::new(token::CloseDelim(delim), span.close))
-                    }
-                };
+                }
             }
         }
 
-        // We are in a more complex case. Just clone the token cursor and use
-        // `next`, skipping delimiters as necessary. Slow but simple.
+        // Just clone the token cursor and use `next`, skipping delimiters as
+        // necessary. Slow but simple.
         let mut cursor = self.token_cursor.clone();
         let mut i = 0;
         let mut token = Token::dummy();
@@ -1541,14 +1537,16 @@ impl<'a> Parser<'a> {
 
                 // we don't need N spans, but we want at least one, so print all of prev_token
                 dbg_fmt.field("prev_token", &parser.prev_token);
-                // make it easier to peek farther ahead by taking TokenKinds only until EOF
-                let tokens = (0..*lookahead)
-                    .map(|i| parser.look_ahead(i, |tok| tok.kind.clone()))
-                    .scan(parser.prev_token == TokenKind::Eof, |eof, tok| {
-                        let current = eof.then_some(tok.clone()); // include a trailing EOF token
-                        *eof |= &tok == &TokenKind::Eof;
-                        current
-                    });
+                let mut tokens = vec![];
+                for i in 0..*lookahead {
+                    let tok = parser.look_ahead(i, |tok| tok.kind.clone());
+                    let is_eof = tok == TokenKind::Eof;
+                    tokens.push(tok);
+                    if is_eof {
+                        // Don't look ahead past EOF.
+                        break;
+                    }
+                }
                 dbg_fmt.field_with("tokens", |field| field.debug_list().entries(tokens).finish());
                 dbg_fmt.field("approx_token_stream_pos", &parser.num_bump_calls);
 
@@ -1607,7 +1605,7 @@ pub(crate) fn make_unclosed_delims_error(
 enum FlatToken {
     /// A token - this holds both delimiter (e.g. '{' and '}')
     /// and non-delimiter tokens
-    Token(Token),
+    Token((Token, Spacing)),
     /// Holds the `AttrsTarget` for an AST node. The `AttrsTarget` is inserted
     /// directly into the constructed `AttrTokenStream` as an
     /// `AttrTokenTree::AttrsTarget`.
diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs
index 6f2b7177159..e4e89615d71 100644
--- a/compiler/rustc_parse/src/parser/pat.rs
+++ b/compiler/rustc_parse/src/parser/pat.rs
@@ -4,11 +4,11 @@ use crate::errors::{
     DotDotDotRestPattern, EnumPatternInsteadOfIdentifier, ExpectedBindingLeftOfAt,
     ExpectedCommaAfterPatternField, GenericArgsInPatRequireTurbofishSyntax,
     InclusiveRangeExtraEquals, InclusiveRangeMatchArrow, InclusiveRangeNoEnd, InvalidMutInPattern,
-    PatternOnWrongSideOfAt, RemoveLet, RepeatedMutInPattern, SwitchRefBoxOrder,
-    TopLevelOrPatternNotAllowed, TopLevelOrPatternNotAllowedSugg, TrailingVertNotAllowed,
-    UnexpectedExpressionInPattern, UnexpectedLifetimeInPattern, UnexpectedParenInRangePat,
-    UnexpectedParenInRangePatSugg, UnexpectedVertVertBeforeFunctionParam,
-    UnexpectedVertVertInPattern,
+    ParenRangeSuggestion, PatternOnWrongSideOfAt, RemoveLet, RepeatedMutInPattern,
+    SwitchRefBoxOrder, TopLevelOrPatternNotAllowed, TopLevelOrPatternNotAllowedSugg,
+    TrailingVertNotAllowed, UnexpectedExpressionInPattern, UnexpectedLifetimeInPattern,
+    UnexpectedParenInRangePat, UnexpectedParenInRangePatSugg,
+    UnexpectedVertVertBeforeFunctionParam, UnexpectedVertVertInPattern, WrapInParens,
 };
 use crate::parser::expr::{could_be_unclosed_char_literal, LhsExpr};
 use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
@@ -24,7 +24,7 @@ use rustc_errors::{Applicability, Diag, PResult};
 use rustc_session::errors::ExprParenthesesNeeded;
 use rustc_span::source_map::{respan, Spanned};
 use rustc_span::symbol::{kw, sym, Ident};
-use rustc_span::{ErrorGuaranteed, Span};
+use rustc_span::{BytePos, ErrorGuaranteed, Span};
 use thin_vec::{thin_vec, ThinVec};
 
 #[derive(PartialEq, Copy, Clone)]
@@ -236,11 +236,15 @@ impl<'a> Parser<'a> {
 
         if let PatKind::Or(pats) = &pat.kind {
             let span = pat.span;
-            let pat = pprust::pat_to_string(&pat);
             let sub = if pats.len() == 1 {
-                Some(TopLevelOrPatternNotAllowedSugg::RemoveLeadingVert { span, pat })
+                Some(TopLevelOrPatternNotAllowedSugg::RemoveLeadingVert {
+                    span: span.with_hi(span.lo() + BytePos(1)),
+                })
             } else {
-                Some(TopLevelOrPatternNotAllowedSugg::WrapInParens { span, pat })
+                Some(TopLevelOrPatternNotAllowedSugg::WrapInParens {
+                    span,
+                    suggestion: WrapInParens { lo: span.shrink_to_lo(), hi: span.shrink_to_hi() },
+                })
             };
 
             let err = self.dcx().create_err(match syntax_loc {
@@ -599,7 +603,10 @@ impl<'a> Parser<'a> {
         self.bump(); // `...`
 
         // The user probably mistook `...` for a rest pattern `..`.
-        self.dcx().emit_err(DotDotDotRestPattern { span: lo });
+        self.dcx().emit_err(DotDotDotRestPattern {
+            span: lo,
+            suggestion: lo.with_lo(lo.hi() - BytePos(1)),
+        });
         PatKind::Rest
     }
 
@@ -664,8 +671,13 @@ impl<'a> Parser<'a> {
             _ => return,
         }
 
-        self.dcx()
-            .emit_err(AmbiguousRangePattern { span: pat.span, pat: pprust::pat_to_string(pat) });
+        self.dcx().emit_err(AmbiguousRangePattern {
+            span: pat.span,
+            suggestion: ParenRangeSuggestion {
+                lo: pat.span.shrink_to_lo(),
+                hi: pat.span.shrink_to_hi(),
+            },
+        });
     }
 
     /// Parse `&pat` / `&mut pat`.
@@ -674,8 +686,11 @@ impl<'a> Parser<'a> {
         if let token::Lifetime(name) = self.token.kind {
             self.bump(); // `'a`
 
-            self.dcx()
-                .emit_err(UnexpectedLifetimeInPattern { span: self.prev_token.span, symbol: name });
+            self.dcx().emit_err(UnexpectedLifetimeInPattern {
+                span: self.prev_token.span,
+                symbol: name,
+                suggestion: self.prev_token.span.until(self.token.span),
+            });
         }
 
         let mutbl = self.parse_mutability();
@@ -913,10 +928,13 @@ impl<'a> Parser<'a> {
                 self.dcx().emit_err(InclusiveRangeExtraEquals { span: span_with_eq })
             }
             token::Gt if no_space => {
-                let after_pat = span.with_hi(span.hi() - rustc_span::BytePos(1)).shrink_to_hi();
+                let after_pat = span.with_hi(span.hi() - BytePos(1)).shrink_to_hi();
                 self.dcx().emit_err(InclusiveRangeMatchArrow { span, arrow: tok.span, after_pat })
             }
-            _ => self.dcx().emit_err(InclusiveRangeNoEnd { span }),
+            _ => self.dcx().emit_err(InclusiveRangeNoEnd {
+                span,
+                suggestion: span.with_lo(span.hi() - BytePos(1)),
+            }),
         }
     }
 
diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs
index 03c647dd527..b9014dea726 100644
--- a/compiler/rustc_parse/src/parser/path.rs
+++ b/compiler/rustc_parse/src/parser/path.rs
@@ -258,6 +258,7 @@ impl<'a> Parser<'a> {
                         self.bump(); // bump past the colon
                         self.dcx().emit_err(PathSingleColon {
                             span: self.prev_token.span,
+                            suggestion: self.prev_token.span.shrink_to_hi(),
                             type_ascription: self
                                 .psess
                                 .unstable_features
@@ -329,6 +330,7 @@ impl<'a> Parser<'a> {
                             err.cancel();
                             err = self.dcx().create_err(PathSingleColon {
                                 span: self.token.span,
+                                suggestion: self.prev_token.span.shrink_to_hi(),
                                 type_ascription: self
                                     .psess
                                     .unstable_features
diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs
index d65f6ff68ee..70d41de00a7 100644
--- a/compiler/rustc_parse/src/parser/stmt.rs
+++ b/compiler/rustc_parse/src/parser/stmt.rs
@@ -430,8 +430,10 @@ impl<'a> Parser<'a> {
         let eq_consumed = match self.token.kind {
             token::BinOpEq(..) => {
                 // Recover `let x <op>= 1` as `let x = 1`
-                self.dcx()
-                    .emit_err(errors::CompoundAssignmentExpressionInLet { span: self.token.span });
+                self.dcx().emit_err(errors::CompoundAssignmentExpressionInLet {
+                    span: self.token.span,
+                    suggestion: self.token.span.with_hi(self.token.span.lo() + BytePos(1)),
+                });
                 self.bump();
                 true
             }
@@ -717,7 +719,7 @@ impl<'a> Parser<'a> {
                                                 e.cancel();
                                                 self.dcx().emit_err(MalformedLoopLabel {
                                                     span: label.ident.span,
-                                                    correct_label: label.ident,
+                                                    suggestion: label.ident.span.shrink_to_lo(),
                                                 });
                                                 *expr = labeled_expr;
                                                 break 'break_recover None;
diff --git a/compiler/rustc_parse/src/parser/tests.rs b/compiler/rustc_parse/src/parser/tests.rs
index 42392ad2163..cf791d332a2 100644
--- a/compiler/rustc_parse/src/parser/tests.rs
+++ b/compiler/rustc_parse/src/parser/tests.rs
@@ -1376,6 +1376,365 @@ fn ttdelim_span() {
     });
 }
 
+#[track_caller]
+fn look(p: &Parser<'_>, dist: usize, kind: rustc_ast::token::TokenKind) {
+    // Do the `assert_eq` outside the closure so that `track_caller` works.
+    // (`#![feature(closure_track_caller)]` + `#[track_caller]` on the closure
+    // doesn't give the line number in the test below if the assertion fails.)
+    let tok = p.look_ahead(dist, |tok| tok.clone());
+    assert_eq!(kind, tok.kind);
+}
+
+#[test]
+fn look_ahead() {
+    create_default_session_globals_then(|| {
+        let sym_f = Symbol::intern("f");
+        let sym_x = Symbol::intern("x");
+        #[allow(non_snake_case)]
+        let sym_S = Symbol::intern("S");
+        let raw_no = IdentIsRaw::No;
+
+        let psess = psess();
+        let mut p = string_to_parser(&psess, "fn f(x: u32) { x } struct S;".to_string());
+
+        // Current position is the `fn`.
+        look(&p, 0, token::Ident(kw::Fn, raw_no));
+        look(&p, 1, token::Ident(sym_f, raw_no));
+        look(&p, 2, token::OpenDelim(Delimiter::Parenthesis));
+        look(&p, 3, token::Ident(sym_x, raw_no));
+        look(&p, 4, token::Colon);
+        look(&p, 5, token::Ident(sym::u32, raw_no));
+        look(&p, 6, token::CloseDelim(Delimiter::Parenthesis));
+        look(&p, 7, token::OpenDelim(Delimiter::Brace));
+        look(&p, 8, token::Ident(sym_x, raw_no));
+        look(&p, 9, token::CloseDelim(Delimiter::Brace));
+        look(&p, 10, token::Ident(kw::Struct, raw_no));
+        look(&p, 11, token::Ident(sym_S, raw_no));
+        look(&p, 12, token::Semi);
+        // Any lookahead past the end of the token stream returns `Eof`.
+        look(&p, 13, token::Eof);
+        look(&p, 14, token::Eof);
+        look(&p, 15, token::Eof);
+        look(&p, 100, token::Eof);
+
+        // Move forward to the first `x`.
+        for _ in 0..3 {
+            p.bump();
+        }
+        look(&p, 0, token::Ident(sym_x, raw_no));
+        look(&p, 1, token::Colon);
+        look(&p, 2, token::Ident(sym::u32, raw_no));
+        look(&p, 3, token::CloseDelim(Delimiter::Parenthesis));
+        look(&p, 4, token::OpenDelim(Delimiter::Brace));
+        look(&p, 5, token::Ident(sym_x, raw_no));
+        look(&p, 6, token::CloseDelim(Delimiter::Brace));
+        look(&p, 7, token::Ident(kw::Struct, raw_no));
+        look(&p, 8, token::Ident(sym_S, raw_no));
+        look(&p, 9, token::Semi);
+        look(&p, 10, token::Eof);
+        look(&p, 11, token::Eof);
+        look(&p, 100, token::Eof);
+
+        // Move forward to the `;`.
+        for _ in 0..9 {
+            p.bump();
+        }
+        look(&p, 0, token::Semi);
+        // Any lookahead past the end of the token stream returns `Eof`.
+        look(&p, 1, token::Eof);
+        look(&p, 100, token::Eof);
+
+        // Move one past the `;`, i.e. past the end of the token stream.
+        p.bump();
+        look(&p, 0, token::Eof);
+        look(&p, 1, token::Eof);
+        look(&p, 100, token::Eof);
+
+        // Bumping after Eof is idempotent.
+        p.bump();
+        look(&p, 0, token::Eof);
+        look(&p, 1, token::Eof);
+        look(&p, 100, token::Eof);
+    });
+}
+
+/// There used to be some buggy behaviour when using `look_ahead` not within
+/// the outermost token stream, which this test covers.
+#[test]
+fn look_ahead_non_outermost_stream() {
+    create_default_session_globals_then(|| {
+        let sym_f = Symbol::intern("f");
+        let sym_x = Symbol::intern("x");
+        #[allow(non_snake_case)]
+        let sym_S = Symbol::intern("S");
+        let raw_no = IdentIsRaw::No;
+
+        let psess = psess();
+        let mut p = string_to_parser(&psess, "mod m { fn f(x: u32) { x } struct S; }".to_string());
+
+        // Move forward to the `fn`, which is not within the outermost token
+        // stream (because it's inside the `mod { ... }`).
+        for _ in 0..3 {
+            p.bump();
+        }
+        look(&p, 0, token::Ident(kw::Fn, raw_no));
+        look(&p, 1, token::Ident(sym_f, raw_no));
+        look(&p, 2, token::OpenDelim(Delimiter::Parenthesis));
+        look(&p, 3, token::Ident(sym_x, raw_no));
+        look(&p, 4, token::Colon);
+        look(&p, 5, token::Ident(sym::u32, raw_no));
+        look(&p, 6, token::CloseDelim(Delimiter::Parenthesis));
+        look(&p, 7, token::OpenDelim(Delimiter::Brace));
+        look(&p, 8, token::Ident(sym_x, raw_no));
+        look(&p, 9, token::CloseDelim(Delimiter::Brace));
+        look(&p, 10, token::Ident(kw::Struct, raw_no));
+        look(&p, 11, token::Ident(sym_S, raw_no));
+        look(&p, 12, token::Semi);
+        look(&p, 13, token::CloseDelim(Delimiter::Brace));
+        // Any lookahead past the end of the token stream returns `Eof`.
+        look(&p, 14, token::Eof);
+        look(&p, 15, token::Eof);
+        look(&p, 100, token::Eof);
+    });
+}
+
+// FIXME(nnethercote) All the output is currently wrong.
+#[test]
+fn debug_lookahead() {
+    create_default_session_globals_then(|| {
+        let psess = psess();
+        let mut p = string_to_parser(&psess, "fn f(x: u32) { x } struct S;".to_string());
+
+        // Current position is the `fn`.
+        assert_eq!(
+            &format!("{:#?}", p.debug_lookahead(0)),
+            "Parser {
+    prev_token: Token {
+        kind: Question,
+        span: Span {
+            lo: BytePos(
+                0,
+            ),
+            hi: BytePos(
+                0,
+            ),
+            ctxt: #0,
+        },
+    },
+    tokens: [],
+    approx_token_stream_pos: 1,
+    ..
+}"
+        );
+        assert_eq!(
+            &format!("{:#?}", p.debug_lookahead(7)),
+            "Parser {
+    prev_token: Token {
+        kind: Question,
+        span: Span {
+            lo: BytePos(
+                0,
+            ),
+            hi: BytePos(
+                0,
+            ),
+            ctxt: #0,
+        },
+    },
+    tokens: [
+        Ident(
+            \"fn\",
+            No,
+        ),
+        Ident(
+            \"f\",
+            No,
+        ),
+        OpenDelim(
+            Parenthesis,
+        ),
+        Ident(
+            \"x\",
+            No,
+        ),
+        Colon,
+        Ident(
+            \"u32\",
+            No,
+        ),
+        CloseDelim(
+            Parenthesis,
+        ),
+    ],
+    approx_token_stream_pos: 1,
+    ..
+}"
+        );
+        // There are 13 tokens. We request 15, get 14; the last one is `Eof`.
+        assert_eq!(
+            &format!("{:#?}", p.debug_lookahead(15)),
+            "Parser {
+    prev_token: Token {
+        kind: Question,
+        span: Span {
+            lo: BytePos(
+                0,
+            ),
+            hi: BytePos(
+                0,
+            ),
+            ctxt: #0,
+        },
+    },
+    tokens: [
+        Ident(
+            \"fn\",
+            No,
+        ),
+        Ident(
+            \"f\",
+            No,
+        ),
+        OpenDelim(
+            Parenthesis,
+        ),
+        Ident(
+            \"x\",
+            No,
+        ),
+        Colon,
+        Ident(
+            \"u32\",
+            No,
+        ),
+        CloseDelim(
+            Parenthesis,
+        ),
+        OpenDelim(
+            Brace,
+        ),
+        Ident(
+            \"x\",
+            No,
+        ),
+        CloseDelim(
+            Brace,
+        ),
+        Ident(
+            \"struct\",
+            No,
+        ),
+        Ident(
+            \"S\",
+            No,
+        ),
+        Semi,
+        Eof,
+    ],
+    approx_token_stream_pos: 1,
+    ..
+}"
+        );
+
+        // Move forward to the second `x`.
+        for _ in 0..8 {
+            p.bump();
+        }
+        assert_eq!(
+            &format!("{:#?}", p.debug_lookahead(1)),
+            "Parser {
+    prev_token: Token {
+        kind: OpenDelim(
+            Brace,
+        ),
+        span: Span {
+            lo: BytePos(
+                13,
+            ),
+            hi: BytePos(
+                14,
+            ),
+            ctxt: #0,
+        },
+    },
+    tokens: [
+        Ident(
+            \"x\",
+            No,
+        ),
+    ],
+    approx_token_stream_pos: 9,
+    ..
+}"
+        );
+        assert_eq!(
+            &format!("{:#?}", p.debug_lookahead(4)),
+            "Parser {
+    prev_token: Token {
+        kind: OpenDelim(
+            Brace,
+        ),
+        span: Span {
+            lo: BytePos(
+                13,
+            ),
+            hi: BytePos(
+                14,
+            ),
+            ctxt: #0,
+        },
+    },
+    tokens: [
+        Ident(
+            \"x\",
+            No,
+        ),
+        CloseDelim(
+            Brace,
+        ),
+        Ident(
+            \"struct\",
+            No,
+        ),
+        Ident(
+            \"S\",
+            No,
+        ),
+    ],
+    approx_token_stream_pos: 9,
+    ..
+}"
+        );
+
+        // Move two past the final token (the `;`).
+        for _ in 0..6 {
+            p.bump();
+        }
+        assert_eq!(
+            &format!("{:#?}", p.debug_lookahead(3)),
+            "Parser {
+    prev_token: Token {
+        kind: Eof,
+        span: Span {
+            lo: BytePos(
+                27,
+            ),
+            hi: BytePos(
+                28,
+            ),
+            ctxt: #0,
+        },
+    },
+    tokens: [
+        Eof,
+    ],
+    approx_token_stream_pos: 15,
+    ..
+}"
+        );
+    });
+}
+
 // This tests that when parsing a string (rather than a file) we don't try
 // and read in a file for a module declaration and just parse a stub.
 // See `recurse_into_file_modules` in the parser.
diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs
index 1e5b227aaa9..94321b1dddd 100644
--- a/compiler/rustc_parse/src/parser/ty.rs
+++ b/compiler/rustc_parse/src/parser/ty.rs
@@ -209,6 +209,7 @@ impl<'a> Parser<'a> {
         recover_qpath: RecoverQPath,
         recover_return_sign: RecoverReturnSign,
     ) -> PResult<'a, FnRetTy> {
+        let lo = self.prev_token.span;
         Ok(if self.eat(&token::RArrow) {
             // FIXME(Centril): Can we unconditionally `allow_plus`?
             let ty = self.parse_ty_common(
@@ -224,7 +225,10 @@ impl<'a> Parser<'a> {
             // Don't `eat` to prevent `=>` from being added as an expected token which isn't
             // actually expected and could only confuse users
             self.bump();
-            self.dcx().emit_err(ReturnTypesUseThinArrow { span: self.prev_token.span });
+            self.dcx().emit_err(ReturnTypesUseThinArrow {
+                span: self.prev_token.span,
+                suggestion: lo.between(self.token.span),
+            });
             let ty = self.parse_ty_common(
                 allow_plus,
                 AllowCVariadic::No,
@@ -794,8 +798,11 @@ impl<'a> Parser<'a> {
         {
             if self.token.is_keyword(kw::Dyn) {
                 // Account for `&dyn Trait + dyn Other`.
-                self.dcx().emit_err(InvalidDynKeyword { span: self.token.span });
                 self.bump();
+                self.dcx().emit_err(InvalidDynKeyword {
+                    span: self.prev_token.span,
+                    suggestion: self.prev_token.span.until(self.token.span),
+                });
             }
             bounds.push(self.parse_generic_bound()?);
             if allow_plus == AllowPlus::No || !self.eat_plus() {
@@ -861,7 +868,7 @@ impl<'a> Parser<'a> {
         if has_parens {
             // FIXME(Centril): Consider not erroring here and accepting `('lt)` instead,
             // possibly introducing `GenericBound::Paren(P<GenericBound>)`?
-            self.recover_paren_lifetime(lo, lt.ident.span)?;
+            self.recover_paren_lifetime(lo)?;
         }
         Ok(bound)
     }
@@ -909,16 +916,12 @@ impl<'a> Parser<'a> {
     }
 
     /// Recover on `('lifetime)` with `(` already eaten.
-    fn recover_paren_lifetime(&mut self, lo: Span, lt_span: Span) -> PResult<'a, ()> {
+    fn recover_paren_lifetime(&mut self, lo: Span) -> PResult<'a, ()> {
         self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
         let span = lo.to(self.prev_token.span);
-        let (sugg, snippet) = if let Ok(snippet) = self.span_to_snippet(lt_span) {
-            (Some(span), snippet)
-        } else {
-            (None, String::new())
-        };
+        let sugg = errors::RemoveParens { lo, hi: self.prev_token.span };
 
-        self.dcx().emit_err(errors::ParenthesizedLifetime { span, sugg, snippet });
+        self.dcx().emit_err(errors::ParenthesizedLifetime { span, sugg });
         Ok(())
     }