From ad2da29ae61b19814c4c45dd8a9635a184e63278 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Wed, 25 Feb 2026 17:57:47 +0800 Subject: [PATCH 1/6] Change derive macro for Eq to not impl Eq trait fn --- .../src/deriving/cmp/eq.rs | 126 +++++-- .../src/deriving/generic/mod.rs | 4 - tests/ui/deriving/deriving-all-codegen.rs | 4 + tests/ui/deriving/deriving-all-codegen.stdout | 349 ++++++++++++------ 4 files changed, 320 insertions(+), 163 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs index 7520df43bf4cc..56e76804c532e 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs @@ -1,10 +1,9 @@ use rustc_ast::{self as ast, MetaItem, Safety}; use rustc_data_structures::fx::FxHashSet; use rustc_expand::base::{Annotatable, ExtCtxt}; -use rustc_span::{Span, sym}; +use rustc_span::{Ident, Span, kw, sym}; use thin_vec::{ThinVec, thin_vec}; -use crate::deriving::generic::ty::*; use crate::deriving::generic::*; use crate::deriving::path_std; @@ -17,6 +16,7 @@ pub(crate) fn expand_deriving_eq( is_const: bool, ) { let span = cx.with_def_site_ctxt(span); + let mut impl_generics = ast::Generics::default(); let trait_def = TraitDef { span, @@ -25,38 +25,87 @@ pub(crate) fn expand_deriving_eq( needs_copy_as_bound_if_packed: true, additional_bounds: Vec::new(), supports_unions: true, - methods: vec![MethodDef { - name: sym::assert_fields_are_eq, - generics: Bounds::empty(), - explicit_self: true, - nonself_args: vec![], - ret_ty: Unit, - attributes: thin_vec![ - // This method will never be called, so doing codegen etc. for it is unnecessary. - // We prevent this by adding `#[inline]`, which improves compile-time. - cx.attr_word(sym::inline, span), - cx.attr_nested_word(sym::doc, sym::hidden, span), - cx.attr_nested_word(sym::coverage, sym::off, span), - ], - fieldless_variants_strategy: FieldlessVariantsStrategy::Unify, - combine_substructure: combine_substructure(Box::new(|a, b, c| { - cs_total_eq_assert(a, b, c) - })), - }], + methods: Vec::new(), associated_types: Vec::new(), is_const, is_staged_api_crate: cx.ecfg.features.staged_api(), safety: Safety::Default, document: true, }; - trait_def.expand_ext(cx, mitem, item, push, true) + trait_def.expand_ext( + cx, + mitem, + item, + &mut |a| { + let Annotatable::Item(item) = &a else { + unreachable!("should have emitted an Item in trait_def.expand_ext"); + }; + let ast::ItemKind::Impl(imp) = &item.kind else { + unreachable!("should have emitted an Impl in trait_def.expand_ext"); + }; + impl_generics = imp.generics.clone(); + push(a) + }, + true, + ); + + let const_body = cx.block( + span, + thin_vec![cx.stmt_item(span, assert_fields_are_eq_fn(cx, span, item, impl_generics),)], + ); + + let unit_ty = cx.ty(span, ast::TyKind::Tup(ThinVec::new())); + let body = cx.expr_block(const_body); + let konst = cx.item_const( + span, + Ident::new(kw::Underscore, span), + unit_ty, + ast::ConstItemRhsKind::new_body(body), + ); + push(Annotatable::Item(konst)); +} + +fn assert_fields_are_eq_fn( + cx: &ExtCtxt<'_>, + span: Span, + item: &Annotatable, + fn_generics: ast::Generics, +) -> Box { + let stmts = eq_assert_stmts_from_item(cx, span, item); + + cx.item( + span, + thin_vec![ + cx.attr_nested_word(sym::doc, sym::hidden, span), + cx.attr_nested_word(sym::coverage, sym::off, span), + ], + ast::ItemKind::Fn(Box::new(ast::Fn { + defaultness: ast::Defaultness::Implicit, + ident: Ident::new(sym::assert_fields_are_eq, span), + generics: fn_generics, + sig: ast::FnSig { + header: ast::FnHeader { + constness: ast::Const::Yes(span), + coroutine_kind: None, + safety: ast::Safety::Default, + ext: ast::Extern::None, + }, + decl: cx.fn_decl(ThinVec::new(), ast::FnRetTy::Default(span)), + span, + }, + contract: None, + define_opaque: None, + body: Some(cx.block(span, stmts)), + eii_impls: ThinVec::new(), + })), + ) } -fn cs_total_eq_assert( +fn eq_assert_stmts_from_item( cx: &ExtCtxt<'_>, - trait_span: Span, - substr: &Substructure<'_>, -) -> BlockOrExpr { + span: Span, + item: &Annotatable, +) -> ThinVec { let mut stmts = ThinVec::new(); let mut seen_type_names = FxHashSet::default(); let mut process_variant = |variant: &ast::VariantData| { @@ -80,17 +129,22 @@ fn cs_total_eq_assert( } } }; - - match *substr.fields { - StaticStruct(vdata, ..) => { - process_variant(vdata); - } - StaticEnum(enum_def, ..) => { - for variant in &enum_def.variants { - process_variant(&variant.data); + match item { + Annotatable::Item(item) => match &item.kind { + ast::ItemKind::Struct(_, _, vdata) => { + process_variant(vdata); } - } - _ => cx.dcx().span_bug(trait_span, "unexpected substructure in `derive(Eq)`"), + ast::ItemKind::Enum(_, _, enum_def) => { + for variant in &enum_def.variants { + process_variant(&variant.data); + } + } + ast::ItemKind::Union(_, _, vdata) => { + process_variant(vdata); + } + _ => cx.dcx().span_bug(span, "unexpected item in `derive(Eq)`"), + }, + _ => cx.dcx().span_bug(span, "unexpected item in `derive(Eq)`"), } - BlockOrExpr::new_stmts(stmts) + stmts } diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs index b392a9623d050..eee57a5da6170 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -362,10 +362,6 @@ struct TypeParameter { pub(crate) struct BlockOrExpr(ThinVec, Option>); impl BlockOrExpr { - pub(crate) fn new_stmts(stmts: ThinVec) -> BlockOrExpr { - BlockOrExpr(stmts, None) - } - pub(crate) fn new_expr(expr: Box) -> BlockOrExpr { BlockOrExpr(ThinVec::new(), Some(expr)) } diff --git a/tests/ui/deriving/deriving-all-codegen.rs b/tests/ui/deriving/deriving-all-codegen.rs index 9f21831960499..3b0663ae3991e 100644 --- a/tests/ui/deriving/deriving-all-codegen.rs +++ b/tests/ui/deriving/deriving-all-codegen.rs @@ -119,6 +119,10 @@ struct Generic { u: U, } +// A generic struct involving a lifetime and an associated type. +#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +struct GenericLifetime<'a, T: Trait>(&'a T); + // A packed, generic tuple struct involving an associated type. Because it is // packed, a `T: Copy` bound is added to all impls (and where clauses within // them) except for `Default`. This is because we must access fields using diff --git a/tests/ui/deriving/deriving-all-codegen.stdout b/tests/ui/deriving/deriving-all-codegen.stdout index 94e8b886436df..e3dc358a434c6 100644 --- a/tests/ui/deriving/deriving-all-codegen.stdout +++ b/tests/ui/deriving/deriving-all-codegen.stdout @@ -61,12 +61,13 @@ impl ::core::cmp::PartialEq for Empty { fn eq(&self, other: &Empty) -> bool { true } } #[automatically_derived] -impl ::core::cmp::Eq for Empty { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) {} -} +impl ::core::cmp::Eq for Empty { } +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() {} + }; #[automatically_derived] impl ::core::cmp::PartialOrd for Empty { #[inline] @@ -138,14 +139,15 @@ impl ::core::cmp::PartialEq for Point { } } #[automatically_derived] -impl ::core::cmp::Eq for Point { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) { - let _: ::core::cmp::AssertParamIsEq; - } -} +impl ::core::cmp::Eq for Point { } +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + } + }; #[automatically_derived] impl ::core::cmp::PartialOrd for Point { #[inline] @@ -226,14 +228,15 @@ impl ::core::cmp::PartialEq for PackedPoint { } } #[automatically_derived] -impl ::core::cmp::Eq for PackedPoint { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) { - let _: ::core::cmp::AssertParamIsEq; - } -} +impl ::core::cmp::Eq for PackedPoint { } +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + } + }; #[automatically_derived] impl ::core::cmp::PartialOrd for PackedPoint { #[inline] @@ -309,14 +312,15 @@ impl ::core::cmp::PartialEq for TupleSingleField { fn eq(&self, other: &TupleSingleField) -> bool { self.0 == other.0 } } #[automatically_derived] -impl ::core::cmp::Eq for TupleSingleField { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) { - let _: ::core::cmp::AssertParamIsEq; - } -} +impl ::core::cmp::Eq for TupleSingleField { } +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + } + }; #[automatically_derived] impl ::core::cmp::PartialOrd for TupleSingleField { #[inline] @@ -384,14 +388,15 @@ impl ::core::cmp::PartialEq for SingleField { fn eq(&self, other: &SingleField) -> bool { self.foo == other.foo } } #[automatically_derived] -impl ::core::cmp::Eq for SingleField { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) { - let _: ::core::cmp::AssertParamIsEq; - } -} +impl ::core::cmp::Eq for SingleField { } +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + } + }; #[automatically_derived] impl ::core::cmp::PartialOrd for SingleField { #[inline] @@ -489,14 +494,15 @@ impl ::core::cmp::PartialEq for Big { } } #[automatically_derived] -impl ::core::cmp::Eq for Big { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) { - let _: ::core::cmp::AssertParamIsEq; - } -} +impl ::core::cmp::Eq for Big { } +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + } + }; #[automatically_derived] impl ::core::cmp::PartialOrd for Big { #[inline] @@ -753,14 +759,15 @@ impl ::core::cmp::PartialEq for Unsized { fn eq(&self, other: &Unsized) -> bool { self.0 == other.0 } } #[automatically_derived] -impl ::core::cmp::Eq for Unsized { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) { - let _: ::core::cmp::AssertParamIsEq<[u32]>; - } -} +impl ::core::cmp::Eq for Unsized { } +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq<[u32]>; + } + }; #[automatically_derived] impl ::core::cmp::PartialOrd for Unsized { #[inline] @@ -849,15 +856,18 @@ impl #[automatically_derived] impl ::core::cmp::Eq for Generic where T::A: ::core::cmp::Eq { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) { - let _: ::core::cmp::AssertParamIsEq; - let _: ::core::cmp::AssertParamIsEq; - let _: ::core::cmp::AssertParamIsEq; - } } +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() where T::A: ::core::cmp::Eq { + let _: ::core::cmp::AssertParamIsEq; + let _: ::core::cmp::AssertParamIsEq; + let _: ::core::cmp::AssertParamIsEq; + } + }; #[automatically_derived] impl ::core::cmp::PartialOrd for Generic where @@ -894,6 +904,85 @@ impl ::core::cmp::Ord for } } +// A generic struct involving a lifetime and an associated type. +struct GenericLifetime<'a, T: Trait>(&'a T); +#[automatically_derived] +impl<'a, T: ::core::clone::Clone + Trait> ::core::clone::Clone for + GenericLifetime<'a, T> { + #[inline] + fn clone(&self) -> GenericLifetime<'a, T> { + GenericLifetime(::core::clone::Clone::clone(&self.0)) + } +} +#[automatically_derived] +impl<'a, T: ::core::marker::Copy + Trait> ::core::marker::Copy for + GenericLifetime<'a, T> { +} +#[automatically_derived] +impl<'a, T: ::core::fmt::Debug + Trait> ::core::fmt::Debug for + GenericLifetime<'a, T> { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::debug_tuple_field1_finish(f, + "GenericLifetime", &&self.0) + } +} +#[automatically_derived] +impl<'a, T: ::core::default::Default + Trait> ::core::default::Default for + GenericLifetime<'a, T> { + #[inline] + fn default() -> GenericLifetime<'a, T> { + GenericLifetime(::core::default::Default::default()) + } +} +#[automatically_derived] +impl<'a, T: ::core::hash::Hash + Trait> ::core::hash::Hash for + GenericLifetime<'a, T> { + #[inline] + fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) { + ::core::hash::Hash::hash(&self.0, state) + } +} +#[automatically_derived] +impl<'a, T: Trait> ::core::marker::StructuralPartialEq for + GenericLifetime<'a, T> { +} +#[automatically_derived] +impl<'a, T: ::core::cmp::PartialEq + Trait> ::core::cmp::PartialEq for + GenericLifetime<'a, T> { + #[inline] + fn eq(&self, other: &GenericLifetime<'a, T>) -> bool { self.0 == other.0 } +} +#[automatically_derived] +impl<'a, T: ::core::cmp::Eq + Trait> ::core::cmp::Eq for + GenericLifetime<'a, T> { +} +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq<'a, T: ::core::cmp::Eq + Trait>() { + let _: ::core::cmp::AssertParamIsEq<&'a T>; + } + }; +#[automatically_derived] +impl<'a, T: ::core::cmp::PartialOrd + Trait> ::core::cmp::PartialOrd for + GenericLifetime<'a, T> { + #[inline] + fn partial_cmp(&self, other: &GenericLifetime<'a, T>) + -> ::core::option::Option<::core::cmp::Ordering> { + ::core::cmp::PartialOrd::partial_cmp(&self.0, &other.0) + } +} +#[automatically_derived] +impl<'a, T: ::core::cmp::Ord + Trait> ::core::cmp::Ord for + GenericLifetime<'a, T> { + #[inline] + fn cmp(&self, other: &GenericLifetime<'a, T>) -> ::core::cmp::Ordering { + ::core::cmp::Ord::cmp(&self.0, &other.0) + } +} + // A packed, generic tuple struct involving an associated type. Because it is // packed, a `T: Copy` bound is added to all impls (and where clauses within // them) except for `Default`. This is because we must access fields using @@ -971,15 +1060,20 @@ impl ::core::cmp::Eq for PackedGeneric where T::A: ::core::cmp::Eq + ::core::marker::Copy { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) { - let _: ::core::cmp::AssertParamIsEq; - let _: ::core::cmp::AssertParamIsEq; - let _: ::core::cmp::AssertParamIsEq; - } } +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() where T::A: ::core::cmp::Eq + + ::core::marker::Copy { + let _: ::core::cmp::AssertParamIsEq; + let _: ::core::cmp::AssertParamIsEq; + let _: ::core::cmp::AssertParamIsEq; + } + }; #[automatically_derived] impl ::core::cmp::PartialOrd @@ -1055,12 +1149,13 @@ impl ::core::cmp::PartialEq for Enum0 { fn eq(&self, other: &Enum0) -> bool { match *self {} } } #[automatically_derived] -impl ::core::cmp::Eq for Enum0 { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) {} -} +impl ::core::cmp::Eq for Enum0 { } +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() {} + }; #[automatically_derived] impl ::core::cmp::PartialOrd for Enum0 { #[inline] @@ -1125,14 +1220,15 @@ impl ::core::cmp::PartialEq for Enum1 { } } #[automatically_derived] -impl ::core::cmp::Eq for Enum1 { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) { - let _: ::core::cmp::AssertParamIsEq; - } -} +impl ::core::cmp::Eq for Enum1 { } +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + } + }; #[automatically_derived] impl ::core::cmp::PartialOrd for Enum1 { #[inline] @@ -1191,12 +1287,13 @@ impl ::core::cmp::PartialEq for Fieldless1 { fn eq(&self, other: &Fieldless1) -> bool { true } } #[automatically_derived] -impl ::core::cmp::Eq for Fieldless1 { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) {} -} +impl ::core::cmp::Eq for Fieldless1 { } +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() {} + }; #[automatically_derived] impl ::core::cmp::PartialOrd for Fieldless1 { #[inline] @@ -1268,12 +1365,13 @@ impl ::core::cmp::PartialEq for Fieldless { } } #[automatically_derived] -impl ::core::cmp::Eq for Fieldless { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) {} -} +impl ::core::cmp::Eq for Fieldless { } +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() {} + }; #[automatically_derived] impl ::core::cmp::PartialOrd for Fieldless { #[inline] @@ -1378,16 +1476,17 @@ impl ::core::cmp::PartialEq for Mixed { } } #[automatically_derived] -impl ::core::cmp::Eq for Mixed { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) { - let _: ::core::cmp::AssertParamIsEq; - let _: ::core::cmp::AssertParamIsEq>; - let _: ::core::cmp::AssertParamIsEq>; - } -} +impl ::core::cmp::Eq for Mixed { } +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + let _: ::core::cmp::AssertParamIsEq>; + let _: ::core::cmp::AssertParamIsEq>; + } + }; #[automatically_derived] impl ::core::cmp::PartialOrd for Mixed { #[inline] @@ -1576,16 +1675,17 @@ impl ::core::cmp::PartialEq for Fielded { } } #[automatically_derived] -impl ::core::cmp::Eq for Fielded { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) { - let _: ::core::cmp::AssertParamIsEq; - let _: ::core::cmp::AssertParamIsEq; - let _: ::core::cmp::AssertParamIsEq>; - } -} +impl ::core::cmp::Eq for Fielded { } +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + let _: ::core::cmp::AssertParamIsEq; + let _: ::core::cmp::AssertParamIsEq>; + } + }; #[automatically_derived] impl ::core::cmp::PartialOrd for Fielded { #[inline] @@ -1699,14 +1799,17 @@ impl #[automatically_derived] impl ::core::cmp::Eq for EnumGeneric { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) { - let _: ::core::cmp::AssertParamIsEq; - let _: ::core::cmp::AssertParamIsEq; - } } +const _: () = + { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + let _: ::core::cmp::AssertParamIsEq; + } + }; #[automatically_derived] impl ::core::cmp::PartialOrd for EnumGeneric { From aeb9f0f154cc94ef5d16485b3dbf747bae61d9ee Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 26 Feb 2026 18:59:16 +0800 Subject: [PATCH 2/6] Emit const fn in impl block instead of const item --- .../src/deriving/cmp/eq.rs | 170 +++++++++--- tests/ui/deriving/deriving-all-codegen.stdout | 245 ++++++++---------- ...ultiple-types-with-same-name-and-derive.rs | 1 + ...ple-types-with-same-name-and-derive.stderr | 15 +- tests/ui/stats/macro-stats.stderr | 2 +- ...onst-generics-structural-demangling.stderr | 2 +- 6 files changed, 247 insertions(+), 188 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs index 56e76804c532e..a54b38f1d7497 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs @@ -1,7 +1,7 @@ use rustc_ast::{self as ast, MetaItem, Safety}; use rustc_data_structures::fx::FxHashSet; use rustc_expand::base::{Annotatable, ExtCtxt}; -use rustc_span::{Ident, Span, kw, sym}; +use rustc_span::{Ident, Span, sym}; use thin_vec::{ThinVec, thin_vec}; use crate::deriving::generic::*; @@ -16,7 +16,9 @@ pub(crate) fn expand_deriving_eq( is_const: bool, ) { let span = cx.with_def_site_ctxt(span); + let mut impl_generics = ast::Generics::default(); + let mut impl_self_ty = None; let trait_def = TraitDef { span, @@ -36,71 +38,153 @@ pub(crate) fn expand_deriving_eq( cx, mitem, item, - &mut |a| { - let Annotatable::Item(item) = &a else { + &mut |mut a| { + let Annotatable::Item(item) = &mut a else { unreachable!("should have emitted an Item in trait_def.expand_ext"); }; - let ast::ItemKind::Impl(imp) = &item.kind else { + let ast::ItemKind::Impl(imp) = &mut item.kind else { unreachable!("should have emitted an Impl in trait_def.expand_ext"); }; impl_generics = imp.generics.clone(); + impl_self_ty = Some(imp.self_ty.clone()); + respan_generics_for_derive(&mut imp.generics, span); push(a) }, true, ); - let const_body = cx.block( - span, - thin_vec![cx.stmt_item(span, assert_fields_are_eq_fn(cx, span, item, impl_generics),)], - ); + let assert_stmts = eq_assert_stmts_from_item(cx, span, item); + + // Skip generating `assert_fields_are_eq` impl if there are no assertions to make + if assert_stmts.is_empty() { + return; + } - let unit_ty = cx.ty(span, ast::TyKind::Tup(ThinVec::new())); - let body = cx.expr_block(const_body); - let konst = cx.item_const( + let impl_self_ty = impl_self_ty + .unwrap_or_else(|| cx.dcx().span_bug(span, "missing self type in `derive(Eq)`")); + push(Annotatable::Item(assert_fields_are_eq_impl( + cx, span, - Ident::new(kw::Underscore, span), - unit_ty, - ast::ConstItemRhsKind::new_body(body), - ); - push(Annotatable::Item(konst)); + strip_const_trait_bounds_from_generics(impl_generics), + impl_self_ty, + assert_stmts, + ))); +} + +fn respan_generics_for_derive(generics: &mut ast::Generics, span: Span) { + for predicate in &mut generics.where_clause.predicates { + predicate.span = span.with_ctxt(predicate.span.ctxt()); + + match &mut predicate.kind { + ast::WherePredicateKind::BoundPredicate(bound_predicate) => { + bound_predicate.bounded_ty.span = + span.with_ctxt(bound_predicate.bounded_ty.span.ctxt()); + + for bound in &mut bound_predicate.bounds { + match bound { + ast::GenericBound::Trait(poly_trait_ref) => { + poly_trait_ref.span = span.with_ctxt(poly_trait_ref.span.ctxt()); + } + _ => {} + } + } + } + ast::WherePredicateKind::RegionPredicate(region_predicate) => { + for bound in &mut region_predicate.bounds { + match bound { + ast::GenericBound::Trait(poly_trait_ref) => { + poly_trait_ref.span = span.with_ctxt(poly_trait_ref.span.ctxt()); + } + _ => {} + } + } + } + ast::WherePredicateKind::EqPredicate(eq_predicate) => { + eq_predicate.lhs_ty.span = span.with_ctxt(eq_predicate.lhs_ty.span.ctxt()); + eq_predicate.rhs_ty.span = span.with_ctxt(eq_predicate.rhs_ty.span.ctxt()); + } + } + } + generics.where_clause.span = span.with_ctxt(generics.where_clause.span.ctxt()); + generics.span = span.with_ctxt(generics.span.ctxt()); } -fn assert_fields_are_eq_fn( +fn assert_fields_are_eq_impl( cx: &ExtCtxt<'_>, span: Span, - item: &Annotatable, - fn_generics: ast::Generics, + impl_generics: ast::Generics, + impl_self_ty: Box, + assert_stmts: ThinVec, ) -> Box { - let stmts = eq_assert_stmts_from_item(cx, span, item); - cx.item( span, - thin_vec![ - cx.attr_nested_word(sym::doc, sym::hidden, span), - cx.attr_nested_word(sym::coverage, sym::off, span), - ], - ast::ItemKind::Fn(Box::new(ast::Fn { - defaultness: ast::Defaultness::Implicit, - ident: Ident::new(sym::assert_fields_are_eq, span), - generics: fn_generics, - sig: ast::FnSig { - header: ast::FnHeader { - constness: ast::Const::Yes(span), - coroutine_kind: None, - safety: ast::Safety::Default, - ext: ast::Extern::None, - }, - decl: cx.fn_decl(ThinVec::new(), ast::FnRetTy::Default(span)), + ast::AttrVec::new(), + ast::ItemKind::Impl(ast::Impl { + generics: impl_generics, + of_trait: None, + constness: ast::Const::No, + self_ty: impl_self_ty, + items: thin_vec![Box::new(ast::AssocItem { + id: ast::DUMMY_NODE_ID, + attrs: thin_vec![ + cx.attr_nested_word(sym::doc, sym::hidden, span), + cx.attr_nested_word(sym::coverage, sym::off, span), + ], span, - }, - contract: None, - define_opaque: None, - body: Some(cx.block(span, stmts)), - eii_impls: ThinVec::new(), - })), + vis: ast::Visibility { + span: span.shrink_to_lo(), + kind: ast::VisibilityKind::Inherited, + tokens: None, + }, + kind: ast::AssocItemKind::Fn(Box::new(ast::Fn { + defaultness: ast::Defaultness::Implicit, + ident: Ident::new(sym::assert_fields_are_eq, span), + generics: ast::Generics { span, ..Default::default() }, + sig: ast::FnSig { + header: ast::FnHeader { + constness: ast::Const::Yes(span), + coroutine_kind: None, + safety: ast::Safety::Default, + ext: ast::Extern::None, + }, + decl: cx.fn_decl(ThinVec::new(), ast::FnRetTy::Default(span)), + span, + }, + contract: None, + define_opaque: None, + body: Some(cx.block(span, assert_stmts)), + eii_impls: ThinVec::new(), + })), + tokens: None, + })], + }), ) } +fn strip_const_trait_bounds_from_generics(mut generics: ast::Generics) -> ast::Generics { + for param in &mut generics.params { + if let ast::GenericParamKind::Type { .. } = ¶m.kind { + strip_constness_from_bounds(&mut param.bounds); + } + } + + for predicate in &mut generics.where_clause.predicates { + if let ast::WherePredicateKind::BoundPredicate(bound_predicate) = &mut predicate.kind { + strip_constness_from_bounds(&mut bound_predicate.bounds); + } + } + + generics +} + +fn strip_constness_from_bounds(bounds: &mut [ast::GenericBound]) { + for bound in bounds { + if let ast::GenericBound::Trait(poly_trait_ref) = bound { + poly_trait_ref.modifiers.constness = ast::BoundConstness::Never; + } + } +} + fn eq_assert_stmts_from_item( cx: &ExtCtxt<'_>, span: Span, diff --git a/tests/ui/deriving/deriving-all-codegen.stdout b/tests/ui/deriving/deriving-all-codegen.stdout index e3dc358a434c6..ead10abc3e1e2 100644 --- a/tests/ui/deriving/deriving-all-codegen.stdout +++ b/tests/ui/deriving/deriving-all-codegen.stdout @@ -62,12 +62,6 @@ impl ::core::cmp::PartialEq for Empty { } #[automatically_derived] impl ::core::cmp::Eq for Empty { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq() {} - }; #[automatically_derived] impl ::core::cmp::PartialOrd for Empty { #[inline] @@ -140,14 +134,13 @@ impl ::core::cmp::PartialEq for Point { } #[automatically_derived] impl ::core::cmp::Eq for Point { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq() { - let _: ::core::cmp::AssertParamIsEq; - } - }; +impl Point { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + } +} #[automatically_derived] impl ::core::cmp::PartialOrd for Point { #[inline] @@ -229,14 +222,13 @@ impl ::core::cmp::PartialEq for PackedPoint { } #[automatically_derived] impl ::core::cmp::Eq for PackedPoint { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq() { - let _: ::core::cmp::AssertParamIsEq; - } - }; +impl PackedPoint { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + } +} #[automatically_derived] impl ::core::cmp::PartialOrd for PackedPoint { #[inline] @@ -313,14 +305,13 @@ impl ::core::cmp::PartialEq for TupleSingleField { } #[automatically_derived] impl ::core::cmp::Eq for TupleSingleField { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq() { - let _: ::core::cmp::AssertParamIsEq; - } - }; +impl TupleSingleField { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + } +} #[automatically_derived] impl ::core::cmp::PartialOrd for TupleSingleField { #[inline] @@ -389,14 +380,13 @@ impl ::core::cmp::PartialEq for SingleField { } #[automatically_derived] impl ::core::cmp::Eq for SingleField { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq() { - let _: ::core::cmp::AssertParamIsEq; - } - }; +impl SingleField { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + } +} #[automatically_derived] impl ::core::cmp::PartialOrd for SingleField { #[inline] @@ -495,14 +485,13 @@ impl ::core::cmp::PartialEq for Big { } #[automatically_derived] impl ::core::cmp::Eq for Big { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq() { - let _: ::core::cmp::AssertParamIsEq; - } - }; +impl Big { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + } +} #[automatically_derived] impl ::core::cmp::PartialOrd for Big { #[inline] @@ -760,14 +749,13 @@ impl ::core::cmp::PartialEq for Unsized { } #[automatically_derived] impl ::core::cmp::Eq for Unsized { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq() { - let _: ::core::cmp::AssertParamIsEq<[u32]>; - } - }; +impl Unsized { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq<[u32]>; + } +} #[automatically_derived] impl ::core::cmp::PartialOrd for Unsized { #[inline] @@ -857,17 +845,16 @@ impl impl ::core::cmp::Eq for Generic where T::A: ::core::cmp::Eq { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq() where T::A: ::core::cmp::Eq { - let _: ::core::cmp::AssertParamIsEq; - let _: ::core::cmp::AssertParamIsEq; - let _: ::core::cmp::AssertParamIsEq; - } - }; +impl Generic where + T::A: ::core::cmp::Eq { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + let _: ::core::cmp::AssertParamIsEq; + let _: ::core::cmp::AssertParamIsEq; + } +} #[automatically_derived] impl ::core::cmp::PartialOrd for Generic where @@ -957,14 +944,13 @@ impl<'a, T: ::core::cmp::PartialEq + Trait> ::core::cmp::PartialEq for impl<'a, T: ::core::cmp::Eq + Trait> ::core::cmp::Eq for GenericLifetime<'a, T> { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq<'a, T: ::core::cmp::Eq + Trait>() { - let _: ::core::cmp::AssertParamIsEq<&'a T>; - } - }; +impl<'a, T: ::core::cmp::Eq + Trait> GenericLifetime<'a, T> { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq<&'a T>; + } +} #[automatically_derived] impl<'a, T: ::core::cmp::PartialOrd + Trait> ::core::cmp::PartialOrd for GenericLifetime<'a, T> { @@ -1061,19 +1047,17 @@ impl ::core::cmp::Eq for PackedGeneric where T::A: ::core::cmp::Eq + ::core::marker::Copy { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq() where T::A: ::core::cmp::Eq + - ::core::marker::Copy { - let _: ::core::cmp::AssertParamIsEq; - let _: ::core::cmp::AssertParamIsEq; - let _: ::core::cmp::AssertParamIsEq; - } - }; +impl PackedGeneric where T::A: ::core::cmp::Eq + + ::core::marker::Copy { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + let _: ::core::cmp::AssertParamIsEq; + let _: ::core::cmp::AssertParamIsEq; + } +} #[automatically_derived] impl ::core::cmp::PartialOrd @@ -1150,12 +1134,6 @@ impl ::core::cmp::PartialEq for Enum0 { } #[automatically_derived] impl ::core::cmp::Eq for Enum0 { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq() {} - }; #[automatically_derived] impl ::core::cmp::PartialOrd for Enum0 { #[inline] @@ -1221,14 +1199,13 @@ impl ::core::cmp::PartialEq for Enum1 { } #[automatically_derived] impl ::core::cmp::Eq for Enum1 { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq() { - let _: ::core::cmp::AssertParamIsEq; - } - }; +impl Enum1 { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + } +} #[automatically_derived] impl ::core::cmp::PartialOrd for Enum1 { #[inline] @@ -1288,12 +1265,6 @@ impl ::core::cmp::PartialEq for Fieldless1 { } #[automatically_derived] impl ::core::cmp::Eq for Fieldless1 { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq() {} - }; #[automatically_derived] impl ::core::cmp::PartialOrd for Fieldless1 { #[inline] @@ -1366,12 +1337,6 @@ impl ::core::cmp::PartialEq for Fieldless { } #[automatically_derived] impl ::core::cmp::Eq for Fieldless { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq() {} - }; #[automatically_derived] impl ::core::cmp::PartialOrd for Fieldless { #[inline] @@ -1477,16 +1442,15 @@ impl ::core::cmp::PartialEq for Mixed { } #[automatically_derived] impl ::core::cmp::Eq for Mixed { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq() { - let _: ::core::cmp::AssertParamIsEq; - let _: ::core::cmp::AssertParamIsEq>; - let _: ::core::cmp::AssertParamIsEq>; - } - }; +impl Mixed { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + let _: ::core::cmp::AssertParamIsEq>; + let _: ::core::cmp::AssertParamIsEq>; + } +} #[automatically_derived] impl ::core::cmp::PartialOrd for Mixed { #[inline] @@ -1676,16 +1640,15 @@ impl ::core::cmp::PartialEq for Fielded { } #[automatically_derived] impl ::core::cmp::Eq for Fielded { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq() { - let _: ::core::cmp::AssertParamIsEq; - let _: ::core::cmp::AssertParamIsEq; - let _: ::core::cmp::AssertParamIsEq>; - } - }; +impl Fielded { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + let _: ::core::cmp::AssertParamIsEq; + let _: ::core::cmp::AssertParamIsEq>; + } +} #[automatically_derived] impl ::core::cmp::PartialOrd for Fielded { #[inline] @@ -1800,16 +1763,14 @@ impl impl ::core::cmp::Eq for EnumGeneric { } -const _: () = - { - #[doc(hidden)] - #[coverage(off)] - const fn assert_fields_are_eq() { - let _: ::core::cmp::AssertParamIsEq; - let _: ::core::cmp::AssertParamIsEq; - } - }; +impl EnumGeneric { + #[doc(hidden)] + #[coverage(off)] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq; + let _: ::core::cmp::AssertParamIsEq; + } +} #[automatically_derived] impl ::core::cmp::PartialOrd for EnumGeneric { diff --git a/tests/ui/duplicate/multiple-types-with-same-name-and-derive.rs b/tests/ui/duplicate/multiple-types-with-same-name-and-derive.rs index e946c0c5350ed..233e5309f9e12 100644 --- a/tests/ui/duplicate/multiple-types-with-same-name-and-derive.rs +++ b/tests/ui/duplicate/multiple-types-with-same-name-and-derive.rs @@ -13,6 +13,7 @@ struct NotSM(T); //~^ ERROR: struct takes 0 generic arguments //~| ERROR: struct takes 0 generic arguments //~| ERROR: struct takes 0 generic arguments +//~| ERROR: struct takes 0 generic arguments //~| ERROR: the name `NotSM` is defined multiple times //~| ERROR: no field `0` diff --git a/tests/ui/duplicate/multiple-types-with-same-name-and-derive.stderr b/tests/ui/duplicate/multiple-types-with-same-name-and-derive.stderr index e80cf35d81e1d..f02fc1b5a2c9c 100644 --- a/tests/ui/duplicate/multiple-types-with-same-name-and-derive.stderr +++ b/tests/ui/duplicate/multiple-types-with-same-name-and-derive.stderr @@ -59,13 +59,26 @@ LL | struct NotSM; | ^^^^^ = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +error[E0107]: struct takes 0 generic arguments but 1 generic argument was supplied + --> $DIR/multiple-types-with-same-name-and-derive.rs:12:8 + | +LL | struct NotSM(T); + | ^^^^^ expected 0 generic arguments + | +note: struct defined here, with 0 generic parameters + --> $DIR/multiple-types-with-same-name-and-derive.rs:8:8 + | +LL | struct NotSM; + | ^^^^^ + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + error[E0609]: no field `0` on type `&NotSM` --> $DIR/multiple-types-with-same-name-and-derive.rs:12:17 | LL | struct NotSM(T); | ^ unknown field -error: aborting due to 6 previous errors +error: aborting due to 7 previous errors Some errors have detailed explanations: E0107, E0428, E0609. For more information about an error, try `rustc --explain E0107`. diff --git a/tests/ui/stats/macro-stats.stderr b/tests/ui/stats/macro-stats.stderr index e46e7e02862fa..70a35778dec6b 100644 --- a/tests/ui/stats/macro-stats.stderr +++ b/tests/ui/stats/macro-stats.stderr @@ -8,7 +8,7 @@ macro-stats #[derive(Hash)] 2 17 8.5 macro-stats q! 1 26 26.0 519 519.0 macro-stats #[derive(Ord)] 1 15 15.0 503 503.0 macro-stats #[derive(Default)] 2 16 8.0 403 201.5 -macro-stats #[derive(Eq)] 1 11 11.0 312 312.0 +macro-stats #[derive(Eq)] 1 11 11.0 311 311.0 macro-stats #[derive(Debug)] 1 8 8.0 277 277.0 macro-stats #[derive(PartialEq)] 1 9 9.0 267 267.0 macro-stats #[derive(Copy)] 1 2 2.0 61 61.0 diff --git a/tests/ui/symbol-names/const-generics-structural-demangling.stderr b/tests/ui/symbol-names/const-generics-structural-demangling.stderr index 270c126e3f553..96dea154d05c0 100644 --- a/tests/ui/symbol-names/const-generics-structural-demangling.stderr +++ b/tests/ui/symbol-names/const-generics-structural-demangling.stderr @@ -124,7 +124,7 @@ error: demangling-alt(_4Bar_KVNtB_3BarS1xh7b_s_1xt1000_EE) +error: symbol-name(_RMsf_CsCRATE_HASH_1cINtB_4Bar_KVNtB_3BarS1xh7b_s_1xt1000_EE) --> $DIR/const-generics-structural-demangling.rs:93:5 | LL | #[rustc_symbol_name] From b5a6295a38c3b8f195eaf5da1cc923bae0fd3899 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Mon, 2 Mar 2026 19:02:31 +0800 Subject: [PATCH 3/6] Emit const item block instead and use MutVisitor pattern --- .../src/deriving/cmp/eq.rs | 252 ++++++++++-------- tests/ui/deriving/deriving-all-codegen.rs | 4 + tests/ui/deriving/deriving-all-codegen.stdout | 88 ++++-- ...ultiple-types-with-same-name-and-derive.rs | 1 - ...ple-types-with-same-name-and-derive.stderr | 15 +- ...ime-used-in-debug-macro-issue-70152.stderr | 5 - tests/ui/stats/macro-stats.stderr | 2 +- ...onst-generics-structural-demangling.stderr | 2 +- 8 files changed, 209 insertions(+), 160 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs index a54b38f1d7497..28848b8612a98 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs @@ -1,12 +1,82 @@ +use rustc_ast::mut_visit::{self, MutVisitor}; use rustc_ast::{self as ast, MetaItem, Safety}; use rustc_data_structures::fx::FxHashSet; use rustc_expand::base::{Annotatable, ExtCtxt}; -use rustc_span::{Ident, Span, sym}; +use rustc_span::{Ident, Span, kw, sym}; use thin_vec::{ThinVec, thin_vec}; use crate::deriving::generic::*; use crate::deriving::path_std; +struct ReplaceSelfTyVisitor(Box); +impl MutVisitor for ReplaceSelfTyVisitor { + fn visit_ty(&mut self, ty: &mut ast::Ty) { + if let ast::TyKind::Path(None, path) = &mut ty.kind + && let [segment] = &path.segments[..] + && *segment == kw::SelfUpper + { + *ty = *self.0.clone(); + } else { + mut_visit::walk_ty(self, ty); + } + } +} + +struct RespanGenericsVisitor(Span); +impl MutVisitor for RespanGenericsVisitor { + fn visit_generics(&mut self, generics: &mut ast::Generics) { + generics.where_clause.span = self.0.with_ctxt(generics.where_clause.span.ctxt()); + generics.span = self.0.with_ctxt(generics.span.ctxt()); + mut_visit::walk_generics(self, generics); + } + fn visit_where_predicate(&mut self, predicate: &mut ast::WherePredicate) { + predicate.span = self.0.with_ctxt(predicate.span.ctxt()); + mut_visit::walk_where_predicate(self, predicate); + } + fn visit_where_predicate_kind(&mut self, kind: &mut ast::WherePredicateKind) { + match kind { + ast::WherePredicateKind::BoundPredicate(bound_predicate) => { + bound_predicate.bounded_ty.span = + self.0.with_ctxt(bound_predicate.bounded_ty.span.ctxt()); + } + ast::WherePredicateKind::EqPredicate(eq_predicate) => { + eq_predicate.lhs_ty.span = self.0.with_ctxt(eq_predicate.lhs_ty.span.ctxt()); + eq_predicate.rhs_ty.span = self.0.with_ctxt(eq_predicate.rhs_ty.span.ctxt()); + } + ast::WherePredicateKind::RegionPredicate(_) => {} + } + mut_visit::walk_where_predicate_kind(self, kind); + } + fn visit_param_bound( + &mut self, + bound: &mut rustc_ast::GenericBound, + _ctxt: rustc_ast::visit::BoundKind, + ) { + match bound { + ast::GenericBound::Trait(poly_trait_ref) => { + poly_trait_ref.span = self.0.with_ctxt(poly_trait_ref.span.ctxt()); + } + ast::GenericBound::Outlives(_) => {} + ast::GenericBound::Use(_, _) => {} + } + ast::mut_visit::walk_param_bound(self, bound); + } +} + +struct StripConstTraitBoundsVisitor; +impl MutVisitor for StripConstTraitBoundsVisitor { + fn visit_param_bound( + &mut self, + bound: &mut rustc_ast::GenericBound, + _ctxt: rustc_ast::visit::BoundKind, + ) { + if let ast::GenericBound::Trait(poly_trait_ref) = bound { + poly_trait_ref.modifiers.constness = ast::BoundConstness::Never; + } + mut_visit::walk_param_bound(self, bound); + } +} + pub(crate) fn expand_deriving_eq( cx: &ExtCtxt<'_>, span: Span, @@ -17,8 +87,8 @@ pub(crate) fn expand_deriving_eq( ) { let span = cx.with_def_site_ctxt(span); - let mut impl_generics = ast::Generics::default(); - let mut impl_self_ty = None; + let mut fn_generics = ast::Generics { span, ..Default::default() }; + let mut self_ty = None; let trait_def = TraitDef { span, @@ -45,150 +115,93 @@ pub(crate) fn expand_deriving_eq( let ast::ItemKind::Impl(imp) = &mut item.kind else { unreachable!("should have emitted an Impl in trait_def.expand_ext"); }; - impl_generics = imp.generics.clone(); - impl_self_ty = Some(imp.self_ty.clone()); - respan_generics_for_derive(&mut imp.generics, span); + use ast::mut_visit::MutVisitor; + RespanGenericsVisitor(span).visit_generics(&mut imp.generics); + fn_generics = imp.generics.clone(); + self_ty = Some(imp.self_ty.clone()); push(a) }, true, ); - let assert_stmts = eq_assert_stmts_from_item(cx, span, item); + let self_ty = + self_ty.unwrap_or_else(|| cx.dcx().span_bug(span, "missing self type in `derive(Eq)`")); + let assert_stmts = eq_assert_stmts_from_item(cx, span, item, ReplaceSelfTyVisitor(self_ty)); // Skip generating `assert_fields_are_eq` impl if there are no assertions to make if assert_stmts.is_empty() { return; } - let impl_self_ty = impl_self_ty - .unwrap_or_else(|| cx.dcx().span_bug(span, "missing self type in `derive(Eq)`")); - push(Annotatable::Item(assert_fields_are_eq_impl( - cx, - span, - strip_const_trait_bounds_from_generics(impl_generics), - impl_self_ty, - assert_stmts, - ))); -} - -fn respan_generics_for_derive(generics: &mut ast::Generics, span: Span) { - for predicate in &mut generics.where_clause.predicates { - predicate.span = span.with_ctxt(predicate.span.ctxt()); - - match &mut predicate.kind { - ast::WherePredicateKind::BoundPredicate(bound_predicate) => { - bound_predicate.bounded_ty.span = - span.with_ctxt(bound_predicate.bounded_ty.span.ctxt()); - - for bound in &mut bound_predicate.bounds { - match bound { - ast::GenericBound::Trait(poly_trait_ref) => { - poly_trait_ref.span = span.with_ctxt(poly_trait_ref.span.ctxt()); - } - _ => {} - } - } - } - ast::WherePredicateKind::RegionPredicate(region_predicate) => { - for bound in &mut region_predicate.bounds { - match bound { - ast::GenericBound::Trait(poly_trait_ref) => { - poly_trait_ref.span = span.with_ctxt(poly_trait_ref.span.ctxt()); - } - _ => {} - } - } - } - ast::WherePredicateKind::EqPredicate(eq_predicate) => { - eq_predicate.lhs_ty.span = span.with_ctxt(eq_predicate.lhs_ty.span.ctxt()); - eq_predicate.rhs_ty.span = span.with_ctxt(eq_predicate.rhs_ty.span.ctxt()); - } - } - } - generics.where_clause.span = span.with_ctxt(generics.where_clause.span.ctxt()); - generics.span = span.with_ctxt(generics.span.ctxt()); + StripConstTraitBoundsVisitor.visit_generics(&mut fn_generics); + push(Annotatable::Item(expand_const_item_block(cx, span, fn_generics, assert_stmts))); } -fn assert_fields_are_eq_impl( +fn expand_const_item_block( cx: &ExtCtxt<'_>, span: Span, - impl_generics: ast::Generics, - impl_self_ty: Box, + fn_generics: ast::Generics, assert_stmts: ThinVec, ) -> Box { cx.item( span, ast::AttrVec::new(), - ast::ItemKind::Impl(ast::Impl { - generics: impl_generics, - of_trait: None, - constness: ast::Const::No, - self_ty: impl_self_ty, - items: thin_vec![Box::new(ast::AssocItem { - id: ast::DUMMY_NODE_ID, - attrs: thin_vec![ - cx.attr_nested_word(sym::doc, sym::hidden, span), - cx.attr_nested_word(sym::coverage, sym::off, span), - ], + ast::ItemKind::ConstBlock(ast::ConstBlockItem { + span, + id: ast::DUMMY_NODE_ID, + block: cx.block( span, - vis: ast::Visibility { - span: span.shrink_to_lo(), - kind: ast::VisibilityKind::Inherited, - tokens: None, - }, - kind: ast::AssocItemKind::Fn(Box::new(ast::Fn { - defaultness: ast::Defaultness::Implicit, - ident: Ident::new(sym::assert_fields_are_eq, span), - generics: ast::Generics { span, ..Default::default() }, - sig: ast::FnSig { - header: ast::FnHeader { - constness: ast::Const::Yes(span), - coroutine_kind: None, - safety: ast::Safety::Default, - ext: ast::Extern::None, - }, - decl: cx.fn_decl(ThinVec::new(), ast::FnRetTy::Default(span)), + thin_vec![ast::Stmt { + span, + id: ast::DUMMY_NODE_ID, + kind: ast::StmtKind::Item(Box::new(ast::Item { span, - }, - contract: None, - define_opaque: None, - body: Some(cx.block(span, assert_stmts)), - eii_impls: ThinVec::new(), - })), - tokens: None, - })], + id: ast::DUMMY_NODE_ID, + attrs: thin_vec![ + cx.attr_nested_word(sym::doc, sym::hidden, span), + cx.attr_nested_word(sym::coverage, sym::off, span), + // This function will never be called, so doing codegen etc. for it is + // unnecessary. We prevent this by adding `#[inline]`, which improves + // compile-time. + cx.attr_word(sym::inline, span), + ], + vis: ast::Visibility { + kind: ast::VisibilityKind::Inherited, + span, + tokens: None, + }, + tokens: None, + kind: ast::ItemKind::Fn(Box::new(ast::Fn { + defaultness: ast::Defaultness::Implicit, + ident: Ident::new(sym::assert_fields_are_eq, span), + generics: fn_generics, + sig: ast::FnSig { + header: ast::FnHeader { + constness: ast::Const::Yes(span), + coroutine_kind: None, + safety: ast::Safety::Default, + ext: ast::Extern::None, + }, + decl: cx.fn_decl(ThinVec::new(), ast::FnRetTy::Default(span)), + span, + }, + contract: None, + define_opaque: None, + body: Some(cx.block(span, assert_stmts)), + eii_impls: ThinVec::new(), + })) + })) + },], + ), }), ) } -fn strip_const_trait_bounds_from_generics(mut generics: ast::Generics) -> ast::Generics { - for param in &mut generics.params { - if let ast::GenericParamKind::Type { .. } = ¶m.kind { - strip_constness_from_bounds(&mut param.bounds); - } - } - - for predicate in &mut generics.where_clause.predicates { - if let ast::WherePredicateKind::BoundPredicate(bound_predicate) = &mut predicate.kind { - strip_constness_from_bounds(&mut bound_predicate.bounds); - } - } - - generics -} - -fn strip_constness_from_bounds(bounds: &mut [ast::GenericBound]) { - for bound in bounds { - if let ast::GenericBound::Trait(poly_trait_ref) = bound { - poly_trait_ref.modifiers.constness = ast::BoundConstness::Never; - } - } -} - fn eq_assert_stmts_from_item( cx: &ExtCtxt<'_>, span: Span, item: &Annotatable, + mut replace_self_ty: ReplaceSelfTyVisitor, ) -> ThinVec { let mut stmts = ThinVec::new(); let mut seen_type_names = FxHashSet::default(); @@ -202,11 +215,14 @@ fn eq_assert_stmts_from_item( { // Already produced an assertion for this type. } else { + use ast::mut_visit::MutVisitor; + let mut field_ty = field.ty.clone(); + replace_self_ty.visit_ty(&mut field_ty); // let _: AssertParamIsEq; super::assert_ty_bounds( cx, &mut stmts, - field.ty.clone(), + field_ty, field.span, &[sym::cmp, sym::AssertParamIsEq], ); diff --git a/tests/ui/deriving/deriving-all-codegen.rs b/tests/ui/deriving/deriving-all-codegen.rs index 3b0663ae3991e..74aa5ee9513d7 100644 --- a/tests/ui/deriving/deriving-all-codegen.rs +++ b/tests/ui/deriving/deriving-all-codegen.rs @@ -79,6 +79,10 @@ struct Reorder { b10: &'static *const bool, } +// A struct with a recursive type. +#[derive(PartialEq, Eq, PartialOrd, Ord)] +struct Recursive(Option>); + // A struct that doesn't impl `Copy`, which means it gets the non-trivial // `clone` implemention that clones the fields individually. #[derive(Clone)] diff --git a/tests/ui/deriving/deriving-all-codegen.stdout b/tests/ui/deriving/deriving-all-codegen.stdout index ead10abc3e1e2..cde61cdbc0df8 100644 --- a/tests/ui/deriving/deriving-all-codegen.stdout +++ b/tests/ui/deriving/deriving-all-codegen.stdout @@ -134,9 +134,10 @@ impl ::core::cmp::PartialEq for Point { } #[automatically_derived] impl ::core::cmp::Eq for Point { } -impl Point { +const { #[doc(hidden)] #[coverage(off)] + #[inline] const fn assert_fields_are_eq() { let _: ::core::cmp::AssertParamIsEq; } @@ -222,9 +223,10 @@ impl ::core::cmp::PartialEq for PackedPoint { } #[automatically_derived] impl ::core::cmp::Eq for PackedPoint { } -impl PackedPoint { +const { #[doc(hidden)] #[coverage(off)] + #[inline] const fn assert_fields_are_eq() { let _: ::core::cmp::AssertParamIsEq; } @@ -305,9 +307,10 @@ impl ::core::cmp::PartialEq for TupleSingleField { } #[automatically_derived] impl ::core::cmp::Eq for TupleSingleField { } -impl TupleSingleField { +const { #[doc(hidden)] #[coverage(off)] + #[inline] const fn assert_fields_are_eq() { let _: ::core::cmp::AssertParamIsEq; } @@ -380,9 +383,10 @@ impl ::core::cmp::PartialEq for SingleField { } #[automatically_derived] impl ::core::cmp::Eq for SingleField { } -impl SingleField { +const { #[doc(hidden)] #[coverage(off)] + #[inline] const fn assert_fields_are_eq() { let _: ::core::cmp::AssertParamIsEq; } @@ -485,9 +489,10 @@ impl ::core::cmp::PartialEq for Big { } #[automatically_derived] impl ::core::cmp::Eq for Big { } -impl Big { +const { #[doc(hidden)] #[coverage(off)] + #[inline] const fn assert_fields_are_eq() { let _: ::core::cmp::AssertParamIsEq; } @@ -670,6 +675,41 @@ impl ::core::cmp::PartialOrd for Reorder { } } +// A struct with a recursive type. +struct Recursive(Option>); +#[automatically_derived] +impl ::core::marker::StructuralPartialEq for Recursive { } +#[automatically_derived] +impl ::core::cmp::PartialEq for Recursive { + #[inline] + fn eq(&self, other: &Recursive) -> bool { self.0 == other.0 } +} +#[automatically_derived] +impl ::core::cmp::Eq for Recursive { } +const { + #[doc(hidden)] + #[coverage(off)] + #[inline] + const fn assert_fields_are_eq() { + let _: ::core::cmp::AssertParamIsEq>>; + } +} +#[automatically_derived] +impl ::core::cmp::PartialOrd for Recursive { + #[inline] + fn partial_cmp(&self, other: &Recursive) + -> ::core::option::Option<::core::cmp::Ordering> { + ::core::cmp::PartialOrd::partial_cmp(&self.0, &other.0) + } +} +#[automatically_derived] +impl ::core::cmp::Ord for Recursive { + #[inline] + fn cmp(&self, other: &Recursive) -> ::core::cmp::Ordering { + ::core::cmp::Ord::cmp(&self.0, &other.0) + } +} + // A struct that doesn't impl `Copy`, which means it gets the non-trivial // `clone` implemention that clones the fields individually. struct NonCopy(u32); @@ -749,9 +789,10 @@ impl ::core::cmp::PartialEq for Unsized { } #[automatically_derived] impl ::core::cmp::Eq for Unsized { } -impl Unsized { +const { #[doc(hidden)] #[coverage(off)] + #[inline] const fn assert_fields_are_eq() { let _: ::core::cmp::AssertParamIsEq<[u32]>; } @@ -845,11 +886,12 @@ impl impl ::core::cmp::Eq for Generic where T::A: ::core::cmp::Eq { } -impl Generic where - T::A: ::core::cmp::Eq { +const { #[doc(hidden)] #[coverage(off)] - const fn assert_fields_are_eq() { + #[inline] + const fn assert_fields_are_eq() where T::A: ::core::cmp::Eq { let _: ::core::cmp::AssertParamIsEq; let _: ::core::cmp::AssertParamIsEq; let _: ::core::cmp::AssertParamIsEq; @@ -944,10 +986,11 @@ impl<'a, T: ::core::cmp::PartialEq + Trait> ::core::cmp::PartialEq for impl<'a, T: ::core::cmp::Eq + Trait> ::core::cmp::Eq for GenericLifetime<'a, T> { } -impl<'a, T: ::core::cmp::Eq + Trait> GenericLifetime<'a, T> { +const { #[doc(hidden)] #[coverage(off)] - const fn assert_fields_are_eq() { + #[inline] + const fn assert_fields_are_eq<'a, T: ::core::cmp::Eq + Trait>() { let _: ::core::cmp::AssertParamIsEq<&'a T>; } } @@ -1047,12 +1090,13 @@ impl ::core::cmp::Eq for PackedGeneric where T::A: ::core::cmp::Eq + ::core::marker::Copy { } -impl PackedGeneric where T::A: ::core::cmp::Eq + - ::core::marker::Copy { +const { #[doc(hidden)] #[coverage(off)] - const fn assert_fields_are_eq() { + #[inline] + const fn assert_fields_are_eq() where + T::A: ::core::cmp::Eq + ::core::marker::Copy { let _: ::core::cmp::AssertParamIsEq; let _: ::core::cmp::AssertParamIsEq; let _: ::core::cmp::AssertParamIsEq; @@ -1199,9 +1243,10 @@ impl ::core::cmp::PartialEq for Enum1 { } #[automatically_derived] impl ::core::cmp::Eq for Enum1 { } -impl Enum1 { +const { #[doc(hidden)] #[coverage(off)] + #[inline] const fn assert_fields_are_eq() { let _: ::core::cmp::AssertParamIsEq; } @@ -1442,9 +1487,10 @@ impl ::core::cmp::PartialEq for Mixed { } #[automatically_derived] impl ::core::cmp::Eq for Mixed { } -impl Mixed { +const { #[doc(hidden)] #[coverage(off)] + #[inline] const fn assert_fields_are_eq() { let _: ::core::cmp::AssertParamIsEq; let _: ::core::cmp::AssertParamIsEq>; @@ -1640,9 +1686,10 @@ impl ::core::cmp::PartialEq for Fielded { } #[automatically_derived] impl ::core::cmp::Eq for Fielded { } -impl Fielded { +const { #[doc(hidden)] #[coverage(off)] + #[inline] const fn assert_fields_are_eq() { let _: ::core::cmp::AssertParamIsEq; let _: ::core::cmp::AssertParamIsEq; @@ -1763,10 +1810,11 @@ impl impl ::core::cmp::Eq for EnumGeneric { } -impl EnumGeneric { +const { #[doc(hidden)] #[coverage(off)] - const fn assert_fields_are_eq() { + #[inline] + const fn assert_fields_are_eq() { let _: ::core::cmp::AssertParamIsEq; let _: ::core::cmp::AssertParamIsEq; } diff --git a/tests/ui/duplicate/multiple-types-with-same-name-and-derive.rs b/tests/ui/duplicate/multiple-types-with-same-name-and-derive.rs index 233e5309f9e12..e946c0c5350ed 100644 --- a/tests/ui/duplicate/multiple-types-with-same-name-and-derive.rs +++ b/tests/ui/duplicate/multiple-types-with-same-name-and-derive.rs @@ -13,7 +13,6 @@ struct NotSM(T); //~^ ERROR: struct takes 0 generic arguments //~| ERROR: struct takes 0 generic arguments //~| ERROR: struct takes 0 generic arguments -//~| ERROR: struct takes 0 generic arguments //~| ERROR: the name `NotSM` is defined multiple times //~| ERROR: no field `0` diff --git a/tests/ui/duplicate/multiple-types-with-same-name-and-derive.stderr b/tests/ui/duplicate/multiple-types-with-same-name-and-derive.stderr index f02fc1b5a2c9c..e80cf35d81e1d 100644 --- a/tests/ui/duplicate/multiple-types-with-same-name-and-derive.stderr +++ b/tests/ui/duplicate/multiple-types-with-same-name-and-derive.stderr @@ -59,26 +59,13 @@ LL | struct NotSM; | ^^^^^ = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error[E0107]: struct takes 0 generic arguments but 1 generic argument was supplied - --> $DIR/multiple-types-with-same-name-and-derive.rs:12:8 - | -LL | struct NotSM(T); - | ^^^^^ expected 0 generic arguments - | -note: struct defined here, with 0 generic parameters - --> $DIR/multiple-types-with-same-name-and-derive.rs:8:8 - | -LL | struct NotSM; - | ^^^^^ - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error[E0609]: no field `0` on type `&NotSM` --> $DIR/multiple-types-with-same-name-and-derive.rs:12:17 | LL | struct NotSM(T); | ^ unknown field -error: aborting due to 7 previous errors +error: aborting due to 6 previous errors Some errors have detailed explanations: E0107, E0428, E0609. For more information about an error, try `rustc --explain E0107`. diff --git a/tests/ui/lifetimes/undeclared-lifetime-used-in-debug-macro-issue-70152.stderr b/tests/ui/lifetimes/undeclared-lifetime-used-in-debug-macro-issue-70152.stderr index f90133e9fb1ab..61a4cadb8b119 100644 --- a/tests/ui/lifetimes/undeclared-lifetime-used-in-debug-macro-issue-70152.stderr +++ b/tests/ui/lifetimes/undeclared-lifetime-used-in-debug-macro-issue-70152.stderr @@ -17,11 +17,6 @@ LL | #[derive(Eq, PartialEq)] LL | struct Test { LL | a: &'b str, | ^^ undeclared lifetime - | -help: consider introducing lifetime `'b` here - | -LL | struct Test<'b> { - | ++++ error[E0261]: use of undeclared lifetime name `'b` --> $DIR/undeclared-lifetime-used-in-debug-macro-issue-70152.rs:13:13 diff --git a/tests/ui/stats/macro-stats.stderr b/tests/ui/stats/macro-stats.stderr index 70a35778dec6b..7e9fb0ba69f94 100644 --- a/tests/ui/stats/macro-stats.stderr +++ b/tests/ui/stats/macro-stats.stderr @@ -8,7 +8,7 @@ macro-stats #[derive(Hash)] 2 17 8.5 macro-stats q! 1 26 26.0 519 519.0 macro-stats #[derive(Ord)] 1 15 15.0 503 503.0 macro-stats #[derive(Default)] 2 16 8.0 403 201.5 -macro-stats #[derive(Eq)] 1 11 11.0 311 311.0 +macro-stats #[derive(Eq)] 1 12 12.0 323 323.0 macro-stats #[derive(Debug)] 1 8 8.0 277 277.0 macro-stats #[derive(PartialEq)] 1 9 9.0 267 267.0 macro-stats #[derive(Copy)] 1 2 2.0 61 61.0 diff --git a/tests/ui/symbol-names/const-generics-structural-demangling.stderr b/tests/ui/symbol-names/const-generics-structural-demangling.stderr index 96dea154d05c0..270c126e3f553 100644 --- a/tests/ui/symbol-names/const-generics-structural-demangling.stderr +++ b/tests/ui/symbol-names/const-generics-structural-demangling.stderr @@ -124,7 +124,7 @@ error: demangling-alt(_4Bar_KVNtB_3BarS1xh7b_s_1xt1000_EE) +error: symbol-name(_RMsd_CsCRATE_HASH_1cINtB_4Bar_KVNtB_3BarS1xh7b_s_1xt1000_EE) --> $DIR/const-generics-structural-demangling.rs:93:5 | LL | #[rustc_symbol_name] From c70ca5493f74e25d21e9f40695e00fccc0f1bc2d Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Mon, 2 Mar 2026 20:52:08 +0800 Subject: [PATCH 4/6] Add dummy self pointer arg to emitted const fn --- .../src/deriving/cmp/eq.rs | 43 +++++++++++-------- tests/ui/deriving/deriving-all-codegen.stdout | 32 ++++++++------ 2 files changed, 44 insertions(+), 31 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs index 28848b8612a98..742fa06c7a7a7 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs @@ -126,7 +126,8 @@ pub(crate) fn expand_deriving_eq( let self_ty = self_ty.unwrap_or_else(|| cx.dcx().span_bug(span, "missing self type in `derive(Eq)`")); - let assert_stmts = eq_assert_stmts_from_item(cx, span, item, ReplaceSelfTyVisitor(self_ty)); + let assert_stmts = + eq_assert_stmts_from_item(cx, span, item, ReplaceSelfTyVisitor(self_ty.clone())); // Skip generating `assert_fields_are_eq` impl if there are no assertions to make if assert_stmts.is_empty() { @@ -134,15 +135,33 @@ pub(crate) fn expand_deriving_eq( } StripConstTraitBoundsVisitor.visit_generics(&mut fn_generics); - push(Annotatable::Item(expand_const_item_block(cx, span, fn_generics, assert_stmts))); + push(Annotatable::Item(expand_const_item_block(cx, span, fn_generics, self_ty, assert_stmts))); } fn expand_const_item_block( cx: &ExtCtxt<'_>, span: Span, fn_generics: ast::Generics, + self_ty: Box, assert_stmts: ThinVec, ) -> Box { + // We need a dummy const pointer to Self argument to ensure well-formedness of the Self type. + // This doesn't add overhead because the fn itself is never called, and in fact should not + // even have any runtime code generated for it as it's an inline const fn. + let const_self_ptr_ty = + cx.ty(span, ast::TyKind::Ptr(ast::MutTy { mutbl: ast::Mutability::Not, ty: self_ty })); + let fn_args = thin_vec![cx.param(span, Ident::new(kw::Underscore, span), const_self_ptr_ty)]; + let fn_sig = ast::FnSig { + header: ast::FnHeader { + constness: ast::Const::Yes(span), + coroutine_kind: None, + safety: ast::Safety::Default, + ext: ast::Extern::None, + }, + decl: cx.fn_decl(fn_args, ast::FnRetTy::Default(span)), + span, + }; + cx.item( span, ast::AttrVec::new(), @@ -151,10 +170,9 @@ fn expand_const_item_block( id: ast::DUMMY_NODE_ID, block: cx.block( span, - thin_vec![ast::Stmt { + thin_vec![cx.stmt_item( span, - id: ast::DUMMY_NODE_ID, - kind: ast::StmtKind::Item(Box::new(ast::Item { + Box::new(ast::Item { span, id: ast::DUMMY_NODE_ID, attrs: thin_vec![ @@ -175,23 +193,14 @@ fn expand_const_item_block( defaultness: ast::Defaultness::Implicit, ident: Ident::new(sym::assert_fields_are_eq, span), generics: fn_generics, - sig: ast::FnSig { - header: ast::FnHeader { - constness: ast::Const::Yes(span), - coroutine_kind: None, - safety: ast::Safety::Default, - ext: ast::Extern::None, - }, - decl: cx.fn_decl(ThinVec::new(), ast::FnRetTy::Default(span)), - span, - }, + sig: fn_sig, contract: None, define_opaque: None, body: Some(cx.block(span, assert_stmts)), eii_impls: ThinVec::new(), })) - })) - },], + }) + ),], ), }), ) diff --git a/tests/ui/deriving/deriving-all-codegen.stdout b/tests/ui/deriving/deriving-all-codegen.stdout index cde61cdbc0df8..94132373c74af 100644 --- a/tests/ui/deriving/deriving-all-codegen.stdout +++ b/tests/ui/deriving/deriving-all-codegen.stdout @@ -138,7 +138,7 @@ const { #[doc(hidden)] #[coverage(off)] #[inline] - const fn assert_fields_are_eq() { + const fn assert_fields_are_eq(_: *const Point) { let _: ::core::cmp::AssertParamIsEq; } } @@ -227,7 +227,7 @@ const { #[doc(hidden)] #[coverage(off)] #[inline] - const fn assert_fields_are_eq() { + const fn assert_fields_are_eq(_: *const PackedPoint) { let _: ::core::cmp::AssertParamIsEq; } } @@ -311,7 +311,7 @@ const { #[doc(hidden)] #[coverage(off)] #[inline] - const fn assert_fields_are_eq() { + const fn assert_fields_are_eq(_: *const TupleSingleField) { let _: ::core::cmp::AssertParamIsEq; } } @@ -387,7 +387,7 @@ const { #[doc(hidden)] #[coverage(off)] #[inline] - const fn assert_fields_are_eq() { + const fn assert_fields_are_eq(_: *const SingleField) { let _: ::core::cmp::AssertParamIsEq; } } @@ -493,7 +493,7 @@ const { #[doc(hidden)] #[coverage(off)] #[inline] - const fn assert_fields_are_eq() { + const fn assert_fields_are_eq(_: *const Big) { let _: ::core::cmp::AssertParamIsEq; } } @@ -690,7 +690,7 @@ const { #[doc(hidden)] #[coverage(off)] #[inline] - const fn assert_fields_are_eq() { + const fn assert_fields_are_eq(_: *const Recursive) { let _: ::core::cmp::AssertParamIsEq>>; } } @@ -793,7 +793,7 @@ const { #[doc(hidden)] #[coverage(off)] #[inline] - const fn assert_fields_are_eq() { + const fn assert_fields_are_eq(_: *const Unsized) { let _: ::core::cmp::AssertParamIsEq<[u32]>; } } @@ -891,7 +891,8 @@ const { #[coverage(off)] #[inline] const fn assert_fields_are_eq() where T::A: ::core::cmp::Eq { + U: ::core::cmp::Eq>(_: *const Generic) where + T::A: ::core::cmp::Eq { let _: ::core::cmp::AssertParamIsEq; let _: ::core::cmp::AssertParamIsEq; let _: ::core::cmp::AssertParamIsEq; @@ -990,7 +991,8 @@ const { #[doc(hidden)] #[coverage(off)] #[inline] - const fn assert_fields_are_eq<'a, T: ::core::cmp::Eq + Trait>() { + const fn assert_fields_are_eq<'a, T: ::core::cmp::Eq + + Trait>(_: *const GenericLifetime<'a, T>) { let _: ::core::cmp::AssertParamIsEq<&'a T>; } } @@ -1095,7 +1097,8 @@ const { #[coverage(off)] #[inline] const fn assert_fields_are_eq() where + Trait, U: ::core::cmp::Eq + + ::core::marker::Copy>(_: *const PackedGeneric) where T::A: ::core::cmp::Eq + ::core::marker::Copy { let _: ::core::cmp::AssertParamIsEq; let _: ::core::cmp::AssertParamIsEq; @@ -1247,7 +1250,7 @@ const { #[doc(hidden)] #[coverage(off)] #[inline] - const fn assert_fields_are_eq() { + const fn assert_fields_are_eq(_: *const Enum1) { let _: ::core::cmp::AssertParamIsEq; } } @@ -1491,7 +1494,7 @@ const { #[doc(hidden)] #[coverage(off)] #[inline] - const fn assert_fields_are_eq() { + const fn assert_fields_are_eq(_: *const Mixed) { let _: ::core::cmp::AssertParamIsEq; let _: ::core::cmp::AssertParamIsEq>; let _: ::core::cmp::AssertParamIsEq>; @@ -1690,7 +1693,7 @@ const { #[doc(hidden)] #[coverage(off)] #[inline] - const fn assert_fields_are_eq() { + const fn assert_fields_are_eq(_: *const Fielded) { let _: ::core::cmp::AssertParamIsEq; let _: ::core::cmp::AssertParamIsEq; let _: ::core::cmp::AssertParamIsEq>; @@ -1814,7 +1817,8 @@ const { #[doc(hidden)] #[coverage(off)] #[inline] - const fn assert_fields_are_eq() { + const fn assert_fields_are_eq(_: *const EnumGeneric) { let _: ::core::cmp::AssertParamIsEq; let _: ::core::cmp::AssertParamIsEq; } From 606910f7a4c89804f8c4a346663d2a868a43526f Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Mon, 2 Mar 2026 22:30:21 +0800 Subject: [PATCH 5/6] Ensure Self type paths are converted to its concrete types --- .../src/deriving/cmp/eq.rs | 32 +++++++- tests/ui/deriving/deriving-all-codegen.rs | 8 ++ tests/ui/deriving/deriving-all-codegen.stdout | 74 +++++++++++++++++++ 3 files changed, 111 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs index 742fa06c7a7a7..46c5280f817a9 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs @@ -12,14 +12,40 @@ struct ReplaceSelfTyVisitor(Box); impl MutVisitor for ReplaceSelfTyVisitor { fn visit_ty(&mut self, ty: &mut ast::Ty) { if let ast::TyKind::Path(None, path) = &mut ty.kind - && let [segment] = &path.segments[..] - && *segment == kw::SelfUpper + && let [first, rest @ ..] = &path.segments[..] + && *first == kw::SelfUpper { - *ty = *self.0.clone(); + if rest.is_empty() { + // Just `Self` — replace the whole type + *ty = *self.0.clone(); + } else { + // `Self::Something` — splice concrete type's segments in + let ast::TyKind::Path(_, concrete_path) = &self.0.kind else { + unreachable!("expected Self type to be a path"); + }; + let mut new_segments = concrete_path.segments.clone(); + new_segments.extend_from_slice(rest); + path.segments = new_segments; + mut_visit::walk_ty(self, ty); + } } else { mut_visit::walk_ty(self, ty); } } + fn visit_expr(&mut self, expr: &mut ast::Expr) { + if let ast::ExprKind::Path(None, path) = &mut expr.kind + && let [first, rest @ ..] = &*path.segments + && *first == kw::SelfUpper + { + let ast::TyKind::Path(_, concrete_path) = &self.0.kind else { + unreachable!("expected Self type to be a path"); + }; + let mut new_segments = concrete_path.segments.clone(); + new_segments.extend_from_slice(rest); + path.segments = new_segments; + } + mut_visit::walk_expr(self, expr); + } } struct RespanGenericsVisitor(Span); diff --git a/tests/ui/deriving/deriving-all-codegen.rs b/tests/ui/deriving/deriving-all-codegen.rs index 74aa5ee9513d7..85ea71d6cd562 100644 --- a/tests/ui/deriving/deriving-all-codegen.rs +++ b/tests/ui/deriving/deriving-all-codegen.rs @@ -136,6 +136,14 @@ struct GenericLifetime<'a, T: Trait>(&'a T); #[repr(packed)] struct PackedGeneric(T, T::A, U); +// A struct with a field referencing an associated constant. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +struct AssociatedConst([u8; Self::LEN]); + +impl AssociatedConst { + const LEN: usize = 10; +} + // An empty enum. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] enum Enum0 {} diff --git a/tests/ui/deriving/deriving-all-codegen.stdout b/tests/ui/deriving/deriving-all-codegen.stdout index 94132373c74af..ef23b1b9bd1ca 100644 --- a/tests/ui/deriving/deriving-all-codegen.stdout +++ b/tests/ui/deriving/deriving-all-codegen.stdout @@ -1146,6 +1146,80 @@ impl AssociatedConst { + let _: ::core::clone::AssertParamIsClone<[u8; Self::LEN]>; + *self + } +} +#[automatically_derived] +impl ::core::marker::Copy for AssociatedConst { } +#[automatically_derived] +impl ::core::fmt::Debug for AssociatedConst { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::debug_tuple_field1_finish(f, + "AssociatedConst", &&self.0) + } +} +#[automatically_derived] +impl ::core::marker::StructuralPartialEq for AssociatedConst { } +#[automatically_derived] +impl ::core::cmp::PartialEq for AssociatedConst { + #[inline] + fn eq(&self, other: &AssociatedConst) -> bool { self.0 == other.0 } +} +#[automatically_derived] +impl ::core::cmp::Eq for AssociatedConst { } +const { + #[doc(hidden)] + #[coverage(off)] + #[inline] + const fn assert_fields_are_eq(_: *const AssociatedConst) { + let _: ::core::cmp::AssertParamIsEq<[u8; AssociatedConst::LEN]>; + } +} +#[automatically_derived] +impl ::core::cmp::PartialOrd for AssociatedConst { + #[inline] + fn partial_cmp(&self, other: &AssociatedConst) + -> ::core::option::Option<::core::cmp::Ordering> { + ::core::cmp::PartialOrd::partial_cmp(&self.0, &other.0) + } +} +#[automatically_derived] +impl ::core::cmp::Ord for AssociatedConst { + #[inline] + fn cmp(&self, other: &AssociatedConst) -> ::core::cmp::Ordering { + ::core::cmp::Ord::cmp(&self.0, &other.0) + } +} +#[automatically_derived] +impl ::core::hash::Hash for AssociatedConst { + #[inline] + fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) { + ::core::hash::Hash::hash(&self.0, state) + } +} +#[automatically_derived] +impl ::core::default::Default for AssociatedConst { + #[inline] + fn default() -> AssociatedConst { + AssociatedConst(::core::default::Default::default()) + } +} + +impl AssociatedConst { + const LEN: usize = 10; +} + // An empty enum. enum Enum0 {} #[automatically_derived] From a0ec53f2b2c82be4b0c9ef4bef2bd4f4ace47a36 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 3 Mar 2026 01:39:57 +0800 Subject: [PATCH 6/6] Do not respan generic parameter declarations --- .../rustc_builtin_macros/src/deriving/cmp/eq.rs | 6 +++++- .../multiple-types-with-same-name-and-derive.rs | 1 + ...ultiple-types-with-same-name-and-derive.stderr | 15 ++++++++++++++- tests/ui/stats/macro-stats.stderr | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs index 46c5280f817a9..4f4cca5fc1c5e 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs @@ -53,7 +53,11 @@ impl MutVisitor for RespanGenericsVisitor { fn visit_generics(&mut self, generics: &mut ast::Generics) { generics.where_clause.span = self.0.with_ctxt(generics.where_clause.span.ctxt()); generics.span = self.0.with_ctxt(generics.span.ctxt()); - mut_visit::walk_generics(self, generics); + // generic parameter declarations don't need to be respanned, so we visit the where clause + // predicates next + for predicate in &mut generics.where_clause.predicates { + self.visit_where_predicate(predicate); + } } fn visit_where_predicate(&mut self, predicate: &mut ast::WherePredicate) { predicate.span = self.0.with_ctxt(predicate.span.ctxt()); diff --git a/tests/ui/duplicate/multiple-types-with-same-name-and-derive.rs b/tests/ui/duplicate/multiple-types-with-same-name-and-derive.rs index e946c0c5350ed..233e5309f9e12 100644 --- a/tests/ui/duplicate/multiple-types-with-same-name-and-derive.rs +++ b/tests/ui/duplicate/multiple-types-with-same-name-and-derive.rs @@ -13,6 +13,7 @@ struct NotSM(T); //~^ ERROR: struct takes 0 generic arguments //~| ERROR: struct takes 0 generic arguments //~| ERROR: struct takes 0 generic arguments +//~| ERROR: struct takes 0 generic arguments //~| ERROR: the name `NotSM` is defined multiple times //~| ERROR: no field `0` diff --git a/tests/ui/duplicate/multiple-types-with-same-name-and-derive.stderr b/tests/ui/duplicate/multiple-types-with-same-name-and-derive.stderr index e80cf35d81e1d..09df35f331f56 100644 --- a/tests/ui/duplicate/multiple-types-with-same-name-and-derive.stderr +++ b/tests/ui/duplicate/multiple-types-with-same-name-and-derive.stderr @@ -21,6 +21,19 @@ note: struct defined here, with 0 generic parameters LL | struct NotSM; | ^^^^^ +error[E0107]: struct takes 0 generic arguments but 1 generic argument was supplied + --> $DIR/multiple-types-with-same-name-and-derive.rs:12:8 + | +LL | struct NotSM(T); + | ^^^^^ expected 0 generic arguments + | +note: struct defined here, with 0 generic parameters + --> $DIR/multiple-types-with-same-name-and-derive.rs:8:8 + | +LL | struct NotSM; + | ^^^^^ + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + error[E0107]: struct takes 0 generic arguments but 1 generic argument was supplied --> $DIR/multiple-types-with-same-name-and-derive.rs:10:10 | @@ -65,7 +78,7 @@ error[E0609]: no field `0` on type `&NotSM` LL | struct NotSM(T); | ^ unknown field -error: aborting due to 6 previous errors +error: aborting due to 7 previous errors Some errors have detailed explanations: E0107, E0428, E0609. For more information about an error, try `rustc --explain E0107`. diff --git a/tests/ui/stats/macro-stats.stderr b/tests/ui/stats/macro-stats.stderr index 7e9fb0ba69f94..a18c10bf1ee42 100644 --- a/tests/ui/stats/macro-stats.stderr +++ b/tests/ui/stats/macro-stats.stderr @@ -8,7 +8,7 @@ macro-stats #[derive(Hash)] 2 17 8.5 macro-stats q! 1 26 26.0 519 519.0 macro-stats #[derive(Ord)] 1 15 15.0 503 503.0 macro-stats #[derive(Default)] 2 16 8.0 403 201.5 -macro-stats #[derive(Eq)] 1 12 12.0 323 323.0 +macro-stats #[derive(Eq)] 1 12 12.0 335 335.0 macro-stats #[derive(Debug)] 1 8 8.0 277 277.0 macro-stats #[derive(PartialEq)] 1 9 9.0 267 267.0 macro-stats #[derive(Copy)] 1 2 2.0 61 61.0