about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-09-16 21:53:05 +0000
committerbors <bors@rust-lang.org>2025-09-16 21:53:05 +0000
commit3f1552a273e43e15f6ed240d00e1efdd6a53e65e (patch)
treedf8382ea88536a3acfce167d59b14969b83c12ce
parenta9d0a6f15533a364816c4d81e2192009ef601d33 (diff)
parentcf035527acbd46608872add6158e1fc23049872d (diff)
downloadrust-3f1552a273e43e15f6ed240d00e1efdd6a53e65e.tar.gz
rust-3f1552a273e43e15f6ed240d00e1efdd6a53e65e.zip
Auto merge of #146650 - matthiaskrgr:rollup-rjrklz9, r=matthiaskrgr
Rollup of 5 pull requests

Successful merges:

 - rust-lang/rust#146442 (Display ?Sized, const, and lifetime parameters in trait item suggestions across a crate boundary)
 - rust-lang/rust#146474 (Improve `core::ascii` coverage)
 - rust-lang/rust#146605 (Bump rustfix 0.8.1 -> 0.8.7)
 - rust-lang/rust#146611 (bootstrap: emit hint if a config key is used in the wrong section)
 - rust-lang/rust#146618 (Do not run ui test if options specific to LLVM are used when another codegen backend is used)

r? `@ghost`
`@rustbot` modify labels: rollup
-rw-r--r--Cargo.lock4
-rw-r--r--compiler/rustc_hir_analysis/src/check/mod.rs44
-rw-r--r--library/coretests/tests/ascii.rs7
-rw-r--r--src/bootstrap/src/core/config/config.rs109
-rw-r--r--src/tools/compiletest/src/common.rs4
-rw-r--r--src/tools/compiletest/src/directives/needs.rs13
-rw-r--r--tests/ui/associated-types/associated-types-for-unimpl-trait.fixed2
-rw-r--r--tests/ui/lifetimes/issue-105507.fixed2
-rw-r--r--tests/ui/parser/expr-as-stmt.fixed4
-rw-r--r--tests/ui/suggestions/apitit-unimplemented-method.rs5
-rw-r--r--tests/ui/suggestions/apitit-unimplemented-method.stderr10
-rw-r--r--tests/ui/suggestions/auxiliary/dep.rs14
-rw-r--r--tests/ui/suggestions/missing-bound-in-derive-copy-impl-2.fixed2
-rw-r--r--tests/ui/suggestions/missing-bound-in-derive-copy-impl-3.fixed2
-rw-r--r--tests/ui/suggestions/trait-impl-bound-suggestions.fixed4
15 files changed, 183 insertions, 43 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4677d34d2a6..d39cfefea0c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4817,9 +4817,9 @@ dependencies = [
 
 [[package]]
 name = "rustfix"
-version = "0.8.1"
+version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81864b097046da5df3758fdc6e4822bbb70afa06317e8ca45ea1b51cb8c5e5a4"
+checksum = "82fa69b198d894d84e23afde8e9ab2af4400b2cba20d6bf2b428a8b01c222c5a"
 dependencies = [
  "serde",
  "serde_json",
diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs
index 63d0f400aef..e70d5505aae 100644
--- a/compiler/rustc_hir_analysis/src/check/mod.rs
+++ b/compiler/rustc_hir_analysis/src/check/mod.rs
@@ -70,6 +70,7 @@ pub mod intrinsic;
 mod region;
 pub mod wfcheck;
 
+use std::borrow::Cow;
 use std::num::NonZero;
 
 pub use check::{check_abi, check_custom_abi};
@@ -86,7 +87,7 @@ use rustc_middle::query::Providers;
 use rustc_middle::ty::error::{ExpectedFound, TypeError};
 use rustc_middle::ty::print::with_types_for_signature;
 use rustc_middle::ty::{
-    self, GenericArgs, GenericArgsRef, GenericParamDefKind, Ty, TyCtxt, TypingMode,
+    self, GenericArgs, GenericArgsRef, OutlivesPredicate, Region, Ty, TyCtxt, TypingMode,
 };
 use rustc_middle::{bug, span_bug};
 use rustc_session::parse::feature_err;
@@ -335,6 +336,7 @@ fn bounds_from_generic_predicates<'tcx>(
     assoc: ty::AssocItem,
 ) -> (String, String) {
     let mut types: FxIndexMap<Ty<'tcx>, Vec<DefId>> = FxIndexMap::default();
+    let mut regions: FxIndexMap<Region<'tcx>, Vec<Region<'tcx>>> = FxIndexMap::default();
     let mut projections = vec![];
     for (predicate, _) in predicates {
         debug!("predicate {:?}", predicate);
@@ -351,20 +353,23 @@ fn bounds_from_generic_predicates<'tcx>(
             ty::ClauseKind::Projection(projection_pred) => {
                 projections.push(bound_predicate.rebind(projection_pred));
             }
+            ty::ClauseKind::RegionOutlives(OutlivesPredicate(a, b)) => {
+                regions.entry(a).or_default().push(b);
+            }
             _ => {}
         }
     }
 
     let mut where_clauses = vec![];
     let generics = tcx.generics_of(assoc.def_id);
-    let types_str = generics
+    let params = generics
         .own_params
         .iter()
-        .filter(|p| matches!(p.kind, GenericParamDefKind::Type { synthetic: false, .. }))
-        .map(|p| {
-            // we just checked that it's a type, so the unwrap can't fail
-            let ty = tcx.mk_param_from_def(p).as_type().unwrap();
-            if let Some(bounds) = types.get(&ty) {
+        .filter(|p| !p.kind.is_synthetic())
+        .map(|p| match tcx.mk_param_from_def(p).kind() {
+            ty::GenericArgKind::Type(ty) => {
+                let bounds =
+                    types.get(&ty).map(Cow::Borrowed).unwrap_or_else(|| Cow::Owned(Vec::new()));
                 let mut bounds_str = vec![];
                 for bound in bounds.iter().copied() {
                     let mut projections_str = vec![];
@@ -377,7 +382,11 @@ fn bounds_from_generic_predicates<'tcx>(
                             projections_str.push(format!("{} = {}", name, p.term));
                         }
                     }
-                    let bound_def_path = tcx.def_path_str(bound);
+                    let bound_def_path = if tcx.is_lang_item(bound, LangItem::MetaSized) {
+                        String::from("?Sized")
+                    } else {
+                        tcx.def_path_str(bound)
+                    };
                     if projections_str.is_empty() {
                         where_clauses.push(format!("{}: {}", ty, bound_def_path));
                     } else {
@@ -393,8 +402,21 @@ fn bounds_from_generic_predicates<'tcx>(
                 } else {
                     format!("{}: {}", ty, bounds_str.join(" + "))
                 }
-            } else {
-                ty.to_string()
+            }
+            ty::GenericArgKind::Const(ct) => {
+                format!("const {ct}: {}", tcx.type_of(p.def_id).skip_binder())
+            }
+            ty::GenericArgKind::Lifetime(region) => {
+                if let Some(v) = regions.get(&region)
+                    && !v.is_empty()
+                {
+                    format!(
+                        "{region}: {}",
+                        v.into_iter().map(Region::to_string).collect::<Vec<_>>().join(" + ")
+                    )
+                } else {
+                    region.to_string()
+                }
             }
         })
         .collect::<Vec<_>>();
@@ -409,7 +431,7 @@ fn bounds_from_generic_predicates<'tcx>(
     }
 
     let generics =
-        if types_str.is_empty() { "".to_string() } else { format!("<{}>", types_str.join(", ")) };
+        if params.is_empty() { "".to_string() } else { format!("<{}>", params.join(", ")) };
 
     let where_clauses = if where_clauses.is_empty() {
         "".to_string()
diff --git a/library/coretests/tests/ascii.rs b/library/coretests/tests/ascii.rs
index ce09ee507f1..297aa114e00 100644
--- a/library/coretests/tests/ascii.rs
+++ b/library/coretests/tests/ascii.rs
@@ -505,3 +505,10 @@ fn test_escape_ascii_iter() {
     let _ = it.advance_back_by(4);
     assert_eq!(it.to_string(), r#"fastpath\xffremainder"#);
 }
+
+#[test]
+fn test_invalid_u8() {
+    for c in 128..=255 {
+        assert_eq!(core::ascii::Char::from_u8(c), None);
+    }
+}
diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs
index 678a9b63952..0213047f3a1 100644
--- a/src/bootstrap/src/core/config/config.rs
+++ b/src/bootstrap/src/core/config/config.rs
@@ -1853,13 +1853,7 @@ fn load_toml_config(
         } else {
             toml_path.clone()
         });
-        (
-            get_toml(&toml_path).unwrap_or_else(|e| {
-                eprintln!("ERROR: Failed to parse '{}': {e}", toml_path.display());
-                exit!(2);
-            }),
-            path,
-        )
+        (get_toml(&toml_path).unwrap_or_else(|e| bad_config(&toml_path, e)), path)
     } else {
         (TomlConfig::default(), None)
     }
@@ -1892,10 +1886,8 @@ fn postprocess_toml(
             .unwrap()
             .join(include_path);
 
-        let included_toml = get_toml(&include_path).unwrap_or_else(|e| {
-            eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display());
-            exit!(2);
-        });
+        let included_toml =
+            get_toml(&include_path).unwrap_or_else(|e| bad_config(&include_path, e));
         toml.merge(
             Some(include_path),
             &mut Default::default(),
@@ -2398,3 +2390,98 @@ pub(crate) fn read_file_by_commit<'a>(
     git.arg("show").arg(format!("{commit}:{}", file.to_str().unwrap()));
     git.run_capture_stdout(dwn_ctx.exec_ctx).stdout()
 }
+
+fn bad_config(toml_path: &Path, e: toml::de::Error) -> ! {
+    eprintln!("ERROR: Failed to parse '{}': {e}", toml_path.display());
+    let e_s = e.to_string();
+    if e_s.contains("unknown field")
+        && let Some(field_name) = e_s.split("`").nth(1)
+        && let sections = find_correct_section_for_field(field_name)
+        && !sections.is_empty()
+    {
+        if sections.len() == 1 {
+            match sections[0] {
+                WouldBeValidFor::TopLevel { is_section } => {
+                    if is_section {
+                        eprintln!(
+                            "hint: section name `{field_name}` used as a key within a section"
+                        );
+                    } else {
+                        eprintln!("hint: try using `{field_name}` as a top level key");
+                    }
+                }
+                WouldBeValidFor::Section(section) => {
+                    eprintln!("hint: try moving `{field_name}` to the `{section}` section")
+                }
+            }
+        } else {
+            eprintln!(
+                "hint: `{field_name}` would be valid {}",
+                join_oxford_comma(sections.iter(), "or"),
+            );
+        }
+    }
+
+    exit!(2);
+}
+
+#[derive(Copy, Clone, Debug)]
+enum WouldBeValidFor {
+    TopLevel { is_section: bool },
+    Section(&'static str),
+}
+
+fn join_oxford_comma(
+    mut parts: impl ExactSizeIterator<Item = impl std::fmt::Display>,
+    conj: &str,
+) -> String {
+    use std::fmt::Write;
+    let mut out = String::new();
+
+    assert!(parts.len() > 1);
+    while let Some(part) = parts.next() {
+        if parts.len() == 0 {
+            write!(&mut out, "{conj} {part}")
+        } else {
+            write!(&mut out, "{part}, ")
+        }
+        .unwrap();
+    }
+    out
+}
+
+impl std::fmt::Display for WouldBeValidFor {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::TopLevel { .. } => write!(f, "at top level"),
+            Self::Section(section_name) => write!(f, "in section `{section_name}`"),
+        }
+    }
+}
+
+fn find_correct_section_for_field(field_name: &str) -> Vec<WouldBeValidFor> {
+    let sections = ["build", "install", "llvm", "gcc", "rust", "dist"];
+    sections
+        .iter()
+        .map(Some)
+        .chain([None])
+        .filter_map(|section_name| {
+            let dummy_config_str = if let Some(section_name) = section_name {
+                format!("{section_name}.{field_name} = 0\n")
+            } else {
+                format!("{field_name} = 0\n")
+            };
+            let is_unknown_field = toml::from_str::<toml::Value>(&dummy_config_str)
+                .and_then(TomlConfig::deserialize)
+                .err()
+                .is_some_and(|e| e.to_string().contains("unknown field"));
+            if is_unknown_field {
+                None
+            } else {
+                Some(section_name.copied().map(WouldBeValidFor::Section).unwrap_or_else(|| {
+                    WouldBeValidFor::TopLevel { is_section: sections.contains(&field_name) }
+                }))
+            }
+        })
+        .collect()
+}
diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs
index 143ccdcb9e5..6da102b1b5f 100644
--- a/src/tools/compiletest/src/common.rs
+++ b/src/tools/compiletest/src/common.rs
@@ -203,6 +203,10 @@ impl CodegenBackend {
             Self::Llvm => "llvm",
         }
     }
+
+    pub fn is_llvm(self) -> bool {
+        matches!(self, Self::Llvm)
+    }
 }
 
 /// Configuration for `compiletest` *per invocation*.
diff --git a/src/tools/compiletest/src/directives/needs.rs b/src/tools/compiletest/src/directives/needs.rs
index ee46f4c70cb..3b7a9478717 100644
--- a/src/tools/compiletest/src/directives/needs.rs
+++ b/src/tools/compiletest/src/directives/needs.rs
@@ -81,8 +81,8 @@ pub(super) fn handle_needs(
         },
         Need {
             name: "needs-enzyme",
-            condition: config.has_enzyme,
-            ignore_reason: "ignored when LLVM Enzyme is disabled",
+            condition: config.has_enzyme && config.default_codegen_backend.is_llvm(),
+            ignore_reason: "ignored when LLVM Enzyme is disabled or LLVM is not the default codegen backend",
         },
         Need {
             name: "needs-run-enabled",
@@ -161,8 +161,8 @@ pub(super) fn handle_needs(
         },
         Need {
             name: "needs-llvm-zstd",
-            condition: cache.llvm_zstd,
-            ignore_reason: "ignored if LLVM wasn't build with zstd for ELF section compression",
+            condition: cache.llvm_zstd && config.default_codegen_backend.is_llvm(),
+            ignore_reason: "ignored if LLVM wasn't build with zstd for ELF section compression or LLVM is not the default codegen backend",
         },
         Need {
             name: "needs-rustc-debug-assertions",
@@ -279,7 +279,10 @@ pub(super) fn handle_needs(
 
     // Handled elsewhere.
     if name == "needs-llvm-components" {
-        return IgnoreDecision::Continue;
+        if config.default_codegen_backend.is_llvm() {
+            return IgnoreDecision::Continue;
+        }
+        return IgnoreDecision::Ignore { reason: "LLVM specific test".into() };
     }
 
     let mut found_valid = false;
diff --git a/tests/ui/associated-types/associated-types-for-unimpl-trait.fixed b/tests/ui/associated-types/associated-types-for-unimpl-trait.fixed
index bce6148f9e1..ae4d0107aee 100644
--- a/tests/ui/associated-types/associated-types-for-unimpl-trait.fixed
+++ b/tests/ui/associated-types/associated-types-for-unimpl-trait.fixed
@@ -8,7 +8,7 @@ trait Get {
 }
 
 trait Other {
-    fn uhoh<U: Get>(&self, foo: U, bar: <Self as Get>::Value) where Self: Sized, Self: Get, Self: Get {}
+    fn uhoh<U: Get>(&self, foo: U, bar: <Self as Get>::Value) where Self: Sized, Self: Get {}
     //~^ ERROR the trait bound `Self: Get` is not satisfied
     //~| ERROR the trait bound `Self: Get` is not satisfied
 }
diff --git a/tests/ui/lifetimes/issue-105507.fixed b/tests/ui/lifetimes/issue-105507.fixed
index 46d4f14a245..a3c4e5784b8 100644
--- a/tests/ui/lifetimes/issue-105507.fixed
+++ b/tests/ui/lifetimes/issue-105507.fixed
@@ -31,7 +31,7 @@ impl<T> ProjectedMyTrait for T
 
 fn require_trait<T: MyTrait>(_: T) {}
 
-fn foo<T : MyTrait + 'static + 'static, U : MyTrait + 'static + 'static>(wrap: Wrapper<'_, Option<T>>, wrap1: Wrapper<'_, Option<U>>) {
+fn foo<T : MyTrait + 'static, U : MyTrait + 'static>(wrap: Wrapper<'_, Option<T>>, wrap1: Wrapper<'_, Option<U>>) {
     //~^ HELP consider restricting the type parameter to the `'static` lifetime
     //~| HELP consider restricting the type parameter to the `'static` lifetime
     require_trait(wrap);
diff --git a/tests/ui/parser/expr-as-stmt.fixed b/tests/ui/parser/expr-as-stmt.fixed
index bfae55047ed..b3a491200ed 100644
--- a/tests/ui/parser/expr-as-stmt.fixed
+++ b/tests/ui/parser/expr-as-stmt.fixed
@@ -66,7 +66,7 @@ fn asteroids() -> impl FnOnce() -> bool {
 
 // https://github.com/rust-lang/rust/issues/105179
 fn r#match() -> i32 {
-    ((match () { () => 1 })) + match () { () => 1 } //~ ERROR expected expression, found `+`
+    (match () { () => 1 }) + match () { () => 1 } //~ ERROR expected expression, found `+`
     //~^ ERROR mismatched types
 }
 
@@ -82,7 +82,7 @@ fn matches() -> bool {
     (match () { _ => true }) && match () { _ => true }; //~ ERROR mismatched types
     //~^ ERROR expected `;`, found keyword `match`
     (match () { _ => true }) && true; //~ ERROR mismatched types
-    ((match () { _ => true })) && true //~ ERROR mismatched types
+    (match () { _ => true }) && true //~ ERROR mismatched types
     //~^ ERROR mismatched types
 }
 fn main() {}
diff --git a/tests/ui/suggestions/apitit-unimplemented-method.rs b/tests/ui/suggestions/apitit-unimplemented-method.rs
index b182e1939b3..c0cd709e230 100644
--- a/tests/ui/suggestions/apitit-unimplemented-method.rs
+++ b/tests/ui/suggestions/apitit-unimplemented-method.rs
@@ -4,9 +4,12 @@ extern crate dep;
 use dep::*;
 
 struct Local;
+
 impl Trait for Local {}
 //~^ ERROR not all trait items implemented
 //~| HELP implement the missing item: `fn foo(_: impl Sized) { todo!() }`
-//~| HELP implement the missing item: `fn bar<T>(_: impl Sized) { todo!() }`
+//~| HELP implement the missing item: `fn bar<T>(_: impl Sized) where Foo<T>: MetaSized { todo!() }`
+//~| HELP implement the missing item: `fn baz<const N: usize>() { todo!() }`
+//~| HELP implement the missing item: `fn quux<'a: 'b, 'b, T>() where T: ?Sized { todo!() }`
 
 fn main() {}
diff --git a/tests/ui/suggestions/apitit-unimplemented-method.stderr b/tests/ui/suggestions/apitit-unimplemented-method.stderr
index b309a64e958..1f2e0ea2cad 100644
--- a/tests/ui/suggestions/apitit-unimplemented-method.stderr
+++ b/tests/ui/suggestions/apitit-unimplemented-method.stderr
@@ -1,11 +1,13 @@
-error[E0046]: not all trait items implemented, missing: `foo`, `bar`
-  --> $DIR/apitit-unimplemented-method.rs:7:1
+error[E0046]: not all trait items implemented, missing: `foo`, `bar`, `baz`, `quux`
+  --> $DIR/apitit-unimplemented-method.rs:8:1
    |
 LL | impl Trait for Local {}
-   | ^^^^^^^^^^^^^^^^^^^^ missing `foo`, `bar` in implementation
+   | ^^^^^^^^^^^^^^^^^^^^ missing `foo`, `bar`, `baz`, `quux` in implementation
    |
    = help: implement the missing item: `fn foo(_: impl Sized) { todo!() }`
-   = help: implement the missing item: `fn bar<T>(_: impl Sized) { todo!() }`
+   = help: implement the missing item: `fn bar<T>(_: impl Sized) where Foo<T>: MetaSized { todo!() }`
+   = help: implement the missing item: `fn baz<const N: usize>() { todo!() }`
+   = help: implement the missing item: `fn quux<'a: 'b, 'b, T>() where T: ?Sized { todo!() }`
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/suggestions/auxiliary/dep.rs b/tests/ui/suggestions/auxiliary/dep.rs
index ac0de418313..c28c8b8a52f 100644
--- a/tests/ui/suggestions/auxiliary/dep.rs
+++ b/tests/ui/suggestions/auxiliary/dep.rs
@@ -1,4 +1,16 @@
+#![feature(sized_hierarchy)]
+
+use std::marker::MetaSized;
+
+pub struct Foo<T> {
+    inner: T,
+}
+
 pub trait Trait {
     fn foo(_: impl Sized);
-    fn bar<T>(_: impl Sized);
+    fn bar<T>(_: impl Sized)
+    where
+        Foo<T>: MetaSized;
+    fn baz<'a, const N: usize>();
+    fn quux<'a: 'b, 'b, T: ?Sized>();
 }
diff --git a/tests/ui/suggestions/missing-bound-in-derive-copy-impl-2.fixed b/tests/ui/suggestions/missing-bound-in-derive-copy-impl-2.fixed
index 99433f73320..8a2be310e0d 100644
--- a/tests/ui/suggestions/missing-bound-in-derive-copy-impl-2.fixed
+++ b/tests/ui/suggestions/missing-bound-in-derive-copy-impl-2.fixed
@@ -8,7 +8,7 @@ pub struct Vector2<T: Debug + Copy + Clone> {
 }
 
 #[derive(Debug, Copy, Clone)]
-pub struct AABB<K: Debug + std::marker::Copy + std::marker::Copy + std::marker::Copy + std::marker::Copy> {
+pub struct AABB<K: Debug + std::marker::Copy> {
     pub loc: Vector2<K>, //~ ERROR the trait bound `K: Copy` is not satisfied
     //~^ ERROR the trait bound `K: Copy` is not satisfied
     //~| ERROR the trait bound `K: Copy` is not satisfied
diff --git a/tests/ui/suggestions/missing-bound-in-derive-copy-impl-3.fixed b/tests/ui/suggestions/missing-bound-in-derive-copy-impl-3.fixed
index 6da3e351ffb..74df1d7c7cf 100644
--- a/tests/ui/suggestions/missing-bound-in-derive-copy-impl-3.fixed
+++ b/tests/ui/suggestions/missing-bound-in-derive-copy-impl-3.fixed
@@ -8,7 +8,7 @@ pub struct Vector2<T: Debug + Copy + Clone>{
 }
 
 #[derive(Debug, Copy, Clone)] //~ ERROR the trait `Copy` cannot be implemented for this type
-pub struct AABB<K: Copy + Debug + std::fmt::Debug + std::fmt::Debug + std::fmt::Debug>{
+pub struct AABB<K: Copy + Debug + std::fmt::Debug>{
     pub loc: Vector2<K>, //~ ERROR `K` doesn't implement `Debug`
     //~^ ERROR `K` doesn't implement `Debug`
     pub size: Vector2<K> //~ ERROR `K` doesn't implement `Debug`
diff --git a/tests/ui/suggestions/trait-impl-bound-suggestions.fixed b/tests/ui/suggestions/trait-impl-bound-suggestions.fixed
index 9d3168f5acd..49793b4b6f4 100644
--- a/tests/ui/suggestions/trait-impl-bound-suggestions.fixed
+++ b/tests/ui/suggestions/trait-impl-bound-suggestions.fixed
@@ -10,7 +10,7 @@ struct ConstrainedStruct<X: Copy> {
 }
 
 #[allow(dead_code)]
-trait InsufficientlyConstrainedGeneric<X=()> where Self: Sized, X: std::marker::Copy, X: std::marker::Copy {
+trait InsufficientlyConstrainedGeneric<X=()> where Self: Sized, X: std::marker::Copy {
     fn return_the_constrained_type(&self, x: X) -> ConstrainedStruct<X> {
         //~^ ERROR the trait bound `X: Copy` is not satisfied
         ConstrainedStruct { x }
@@ -20,7 +20,7 @@ trait InsufficientlyConstrainedGeneric<X=()> where Self: Sized, X: std::marker::
 
 // Regression test for #120838
 #[allow(dead_code)]
-trait InsufficientlyConstrainedGenericWithEmptyWhere<X=()> where Self: Sized, X: std::marker::Copy, X: std::marker::Copy {
+trait InsufficientlyConstrainedGenericWithEmptyWhere<X=()> where Self: Sized, X: std::marker::Copy {
     fn return_the_constrained_type(&self, x: X) -> ConstrainedStruct<X> {
         //~^ ERROR the trait bound `X: Copy` is not satisfied
         ConstrainedStruct { x }