about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2023-04-25 05:15:50 +0000
committerMichael Goulet <michael@errs.io>2023-05-02 22:36:24 +0000
commit6e01e910cb8ab0109235be7cc7ab7ef465724255 (patch)
treed63e7272022d540f0a93d43f36e359ff31c3f56d
parent98c33e47a495fbd7b22bce9ce32f2815991bc414 (diff)
downloadrust-6e01e910cb8ab0109235be7cc7ab7ef465724255.tar.gz
rust-6e01e910cb8ab0109235be7cc7ab7ef465724255.zip
Implement negative bounds
-rw-r--r--compiler/rustc_ast/src/ast.rs18
-rw-r--r--compiler/rustc_ast_lowering/src/lib.rs17
-rw-r--r--compiler/rustc_ast_passes/messages.ftl5
-rw-r--r--compiler/rustc_ast_passes/src/ast_validation.rs5
-rw-r--r--compiler/rustc_ast_passes/src/errors.rs8
-rw-r--r--compiler/rustc_ast_passes/src/feature_gate.rs6
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state.rs7
-rw-r--r--compiler/rustc_feature/src/active.rs2
-rw-r--r--compiler/rustc_hir/src/hir.rs1
-rw-r--r--compiler/rustc_hir_analysis/src/astconv/mod.rs26
-rw-r--r--compiler/rustc_hir_analysis/src/bounds.rs8
-rw-r--r--compiler/rustc_hir_analysis/src/lib.rs1
-rw-r--r--compiler/rustc_middle/src/ty/print/pretty.rs3
-rw-r--r--compiler/rustc_parse/messages.ftl10
-rw-r--r--compiler/rustc_parse/src/errors.rs31
-rw-r--r--compiler/rustc_parse/src/parser/diagnostics.rs2
-rw-r--r--compiler/rustc_parse/src/parser/generics.rs4
-rw-r--r--compiler/rustc_parse/src/parser/item.rs10
-rw-r--r--compiler/rustc_parse/src/parser/path.rs2
-rw-r--r--compiler/rustc_parse/src/parser/ty.rs125
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs4
-rw-r--r--tests/ui/feature-gates/feature-gate-negative_bounds.rs4
-rw-r--r--tests/ui/feature-gates/feature-gate-negative_bounds.stderr8
-rw-r--r--tests/ui/issues/issue-58857.stderr4
-rw-r--r--tests/ui/parser/issues/issue-33418.fixed19
-rw-r--r--tests/ui/parser/issues/issue-33418.rs8
-rw-r--r--tests/ui/parser/issues/issue-33418.stderr38
-rw-r--r--tests/ui/parser/issues/issue-67146-negative-outlives-bound-syntactic-fail.fixed9
-rw-r--r--tests/ui/parser/issues/issue-67146-negative-outlives-bound-syntactic-fail.rs3
-rw-r--r--tests/ui/parser/issues/issue-67146-negative-outlives-bound-syntactic-fail.stderr32
-rw-r--r--tests/ui/traits/negative-bounds/simple.rs42
-rw-r--r--tests/ui/traits/negative-bounds/simple.stderr82
33 files changed, 360 insertions, 185 deletions
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index dce7106d128..b5dba0713bf 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -287,12 +287,20 @@ pub enum TraitBoundModifier {
     /// No modifiers
     None,
 
+    /// `!Trait`
+    Negative,
+
     /// `?Trait`
     Maybe,
 
     /// `~const Trait`
     MaybeConst,
 
+    /// `~const !Trait`
+    //
+    // This parses but will be rejected during AST validation.
+    MaybeConstNegative,
+
     /// `~const ?Trait`
     //
     // This parses but will be rejected during AST validation.
@@ -2446,6 +2454,16 @@ impl fmt::Debug for ImplPolarity {
     }
 }
 
+#[derive(Copy, Clone, PartialEq, Encodable, Decodable, HashStable_Generic)]
+pub enum BoundPolarity {
+    /// `Type: Trait`
+    Positive,
+    /// `Type: !Trait`
+    Negative(Span),
+    /// `Type: ?Trait`
+    Maybe(Span),
+}
+
 #[derive(Clone, Encodable, Decodable, Debug)]
 pub enum FnRetTy {
     /// Returns type is not specified.
diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs
index b5b28bf8e31..4100efb6eb3 100644
--- a/compiler/rustc_ast_lowering/src/lib.rs
+++ b/compiler/rustc_ast_lowering/src/lib.rs
@@ -1368,13 +1368,17 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
                         this.arena.alloc_from_iter(bounds.iter().filter_map(|bound| match bound {
                             GenericBound::Trait(
                                 ty,
-                                TraitBoundModifier::None | TraitBoundModifier::MaybeConst,
+                                TraitBoundModifier::None
+                                | TraitBoundModifier::MaybeConst
+                                | TraitBoundModifier::Negative,
                             ) => Some(this.lower_poly_trait_ref(ty, itctx)),
                             // `~const ?Bound` will cause an error during AST validation
                             // anyways, so treat it like `?Bound` as compilation proceeds.
                             GenericBound::Trait(
                                 _,
-                                TraitBoundModifier::Maybe | TraitBoundModifier::MaybeConstMaybe,
+                                TraitBoundModifier::Maybe
+                                | TraitBoundModifier::MaybeConstMaybe
+                                | TraitBoundModifier::MaybeConstNegative,
                             ) => None,
                             GenericBound::Outlives(lifetime) => {
                                 if lifetime_bound.is_none() {
@@ -2421,11 +2425,20 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
             TraitBoundModifier::None => hir::TraitBoundModifier::None,
             TraitBoundModifier::MaybeConst => hir::TraitBoundModifier::MaybeConst,
 
+            TraitBoundModifier::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
             }
+            TraitBoundModifier::MaybeConstNegative => hir::TraitBoundModifier::MaybeConst,
         }
     }
 
diff --git a/compiler/rustc_ast_passes/messages.ftl b/compiler/rustc_ast_passes/messages.ftl
index ffca37ae9d5..90535af781d 100644
--- a/compiler/rustc_ast_passes/messages.ftl
+++ b/compiler/rustc_ast_passes/messages.ftl
@@ -206,7 +206,7 @@ ast_passes_tilde_const_disallowed = `~const` is not allowed here
     .closure = closures cannot have `~const` trait bounds
     .function = this function is not `const`, so it cannot have `~const` trait bounds
 
-ast_passes_optional_const_exclusive = `~const` and `?` are mutually exclusive
+ast_passes_optional_const_exclusive = `~const` and `{$modifier}` are mutually exclusive
 
 ast_passes_const_and_async = functions cannot be both `const` and `async`
     .const = `const` because of this
@@ -235,3 +235,6 @@ ast_passes_incompatible_features = `{$f1}` and `{$f2}` are incompatible, using t
     .help = remove one of these features
 
 ast_passes_show_span = {$msg}
+
+ast_passes_negative_bound_not_supported =
+    negative bounds are not supported
diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs
index c79626ccd76..6e9d33516ce 100644
--- a/compiler/rustc_ast_passes/src/ast_validation.rs
+++ b/compiler/rustc_ast_passes/src/ast_validation.rs
@@ -1168,7 +1168,10 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
                     });
                 }
                 (_, TraitBoundModifier::MaybeConstMaybe) => {
-                    self.err_handler().emit_err(errors::OptionalConstExclusive {span: bound.span()});
+                    self.err_handler().emit_err(errors::OptionalConstExclusive {span: bound.span(), modifier: "?" });
+                }
+                (_, TraitBoundModifier::MaybeConstNegative) => {
+                    self.err_handler().emit_err(errors::OptionalConstExclusive {span: bound.span(), modifier: "!" });
                 }
                 _ => {}
             }
diff --git a/compiler/rustc_ast_passes/src/errors.rs b/compiler/rustc_ast_passes/src/errors.rs
index 1732865f0bb..66a47db9074 100644
--- a/compiler/rustc_ast_passes/src/errors.rs
+++ b/compiler/rustc_ast_passes/src/errors.rs
@@ -567,6 +567,7 @@ pub enum TildeConstReason {
 pub struct OptionalConstExclusive {
     #[primary_span]
     pub span: Span,
+    pub modifier: &'static str,
 }
 
 #[derive(Diagnostic)]
@@ -693,3 +694,10 @@ pub struct ShowSpan {
     pub span: Span,
     pub msg: &'static str,
 }
+
+#[derive(Diagnostic)]
+#[diag(ast_passes_negative_bound_not_supported)]
+pub struct NegativeBoundUnsupported {
+    #[primary_span]
+    pub span: Span,
+}
diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs
index 17bcd24ee39..77e83fdbbaa 100644
--- a/compiler/rustc_ast_passes/src/feature_gate.rs
+++ b/compiler/rustc_ast_passes/src/feature_gate.rs
@@ -603,6 +603,12 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session) {
     gate_all!(dyn_star, "`dyn*` trait objects are experimental");
     gate_all!(const_closures, "const closures are experimental");
 
+    if !visitor.features.negative_bounds {
+        for &span in spans.get(&sym::negative_bounds).iter().copied().flatten() {
+            sess.emit_err(errors::NegativeBoundUnsupported { span });
+        }
+    }
+
     // All uses of `gate_all!` below this point were added in #65742,
     // and subsequently disabled (with the non-early gating readded).
     // We emit an early future-incompatible warning for these.
diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs
index 849336c8669..ae346510ccc 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state.rs
@@ -1570,12 +1570,19 @@ impl<'a> State<'a> {
                 GenericBound::Trait(tref, modifier) => {
                     match modifier {
                         TraitBoundModifier::None => {}
+                        TraitBoundModifier::Negative => {
+                            self.word("!");
+                        }
                         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("?");
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index 6201e5b619b..294ed18a239 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -164,6 +164,8 @@ declare_features! (
     (active, link_cfg, "1.14.0", None, None),
     /// Allows the `multiple_supertrait_upcastable` lint.
     (active, multiple_supertrait_upcastable, "1.69.0", None, None),
+    /// Allow negative trait bounds. This is an internal-only feature for testing the trait solver!
+    (incomplete, negative_bounds, "CURRENT_RUSTC_VERSION", None, None),
     /// Allows using `#[omit_gdb_pretty_printer_section]`.
     (active, omit_gdb_pretty_printer_section, "1.5.0", None, None),
     /// Allows using `#[prelude_import]` on glob `use` items.
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index e220a029339..38cd5865cc3 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -435,6 +435,7 @@ pub enum GenericArgsParentheses {
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, HashStable_Generic)]
 pub enum TraitBoundModifier {
     None,
+    Negative,
     Maybe,
     MaybeConst,
 }
diff --git a/compiler/rustc_hir_analysis/src/astconv/mod.rs b/compiler/rustc_hir_analysis/src/astconv/mod.rs
index 6cb008bc5f8..0faf7a4ba45 100644
--- a/compiler/rustc_hir_analysis/src/astconv/mod.rs
+++ b/compiler/rustc_hir_analysis/src/astconv/mod.rs
@@ -665,6 +665,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
         span: Span,
         binding_span: Option<Span>,
         constness: ty::BoundConstness,
+        polarity: ty::ImplPolarity,
         bounds: &mut Bounds<'tcx>,
         speculative: bool,
         trait_ref_span: Span,
@@ -696,10 +697,12 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
             ty::Binder::bind_with_vars(tcx.mk_trait_ref(trait_def_id, substs), bound_vars);
 
         debug!(?poly_trait_ref, ?assoc_bindings);
-        bounds.push_trait_bound(tcx, poly_trait_ref, span, constness);
+        bounds.push_trait_bound(tcx, poly_trait_ref, span, constness, polarity);
 
         let mut dup_bindings = FxHashMap::default();
         for binding in &assoc_bindings {
+            // TODO: negative polarity can't have associated type bindings!
+
             // Specify type to assert that error was already reported in `Err` case.
             let _: Result<_, ErrorGuaranteed> = self.add_predicates_for_ast_type_binding(
                 hir_id,
@@ -711,6 +714,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
                 binding_span.unwrap_or(binding.span),
                 constness,
                 only_self_bounds,
+                polarity,
             );
             // Okay to ignore `Err` because of `ErrorGuaranteed` (see above).
         }
@@ -743,6 +747,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
         trait_ref: &hir::TraitRef<'_>,
         span: Span,
         constness: ty::BoundConstness,
+        polarity: ty::ImplPolarity,
         self_ty: Ty<'tcx>,
         bounds: &mut Bounds<'tcx>,
         speculative: bool,
@@ -764,6 +769,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
             span,
             binding_span,
             constness,
+            polarity,
             bounds,
             speculative,
             trait_ref_span,
@@ -799,6 +805,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
             span,
             binding_span,
             constness,
+            ty::ImplPolarity::Positive,
             bounds,
             speculative,
             trait_ref_span,
@@ -961,16 +968,23 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
         for ast_bound in ast_bounds {
             match ast_bound {
                 hir::GenericBound::Trait(poly_trait_ref, modifier) => {
-                    let constness = match modifier {
-                        hir::TraitBoundModifier::MaybeConst => ty::BoundConstness::ConstIfConst,
-                        hir::TraitBoundModifier::None => ty::BoundConstness::NotConst,
+                    let (constness, polarity) = match modifier {
+                        hir::TraitBoundModifier::MaybeConst => {
+                            (ty::BoundConstness::ConstIfConst, ty::ImplPolarity::Positive)
+                        }
+                        hir::TraitBoundModifier::None => {
+                            (ty::BoundConstness::NotConst, ty::ImplPolarity::Positive)
+                        }
+                        hir::TraitBoundModifier::Negative => {
+                            (ty::BoundConstness::NotConst, ty::ImplPolarity::Negative)
+                        }
                         hir::TraitBoundModifier::Maybe => continue,
                     };
-
                     let _ = self.instantiate_poly_trait_ref(
                         &poly_trait_ref.trait_ref,
                         poly_trait_ref.span,
                         constness,
+                        polarity,
                         param_ty,
                         bounds,
                         false,
@@ -1088,6 +1102,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
         path_span: Span,
         constness: ty::BoundConstness,
         only_self_bounds: OnlySelfBounds,
+        polarity: ty::ImplPolarity,
     ) -> Result<(), ErrorGuaranteed> {
         // Given something like `U: SomeTrait<T = X>`, we want to produce a
         // predicate like `<U as SomeTrait>::T = X`. This is somewhat
@@ -1438,6 +1453,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
                 &trait_bound.trait_ref,
                 trait_bound.span,
                 ty::BoundConstness::NotConst,
+                ty::ImplPolarity::Positive,
                 dummy_self,
                 &mut bounds,
                 false,
diff --git a/compiler/rustc_hir_analysis/src/bounds.rs b/compiler/rustc_hir_analysis/src/bounds.rs
index 284b099e7bc..7156fea8f89 100644
--- a/compiler/rustc_hir_analysis/src/bounds.rs
+++ b/compiler/rustc_hir_analysis/src/bounds.rs
@@ -42,8 +42,14 @@ impl<'tcx> Bounds<'tcx> {
         trait_ref: ty::PolyTraitRef<'tcx>,
         span: Span,
         constness: ty::BoundConstness,
+        polarity: ty::ImplPolarity,
     ) {
-        self.predicates.push((trait_ref.with_constness(constness).to_predicate(tcx), span));
+        self.predicates.push((
+            trait_ref
+                .map_bound(|trait_ref| ty::TraitPredicate { trait_ref, constness, polarity })
+                .to_predicate(tcx),
+            span,
+        ));
     }
 
     pub fn push_projection_bound(
diff --git a/compiler/rustc_hir_analysis/src/lib.rs b/compiler/rustc_hir_analysis/src/lib.rs
index 961457b7579..3fe34f23aef 100644
--- a/compiler/rustc_hir_analysis/src/lib.rs
+++ b/compiler/rustc_hir_analysis/src/lib.rs
@@ -528,6 +528,7 @@ pub fn hir_trait_to_predicates<'tcx>(
         hir_trait,
         DUMMY_SP,
         ty::BoundConstness::NotConst,
+        ty::ImplPolarity::Positive,
         self_ty,
         &mut bounds,
         true,
diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs
index 2aced27f7bb..6ac9f950450 100644
--- a/compiler/rustc_middle/src/ty/print/pretty.rs
+++ b/compiler/rustc_middle/src/ty/print/pretty.rs
@@ -2816,6 +2816,9 @@ define_print_and_forward_display! {
         if let ty::BoundConstness::ConstIfConst = self.constness && cx.tcx().features().const_trait_impl {
             p!("~const ");
         }
+        if let ty::ImplPolarity::Negative = self.polarity {
+            p!("!");
+        }
         p!(print(self.trait_ref.print_only_trait_path()))
     }
 
diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl
index 9a5232b1bcd..cd296dca133 100644
--- a/compiler/rustc_parse/messages.ftl
+++ b/compiler/rustc_parse/messages.ftl
@@ -615,13 +615,6 @@ parse_invalid_dyn_keyword = invalid `dyn` keyword
     .help = `dyn` is only needed at the start of a trait `+`-separated list
     .suggestion = remove this keyword
 
-parse_negative_bounds_not_supported = negative bounds are not supported
-    .label = negative bounds are not supported
-    .suggestion = {$num_bounds ->
-            [one] remove the bound
-           *[other] remove the bounds
-        }
-
 parse_help_set_edition_cargo = set `edition = "{$edition}"` in `Cargo.toml`
 parse_help_set_edition_standalone = pass `--edition {$edition}` to `rustc`
 parse_note_edition_guide = for more on editions, read https://doc.rust-lang.org/edition-guide
@@ -772,7 +765,8 @@ parse_assoc_lifetime = associated lifetimes are not supported
 
 parse_tilde_const_lifetime = `~const` may only modify trait bounds, not lifetime bounds
 
-parse_maybe_lifetime = `?` may only modify trait bounds, not lifetime bounds
+parse_modifier_lifetime = `{$sigil}` may only modify trait bounds, not lifetime bounds
+    .suggestion = remove the `{$sigil}`
 
 parse_parenthesized_lifetime = parenthesized lifetime bounds are not supported
     .suggestion = remove the parentheses
diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs
index b445ccc7ad0..010a13aefa4 100644
--- a/compiler/rustc_parse/src/errors.rs
+++ b/compiler/rustc_parse/src/errors.rs
@@ -2280,31 +2280,6 @@ pub(crate) struct InvalidDynKeyword {
     pub span: Span,
 }
 
-#[derive(Diagnostic)]
-#[diag(parse_negative_bounds_not_supported)]
-pub(crate) struct NegativeBoundsNotSupported {
-    #[primary_span]
-    pub negative_bounds: Vec<Span>,
-    #[label]
-    pub last_span: Span,
-    #[subdiagnostic]
-    pub sub: Option<NegativeBoundsNotSupportedSugg>,
-}
-
-#[derive(Subdiagnostic)]
-#[suggestion(
-    parse_suggestion,
-    style = "tool-only",
-    code = "{fixed}",
-    applicability = "machine-applicable"
-)]
-pub(crate) struct NegativeBoundsNotSupportedSugg {
-    #[primary_span]
-    pub bound_list: Span,
-    pub num_bounds: usize,
-    pub fixed: String,
-}
-
 #[derive(Subdiagnostic)]
 pub enum HelpUseLatestEdition {
     #[help(parse_help_set_edition_cargo)]
@@ -2412,10 +2387,12 @@ pub(crate) struct TildeConstLifetime {
 }
 
 #[derive(Diagnostic)]
-#[diag(parse_maybe_lifetime)]
-pub(crate) struct MaybeLifetime {
+#[diag(parse_modifier_lifetime)]
+pub(crate) struct ModifierLifetime {
     #[primary_span]
+    #[suggestion(style = "tool-only", applicability = "maybe-incorrect", code = "")]
     pub span: Span,
+    pub sigil: &'static str,
 }
 
 #[derive(Diagnostic)]
diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs
index 638a432cea5..e974df61dc9 100644
--- a/compiler/rustc_parse/src/parser/diagnostics.rs
+++ b/compiler/rustc_parse/src/parser/diagnostics.rs
@@ -1284,7 +1284,7 @@ impl<'a> Parser<'a> {
         }
 
         self.bump(); // `+`
-        let bounds = self.parse_generic_bounds(None)?;
+        let bounds = self.parse_generic_bounds()?;
         let sum_span = ty.span.to(self.prev_token.span);
 
         let sub = match &ty.kind {
diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs
index 61a7ae93bfa..e6d0f9fbc76 100644
--- a/compiler/rustc_parse/src/parser/generics.rs
+++ b/compiler/rustc_parse/src/parser/generics.rs
@@ -78,7 +78,7 @@ impl<'a> Parser<'a> {
                 }
                 self.restore_snapshot(snapshot);
             }
-            self.parse_generic_bounds(colon_span)?
+            self.parse_generic_bounds()?
         } else {
             Vec::new()
         };
@@ -419,7 +419,7 @@ impl<'a> Parser<'a> {
         // or with mandatory equality sign and the second type.
         let ty = self.parse_ty_for_where_clause()?;
         if self.eat(&token::Colon) {
-            let bounds = self.parse_generic_bounds(Some(self.prev_token.span))?;
+            let bounds = self.parse_generic_bounds()?;
             Ok(ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate {
                 span: lo.to(self.prev_token.span),
                 bound_generic_params: lifetime_defs,
diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs
index 64ff7f1fb2c..10a95cae3e1 100644
--- a/compiler/rustc_parse/src/parser/item.rs
+++ b/compiler/rustc_parse/src/parser/item.rs
@@ -788,11 +788,7 @@ impl<'a> Parser<'a> {
         // Parse optional colon and supertrait bounds.
         let had_colon = self.eat(&token::Colon);
         let span_at_colon = self.prev_token.span;
-        let bounds = if had_colon {
-            self.parse_generic_bounds(Some(self.prev_token.span))?
-        } else {
-            Vec::new()
-        };
+        let bounds = if had_colon { self.parse_generic_bounds()? } else { Vec::new() };
 
         let span_before_eq = self.prev_token.span;
         if self.eat(&token::Eq) {
@@ -802,7 +798,7 @@ impl<'a> Parser<'a> {
                 self.sess.emit_err(errors::BoundsNotAllowedOnTraitAliases { span });
             }
 
-            let bounds = self.parse_generic_bounds(None)?;
+            let bounds = self.parse_generic_bounds()?;
             generics.where_clause = self.parse_where_clause()?;
             self.expect_semi()?;
 
@@ -883,7 +879,7 @@ impl<'a> Parser<'a> {
 
         // Parse optional colon and param bounds.
         let bounds =
-            if self.eat(&token::Colon) { self.parse_generic_bounds(None)? } else { Vec::new() };
+            if self.eat(&token::Colon) { self.parse_generic_bounds()? } else { Vec::new() };
         let before_where_clause = self.parse_where_clause()?;
 
         let ty = if self.eat(&token::Eq) { Some(self.parse_ty()?) } else { None };
diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs
index b9a2b141bce..205d4d15e2e 100644
--- a/compiler/rustc_parse/src/parser/path.rs
+++ b/compiler/rustc_parse/src/parser/path.rs
@@ -606,7 +606,7 @@ impl<'a> Parser<'a> {
                     let kind = if self.eat(&token::Colon) {
                         // Parse associated type constraint bound.
 
-                        let bounds = self.parse_generic_bounds(Some(self.prev_token.span))?;
+                        let bounds = self.parse_generic_bounds()?;
                         AssocConstraintKind::Bound { bounds }
                     } else if self.eat(&token::Eq) {
                         self.parse_assoc_equality_term(ident, self.prev_token.span)?
diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs
index 37c441fbecb..04f7eea90ed 100644
--- a/compiler/rustc_parse/src/parser/ty.rs
+++ b/compiler/rustc_parse/src/parser/ty.rs
@@ -3,8 +3,7 @@ use super::{Parser, PathStyle, TokenType};
 use crate::errors::{
     self, DynAfterMut, ExpectedFnPathFoundFnKeyword, ExpectedMutOrConstInRawPointerType,
     FnPointerCannotBeAsync, FnPointerCannotBeConst, FnPtrWithGenerics, FnPtrWithGenericsSugg,
-    InvalidDynKeyword, LifetimeAfterMut, NeedPlusAfterTraitObjectLifetime,
-    NegativeBoundsNotSupported, NegativeBoundsNotSupportedSugg, NestedCVariadicType,
+    InvalidDynKeyword, LifetimeAfterMut, NeedPlusAfterTraitObjectLifetime, NestedCVariadicType,
     ReturnTypesUseThinArrow,
 };
 use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
@@ -14,8 +13,9 @@ 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, FnRetTy, GenericBound, GenericBounds, GenericParam, Generics, Lifetime,
-    MacCall, MutTy, Mutability, PolyTraitRef, TraitBoundModifier, TraitObjectSyntax, Ty, TyKind,
+    self as ast, BareFnTy, BoundPolarity, FnRetTy, GenericBound, GenericBounds, GenericParam,
+    Generics, Lifetime, MacCall, MutTy, Mutability, PolyTraitRef, TraitBoundModifier,
+    TraitObjectSyntax, Ty, TyKind,
 };
 use rustc_errors::{Applicability, PResult};
 use rustc_span::source_map::Span;
@@ -23,10 +23,10 @@ use rustc_span::symbol::{kw, sym, Ident};
 use rustc_span::Symbol;
 use thin_vec::{thin_vec, ThinVec};
 
-/// Any `?` or `~const` modifiers that appear at the start of a bound.
+/// Any `?`, `!`, or `~const` modifiers that appear at the start of a bound.
 struct BoundModifiers {
     /// `?Trait`.
-    maybe: Option<Span>,
+    bound_polarity: BoundPolarity,
 
     /// `~const Trait`.
     maybe_const: Option<Span>,
@@ -34,11 +34,13 @@ struct BoundModifiers {
 
 impl BoundModifiers {
     fn to_trait_bound_modifier(&self) -> TraitBoundModifier {
-        match (self.maybe, self.maybe_const) {
-            (None, None) => TraitBoundModifier::None,
-            (Some(_), None) => TraitBoundModifier::Maybe,
-            (None, Some(_)) => TraitBoundModifier::MaybeConst,
-            (Some(_), Some(_)) => TraitBoundModifier::MaybeConstMaybe,
+        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(_)) => TraitBoundModifier::MaybeConst,
+            (BoundPolarity::Negative(_), Some(_)) => TraitBoundModifier::MaybeConstNegative,
+            (BoundPolarity::Maybe(_), Some(_)) => TraitBoundModifier::MaybeConstMaybe,
         }
     }
 }
@@ -368,7 +370,7 @@ impl<'a> Parser<'a> {
 
     fn parse_bare_trait_object(&mut self, lo: Span, allow_plus: AllowPlus) -> PResult<'a, TyKind> {
         let lt_no_plus = self.check_lifetime() && !self.look_ahead(1, |t| t.is_like_plus());
-        let bounds = self.parse_generic_bounds_common(allow_plus, None)?;
+        let bounds = self.parse_generic_bounds_common(allow_plus)?;
         if lt_no_plus {
             self.sess.emit_err(NeedPlusAfterTraitObjectLifetime { span: lo });
         }
@@ -395,7 +397,7 @@ impl<'a> Parser<'a> {
     ) -> PResult<'a, TyKind> {
         if plus {
             self.eat_plus(); // `+`, or `+=` gets split and `+` is discarded
-            bounds.append(&mut self.parse_generic_bounds(Some(self.prev_token.span))?);
+            bounds.append(&mut self.parse_generic_bounds()?);
         }
         Ok(TyKind::TraitObject(bounds, TraitObjectSyntax::None))
     }
@@ -598,7 +600,7 @@ impl<'a> Parser<'a> {
                 }
             })
         }
-        let bounds = self.parse_generic_bounds(None)?;
+        let bounds = self.parse_generic_bounds()?;
         *impl_dyn_multi = bounds.len() > 1 || self.prev_token.kind == TokenKind::BinOp(token::Plus);
         Ok(TyKind::ImplTrait(ast::DUMMY_NODE_ID, bounds))
     }
@@ -629,7 +631,7 @@ impl<'a> Parser<'a> {
         };
 
         // Always parse bounds greedily for better error recovery.
-        let bounds = self.parse_generic_bounds(None)?;
+        let bounds = self.parse_generic_bounds()?;
         *impl_dyn_multi = bounds.len() > 1 || self.prev_token.kind == TokenKind::BinOp(token::Plus);
         Ok(TyKind::TraitObject(bounds, syntax))
     }
@@ -660,23 +662,15 @@ impl<'a> Parser<'a> {
         }
     }
 
-    pub(super) fn parse_generic_bounds(
-        &mut self,
-        colon_span: Option<Span>,
-    ) -> PResult<'a, GenericBounds> {
-        self.parse_generic_bounds_common(AllowPlus::Yes, colon_span)
+    pub(super) fn parse_generic_bounds(&mut self) -> PResult<'a, GenericBounds> {
+        self.parse_generic_bounds_common(AllowPlus::Yes)
     }
 
     /// Parses bounds of a type parameter `BOUND + BOUND + ...`, possibly with trailing `+`.
     ///
     /// See `parse_generic_bound` for the `BOUND` grammar.
-    fn parse_generic_bounds_common(
-        &mut self,
-        allow_plus: AllowPlus,
-        colon_span: Option<Span>,
-    ) -> PResult<'a, GenericBounds> {
+    fn parse_generic_bounds_common(&mut self, allow_plus: AllowPlus) -> PResult<'a, GenericBounds> {
         let mut bounds = Vec::new();
-        let mut negative_bounds = Vec::new();
 
         // In addition to looping while we find generic bounds:
         // We continue even if we find a keyword. This is necessary for error recovery on,
@@ -693,19 +687,12 @@ impl<'a> Parser<'a> {
                 self.sess.emit_err(InvalidDynKeyword { span: self.token.span });
                 self.bump();
             }
-            match self.parse_generic_bound()? {
-                Ok(bound) => bounds.push(bound),
-                Err(neg_sp) => negative_bounds.push(neg_sp),
-            }
+            bounds.push(self.parse_generic_bound()?);
             if allow_plus == AllowPlus::No || !self.eat_plus() {
                 break;
             }
         }
 
-        if !negative_bounds.is_empty() {
-            self.error_negative_bounds(colon_span, &bounds, negative_bounds);
-        }
-
         Ok(bounds)
     }
 
@@ -713,55 +700,22 @@ impl<'a> Parser<'a> {
     fn can_begin_bound(&mut self) -> bool {
         // This needs to be synchronized with `TokenKind::can_begin_bound`.
         self.check_path()
-        || self.check_lifetime()
-        || self.check(&token::Not) // Used for error reporting only.
-        || self.check(&token::Question)
-        || self.check(&token::Tilde)
-        || self.check_keyword(kw::For)
-        || self.check(&token::OpenDelim(Delimiter::Parenthesis))
-    }
-
-    fn error_negative_bounds(
-        &self,
-        colon_span: Option<Span>,
-        bounds: &[GenericBound],
-        negative_bounds: Vec<Span>,
-    ) {
-        let sub = if let Some(bound_list) = colon_span {
-            let bound_list = bound_list.to(self.prev_token.span);
-            let mut new_bound_list = String::new();
-            if !bounds.is_empty() {
-                let mut snippets = bounds.iter().map(|bound| self.span_to_snippet(bound.span()));
-                while let Some(Ok(snippet)) = snippets.next() {
-                    new_bound_list.push_str(" + ");
-                    new_bound_list.push_str(&snippet);
-                }
-                new_bound_list = new_bound_list.replacen(" +", ":", 1);
-            }
-
-            Some(NegativeBoundsNotSupportedSugg {
-                bound_list,
-                num_bounds: negative_bounds.len(),
-                fixed: new_bound_list,
-            })
-        } else {
-            None
-        };
-
-        let last_span = *negative_bounds.last().expect("no negative bounds, but still error?");
-        self.sess.emit_err(NegativeBoundsNotSupported { negative_bounds, last_span, sub });
+            || self.check_lifetime()
+            || self.check(&token::Not)
+            || self.check(&token::Question)
+            || self.check(&token::Tilde)
+            || self.check_keyword(kw::For)
+            || self.check(&token::OpenDelim(Delimiter::Parenthesis))
     }
 
     /// Parses a bound according to the grammar:
     /// ```ebnf
     /// BOUND = TY_BOUND | LT_BOUND
     /// ```
-    fn parse_generic_bound(&mut self) -> PResult<'a, Result<GenericBound, Span>> {
-        let anchor_lo = self.prev_token.span;
+    fn parse_generic_bound(&mut self) -> PResult<'a, GenericBound> {
         let lo = self.token.span;
         let has_parens = self.eat(&token::OpenDelim(Delimiter::Parenthesis));
         let inner_lo = self.token.span;
-        let is_negative = self.eat(&token::Not);
 
         let modifiers = self.parse_ty_bound_modifiers()?;
         let bound = if self.token.is_lifetime() {
@@ -771,7 +725,7 @@ impl<'a> Parser<'a> {
             self.parse_generic_ty_bound(lo, has_parens, modifiers)?
         };
 
-        Ok(if is_negative { Err(anchor_lo.to(self.prev_token.span)) } else { Ok(bound) })
+        Ok(bound)
     }
 
     /// Parses a lifetime ("outlives") bound, e.g. `'a`, according to:
@@ -799,8 +753,14 @@ impl<'a> Parser<'a> {
             self.sess.emit_err(errors::TildeConstLifetime { span });
         }
 
-        if let Some(span) = modifiers.maybe {
-            self.sess.emit_err(errors::MaybeLifetime { span });
+        match modifiers.bound_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: "?" });
+            }
         }
     }
 
@@ -843,9 +803,16 @@ impl<'a> Parser<'a> {
             None
         };
 
-        let maybe = self.eat(&token::Question).then_some(self.prev_token.span);
+        let bound_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);
+            BoundPolarity::Negative(self.prev_token.span)
+        } else {
+            BoundPolarity::Positive
+        };
 
-        Ok(BoundModifiers { maybe, maybe_const })
+        Ok(BoundModifiers { bound_polarity, maybe_const })
     }
 
     /// Parses a type bound according to:
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 7969b848fd9..714d10f2341 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -984,6 +984,7 @@ symbols! {
         needs_panic_runtime,
         neg,
         negate_unsigned,
+        negative_bounds,
         negative_impls,
         neon,
         never,
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
index 595f6e0b927..d87a2979428 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
@@ -530,6 +530,10 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
         associated_ty: Option<(&'static str, Ty<'tcx>)>,
         mut body_id: LocalDefId,
     ) {
+        if trait_pred.skip_binder().polarity == ty::ImplPolarity::Negative {
+            return;
+        }
+
         let trait_pred = self.resolve_numeric_literals_with_default(trait_pred);
 
         let self_ty = trait_pred.skip_binder().self_ty();
diff --git a/tests/ui/feature-gates/feature-gate-negative_bounds.rs b/tests/ui/feature-gates/feature-gate-negative_bounds.rs
new file mode 100644
index 00000000000..533cb0ce5bc
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-negative_bounds.rs
@@ -0,0 +1,4 @@
+fn test<T: !Copy>() {}
+//~^ ERROR negative bounds are not supported
+
+fn main() {}
diff --git a/tests/ui/feature-gates/feature-gate-negative_bounds.stderr b/tests/ui/feature-gates/feature-gate-negative_bounds.stderr
new file mode 100644
index 00000000000..ae010fdf3f8
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-negative_bounds.stderr
@@ -0,0 +1,8 @@
+error: negative bounds are not supported
+  --> $DIR/feature-gate-negative_bounds.rs:1:12
+   |
+LL | fn test<T: !Copy>() {}
+   |            ^
+
+error: aborting due to previous error
+
diff --git a/tests/ui/issues/issue-58857.stderr b/tests/ui/issues/issue-58857.stderr
index e2acec47e5a..6aef35f0bb9 100644
--- a/tests/ui/issues/issue-58857.stderr
+++ b/tests/ui/issues/issue-58857.stderr
@@ -1,8 +1,8 @@
 error: negative bounds are not supported
-  --> $DIR/issue-58857.rs:4:7
+  --> $DIR/issue-58857.rs:4:9
    |
 LL | impl<A: !Valid> Conj<A>{}
-   |       ^^^^^^^^ negative bounds are not supported
+   |         ^
 
 error: aborting due to previous error
 
diff --git a/tests/ui/parser/issues/issue-33418.fixed b/tests/ui/parser/issues/issue-33418.fixed
deleted file mode 100644
index ed885ae1435..00000000000
--- a/tests/ui/parser/issues/issue-33418.fixed
+++ /dev/null
@@ -1,19 +0,0 @@
-// run-rustfix
-
-trait Tr {}
-//~^ ERROR negative bounds are not supported
-trait Tr2: SuperA {}
-//~^ ERROR negative bounds are not supported
-trait Tr3: SuperB {}
-//~^ ERROR negative bounds are not supported
-trait Tr4: SuperB + SuperD {}
-//~^ ERROR negative bounds are not supported
-trait Tr5 {}
-//~^ ERROR negative bounds are not supported
-
-trait SuperA {}
-trait SuperB {}
-trait SuperC {}
-trait SuperD {}
-
-fn main() {}
diff --git a/tests/ui/parser/issues/issue-33418.rs b/tests/ui/parser/issues/issue-33418.rs
index 9934284abfb..4ebd5871e53 100644
--- a/tests/ui/parser/issues/issue-33418.rs
+++ b/tests/ui/parser/issues/issue-33418.rs
@@ -1,5 +1,3 @@
-// run-rustfix
-
 trait Tr: !SuperA {}
 //~^ ERROR negative bounds are not supported
 trait Tr2: SuperA + !SuperB {}
@@ -7,10 +5,12 @@ trait Tr2: SuperA + !SuperB {}
 trait Tr3: !SuperA + SuperB {}
 //~^ ERROR negative bounds are not supported
 trait Tr4: !SuperA + SuperB
-    + !SuperC + SuperD {}
+//~^ ERROR negative bounds are not supported
++ !SuperC + SuperD {}
 //~^ ERROR negative bounds are not supported
 trait Tr5: !SuperA
-    + !SuperB {}
+//~^ ERROR negative bounds are not supported
++ !SuperB {}
 //~^ ERROR negative bounds are not supported
 
 trait SuperA {}
diff --git a/tests/ui/parser/issues/issue-33418.stderr b/tests/ui/parser/issues/issue-33418.stderr
index 9a8733e8929..b111bcfd240 100644
--- a/tests/ui/parser/issues/issue-33418.stderr
+++ b/tests/ui/parser/issues/issue-33418.stderr
@@ -1,36 +1,44 @@
 error: negative bounds are not supported
-  --> $DIR/issue-33418.rs:3:9
+  --> $DIR/issue-33418.rs:1:11
    |
 LL | trait Tr: !SuperA {}
-   |         ^^^^^^^^^ negative bounds are not supported
+   |           ^
 
 error: negative bounds are not supported
-  --> $DIR/issue-33418.rs:5:19
+  --> $DIR/issue-33418.rs:3:21
    |
 LL | trait Tr2: SuperA + !SuperB {}
-   |                   ^^^^^^^^^ negative bounds are not supported
+   |                     ^
 
 error: negative bounds are not supported
-  --> $DIR/issue-33418.rs:7:10
+  --> $DIR/issue-33418.rs:5:12
    |
 LL | trait Tr3: !SuperA + SuperB {}
-   |          ^^^^^^^^^ negative bounds are not supported
+   |            ^
 
 error: negative bounds are not supported
-  --> $DIR/issue-33418.rs:9:10
+  --> $DIR/issue-33418.rs:7:12
    |
 LL | trait Tr4: !SuperA + SuperB
-   |          ^^^^^^^^^
-LL |     + !SuperC + SuperD {}
-   |     ^^^^^^^^^ negative bounds are not supported
+   |            ^
 
 error: negative bounds are not supported
-  --> $DIR/issue-33418.rs:12:10
+  --> $DIR/issue-33418.rs:9:3
+   |
+LL | + !SuperC + SuperD {}
+   |   ^
+
+error: negative bounds are not supported
+  --> $DIR/issue-33418.rs:11:12
    |
 LL | trait Tr5: !SuperA
-   |          ^^^^^^^^^
-LL |     + !SuperB {}
-   |     ^^^^^^^^^ negative bounds are not supported
+   |            ^
+
+error: negative bounds are not supported
+  --> $DIR/issue-33418.rs:13:3
+   |
+LL | + !SuperB {}
+   |   ^
 
-error: aborting due to 5 previous errors
+error: aborting due to 7 previous errors
 
diff --git a/tests/ui/parser/issues/issue-67146-negative-outlives-bound-syntactic-fail.fixed b/tests/ui/parser/issues/issue-67146-negative-outlives-bound-syntactic-fail.fixed
index 95019b27869..2c42f973174 100644
--- a/tests/ui/parser/issues/issue-67146-negative-outlives-bound-syntactic-fail.fixed
+++ b/tests/ui/parser/issues/issue-67146-negative-outlives-bound-syntactic-fail.fixed
@@ -6,9 +6,12 @@
 
 fn main() {}
 
-pub fn f1<T>() {}
+pub fn f1<T: 'static>() {}
 //~^ ERROR negative bounds are not supported
-pub fn f2<'a, T: Ord>() {}
+//~| ERROR `!` may only modify trait bounds, not lifetime bound
+pub fn f2<'a, T: Ord + 'a>() {}
 //~^ ERROR negative bounds are not supported
-pub fn f3<'a, T: Ord>() {}
+//~| ERROR `!` may only modify trait bounds, not lifetime bound
+pub fn f3<'a, T: 'a + Ord>() {}
 //~^ ERROR negative bounds are not supported
+//~| ERROR `!` may only modify trait bounds, not lifetime bound
diff --git a/tests/ui/parser/issues/issue-67146-negative-outlives-bound-syntactic-fail.rs b/tests/ui/parser/issues/issue-67146-negative-outlives-bound-syntactic-fail.rs
index 82f54f8faa9..e510efaae5b 100644
--- a/tests/ui/parser/issues/issue-67146-negative-outlives-bound-syntactic-fail.rs
+++ b/tests/ui/parser/issues/issue-67146-negative-outlives-bound-syntactic-fail.rs
@@ -8,7 +8,10 @@ fn main() {}
 
 pub fn f1<T: !'static>() {}
 //~^ ERROR negative bounds are not supported
+//~| ERROR `!` may only modify trait bounds, not lifetime bound
 pub fn f2<'a, T: Ord + !'a>() {}
 //~^ ERROR negative bounds are not supported
+//~| ERROR `!` may only modify trait bounds, not lifetime bound
 pub fn f3<'a, T: !'a + Ord>() {}
 //~^ ERROR negative bounds are not supported
+//~| ERROR `!` may only modify trait bounds, not lifetime bound
diff --git a/tests/ui/parser/issues/issue-67146-negative-outlives-bound-syntactic-fail.stderr b/tests/ui/parser/issues/issue-67146-negative-outlives-bound-syntactic-fail.stderr
index a4a422948ac..91fe02db3a6 100644
--- a/tests/ui/parser/issues/issue-67146-negative-outlives-bound-syntactic-fail.stderr
+++ b/tests/ui/parser/issues/issue-67146-negative-outlives-bound-syntactic-fail.stderr
@@ -1,20 +1,38 @@
+error: `!` may only modify trait bounds, not lifetime bounds
+  --> $DIR/issue-67146-negative-outlives-bound-syntactic-fail.rs:9:14
+   |
+LL | pub fn f1<T: !'static>() {}
+   |              ^
+
+error: `!` may only modify trait bounds, not lifetime bounds
+  --> $DIR/issue-67146-negative-outlives-bound-syntactic-fail.rs:12:24
+   |
+LL | pub fn f2<'a, T: Ord + !'a>() {}
+   |                        ^
+
+error: `!` may only modify trait bounds, not lifetime bounds
+  --> $DIR/issue-67146-negative-outlives-bound-syntactic-fail.rs:15:18
+   |
+LL | pub fn f3<'a, T: !'a + Ord>() {}
+   |                  ^
+
 error: negative bounds are not supported
-  --> $DIR/issue-67146-negative-outlives-bound-syntactic-fail.rs:9:12
+  --> $DIR/issue-67146-negative-outlives-bound-syntactic-fail.rs:9:14
    |
 LL | pub fn f1<T: !'static>() {}
-   |            ^^^^^^^^^^ negative bounds are not supported
+   |              ^
 
 error: negative bounds are not supported
-  --> $DIR/issue-67146-negative-outlives-bound-syntactic-fail.rs:11:22
+  --> $DIR/issue-67146-negative-outlives-bound-syntactic-fail.rs:12:24
    |
 LL | pub fn f2<'a, T: Ord + !'a>() {}
-   |                      ^^^^^ negative bounds are not supported
+   |                        ^
 
 error: negative bounds are not supported
-  --> $DIR/issue-67146-negative-outlives-bound-syntactic-fail.rs:13:16
+  --> $DIR/issue-67146-negative-outlives-bound-syntactic-fail.rs:15:18
    |
 LL | pub fn f3<'a, T: !'a + Ord>() {}
-   |                ^^^^^ negative bounds are not supported
+   |                  ^
 
-error: aborting due to 3 previous errors
+error: aborting due to 6 previous errors
 
diff --git a/tests/ui/traits/negative-bounds/simple.rs b/tests/ui/traits/negative-bounds/simple.rs
new file mode 100644
index 00000000000..f6d1d5169c4
--- /dev/null
+++ b/tests/ui/traits/negative-bounds/simple.rs
@@ -0,0 +1,42 @@
+#![feature(negative_bounds, negative_impls)]
+//~^ WARN the feature `negative_bounds` is incomplete and may not be safe to use and/or cause compiler crashes
+
+fn not_copy<T: !Copy>() {}
+
+fn neg_param_env<T: !Copy>() {
+    not_copy::<T>();
+}
+
+fn pos_param_env<T: Copy>() {
+    not_copy::<T>();
+    //~^ ERROR the trait bound `T: !Copy` is not satisfied
+}
+
+fn unknown<T>() {
+    not_copy::<T>();
+    //~^ ERROR the trait bound `T: !Copy` is not satisfied
+}
+
+struct NotCopyable;
+impl !Copy for NotCopyable {}
+
+fn neg_impl() {
+    not_copy::<NotCopyable>();
+}
+
+#[derive(Copy, Clone)]
+struct Copyable;
+
+fn pos_impl() {
+    not_copy::<Copyable>();
+    //~^ ERROR the trait bound `Copyable: !Copy` is not satisfied
+}
+
+struct NotNecessarilyCopyable;
+
+fn unknown_impl() {
+    not_copy::<NotNecessarilyCopyable>();
+    //~^ ERROR the trait bound `NotNecessarilyCopyable: !Copy` is not satisfied
+}
+
+fn main() {}
diff --git a/tests/ui/traits/negative-bounds/simple.stderr b/tests/ui/traits/negative-bounds/simple.stderr
new file mode 100644
index 00000000000..efd82c09d62
--- /dev/null
+++ b/tests/ui/traits/negative-bounds/simple.stderr
@@ -0,0 +1,82 @@
+warning: the feature `negative_bounds` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/simple.rs:1:12
+   |
+LL | #![feature(negative_bounds, negative_impls)]
+   |            ^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+
+error[E0277]: the trait bound `T: !Copy` is not satisfied
+  --> $DIR/simple.rs:6:16
+   |
+LL |     not_copy::<T>();
+   |                ^ the trait `!Copy` is not implemented for `T`
+   |
+note: required by a bound in `not_copy`
+  --> $DIR/simple.rs:3:16
+   |
+LL | fn not_copy<T: !Copy>() {}
+   |                ^^^^^ required by this bound in `not_copy`
+
+error[E0277]: the trait bound `T: !Copy` is not satisfied
+  --> $DIR/simple.rs:10:16
+   |
+LL |     not_copy::<T>();
+   |                ^ the trait `!Copy` is not implemented for `T`
+   |
+note: required by a bound in `not_copy`
+  --> $DIR/simple.rs:4:16
+   |
+LL | fn not_copy<T: !Copy>() {}
+   |                ^^^^^ required by this bound in `not_copy`
+
+error[E0277]: the trait bound `T: !Copy` is not satisfied
+  --> $DIR/simple.rs:16:16
+   |
+LL |     not_copy::<T>();
+   |                ^ the trait `!Copy` is not implemented for `T`
+   |
+note: required by a bound in `not_copy`
+  --> $DIR/simple.rs:4:16
+   |
+LL | fn not_copy<T: !Copy>() {}
+   |                ^^^^^ required by this bound in `not_copy`
+
+error[E0277]: the trait bound `Copyable: !Copy` is not satisfied
+  --> $DIR/simple.rs:31:16
+   |
+LL |     not_copy::<Copyable>();
+   |                ^^^^^^^^ the trait `!Copy` is not implemented for `Copyable`
+   |
+   = help: the trait `Copy` is implemented for `Copyable`
+note: required by a bound in `not_copy`
+  --> $DIR/simple.rs:4:16
+   |
+LL | fn not_copy<T: !Copy>() {}
+   |                ^^^^^ required by this bound in `not_copy`
+help: consider annotating `Copyable` with `#[derive(Copy)]`
+   |
+LL + #[derive(Copy)]
+LL | struct Copyable;
+   |
+
+error[E0277]: the trait bound `NotNecessarilyCopyable: !Copy` is not satisfied
+  --> $DIR/simple.rs:38:16
+   |
+LL |     not_copy::<NotNecessarilyCopyable>();
+   |                ^^^^^^^^^^^^^^^^^^^^^^ the trait `!Copy` is not implemented for `NotNecessarilyCopyable`
+   |
+note: required by a bound in `not_copy`
+  --> $DIR/simple.rs:4:16
+   |
+LL | fn not_copy<T: !Copy>() {}
+   |                ^^^^^ required by this bound in `not_copy`
+help: consider annotating `NotNecessarilyCopyable` with `#[derive(Copy)]`
+   |
+LL + #[derive(Copy)]
+LL | struct NotNecessarilyCopyable;
+   |
+
+error: aborting due to 5 previous errors
+
+For more information about this error, try `rustc --explain E0277`.