diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
index 745358fde4b..a000e4895d1 100644
--- a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
@@ -18,6 +18,20 @@ pub fn expand_deriving_eq(
     is_const: bool,
 ) {
     let span = cx.with_def_site_ctxt(span);
+
+    let structural_trait_def = TraitDef {
+        span,
+        path: path_std!(marker::StructuralEq),
+        skip_path_as_bound: true, // crucial!
+        needs_copy_as_bound_if_packed: false,
+        additional_bounds: Vec::new(),
+        supports_unions: true,
+        methods: Vec::new(),
+        associated_types: Vec::new(),
+        is_const: false,
+    };
+    structural_trait_def.expand(cx, mitem, item, push);
+
     let trait_def = TraitDef {
         span,
         path: path_std!(cmp::Eq),
@@ -44,9 +58,6 @@ pub fn expand_deriving_eq(
         associated_types: Vec::new(),
         is_const,
     };
-
-    super::inject_impl_of_structural_trait(cx, span, item, path_std!(marker::StructuralEq), push);
-
     trait_def.expand_ext(cx, mitem, item, push, true)
 }
 
diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs
index a71ecc5db7d..a170468b413 100644
--- a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs
@@ -72,13 +72,20 @@ pub fn expand_deriving_partial_eq(
         BlockOrExpr::new_expr(expr)
     }
 
-    super::inject_impl_of_structural_trait(
-        cx,
+    let structural_trait_def = TraitDef {
         span,
-        item,
-        path_std!(marker::StructuralPartialEq),
-        push,
-    );
+        path: path_std!(marker::StructuralPartialEq),
+        skip_path_as_bound: true, // crucial!
+        needs_copy_as_bound_if_packed: false,
+        additional_bounds: Vec::new(),
+        // We really don't support unions, but that's already checked by the impl generated below;
+        // a second check here would lead to redundant error messages.
+        supports_unions: true,
+        methods: Vec::new(),
+        associated_types: Vec::new(),
+        is_const: false,
+    };
+    structural_trait_def.expand(cx, mitem, item, push);
 
     // No need to generate `ne`, the default suffices, and not generating it is
     // faster.
diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
index 6597ee3cf1b..110581f8197 100644
--- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
@@ -711,7 +711,9 @@ impl<'a> TraitDef<'a> {
                             .collect();
 
                         // Require the current trait.
-                        bounds.push(cx.trait_bound(trait_path.clone(), self.is_const));
+                        if !self.skip_path_as_bound {
+                            bounds.push(cx.trait_bound(trait_path.clone(), self.is_const));
+                        }
 
                         // Add a `Copy` bound if required.
                         if is_packed && self.needs_copy_as_bound_if_packed {
@@ -722,15 +724,17 @@ impl<'a> TraitDef<'a> {
                             ));
                         }
 
-                        let predicate = ast::WhereBoundPredicate {
-                            span: self.span,
-                            bound_generic_params: field_ty_param.bound_generic_params,
-                            bounded_ty: field_ty_param.ty,
-                            bounds,
-                        };
+                        if !bounds.is_empty() {
+                            let predicate = ast::WhereBoundPredicate {
+                                span: self.span,
+                                bound_generic_params: field_ty_param.bound_generic_params,
+                                bounded_ty: field_ty_param.ty,
+                                bounds,
+                            };
 
-                        let predicate = ast::WherePredicate::BoundPredicate(predicate);
-                        where_clause.predicates.push(predicate);
+                            let predicate = ast::WherePredicate::BoundPredicate(predicate);
+                            where_clause.predicates.push(predicate);
+                        }
                     }
                 }
             }
diff --git a/compiler/rustc_builtin_macros/src/deriving/mod.rs b/compiler/rustc_builtin_macros/src/deriving/mod.rs
index d34336e7679..a6f3252e7be 100644
--- a/compiler/rustc_builtin_macros/src/deriving/mod.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/mod.rs
@@ -2,9 +2,9 @@
 
 use rustc_ast as ast;
 use rustc_ast::ptr::P;
-use rustc_ast::{GenericArg, Impl, ItemKind, MetaItem};
+use rustc_ast::{GenericArg, MetaItem};
 use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, MultiItemModifier};
-use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::symbol::{sym, Symbol};
 use rustc_span::Span;
 use thin_vec::{thin_vec, ThinVec};
 
@@ -116,100 +116,6 @@ fn call_unreachable(cx: &ExtCtxt<'_>, span: Span) -> P<ast::Expr> {
     }))
 }
 
-// Injects `impl<...> Structural for ItemType<...> { }`. In particular,
-// does *not* add `where T: Structural` for parameters `T` in `...`.
-// (That's the main reason we cannot use TraitDef here.)
-fn inject_impl_of_structural_trait(
-    cx: &mut ExtCtxt<'_>,
-    span: Span,
-    item: &Annotatable,
-    structural_path: generic::ty::Path,
-    push: &mut dyn FnMut(Annotatable),
-) {
-    let Annotatable::Item(item) = item else {
-        unreachable!();
-    };
-
-    let generics = match &item.kind {
-        ItemKind::Struct(_, generics) | ItemKind::Enum(_, generics) => generics,
-        // Do not inject `impl Structural for Union`. (`PartialEq` does not
-        // support unions, so we will see error downstream.)
-        ItemKind::Union(..) => return,
-        _ => unreachable!(),
-    };
-
-    // Create generics param list for where clauses and impl headers
-    let mut generics = generics.clone();
-
-    let ctxt = span.ctxt();
-
-    // Create the type of `self`.
-    //
-    // in addition, remove defaults from generic params (impls cannot have them).
-    let self_params: Vec<_> = generics
-        .params
-        .iter_mut()
-        .map(|param| match &mut param.kind {
-            ast::GenericParamKind::Lifetime => ast::GenericArg::Lifetime(
-                cx.lifetime(param.ident.span.with_ctxt(ctxt), param.ident),
-            ),
-            ast::GenericParamKind::Type { default } => {
-                *default = None;
-                ast::GenericArg::Type(cx.ty_ident(param.ident.span.with_ctxt(ctxt), param.ident))
-            }
-            ast::GenericParamKind::Const { ty: _, kw_span: _, default } => {
-                *default = None;
-                ast::GenericArg::Const(
-                    cx.const_ident(param.ident.span.with_ctxt(ctxt), param.ident),
-                )
-            }
-        })
-        .collect();
-
-    let type_ident = item.ident;
-
-    let trait_ref = cx.trait_ref(structural_path.to_path(cx, span, type_ident, &generics));
-    let self_type = cx.ty_path(cx.path_all(span, false, vec![type_ident], self_params));
-
-    // It would be nice to also encode constraint `where Self: Eq` (by adding it
-    // onto `generics` cloned above). Unfortunately, that strategy runs afoul of
-    // rust-lang/rust#48214. So we perform that additional check in the compiler
-    // itself, instead of encoding it here.
-
-    // Keep the lint and stability attributes of the original item, to control
-    // how the generated implementation is linted.
-    let mut attrs = ast::AttrVec::new();
-    attrs.extend(
-        item.attrs
-            .iter()
-            .filter(|a| {
-                [sym::allow, sym::warn, sym::deny, sym::forbid, sym::stable, sym::unstable]
-                    .contains(&a.name_or_empty())
-            })
-            .cloned(),
-    );
-    // Mark as `automatically_derived` to avoid some silly lints.
-    attrs.push(cx.attr_word(sym::automatically_derived, span));
-
-    let newitem = cx.item(
-        span,
-        Ident::empty(),
-        attrs,
-        ItemKind::Impl(Box::new(Impl {
-            unsafety: ast::Unsafe::No,
-            polarity: ast::ImplPolarity::Positive,
-            defaultness: ast::Defaultness::Final,
-            constness: ast::Const::No,
-            generics,
-            of_trait: Some(trait_ref),
-            self_ty: self_type,
-            items: ThinVec::new(),
-        })),
-    );
-
-    push(Annotatable::Item(newitem));
-}
-
 fn assert_ty_bounds(
     cx: &mut ExtCtxt<'_>,
     stmts: &mut ThinVec<ast::Stmt>,