about summary refs log tree commit diff
path: root/compiler/rustc_parse/src
diff options
context:
space:
mode:
authorXiretza <xiretza@xiretza.xyz>2022-08-24 22:41:51 +0200
committerXiretza <xiretza@xiretza.xyz>2022-09-27 20:29:18 +0200
commitab7c7dc7ce422bfa2cdaf8ddd41b6260736a2e9f (patch)
treebda0f9c1e3db30140dc0e39fb8e5f7395728fce0 /compiler/rustc_parse/src
parent4d02892acfdb43490db815010d718bd324f853ad (diff)
downloadrust-ab7c7dc7ce422bfa2cdaf8ddd41b6260736a2e9f.tar.gz
rust-ab7c7dc7ce422bfa2cdaf8ddd41b6260736a2e9f.zip
Migrate more diagnostics in rustc_parse to diagnostic structs
Diffstat (limited to 'compiler/rustc_parse/src')
-rw-r--r--compiler/rustc_parse/src/parser/diagnostics.rs250
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs327
-rw-r--r--compiler/rustc_parse/src/parser/mod.rs43
3 files changed, 402 insertions, 218 deletions
diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs
index ccdffb8ce07..d8e1382d743 100644
--- a/compiler/rustc_parse/src/parser/diagnostics.rs
+++ b/compiler/rustc_parse/src/parser/diagnostics.rs
@@ -21,6 +21,7 @@ use rustc_errors::{
 };
 use rustc_errors::{pluralize, struct_span_err, Diagnostic, ErrorGuaranteed};
 use rustc_macros::{Diagnostic, Subdiagnostic};
+use rustc_session::errors::ExprParenthesesNeeded;
 use rustc_span::source_map::Spanned;
 use rustc_span::symbol::{kw, sym, Ident};
 use rustc_span::{Span, SpanSnippetError, DUMMY_SP};
@@ -487,11 +488,24 @@ pub(crate) struct MacroInvocationWithQualifiedPath(#[primary_span] pub Span);
 
 #[derive(Diagnostic)]
 #[diag(parser::unexpected_token_after_label)]
-pub(crate) struct UnexpectedTokenAfterLabel(
+pub(crate) struct UnexpectedTokenAfterLabel {
     #[primary_span]
     #[label(parser::unexpected_token_after_label)]
-    pub Span,
-);
+    pub span: Span,
+    #[suggestion_verbose(parser::suggestion_remove_label, code = "")]
+    pub remove_label: Option<Span>,
+    #[subdiagnostic]
+    pub enclose_in_block: Option<UnexpectedTokenAfterLabelSugg>,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(parser::suggestion_enclose_in_block, applicability = "machine-applicable")]
+pub(crate) struct UnexpectedTokenAfterLabelSugg {
+    #[suggestion_part(code = "{{ ")]
+    pub left: Span,
+    #[suggestion_part(code = " }}")]
+    pub right: Span,
+}
 
 #[derive(Diagnostic)]
 #[diag(parser::require_colon_after_labeled_expression)]
@@ -753,6 +767,236 @@ pub(crate) struct UseEmptyBlockNotSemi {
     pub span: Span,
 }
 
+#[derive(Diagnostic)]
+#[diag(parser::comparison_interpreted_as_generic)]
+pub(crate) struct ComparisonInterpretedAsGeneric {
+    #[primary_span]
+    #[label(parser::label_comparison)]
+    pub comparison: Span,
+    pub typename: String,
+    #[label(parser::label_args)]
+    pub args: Span,
+    #[subdiagnostic]
+    pub suggestion: ComparisonOrShiftInterpretedAsGenericSugg,
+}
+
+#[derive(Diagnostic)]
+#[diag(parser::shift_interpreted_as_generic)]
+pub(crate) struct ShiftInterpretedAsGeneric {
+    #[primary_span]
+    #[label(parser::label_comparison)]
+    pub shift: Span,
+    pub typename: String,
+    #[label(parser::label_args)]
+    pub args: Span,
+    #[subdiagnostic]
+    pub suggestion: ComparisonOrShiftInterpretedAsGenericSugg,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(parser::suggestion, applicability = "machine-applicable")]
+pub(crate) struct ComparisonOrShiftInterpretedAsGenericSugg {
+    #[suggestion_part(code = "(")]
+    pub left: Span,
+    #[suggestion_part(code = ")")]
+    pub right: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(parser::found_expr_would_be_stmt)]
+pub(crate) struct FoundExprWouldBeStmt {
+    #[primary_span]
+    #[label]
+    pub span: Span,
+    pub token: String,
+    #[subdiagnostic]
+    pub suggestion: ExprParenthesesNeeded,
+}
+
+#[derive(Diagnostic)]
+#[diag(parser::leading_plus_not_supported)]
+pub(crate) struct LeadingPlusNotSupported {
+    #[primary_span]
+    #[label]
+    pub span: Span,
+    #[suggestion_verbose(
+        parser::suggestion_remove_plus,
+        code = "",
+        applicability = "machine-applicable"
+    )]
+    pub remove_plus: Option<Span>,
+    #[subdiagnostic]
+    pub add_parentheses: Option<ExprParenthesesNeeded>,
+}
+
+#[derive(Diagnostic)]
+#[diag(parser::parentheses_with_struct_fields)]
+pub(crate) struct ParenthesesWithStructFields {
+    #[primary_span]
+    pub span: Span,
+    pub name: String,
+    #[subdiagnostic]
+    pub braces_for_struct: BracesForStructLiteral,
+    #[subdiagnostic]
+    pub no_fields_for_fn: NoFieldsForFnCall,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(parser::suggestion_braces_for_struct, applicability = "maybe-incorrect")]
+pub(crate) struct BracesForStructLiteral {
+    #[suggestion_part(code = " {{ ")]
+    pub first: Span,
+    #[suggestion_part(code = " }}")]
+    pub second: Span,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(parser::suggestion_no_fields_for_fn, applicability = "maybe-incorrect")]
+pub(crate) struct NoFieldsForFnCall {
+    #[suggestion_part(code = "")]
+    pub fields: Vec<Span>,
+}
+
+#[derive(Diagnostic)]
+#[diag(parser::labeled_loop_in_break)]
+pub(crate) struct LabeledLoopInBreak {
+    #[primary_span]
+    pub span: Span,
+    #[subdiagnostic]
+    pub sub: LabeledLoopInBreakSub,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(parser::suggestion, applicability = "machine-applicable")]
+pub(crate) struct LabeledLoopInBreakSub {
+    #[suggestion_part(code = "(")]
+    pub first: Span,
+    #[suggestion_part(code = ")")]
+    pub second: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(parser::array_brackets_instead_of_braces)]
+pub(crate) struct ArrayBracketsInsteadOfSpaces {
+    #[primary_span]
+    pub span: Span,
+    #[subdiagnostic]
+    pub sub: ArrayBracketsInsteadOfSpacesSugg,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(parser::suggestion, applicability = "maybe-incorrect")]
+pub(crate) struct ArrayBracketsInsteadOfSpacesSugg {
+    #[suggestion_part(code = "[")]
+    pub left: Span,
+    #[suggestion_part(code = "]")]
+    pub right: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(parser::match_arm_body_without_braces)]
+pub(crate) struct MatchArmBodyWithoutBraces {
+    #[primary_span]
+    #[label(parser::label_statements)]
+    pub statements: Span,
+    #[label(parser::label_arrow)]
+    pub arrow: Span,
+    pub num_statements: usize,
+    #[subdiagnostic]
+    pub sub: MatchArmBodyWithoutBracesSugg,
+}
+
+#[derive(Subdiagnostic)]
+pub(crate) enum MatchArmBodyWithoutBracesSugg {
+    #[multipart_suggestion(parser::suggestion_add_braces, applicability = "machine-applicable")]
+    AddBraces {
+        #[suggestion_part(code = "{{ ")]
+        left: Span,
+        #[suggestion_part(code = " }}")]
+        right: Span,
+    },
+    #[suggestion(
+        parser::suggestion_use_comma_not_semicolon,
+        code = ",",
+        applicability = "machine-applicable"
+    )]
+    UseComma {
+        #[primary_span]
+        semicolon: Span,
+    },
+}
+
+#[derive(Diagnostic)]
+#[diag(parser::struct_literal_not_allowed_here)]
+pub(crate) struct StructLiteralNotAllowedHere {
+    #[primary_span]
+    pub span: Span,
+    #[subdiagnostic]
+    pub sub: StructLiteralNotAllowedHereSugg,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(parser::suggestion, applicability = "machine-applicable")]
+pub(crate) struct StructLiteralNotAllowedHereSugg {
+    #[suggestion_part(code = "(")]
+    pub left: Span,
+    #[suggestion_part(code = ")")]
+    pub right: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(parser::invalid_interpolated_expression)]
+pub(crate) struct InvalidInterpolatedExpression {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(parser::hexadecimal_float_literal_not_supported)]
+pub(crate) struct HexadecimalFloatLiteralNotSupported {
+    #[primary_span]
+    #[label(parser::not_supported)]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(parser::octal_float_literal_not_supported)]
+pub(crate) struct OctalFloatLiteralNotSupported {
+    #[primary_span]
+    #[label(parser::not_supported)]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(parser::binary_float_literal_not_supported)]
+pub(crate) struct BinaryFloatLiteralNotSupported {
+    #[primary_span]
+    #[label(parser::not_supported)]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(parser::non_string_abi_literal)]
+pub(crate) struct NonStringAbiLiteral {
+    #[primary_span]
+    #[suggestion(code = "\"C\"", applicability = "maybe-incorrect")]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(parser::mismatched_closing_delimiter)]
+pub(crate) struct MismatchedClosingDelimiter {
+    #[primary_span]
+    pub spans: Vec<Span>,
+    pub delimiter: String,
+    #[label(parser::label_unmatched)]
+    pub unmatched: Span,
+    #[label(parser::label_opening_candidate)]
+    pub opening_candidate: Option<Span>,
+    #[label(parser::label_unclosed)]
+    pub unclosed: Option<Span>,
+}
+
 // SnapshotParser is used to create a snapshot of the parser
 // without causing duplicate errors being emitted when the `Parser`
 // is dropped.
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index ae77961b7bc..6e77475fbe6 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -1,14 +1,19 @@
 use super::diagnostics::{
-    CatchAfterTry, CommaAfterBaseStruct, DoCatchSyntaxRemoved, DotDotDot, EqFieldInit,
+    ArrayBracketsInsteadOfSpaces, ArrayBracketsInsteadOfSpacesSugg, BracesForStructLiteral,
+    CatchAfterTry, CommaAfterBaseStruct, ComparisonInterpretedAsGeneric,
+    ComparisonOrShiftInterpretedAsGenericSugg, DoCatchSyntaxRemoved, DotDotDot, EqFieldInit,
     ExpectedElseBlock, ExpectedExpressionFoundLet, FieldExpressionWithGeneric,
-    FloatLiteralRequiresIntegerPart, IfExpressionMissingCondition, IfExpressionMissingThenBlock,
-    IfExpressionMissingThenBlockSub, InvalidBlockMacroSegment, InvalidComparisonOperator,
-    InvalidComparisonOperatorSub, InvalidLogicalOperator, InvalidLogicalOperatorSub,
-    LeftArrowOperator, LifetimeInBorrowExpression, MacroInvocationWithQualifiedPath,
-    MalformedLoopLabel, MissingInInForLoop, MissingInInForLoopSub, MissingSemicolonBeforeArray,
-    NotAsNegationOperator, NotAsNegationOperatorSub, OuterAttributeNotAllowedOnIfElse,
-    RequireColonAfterLabeledExpression, SnapshotParser, TildeAsUnaryOperator,
-    UnexpectedTokenAfterLabel,
+    FloatLiteralRequiresIntegerPart, FoundExprWouldBeStmt, IfExpressionMissingCondition,
+    IfExpressionMissingThenBlock, IfExpressionMissingThenBlockSub, InvalidBlockMacroSegment,
+    InvalidComparisonOperator, InvalidComparisonOperatorSub, InvalidInterpolatedExpression,
+    InvalidLogicalOperator, InvalidLogicalOperatorSub, LabeledLoopInBreak, LeftArrowOperator,
+    LifetimeInBorrowExpression, MacroInvocationWithQualifiedPath, MalformedLoopLabel,
+    MatchArmBodyWithoutBraces, MissingInInForLoop, MissingInInForLoopSub,
+    MissingSemicolonBeforeArray, NoFieldsForFnCall, NotAsNegationOperator,
+    NotAsNegationOperatorSub, OuterAttributeNotAllowedOnIfElse, ParenthesesWithStructFields,
+    RequireColonAfterLabeledExpression, ShiftInterpretedAsGeneric, SnapshotParser,
+    StructLiteralNotAllowedHere, TildeAsUnaryOperator, UnexpectedTokenAfterLabel,
+    UnexpectedTokenAfterLabelSugg,
 };
 use super::pat::{CommaRecoveryMode, RecoverColon, RecoverComma, PARAM_EXPECTED};
 use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
@@ -18,9 +23,11 @@ use super::{
 };
 use crate::maybe_recover_from_interpolated_ty_qpath;
 use crate::parser::diagnostics::{
-    IntLiteralTooLarge, InvalidFloatLiteralSuffix, InvalidFloatLiteralWidth,
-    InvalidIntLiteralWidth, InvalidNumLiteralBasePrefix, InvalidNumLiteralSuffix,
-    MissingCommaAfterMatchArm,
+    BinaryFloatLiteralNotSupported, HexadecimalFloatLiteralNotSupported, IntLiteralTooLarge,
+    InvalidFloatLiteralSuffix, InvalidFloatLiteralWidth, InvalidIntLiteralWidth,
+    InvalidNumLiteralBasePrefix, InvalidNumLiteralSuffix, LabeledLoopInBreakSub,
+    LeadingPlusNotSupported, MatchArmBodyWithoutBracesSugg, MissingCommaAfterMatchArm,
+    OctalFloatLiteralNotSupported, StructLiteralNotAllowedHereSugg,
 };
 
 use core::mem;
@@ -38,6 +45,7 @@ use rustc_ast::{ClosureBinder, StmtKind};
 use rustc_ast_pretty::pprust;
 use rustc_errors::IntoDiagnostic;
 use rustc_errors::{Applicability, Diagnostic, PResult};
+use rustc_session::errors::ExprParenthesesNeeded;
 use rustc_session::lint::builtin::BREAK_WITH_LABEL_AND_LOOP;
 use rustc_session::lint::BuiltinLintDiagnostics;
 use rustc_span::source_map::{self, Span, Spanned};
@@ -421,13 +429,12 @@ impl<'a> Parser<'a> {
     /// but the next token implies this should be parsed as an expression.
     /// For example: `if let Some(x) = x { x } else { 0 } / 2`.
     fn error_found_expr_would_be_stmt(&self, lhs: &Expr) {
-        let mut err = self.struct_span_err(
-            self.token.span,
-            &format!("expected expression, found `{}`", pprust::token_to_string(&self.token),),
-        );
-        err.span_label(self.token.span, "expected expression");
-        self.sess.expr_parentheses_needed(&mut err, lhs.span);
-        err.emit();
+        self.sess.emit_err(FoundExprWouldBeStmt {
+            span: self.token.span,
+            // FIXME(#100717)
+            token: pprust::token_to_string(&self.token).to_string(),
+            suggestion: ExprParenthesesNeeded::surrounding(lhs.span),
+        });
     }
 
     /// Possibly translate the current token to an associative operator.
@@ -578,21 +585,16 @@ impl<'a> Parser<'a> {
                 make_it!(this, attrs, |this, _| this.parse_borrow_expr(lo))
             }
             token::BinOp(token::Plus) if this.look_ahead(1, |tok| tok.is_numeric_lit()) => {
-                let mut err = this.struct_span_err(lo, "leading `+` is not supported");
-                err.span_label(lo, "unexpected `+`");
+                let mut err =
+                    LeadingPlusNotSupported { span: lo, remove_plus: None, add_parentheses: None };
 
                 // a block on the LHS might have been intended to be an expression instead
                 if let Some(sp) = this.sess.ambiguous_block_expr_parse.borrow().get(&lo) {
-                    this.sess.expr_parentheses_needed(&mut err, *sp);
+                    err.add_parentheses = Some(ExprParenthesesNeeded::surrounding(*sp));
                 } else {
-                    err.span_suggestion_verbose(
-                        lo,
-                        "try removing the `+`",
-                        "",
-                        Applicability::MachineApplicable,
-                    );
+                    err.remove_plus = Some(lo);
                 }
-                err.emit();
+                this.sess.emit_err(err);
 
                 this.bump();
                 this.parse_prefix_expr(None)
@@ -755,9 +757,33 @@ impl<'a> Parser<'a> {
 
                 match self.parse_path(PathStyle::Expr) {
                     Ok(path) => {
-                        let (op_noun, op_verb) = match self.token.kind {
-                            token::Lt => ("comparison", "comparing"),
-                            token::BinOp(token::Shl) => ("shift", "shifting"),
+                        let typename = pprust::path_to_string(&path);
+
+                        let span_after_type = parser_snapshot_after_type.token.span;
+                        let expr =
+                            mk_expr(self, lhs, self.mk_ty(path.span, TyKind::Path(None, path)));
+
+                        let args_span = self.look_ahead(1, |t| t.span).to(span_after_type);
+                        let suggestion = ComparisonOrShiftInterpretedAsGenericSugg {
+                            left: expr.span.shrink_to_lo(),
+                            right: expr.span.shrink_to_hi(),
+                        };
+
+                        match self.token.kind {
+                            token::Lt => self.sess.emit_err(ComparisonInterpretedAsGeneric {
+                                comparison: self.token.span,
+                                typename,
+                                args: args_span,
+                                suggestion,
+                            }),
+                            token::BinOp(token::Shl) => {
+                                self.sess.emit_err(ShiftInterpretedAsGeneric {
+                                    shift: self.token.span,
+                                    typename,
+                                    args: args_span,
+                                    suggestion,
+                                })
+                            }
                             _ => {
                                 // We can end up here even without `<` being the next token, for
                                 // example because `parse_ty_no_plus` returns `Err` on keywords,
@@ -771,33 +797,7 @@ impl<'a> Parser<'a> {
                         // Successfully parsed the type path leaving a `<` yet to parse.
                         type_err.cancel();
 
-                        // Report non-fatal diagnostics, keep `x as usize` as an expression
-                        // in AST and continue parsing.
-                        let msg = format!(
-                            "`<` is interpreted as a start of generic arguments for `{}`, not a {}",
-                            pprust::path_to_string(&path),
-                            op_noun,
-                        );
-                        let span_after_type = parser_snapshot_after_type.token.span;
-                        let expr =
-                            mk_expr(self, lhs, self.mk_ty(path.span, TyKind::Path(None, path)));
-
-                        self.struct_span_err(self.token.span, &msg)
-                            .span_label(
-                                self.look_ahead(1, |t| t.span).to(span_after_type),
-                                "interpreted as generic arguments",
-                            )
-                            .span_label(self.token.span, format!("not interpreted as {op_noun}"))
-                            .multipart_suggestion(
-                                &format!("try {op_verb} the cast value"),
-                                vec![
-                                    (expr.span.shrink_to_lo(), "(".to_string()),
-                                    (expr.span.shrink_to_hi(), ")".to_string()),
-                                ],
-                                Applicability::MachineApplicable,
-                            )
-                            .emit();
-
+                        // Keep `x as usize` as an expression in AST and continue parsing.
                         expr
                     }
                     Err(path_err) => {
@@ -1208,29 +1208,25 @@ impl<'a> Parser<'a> {
                         let close_paren = self.prev_token.span;
                         let span = lo.to(self.prev_token.span);
                         if !fields.is_empty() {
-                            let replacement_err = self.struct_span_err(
+                            let mut replacement_err = ParenthesesWithStructFields {
                                 span,
-                                "invalid `struct` delimiters or `fn` call arguments",
-                            );
-                            mem::replace(err, replacement_err).cancel();
-
-                            err.multipart_suggestion(
-                                &format!("if `{name}` is a struct, use braces as delimiters"),
-                                vec![
-                                    (open_paren, " { ".to_string()),
-                                    (close_paren, " }".to_string()),
-                                ],
-                                Applicability::MaybeIncorrect,
-                            );
-                            err.multipart_suggestion(
-                                &format!("if `{name}` is a function, use the arguments directly"),
-                                fields
-                                    .into_iter()
-                                    .map(|field| (field.span.until(field.expr.span), String::new()))
-                                    .collect(),
-                                Applicability::MaybeIncorrect,
-                            );
-                            err.emit();
+                                name,
+                                braces_for_struct: BracesForStructLiteral {
+                                    first: open_paren,
+                                    second: close_paren,
+                                },
+                                no_fields_for_fn: NoFieldsForFnCall {
+                                    fields: fields
+                                        .into_iter()
+                                        .map(|field| field.span.until(field.expr.span))
+                                        .collect(),
+                                },
+                            }
+                            .into_diagnostic(&self.sess.span_diagnostic);
+                            replacement_err.emit();
+
+                            let old_err = mem::replace(err, replacement_err);
+                            old_err.cancel();
                         } else {
                             err.emit();
                         }
@@ -1537,15 +1533,19 @@ impl<'a> Parser<'a> {
             && (self.check_noexpect(&TokenKind::Comma) || self.check_noexpect(&TokenKind::Gt))
         {
             // We're probably inside of a `Path<'a>` that needs a turbofish
-            self.sess.emit_err(UnexpectedTokenAfterLabel(self.token.span));
+            self.sess.emit_err(UnexpectedTokenAfterLabel {
+                span: self.token.span,
+                remove_label: None,
+                enclose_in_block: None,
+            });
             consume_colon = false;
             Ok(self.mk_expr_err(lo))
         } else {
-            // FIXME: use UnexpectedTokenAfterLabel, needs multipart suggestions
-            let msg = "expected `while`, `for`, `loop` or `{` after a label";
-
-            let mut err = self.struct_span_err(self.token.span, msg);
-            err.span_label(self.token.span, msg);
+            let mut err = UnexpectedTokenAfterLabel {
+                span: self.token.span,
+                remove_label: None,
+                enclose_in_block: None,
+            };
 
             // Continue as an expression in an effort to recover on `'label: non_block_expr`.
             let expr = self.parse_expr().map(|expr| {
@@ -1572,28 +1572,15 @@ impl<'a> Parser<'a> {
                 // If there are no breaks that may use this label, suggest removing the label and
                 // recover to the unmodified expression.
                 if !found_labeled_breaks {
-                    let msg = "consider removing the label";
-                    err.span_suggestion_verbose(
-                        lo.until(span),
-                        msg,
-                        "",
-                        Applicability::MachineApplicable,
-                    );
+                    err.remove_label = Some(lo.until(span));
 
                     return expr;
                 }
 
-                let sugg_msg = "consider enclosing expression in a block";
-                let suggestions = vec![
-                    (span.shrink_to_lo(), "{ ".to_owned()),
-                    (span.shrink_to_hi(), " }".to_owned()),
-                ];
-
-                err.multipart_suggestion_verbose(
-                    sugg_msg,
-                    suggestions,
-                    Applicability::MachineApplicable,
-                );
+                err.enclose_in_block = Some(UnexpectedTokenAfterLabelSugg {
+                    left: span.shrink_to_lo(),
+                    right: span.shrink_to_hi(),
+                });
 
                 // Replace `'label: non_block_expr` with `'label: {non_block_expr}` in order to suppress future errors about `break 'label`.
                 let stmt = self.mk_stmt(span, StmtKind::Expr(expr));
@@ -1601,7 +1588,7 @@ impl<'a> Parser<'a> {
                 self.mk_expr(span, ExprKind::Block(blk, label))
             });
 
-            err.emit();
+            self.sess.emit_err(err);
             expr
         }?;
 
@@ -1672,19 +1659,13 @@ impl<'a> Parser<'a> {
             // The value expression can be a labeled loop, see issue #86948, e.g.:
             // `loop { break 'label: loop { break 'label 42; }; }`
             let lexpr = self.parse_labeled_expr(label.take().unwrap(), true)?;
-            self.struct_span_err(
-                lexpr.span,
-                "parentheses are required around this expression to avoid confusion with a labeled break expression",
-            )
-            .multipart_suggestion(
-                "wrap the expression in parentheses",
-                vec![
-                    (lexpr.span.shrink_to_lo(), "(".to_string()),
-                    (lexpr.span.shrink_to_hi(), ")".to_string()),
-                ],
-                Applicability::MachineApplicable,
-            )
-            .emit();
+            self.sess.emit_err(LabeledLoopInBreak {
+                span: lexpr.span,
+                sub: LabeledLoopInBreakSub {
+                    first: lexpr.span.shrink_to_lo(),
+                    second: lexpr.span.shrink_to_hi(),
+                },
+            });
             Some(lexpr)
         } else if self.token != token::OpenDelim(Delimiter::Brace)
             || !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL)
@@ -1756,9 +1737,8 @@ impl<'a> Parser<'a> {
                 };
                 if let Some(expr) = expr {
                     if matches!(expr.kind, ExprKind::Err) {
-                        let mut err = self
-                            .diagnostic()
-                            .struct_span_err(self.token.span, "invalid interpolated expression");
+                        let mut err = InvalidInterpolatedExpression { span: self.token.span }
+                            .into_diagnostic(&self.sess.span_diagnostic);
                         err.downgrade_to_delayed_bug();
                         return err;
                     }
@@ -1790,7 +1770,10 @@ impl<'a> Parser<'a> {
             });
             if let Some(token) = &recovered {
                 self.bump();
-                self.error_float_lits_must_have_int_part(&token);
+                self.sess.emit_err(FloatLiteralRequiresIntegerPart {
+                    span: token.span,
+                    correct: pprust::token_to_string(token).into_owned(),
+                });
             }
         }
 
@@ -1818,13 +1801,6 @@ impl<'a> Parser<'a> {
         }
     }
 
-    fn error_float_lits_must_have_int_part(&self, token: &Token) {
-        self.sess.emit_err(FloatLiteralRequiresIntegerPart {
-            span: token.span,
-            correct: pprust::token_to_string(token).into_owned(),
-        });
-    }
-
     fn report_lit_error(&self, err: LitError, lit: token::Lit, span: Span) {
         // Checks if `s` looks like i32 or u1234 etc.
         fn looks_like_width_suffix(first_chars: &[char], s: &str) -> bool {
@@ -1883,15 +1859,12 @@ impl<'a> Parser<'a> {
                 }
             }
             LitError::NonDecimalFloat(base) => {
-                let descr = match base {
-                    16 => "hexadecimal",
-                    8 => "octal",
-                    2 => "binary",
+                match base {
+                    16 => self.sess.emit_err(HexadecimalFloatLiteralNotSupported { span }),
+                    8 => self.sess.emit_err(OctalFloatLiteralNotSupported { span }),
+                    2 => self.sess.emit_err(BinaryFloatLiteralNotSupported { span }),
                     _ => unreachable!(),
                 };
-                self.struct_span_err(span, &format!("{descr} float literal is not supported"))
-                    .span_label(span, "not supported")
-                    .emit();
             }
             LitError::IntTooLarge => {
                 self.sess.emit_err(IntLiteralTooLarge { span });
@@ -1964,14 +1937,13 @@ impl<'a> Parser<'a> {
         let mut snapshot = self.create_snapshot_for_diagnostic();
         match snapshot.parse_array_or_repeat_expr(Delimiter::Brace) {
             Ok(arr) => {
-                let hi = snapshot.prev_token.span;
-                self.struct_span_err(arr.span, "this is a block expression, not an array")
-                    .multipart_suggestion(
-                        "to make an array, use square brackets instead of curly braces",
-                        vec![(lo, "[".to_owned()), (hi, "]".to_owned())],
-                        Applicability::MaybeIncorrect,
-                    )
-                    .emit();
+                self.sess.emit_err(ArrayBracketsInsteadOfSpaces {
+                    span: arr.span,
+                    sub: ArrayBracketsInsteadOfSpacesSugg {
+                        left: lo,
+                        right: snapshot.prev_token.span,
+                    },
+                });
 
                 self.restore_snapshot(snapshot);
                 Some(self.mk_expr_err(arr.span))
@@ -2515,39 +2487,22 @@ impl<'a> Parser<'a> {
         self.bump(); // `;`
         let mut stmts =
             vec![self.mk_stmt(first_expr.span, ast::StmtKind::Expr(first_expr.clone()))];
-        let err = |this: &mut Parser<'_>, stmts: Vec<ast::Stmt>| {
+        let err = |this: &Parser<'_>, stmts: Vec<ast::Stmt>| {
             let span = stmts[0].span.to(stmts[stmts.len() - 1].span);
-            let mut err = this.struct_span_err(span, "`match` arm body without braces");
-            let (these, s, are) =
-                if stmts.len() > 1 { ("these", "s", "are") } else { ("this", "", "is") };
-            err.span_label(
-                span,
-                &format!(
-                    "{these} statement{s} {are} not surrounded by a body",
-                    these = these,
-                    s = s,
-                    are = are
-                ),
-            );
-            err.span_label(arrow_span, "while parsing the `match` arm starting here");
-            if stmts.len() > 1 {
-                err.multipart_suggestion(
-                    &format!("surround the statement{s} with a body"),
-                    vec![
-                        (span.shrink_to_lo(), "{ ".to_string()),
-                        (span.shrink_to_hi(), " }".to_string()),
-                    ],
-                    Applicability::MachineApplicable,
-                );
-            } else {
-                err.span_suggestion(
-                    semi_sp,
-                    "use a comma to end a `match` arm expression",
-                    ",",
-                    Applicability::MachineApplicable,
-                );
-            }
-            err.emit();
+
+            this.sess.emit_err(MatchArmBodyWithoutBraces {
+                statements: span,
+                arrow: arrow_span,
+                num_statements: stmts.len(),
+                sub: if stmts.len() > 1 {
+                    MatchArmBodyWithoutBracesSugg::AddBraces {
+                        left: span.shrink_to_lo(),
+                        right: span.shrink_to_hi(),
+                    }
+                } else {
+                    MatchArmBodyWithoutBracesSugg::UseComma { semicolon: semi_sp }
+                },
+            });
             this.mk_expr_err(span)
         };
         // We might have either a `,` -> `;` typo, or a block without braces. We need
@@ -2836,23 +2791,19 @@ impl<'a> Parser<'a> {
             let expr = self.parse_struct_expr(qself.cloned(), path.clone(), true);
             if let (Ok(expr), false) = (&expr, struct_allowed) {
                 // This is a struct literal, but we don't can't accept them here.
-                self.error_struct_lit_not_allowed_here(path.span, expr.span);
+                self.sess.emit_err(StructLiteralNotAllowedHere {
+                    span: expr.span,
+                    sub: StructLiteralNotAllowedHereSugg {
+                        left: path.span.shrink_to_lo(),
+                        right: expr.span.shrink_to_hi(),
+                    },
+                });
             }
             return Some(expr);
         }
         None
     }
 
-    fn error_struct_lit_not_allowed_here(&self, lo: Span, sp: Span) {
-        self.struct_span_err(sp, "struct literals are not allowed here")
-            .multipart_suggestion(
-                "surround the struct literal with parentheses",
-                vec![(lo.shrink_to_lo(), "(".to_string()), (sp.shrink_to_hi(), ")".to_string())],
-                Applicability::MachineApplicable,
-            )
-            .emit();
-    }
-
     pub(super) fn parse_struct_fields(
         &mut self,
         pth: ast::Path,
diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs
index 4cb198561e0..f2f6f1feda3 100644
--- a/compiler/rustc_parse/src/parser/mod.rs
+++ b/compiler/rustc_parse/src/parser/mod.rs
@@ -32,7 +32,8 @@ use rustc_ast_pretty::pprust;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::PResult;
 use rustc_errors::{
-    struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed, FatalError, MultiSpan,
+    struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed, FatalError, IntoDiagnostic,
+    MultiSpan,
 };
 use rustc_session::parse::ParseSess;
 use rustc_span::source_map::{Span, DUMMY_SP};
@@ -41,6 +42,8 @@ use rustc_span::symbol::{kw, sym, Ident, Symbol};
 use std::ops::Range;
 use std::{cmp, mem, slice};
 
+use self::diagnostics::{MismatchedClosingDelimiter, NonStringAbiLiteral};
+
 bitflags::bitflags! {
     struct Restrictions: u8 {
         const STMT_EXPR         = 1 << 0;
@@ -1384,14 +1387,7 @@ impl<'a> Parser<'a> {
             Err(Some(lit)) => match lit.kind {
                 ast::LitKind::Err => None,
                 _ => {
-                    self.struct_span_err(lit.span, "non-string ABI literal")
-                        .span_suggestion(
-                            lit.span,
-                            "specify the ABI with a string literal",
-                            "\"C\"",
-                            Applicability::MaybeIncorrect,
-                        )
-                        .emit();
+                    self.sess.emit_err(NonStringAbiLiteral { span: lit.span });
                     None
                 }
             },
@@ -1432,25 +1428,18 @@ pub(crate) fn make_unclosed_delims_error(
     // `None` here means an `Eof` was found. We already emit those errors elsewhere, we add them to
     // `unmatched_braces` only for error recovery in the `Parser`.
     let found_delim = unmatched.found_delim?;
-    let span: MultiSpan = if let Some(sp) = unmatched.unclosed_span {
-        vec![unmatched.found_span, sp].into()
-    } else {
-        unmatched.found_span.into()
-    };
-    let mut err = sess.span_diagnostic.struct_span_err(
-        span,
-        &format!(
-            "mismatched closing delimiter: `{}`",
-            pprust::token_kind_to_string(&token::CloseDelim(found_delim)),
-        ),
-    );
-    err.span_label(unmatched.found_span, "mismatched closing delimiter");
-    if let Some(sp) = unmatched.candidate_span {
-        err.span_label(sp, "closing delimiter possibly meant for this");
-    }
+    let mut spans = vec![unmatched.found_span];
     if let Some(sp) = unmatched.unclosed_span {
-        err.span_label(sp, "unclosed delimiter");
-    }
+        spans.push(sp);
+    };
+    let err = MismatchedClosingDelimiter {
+        spans,
+        delimiter: pprust::token_kind_to_string(&token::CloseDelim(found_delim)).to_string(),
+        unmatched: unmatched.found_span,
+        opening_candidate: unmatched.candidate_span,
+        unclosed: unmatched.unclosed_span,
+    }
+    .into_diagnostic(&sess.span_diagnostic);
     Some(err)
 }