about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-12-22 02:00:55 +0000
committerbors <bors@rust-lang.org>2023-12-22 02:00:55 +0000
commitaaef5fe4971c42bae088b18e765339f3ec8853d0 (patch)
tree9214832213852a7e1d960460458ef363554154e3
parentcee794ee98d49b45a55ba225680d98e0c4672736 (diff)
parent5e4f12b41a13d6adf883cfeae8a17724ea457faf (diff)
downloadrust-aaef5fe4971c42bae088b18e765339f3ec8853d0.tar.gz
rust-aaef5fe4971c42bae088b18e765339f3ec8853d0.zip
Auto merge of #119163 - fmease:refactor-ast-trait-bound-modifiers, r=compiler-errors
Refactor AST trait bound modifiers

Instead of having two types to represent trait bound modifiers in the parser / the AST (`parser::ty::BoundModifiers` & `ast::TraitBoundModifier`), only to map one to the other later, just use `parser::ty::BoundModifiers` (moved & renamed to `ast::TraitBoundModifiers`).

The struct type is more extensible and easier to deal with (see [here](https://github.com/rust-lang/rust/pull/119099/files#r1430749981) and [here](https://github.com/rust-lang/rust/pull/119099/files#r1430752116) for context) since it more closely models what it represents: A compound of two kinds of modifiers, constness and polarity. Modeling this as an enum (the now removed `ast::TraitBoundModifier`) meant one had to add a new variant per *combination* of modifier kind, which simply isn't scalable and which lead to a lot of explicit non-DRY matches.

NB: `hir::TraitBoundModifier` being an enum is fine since HIR doesn't need to worry representing invalid modifier kind combinations as those get rejected during AST validation thereby immensely cutting down the number of possibilities.
-rw-r--r--compiler/rustc_ast/src/ast.rs87
-rw-r--r--compiler/rustc_ast_lowering/src/item.rs8
-rw-r--r--compiler/rustc_ast_lowering/src/lib.rs53
-rw-r--r--compiler/rustc_ast_passes/messages.ftl4
-rw-r--r--compiler/rustc_ast_passes/src/ast_validation.rs33
-rw-r--r--compiler/rustc_ast_passes/src/errors.rs7
-rw-r--r--compiler/rustc_ast_pretty/src/lib.rs1
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state.rs30
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state/item.rs8
-rw-r--r--compiler/rustc_expand/src/build.rs11
-rw-r--r--compiler/rustc_hir/src/hir.rs3
-rw-r--r--compiler/rustc_parse/src/parser/path.rs2
-rw-r--r--compiler/rustc_parse/src/parser/ty.rs76
-rw-r--r--compiler/rustc_resolve/src/late/diagnostics.rs6
-rw-r--r--src/tools/rustfmt/src/types.rs31
-rw-r--r--tests/ui/stats/hir-stats.stderr34
16 files changed, 194 insertions, 200 deletions
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index a121b5a9bed..3d59c034210 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -286,41 +286,16 @@ impl ParenthesizedArgs {
 
 pub use crate::node_id::{NodeId, CRATE_NODE_ID, DUMMY_NODE_ID};
 
-/// A modifier on a bound, e.g., `?Trait` or `~const Trait`.
-///
-/// Negative bounds should also be handled here.
+/// Modifiers on a trait bound like `~const`, `?` and `!`.
 #[derive(Copy, Clone, PartialEq, Eq, Encodable, Decodable, Debug)]
-pub enum TraitBoundModifier {
-    /// No modifiers
-    None,
-
-    /// `!Trait`
-    Negative,
-
-    /// `?Trait`
-    Maybe,
-
-    /// `~const Trait`
-    MaybeConst(Span),
-
-    /// `~const !Trait`
-    //
-    // This parses but will be rejected during AST validation.
-    MaybeConstNegative,
-
-    /// `~const ?Trait`
-    //
-    // This parses but will be rejected during AST validation.
-    MaybeConstMaybe,
+pub struct TraitBoundModifiers {
+    pub constness: BoundConstness,
+    pub polarity: BoundPolarity,
 }
 
-impl TraitBoundModifier {
-    pub fn to_constness(self) -> Const {
-        match self {
-            Self::MaybeConst(span) => Const::Yes(span),
-            _ => Const::No,
-        }
-    }
+impl TraitBoundModifiers {
+    pub const NONE: Self =
+        Self { constness: BoundConstness::Never, polarity: BoundPolarity::Positive };
 }
 
 /// The AST represents all type param bounds as types.
@@ -329,7 +304,7 @@ impl TraitBoundModifier {
 /// detects `Copy`, `Send` and `Sync`.
 #[derive(Clone, Encodable, Decodable, Debug)]
 pub enum GenericBound {
-    Trait(PolyTraitRef, TraitBoundModifier),
+    Trait(PolyTraitRef, TraitBoundModifiers),
     Outlives(Lifetime),
 }
 
@@ -1193,7 +1168,7 @@ impl Expr {
         match &self.kind {
             ExprKind::Path(None, path) => Some(GenericBound::Trait(
                 PolyTraitRef::new(ThinVec::new(), path.clone(), self.span),
-                TraitBoundModifier::None,
+                TraitBoundModifiers::NONE,
             )),
             _ => None,
         }
@@ -2491,6 +2466,15 @@ pub enum Const {
     No,
 }
 
+impl From<BoundConstness> for Const {
+    fn from(constness: BoundConstness) -> Self {
+        match constness {
+            BoundConstness::Maybe(span) => Self::Yes(span),
+            BoundConstness::Never => Self::No,
+        }
+    }
+}
+
 /// Item defaultness.
 /// For details see the [RFC #2532](https://github.com/rust-lang/rfcs/pull/2532).
 #[derive(Copy, Clone, PartialEq, Encodable, Decodable, Debug, HashStable_Generic)]
@@ -2516,7 +2500,9 @@ impl fmt::Debug for ImplPolarity {
     }
 }
 
-#[derive(Copy, Clone, PartialEq, Encodable, Decodable, HashStable_Generic)]
+/// The polarity of a trait bound.
+#[derive(Copy, Clone, PartialEq, Eq, Encodable, Decodable, Debug)]
+#[derive(HashStable_Generic)]
 pub enum BoundPolarity {
     /// `Type: Trait`
     Positive,
@@ -2526,6 +2512,35 @@ pub enum BoundPolarity {
     Maybe(Span),
 }
 
+impl BoundPolarity {
+    pub fn as_str(self) -> &'static str {
+        match self {
+            Self::Positive => "",
+            Self::Negative(_) => "!",
+            Self::Maybe(_) => "?",
+        }
+    }
+}
+
+/// The constness of a trait bound.
+#[derive(Copy, Clone, PartialEq, Eq, Encodable, Decodable, Debug)]
+#[derive(HashStable_Generic)]
+pub enum BoundConstness {
+    /// `Type: Trait`
+    Never,
+    /// `Type: ~const Trait`
+    Maybe(Span),
+}
+
+impl BoundConstness {
+    pub fn as_str(self) -> &'static str {
+        match self {
+            Self::Never => "",
+            Self::Maybe(_) => "~const",
+        }
+    }
+}
+
 #[derive(Clone, Encodable, Decodable, Debug)]
 pub enum FnRetTy {
     /// Returns type is not specified.
@@ -3259,7 +3274,7 @@ mod size_asserts {
     static_assert_size!(ForeignItem, 96);
     static_assert_size!(ForeignItemKind, 24);
     static_assert_size!(GenericArg, 24);
-    static_assert_size!(GenericBound, 64);
+    static_assert_size!(GenericBound, 72);
     static_assert_size!(Generics, 40);
     static_assert_size!(Impl, 136);
     static_assert_size!(Item, 136);
diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs
index 5bddbe5f417..9c990cb4619 100644
--- a/compiler/rustc_ast_lowering/src/item.rs
+++ b/compiler/rustc_ast_lowering/src/item.rs
@@ -1372,7 +1372,13 @@ impl<'hir> LoweringContext<'_, 'hir> {
             // need to compute this at all unless there is a Maybe bound.
             let mut is_param: Option<bool> = None;
             for bound in &bound_pred.bounds {
-                if !matches!(*bound, GenericBound::Trait(_, TraitBoundModifier::Maybe)) {
+                if !matches!(
+                    *bound,
+                    GenericBound::Trait(
+                        _,
+                        TraitBoundModifiers { polarity: BoundPolarity::Maybe(_), .. }
+                    )
+                ) {
                     continue;
                 }
                 let is_param = *is_param.get_or_insert_with(compute_is_param);
diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs
index 47b92981626..ed033d86008 100644
--- a/compiler/rustc_ast_lowering/src/lib.rs
+++ b/compiler/rustc_ast_lowering/src/lib.rs
@@ -1425,19 +1425,16 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
                         this.arena.alloc_from_iter(bounds.iter().filter_map(|bound| match bound {
                             GenericBound::Trait(
                                 ty,
-                                modifier @ (TraitBoundModifier::None
-                                | TraitBoundModifier::MaybeConst(_)
-                                | TraitBoundModifier::Negative),
-                            ) => {
-                                Some(this.lower_poly_trait_ref(ty, itctx, modifier.to_constness()))
-                            }
-                            // `~const ?Bound` will cause an error during AST validation
-                            // anyways, so treat it like `?Bound` as compilation proceeds.
+                                TraitBoundModifiers {
+                                    polarity: BoundPolarity::Positive | BoundPolarity::Negative(_),
+                                    constness,
+                                },
+                            ) => Some(this.lower_poly_trait_ref(ty, itctx, (*constness).into())),
+                            // We can safely ignore constness here, since AST validation
+                            // will take care of invalid modifier combinations.
                             GenericBound::Trait(
                                 _,
-                                TraitBoundModifier::Maybe
-                                | TraitBoundModifier::MaybeConstMaybe
-                                | TraitBoundModifier::MaybeConstNegative,
+                                TraitBoundModifiers { polarity: BoundPolarity::Maybe(_), .. },
                             ) => None,
                             GenericBound::Outlives(lifetime) => {
                                 if lifetime_bound.is_none() {
@@ -2028,9 +2025,9 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
         itctx: &ImplTraitContext,
     ) -> hir::GenericBound<'hir> {
         match tpb {
-            GenericBound::Trait(p, modifier) => hir::GenericBound::Trait(
-                self.lower_poly_trait_ref(p, itctx, modifier.to_constness()),
-                self.lower_trait_bound_modifier(*modifier),
+            GenericBound::Trait(p, modifiers) => hir::GenericBound::Trait(
+                self.lower_poly_trait_ref(p, itctx, modifiers.constness.into()),
+                self.lower_trait_bound_modifiers(*modifiers),
             ),
             GenericBound::Outlives(lifetime) => {
                 hir::GenericBound::Outlives(self.lower_lifetime(lifetime))
@@ -2316,25 +2313,29 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
         }
     }
 
-    fn lower_trait_bound_modifier(&mut self, f: TraitBoundModifier) -> hir::TraitBoundModifier {
-        match f {
-            TraitBoundModifier::None => hir::TraitBoundModifier::None,
-            TraitBoundModifier::MaybeConst(_) => hir::TraitBoundModifier::MaybeConst,
-
-            TraitBoundModifier::Negative => {
+    fn lower_trait_bound_modifiers(
+        &mut self,
+        modifiers: TraitBoundModifiers,
+    ) -> hir::TraitBoundModifier {
+        match (modifiers.constness, modifiers.polarity) {
+            (BoundConstness::Never, BoundPolarity::Positive) => hir::TraitBoundModifier::None,
+            (BoundConstness::Never, BoundPolarity::Maybe(_)) => hir::TraitBoundModifier::Maybe,
+            (BoundConstness::Never, BoundPolarity::Negative(_)) => {
                 if self.tcx.features().negative_bounds {
                     hir::TraitBoundModifier::Negative
                 } else {
                     hir::TraitBoundModifier::None
                 }
             }
-
-            // `MaybeConstMaybe` will cause an error during AST validation, but we need to pick a
-            // placeholder for compilation to proceed.
-            TraitBoundModifier::MaybeConstMaybe | TraitBoundModifier::Maybe => {
-                hir::TraitBoundModifier::Maybe
+            (BoundConstness::Maybe(_), BoundPolarity::Positive) => {
+                hir::TraitBoundModifier::MaybeConst
+            }
+            // Invalid modifier combinations will cause an error during AST validation.
+            // Arbitrarily pick a placeholder for compilation to proceed.
+            (BoundConstness::Maybe(_), BoundPolarity::Maybe(_)) => hir::TraitBoundModifier::Maybe,
+            (BoundConstness::Maybe(_), BoundPolarity::Negative(_)) => {
+                hir::TraitBoundModifier::MaybeConst
             }
-            TraitBoundModifier::MaybeConstNegative => hir::TraitBoundModifier::MaybeConst,
         }
     }
 
diff --git a/compiler/rustc_ast_passes/messages.ftl b/compiler/rustc_ast_passes/messages.ftl
index 1c5ad820bd6..ea3cd3e4bee 100644
--- a/compiler/rustc_ast_passes/messages.ftl
+++ b/compiler/rustc_ast_passes/messages.ftl
@@ -152,6 +152,8 @@ ast_passes_impl_trait_path = `impl Trait` is not allowed in path parameters
 ast_passes_incompatible_features = `{$f1}` and `{$f2}` are incompatible, using them at the same time is not allowed
     .help = remove one of these features
 
+ast_passes_incompatible_trait_bound_modifiers = `{$left}` and `{$right}` are mutually exclusive
+
 ast_passes_inherent_cannot_be = inherent impls cannot be {$annotation}
     .because = {$annotation} because of this
     .type = inherent impl for this type
@@ -195,8 +197,6 @@ ast_passes_nomangle_ascii = `#[no_mangle]` requires ASCII identifier
 ast_passes_obsolete_auto = `impl Trait for .. {"{}"}` is an obsolete syntax
     .help = use `auto trait Trait {"{}"}` instead
 
-ast_passes_optional_const_exclusive = `~const` and `{$modifier}` are mutually exclusive
-
 ast_passes_optional_trait_object = `?Trait` is not permitted in trait object types
 
 ast_passes_optional_trait_supertrait = `?Trait` is not permitted in supertraits
diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs
index 887cb434a60..23a45749455 100644
--- a/compiler/rustc_ast_passes/src/ast_validation.rs
+++ b/compiler/rustc_ast_passes/src/ast_validation.rs
@@ -1196,18 +1196,18 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
     }
 
     fn visit_param_bound(&mut self, bound: &'a GenericBound, ctxt: BoundKind) {
-        if let GenericBound::Trait(poly, modify) = bound {
-            match (ctxt, modify) {
-                (BoundKind::SuperTraits, TraitBoundModifier::Maybe) => {
+        if let GenericBound::Trait(poly, modifiers) = bound {
+            match (ctxt, modifiers.constness, modifiers.polarity) {
+                (BoundKind::SuperTraits, BoundConstness::Never, BoundPolarity::Maybe(_)) => {
                     self.dcx().emit_err(errors::OptionalTraitSupertrait {
                         span: poly.span,
                         path_str: pprust::path_to_string(&poly.trait_ref.path),
                     });
                 }
-                (BoundKind::TraitObject, TraitBoundModifier::Maybe) => {
+                (BoundKind::TraitObject, BoundConstness::Never, BoundPolarity::Maybe(_)) => {
                     self.dcx().emit_err(errors::OptionalTraitObject { span: poly.span });
                 }
-                (_, &TraitBoundModifier::MaybeConst(span))
+                (_, BoundConstness::Maybe(span), BoundPolarity::Positive)
                     if let Some(reason) = &self.disallow_tilde_const =>
                 {
                     let reason = match reason {
@@ -1235,16 +1235,15 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
                     };
                     self.dcx().emit_err(errors::TildeConstDisallowed { span, reason });
                 }
-                (_, TraitBoundModifier::MaybeConstMaybe) => {
-                    self.dcx().emit_err(errors::OptionalConstExclusive {
+                (
+                    _,
+                    BoundConstness::Maybe(_),
+                    BoundPolarity::Maybe(_) | BoundPolarity::Negative(_),
+                ) => {
+                    self.dcx().emit_err(errors::IncompatibleTraitBoundModifiers {
                         span: bound.span(),
-                        modifier: "?",
-                    });
-                }
-                (_, TraitBoundModifier::MaybeConstNegative) => {
-                    self.dcx().emit_err(errors::OptionalConstExclusive {
-                        span: bound.span(),
-                        modifier: "!",
+                        left: modifiers.constness.as_str(),
+                        right: modifiers.polarity.as_str(),
                     });
                 }
                 _ => {}
@@ -1252,7 +1251,8 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
         }
 
         // Negative trait bounds are not allowed to have associated constraints
-        if let GenericBound::Trait(trait_ref, TraitBoundModifier::Negative) = bound
+        if let GenericBound::Trait(trait_ref, modifiers) = bound
+            && let BoundPolarity::Negative(_) = modifiers.polarity
             && let Some(segment) = trait_ref.trait_ref.path.segments.last()
             && let Some(ast::GenericArgs::AngleBracketed(args)) = segment.args.as_deref()
         {
@@ -1494,7 +1494,8 @@ fn deny_equality_constraints(
             for param in &generics.params {
                 if param.ident == potential_param.ident {
                     for bound in &param.bounds {
-                        if let ast::GenericBound::Trait(trait_ref, TraitBoundModifier::None) = bound
+                        if let ast::GenericBound::Trait(trait_ref, TraitBoundModifiers::NONE) =
+                            bound
                         {
                             if let [trait_segment] = &trait_ref.trait_ref.path.segments[..] {
                                 let assoc = pprust::path_to_string(&ast::Path::from_ident(
diff --git a/compiler/rustc_ast_passes/src/errors.rs b/compiler/rustc_ast_passes/src/errors.rs
index 304c5c1bde9..a5b842b320e 100644
--- a/compiler/rustc_ast_passes/src/errors.rs
+++ b/compiler/rustc_ast_passes/src/errors.rs
@@ -580,11 +580,12 @@ pub enum TildeConstReason {
 }
 
 #[derive(Diagnostic)]
-#[diag(ast_passes_optional_const_exclusive)]
-pub struct OptionalConstExclusive {
+#[diag(ast_passes_incompatible_trait_bound_modifiers)]
+pub struct IncompatibleTraitBoundModifiers {
     #[primary_span]
     pub span: Span,
-    pub modifier: &'static str,
+    pub left: &'static str,
+    pub right: &'static str,
 }
 
 #[derive(Diagnostic)]
diff --git a/compiler/rustc_ast_pretty/src/lib.rs b/compiler/rustc_ast_pretty/src/lib.rs
index 670f2a45835..100b2988982 100644
--- a/compiler/rustc_ast_pretty/src/lib.rs
+++ b/compiler/rustc_ast_pretty/src/lib.rs
@@ -4,6 +4,7 @@
 #![deny(rustc::untranslatable_diagnostic)]
 #![deny(rustc::diagnostic_outside_of_impl)]
 #![feature(box_patterns)]
+#![feature(let_chains)]
 #![recursion_limit = "256"]
 
 mod helpers;
diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs
index d6c15ec35b6..2ad8aa38bcc 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state.rs
@@ -17,7 +17,7 @@ use rustc_ast::util::comments::{gather_comments, Comment, CommentStyle};
 use rustc_ast::util::parser;
 use rustc_ast::{self as ast, AttrArgs, AttrArgsEq, BlockCheckMode, PatKind};
 use rustc_ast::{attr, BindingAnnotation, ByRef, DelimArgs, RangeEnd, RangeSyntax, Term};
-use rustc_ast::{GenericArg, GenericBound, SelfKind, TraitBoundModifier};
+use rustc_ast::{GenericArg, GenericBound, SelfKind};
 use rustc_ast::{InlineAsmOperand, InlineAsmRegOrRegClass};
 use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
 use rustc_span::edition::Edition;
@@ -1559,26 +1559,20 @@ impl<'a> State<'a> {
 
             match bound {
                 GenericBound::Trait(tref, modifier) => {
-                    match modifier {
-                        TraitBoundModifier::None => {}
-                        TraitBoundModifier::Negative => {
-                            self.word("!");
+                    match modifier.constness {
+                        ast::BoundConstness::Never => {}
+                        ast::BoundConstness::Maybe(_) => {
+                            self.word_space(modifier.constness.as_str());
                         }
-                        TraitBoundModifier::Maybe => {
-                            self.word("?");
-                        }
-                        TraitBoundModifier::MaybeConst(_) => {
-                            self.word_space("~const");
-                        }
-                        TraitBoundModifier::MaybeConstNegative => {
-                            self.word_space("~const");
-                            self.word("!");
-                        }
-                        TraitBoundModifier::MaybeConstMaybe => {
-                            self.word_space("~const");
-                            self.word("?");
+                    }
+
+                    match modifier.polarity {
+                        ast::BoundPolarity::Positive => {}
+                        ast::BoundPolarity::Negative(_) | ast::BoundPolarity::Maybe(_) => {
+                            self.word(modifier.polarity.as_str());
                         }
                     }
+
                     self.print_poly_trait_ref(tref);
                 }
                 GenericBound::Outlives(lt) => self.print_lifetime(*lt),
diff --git a/compiler/rustc_ast_pretty/src/pprust/state/item.rs b/compiler/rustc_ast_pretty/src/pprust/state/item.rs
index 405ccc722d4..247061c5ca7 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs
@@ -339,13 +339,15 @@ impl<'a> State<'a> {
                 self.print_ident(item.ident);
                 self.print_generic_params(&generics.params);
                 let mut real_bounds = Vec::with_capacity(bounds.len());
-                for b in bounds.iter() {
-                    if let GenericBound::Trait(ptr, ast::TraitBoundModifier::Maybe) = b {
+                for bound in bounds.iter() {
+                    if let GenericBound::Trait(ptr, modifiers) = bound
+                        && let ast::BoundPolarity::Maybe(_) = modifiers.polarity
+                    {
                         self.space();
                         self.word_space("for ?");
                         self.print_trait_ref(&ptr.trait_ref);
                     } else {
-                        real_bounds.push(b.clone());
+                        real_bounds.push(bound.clone());
                     }
                 }
                 if !real_bounds.is_empty() {
diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs
index 86f555fa08b..f9eddfeeaa8 100644
--- a/compiler/rustc_expand/src/build.rs
+++ b/compiler/rustc_expand/src/build.rs
@@ -134,10 +134,13 @@ impl<'a> ExtCtxt<'a> {
     pub fn trait_bound(&self, path: ast::Path, is_const: bool) -> ast::GenericBound {
         ast::GenericBound::Trait(
             self.poly_trait_ref(path.span, path),
-            if is_const {
-                ast::TraitBoundModifier::MaybeConst(DUMMY_SP)
-            } else {
-                ast::TraitBoundModifier::None
+            ast::TraitBoundModifiers {
+                polarity: ast::BoundPolarity::Positive,
+                constness: if is_const {
+                    ast::BoundConstness::Maybe(DUMMY_SP)
+                } else {
+                    ast::BoundConstness::Never
+                },
             },
         )
     }
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index 3179fd73604..26430dcf965 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -417,8 +417,7 @@ pub enum GenericArgsParentheses {
     ParenSugar,
 }
 
-/// A modifier on a bound, currently this is only used for `?Sized`, where the
-/// modifier is `Maybe`. Negative bounds should also be handled here.
+/// A modifier on a trait bound.
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, HashStable_Generic)]
 pub enum TraitBoundModifier {
     None,
diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs
index 8970a99f1b7..405531c1e41 100644
--- a/compiler/rustc_parse/src/parser/path.rs
+++ b/compiler/rustc_parse/src/parser/path.rs
@@ -840,7 +840,7 @@ impl<'a> Parser<'a> {
             {
                 return Ok((false, seg.ident, seg.args.as_deref().cloned()));
             } else if let ast::TyKind::TraitObject(bounds, ast::TraitObjectSyntax::None) = &ty.kind
-                && let [ast::GenericBound::Trait(trait_ref, ast::TraitBoundModifier::None)] =
+                && let [ast::GenericBound::Trait(trait_ref, ast::TraitBoundModifiers::NONE)] =
                     bounds.as_slice()
                 && let [seg] = trait_ref.trait_ref.path.segments.as_slice()
             {
diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs
index da8cc05ff66..f89d6d1d965 100644
--- a/compiler/rustc_parse/src/parser/ty.rs
+++ b/compiler/rustc_parse/src/parser/ty.rs
@@ -13,37 +13,15 @@ use rustc_ast::ptr::P;
 use rustc_ast::token::{self, Delimiter, Token, TokenKind};
 use rustc_ast::util::case::Case;
 use rustc_ast::{
-    self as ast, BareFnTy, BoundPolarity, FnRetTy, GenericBound, GenericBounds, GenericParam,
-    Generics, Lifetime, MacCall, MutTy, Mutability, PolyTraitRef, TraitBoundModifier,
-    TraitObjectSyntax, Ty, TyKind,
+    self as ast, BareFnTy, BoundConstness, BoundPolarity, FnRetTy, GenericBound, GenericBounds,
+    GenericParam, Generics, Lifetime, MacCall, MutTy, Mutability, PolyTraitRef,
+    TraitBoundModifiers, TraitObjectSyntax, Ty, TyKind,
 };
 use rustc_errors::{Applicability, PResult};
 use rustc_span::symbol::{kw, sym, Ident};
 use rustc_span::{Span, Symbol};
 use thin_vec::{thin_vec, ThinVec};
 
-/// Any `?`, `!`, or `~const` modifiers that appear at the start of a bound.
-struct BoundModifiers {
-    /// `?Trait`.
-    bound_polarity: BoundPolarity,
-
-    /// `~const Trait`.
-    maybe_const: Option<Span>,
-}
-
-impl BoundModifiers {
-    fn to_trait_bound_modifier(&self) -> TraitBoundModifier {
-        match (self.bound_polarity, self.maybe_const) {
-            (BoundPolarity::Positive, None) => TraitBoundModifier::None,
-            (BoundPolarity::Negative(_), None) => TraitBoundModifier::Negative,
-            (BoundPolarity::Maybe(_), None) => TraitBoundModifier::Maybe,
-            (BoundPolarity::Positive, Some(sp)) => TraitBoundModifier::MaybeConst(sp),
-            (BoundPolarity::Negative(_), Some(_)) => TraitBoundModifier::MaybeConstNegative,
-            (BoundPolarity::Maybe(_), Some(_)) => TraitBoundModifier::MaybeConstMaybe,
-        }
-    }
-}
-
 #[derive(Copy, Clone, PartialEq)]
 pub(super) enum AllowPlus {
     Yes,
@@ -461,7 +439,7 @@ impl<'a> Parser<'a> {
         parse_plus: bool,
     ) -> PResult<'a, TyKind> {
         let poly_trait_ref = PolyTraitRef::new(generic_params, path, lo.to(self.prev_token.span));
-        let bounds = vec![GenericBound::Trait(poly_trait_ref, TraitBoundModifier::None)];
+        let bounds = vec![GenericBound::Trait(poly_trait_ref, TraitBoundModifiers::NONE)];
         self.parse_remaining_bounds(bounds, parse_plus)
     }
 
@@ -800,7 +778,7 @@ impl<'a> Parser<'a> {
         let has_parens = self.eat(&token::OpenDelim(Delimiter::Parenthesis));
         let inner_lo = self.token.span;
 
-        let modifiers = self.parse_ty_bound_modifiers()?;
+        let modifiers = self.parse_trait_bound_modifiers()?;
         let bound = if self.token.is_lifetime() {
             self.error_lt_bound_with_modifiers(modifiers);
             self.parse_generic_lt_bound(lo, inner_lo, has_parens)?
@@ -831,18 +809,21 @@ impl<'a> Parser<'a> {
     }
 
     /// Emits an error if any trait bound modifiers were present.
-    fn error_lt_bound_with_modifiers(&self, modifiers: BoundModifiers) {
-        if let Some(span) = modifiers.maybe_const {
-            self.sess.emit_err(errors::TildeConstLifetime { span });
+    fn error_lt_bound_with_modifiers(&self, modifiers: TraitBoundModifiers) {
+        match modifiers.constness {
+            BoundConstness::Never => {}
+            BoundConstness::Maybe(span) => {
+                self.sess.emit_err(errors::TildeConstLifetime { span });
+            }
         }
 
-        match modifiers.bound_polarity {
+        match modifiers.polarity {
             BoundPolarity::Positive => {}
-            BoundPolarity::Negative(span) => {
-                self.sess.emit_err(errors::ModifierLifetime { span, sigil: "!" });
-            }
-            BoundPolarity::Maybe(span) => {
-                self.sess.emit_err(errors::ModifierLifetime { span, sigil: "?" });
+            BoundPolarity::Negative(span) | BoundPolarity::Maybe(span) => {
+                self.sess.emit_err(errors::ModifierLifetime {
+                    span,
+                    sigil: modifiers.polarity.as_str(),
+                });
             }
         }
     }
@@ -867,26 +848,26 @@ impl<'a> Parser<'a> {
     /// If no modifiers are present, this does not consume any tokens.
     ///
     /// ```ebnf
-    /// TY_BOUND_MODIFIERS = ["~const"] ["?"]
+    /// TRAIT_BOUND_MODIFIERS = ["~const"] ["?" | "!"]
     /// ```
-    fn parse_ty_bound_modifiers(&mut self) -> PResult<'a, BoundModifiers> {
-        let maybe_const = if self.eat(&token::Tilde) {
+    fn parse_trait_bound_modifiers(&mut self) -> PResult<'a, TraitBoundModifiers> {
+        let constness = if self.eat(&token::Tilde) {
             let tilde = self.prev_token.span;
             self.expect_keyword(kw::Const)?;
             let span = tilde.to(self.prev_token.span);
             self.sess.gated_spans.gate(sym::const_trait_impl, span);
-            Some(span)
+            BoundConstness::Maybe(span)
         } else if self.eat_keyword(kw::Const) {
             let span = self.prev_token.span;
             self.sess.gated_spans.gate(sym::const_trait_impl, span);
             self.sess.emit_err(errors::ConstMissingTilde { span, start: span.shrink_to_lo() });
 
-            Some(span)
+            BoundConstness::Maybe(span)
         } else {
-            None
+            BoundConstness::Never
         };
 
-        let bound_polarity = if self.eat(&token::Question) {
+        let polarity = if self.eat(&token::Question) {
             BoundPolarity::Maybe(self.prev_token.span)
         } else if self.eat(&token::Not) {
             self.sess.gated_spans.gate(sym::negative_bounds, self.prev_token.span);
@@ -895,13 +876,13 @@ impl<'a> Parser<'a> {
             BoundPolarity::Positive
         };
 
-        Ok(BoundModifiers { bound_polarity, maybe_const })
+        Ok(TraitBoundModifiers { constness, polarity })
     }
 
     /// Parses a type bound according to:
     /// ```ebnf
     /// TY_BOUND = TY_BOUND_NOPAREN | (TY_BOUND_NOPAREN)
-    /// TY_BOUND_NOPAREN = [TY_BOUND_MODIFIERS] [for<LT_PARAM_DEFS>] SIMPLE_PATH
+    /// TY_BOUND_NOPAREN = [TRAIT_BOUND_MODIFIERS] [for<LT_PARAM_DEFS>] SIMPLE_PATH
     /// ```
     ///
     /// For example, this grammar accepts `~const ?for<'a: 'b> m::Trait<'a>`.
@@ -909,7 +890,7 @@ impl<'a> Parser<'a> {
         &mut self,
         lo: Span,
         has_parens: bool,
-        modifiers: BoundModifiers,
+        modifiers: TraitBoundModifiers,
         leading_token: &Token,
     ) -> PResult<'a, GenericBound> {
         let mut lifetime_defs = self.parse_late_bound_lifetime_defs()?;
@@ -991,9 +972,8 @@ impl<'a> Parser<'a> {
             }
         }
 
-        let modifier = modifiers.to_trait_bound_modifier();
         let poly_trait = PolyTraitRef::new(lifetime_defs, path, lo.to(self.prev_token.span));
-        Ok(GenericBound::Trait(poly_trait, modifier))
+        Ok(GenericBound::Trait(poly_trait, modifiers))
     }
 
     // recovers a `Fn(..)` parenthesized-style path from `fn(..)`
diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs
index d767ed74139..376ccfba9d8 100644
--- a/compiler/rustc_resolve/src/late/diagnostics.rs
+++ b/compiler/rustc_resolve/src/late/diagnostics.rs
@@ -520,7 +520,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
                     continue;
                 };
                 for bound in bounds {
-                    let ast::GenericBound::Trait(trait_ref, ast::TraitBoundModifier::None) = bound
+                    let ast::GenericBound::Trait(trait_ref, ast::TraitBoundModifiers::NONE) = bound
                     else {
                         continue;
                     };
@@ -1242,7 +1242,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
             }
             if let (
                 [ast::PathSegment { args: None, .. }],
-                [ast::GenericBound::Trait(poly_trait_ref, ast::TraitBoundModifier::None)],
+                [ast::GenericBound::Trait(poly_trait_ref, ast::TraitBoundModifiers::NONE)],
             ) = (&type_param_path.segments[..], &bounds[..])
             {
                 if let [ast::PathSegment { ident, args: None, .. }] =
@@ -3276,7 +3276,7 @@ fn mk_where_bound_predicate(
                 },
                 span: DUMMY_SP,
             },
-            ast::TraitBoundModifier::None,
+            ast::TraitBoundModifiers::NONE,
         )],
     };
 
diff --git a/src/tools/rustfmt/src/types.rs b/src/tools/rustfmt/src/types.rs
index a5a4244903c..cd2582e66be 100644
--- a/src/tools/rustfmt/src/types.rs
+++ b/src/tools/rustfmt/src/types.rs
@@ -537,28 +537,19 @@ impl Rewrite for ast::Lifetime {
 impl Rewrite for ast::GenericBound {
     fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
         match *self {
-            ast::GenericBound::Trait(ref poly_trait_ref, trait_bound_modifier) => {
+            ast::GenericBound::Trait(ref poly_trait_ref, modifiers) => {
                 let snippet = context.snippet(self.span());
                 let has_paren = snippet.starts_with('(') && snippet.ends_with(')');
-                let rewrite = match trait_bound_modifier {
-                    ast::TraitBoundModifier::None => poly_trait_ref.rewrite(context, shape),
-                    ast::TraitBoundModifier::Maybe => poly_trait_ref
-                        .rewrite(context, shape.offset_left(1)?)
-                        .map(|s| format!("?{}", s)),
-                    ast::TraitBoundModifier::MaybeConst(_) => poly_trait_ref
-                        .rewrite(context, shape.offset_left(7)?)
-                        .map(|s| format!("~const {}", s)),
-                    ast::TraitBoundModifier::MaybeConstMaybe => poly_trait_ref
-                        .rewrite(context, shape.offset_left(8)?)
-                        .map(|s| format!("~const ?{}", s)),
-                    ast::TraitBoundModifier::Negative => poly_trait_ref
-                        .rewrite(context, shape.offset_left(1)?)
-                        .map(|s| format!("!{}", s)),
-                    ast::TraitBoundModifier::MaybeConstNegative => poly_trait_ref
-                        .rewrite(context, shape.offset_left(8)?)
-                        .map(|s| format!("~const !{}", s)),
-                };
-                rewrite.map(|s| if has_paren { format!("({})", s) } else { s })
+                let mut constness = modifiers.constness.as_str().to_string();
+                if !constness.is_empty() {
+                    constness.push(' ');
+                }
+                let polarity = modifiers.polarity.as_str();
+                let shape = shape.offset_left(constness.len() + polarity.len())?;
+                poly_trait_ref
+                    .rewrite(context, shape)
+                    .map(|s| format!("{constness}{polarity}{s}"))
+                    .map(|s| if has_paren { format!("({})", s) } else { s })
             }
             ast::GenericBound::Outlives(ref lifetime) => lifetime.rewrite(context, shape),
         }
diff --git a/tests/ui/stats/hir-stats.stderr b/tests/ui/stats/hir-stats.stderr
index e6da83296ce..070dbbb10bb 100644
--- a/tests/ui/stats/hir-stats.stderr
+++ b/tests/ui/stats/hir-stats.stderr
@@ -15,20 +15,20 @@ ast-stats-1 Arm                       96 ( 1.5%)             2            48
 ast-stats-1 ForeignItem               96 ( 1.5%)             1            96
 ast-stats-1 - Fn                        96 ( 1.5%)             1
 ast-stats-1 FnDecl                   120 ( 1.8%)             5            24
-ast-stats-1 FieldDef                 160 ( 2.5%)             2            80
-ast-stats-1 Stmt                     160 ( 2.5%)             5            32
+ast-stats-1 FieldDef                 160 ( 2.4%)             2            80
+ast-stats-1 Stmt                     160 ( 2.4%)             5            32
 ast-stats-1 - Local                     32 ( 0.5%)             1
 ast-stats-1 - MacCall                   32 ( 0.5%)             1
 ast-stats-1 - Expr                      96 ( 1.5%)             3
-ast-stats-1 Param                    160 ( 2.5%)             4            40
+ast-stats-1 Param                    160 ( 2.4%)             4            40
 ast-stats-1 Block                    192 ( 2.9%)             6            32
 ast-stats-1 Variant                  208 ( 3.2%)             2           104
-ast-stats-1 GenericBound             256 ( 3.9%)             4            64
-ast-stats-1 - Trait                    256 ( 3.9%)             4
+ast-stats-1 GenericBound             288 ( 4.4%)             4            72
+ast-stats-1 - Trait                    288 ( 4.4%)             4
 ast-stats-1 AssocItem                352 ( 5.4%)             4            88
 ast-stats-1 - Type                     176 ( 2.7%)             2
 ast-stats-1 - Fn                       176 ( 2.7%)             2
-ast-stats-1 GenericParam             480 ( 7.4%)             5            96
+ast-stats-1 GenericParam             480 ( 7.3%)             5            96
 ast-stats-1 Pat                      504 ( 7.7%)             7            72
 ast-stats-1 - Struct                    72 ( 1.1%)             1
 ast-stats-1 - Wild                      72 ( 1.1%)             1
@@ -45,15 +45,15 @@ ast-stats-1 - Ptr                       64 ( 1.0%)             1
 ast-stats-1 - Ref                       64 ( 1.0%)             1
 ast-stats-1 - ImplicitSelf             128 ( 2.0%)             2
 ast-stats-1 - Path                     640 ( 9.8%)            10
-ast-stats-1 Item                   1_224 (18.8%)             9           136
+ast-stats-1 Item                   1_224 (18.7%)             9           136
 ast-stats-1 - Trait                    136 ( 2.1%)             1
 ast-stats-1 - Enum                     136 ( 2.1%)             1
 ast-stats-1 - ForeignMod               136 ( 2.1%)             1
 ast-stats-1 - Impl                     136 ( 2.1%)             1
 ast-stats-1 - Fn                       272 ( 4.2%)             2
-ast-stats-1 - Use                      408 ( 6.3%)             3
+ast-stats-1 - Use                      408 ( 6.2%)             3
 ast-stats-1 ----------------------------------------------------------------
-ast-stats-1 Total                  6_520
+ast-stats-1 Total                  6_552
 ast-stats-1
 ast-stats-2 POST EXPANSION AST STATS
 ast-stats-2 Name                Accumulated Size         Count     Item Size
@@ -81,16 +81,16 @@ ast-stats-2 - Expr                      96 ( 1.3%)             3
 ast-stats-2 Param                    160 ( 2.2%)             4            40
 ast-stats-2 Block                    192 ( 2.7%)             6            32
 ast-stats-2 Variant                  208 ( 2.9%)             2           104
-ast-stats-2 GenericBound             256 ( 3.6%)             4            64
-ast-stats-2 - Trait                    256 ( 3.6%)             4
+ast-stats-2 GenericBound             288 ( 4.0%)             4            72
+ast-stats-2 - Trait                    288 ( 4.0%)             4
 ast-stats-2 AssocItem                352 ( 4.9%)             4            88
 ast-stats-2 - Type                     176 ( 2.5%)             2
 ast-stats-2 - Fn                       176 ( 2.5%)             2
 ast-stats-2 GenericParam             480 ( 6.7%)             5            96
-ast-stats-2 Pat                      504 ( 7.1%)             7            72
+ast-stats-2 Pat                      504 ( 7.0%)             7            72
 ast-stats-2 - Struct                    72 ( 1.0%)             1
 ast-stats-2 - Wild                      72 ( 1.0%)             1
-ast-stats-2 - Ident                    360 ( 5.1%)             5
+ast-stats-2 - Ident                    360 ( 5.0%)             5
 ast-stats-2 Expr                     648 ( 9.1%)             9            72
 ast-stats-2 - Path                      72 ( 1.0%)             1
 ast-stats-2 - Match                     72 ( 1.0%)             1
@@ -99,12 +99,12 @@ ast-stats-2 - InlineAsm                 72 ( 1.0%)             1
 ast-stats-2 - Lit                      144 ( 2.0%)             2
 ast-stats-2 - Block                    216 ( 3.0%)             3
 ast-stats-2 PathSegment              792 (11.1%)            33            24
-ast-stats-2 Ty                       896 (12.6%)            14            64
+ast-stats-2 Ty                       896 (12.5%)            14            64
 ast-stats-2 - Ptr                       64 ( 0.9%)             1
 ast-stats-2 - Ref                       64 ( 0.9%)             1
 ast-stats-2 - ImplicitSelf             128 ( 1.8%)             2
-ast-stats-2 - Path                     640 ( 9.0%)            10
-ast-stats-2 Item                   1_496 (21.0%)            11           136
+ast-stats-2 - Path                     640 ( 8.9%)            10
+ast-stats-2 Item                   1_496 (20.9%)            11           136
 ast-stats-2 - Trait                    136 ( 1.9%)             1
 ast-stats-2 - Enum                     136 ( 1.9%)             1
 ast-stats-2 - ExternCrate              136 ( 1.9%)             1
@@ -113,7 +113,7 @@ ast-stats-2 - Impl                     136 ( 1.9%)             1
 ast-stats-2 - Fn                       272 ( 3.8%)             2
 ast-stats-2 - Use                      544 ( 7.6%)             4
 ast-stats-2 ----------------------------------------------------------------
-ast-stats-2 Total                  7_120
+ast-stats-2 Total                  7_152
 ast-stats-2
 hir-stats HIR STATS
 hir-stats Name                Accumulated Size         Count     Item Size