Skip to content

Change derive macro for Eq to not impl Eq trait fn - #153125

Open
KiChjang wants to merge 6 commits into
rust-lang:mainfrom
KiChjang:derive-eq-const-assertion
Open

Change derive macro for Eq to not impl Eq trait fn#153125
KiChjang wants to merge 6 commits into
rust-lang:mainfrom
KiChjang:derive-eq-const-assertion

Conversation

@KiChjang

@KiChjangKiChjang commented Feb 26, 2026

Copy link
Copy Markdown
Contributor

@rustbot

Copy link
Copy Markdown
Collaborator

Changes to the code generated for builtin derived traits.

cc @nnethercote

@rustbotrustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Feb 26, 2026
@rust-log-analyzer

This comment has been minimized.

Comment threadtests/ui/deriving/deriving-all-codegen.stdout Outdated
@theemathas

Copy link
Copy Markdown
Contributor

A test case that seems like might break with this:

traitTrait<T>{}#[derive(PartialEq,Eq)]structThing<T:Trait<Self>>(T);

@theemathas

Copy link
Copy Markdown
Contributor

A less exotic test case:

#[derive(PartialEq,Eq)]structThing(Option<Box<Self>>);

@Kivooeo

Copy link
Copy Markdown
Member

Nick, I would reassign to you (feel free to reroll) since @cyrgani is a triage member and can't approve compiler changes

r? nnethercote

@rustbotrustbot assigned nnethercote and unassigned cyrganiFeb 26, 2026
@KiChjang

KiChjang commented Feb 26, 2026

Copy link
Copy Markdown
ContributorAuthor

A less exotic test case:

#[derive(PartialEq,Eq)]structThing(Option<Box<Self>>);

Gah, because we're now generating a const item instead of a trait impl block, we'd need to rewrite the Self reference to its concrete type, but this is a bit tough as it would require us to supply the correct generic params to the Self concrete type if it has any.

A better solution now would be to emit the following:

impl<...> Type<...> {constfnassert_fields_are_eq(){let _:::core::cmp::AssertParamIsEq<Option<Box<Self>>>;}}

This would side-step the issue of rewriting Self into its concrete type.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@KiChjang

Copy link
Copy Markdown
ContributorAuthor

Looks like CI is passing now, r? @nnethercote

@rustbot

Copy link
Copy Markdown
Collaborator

Requested reviewer is already assigned to this pull request.

Please choose another assignee.

@cyrgani

Copy link
Copy Markdown
Contributor

You should be able to delete the fn assert_fields_are_eq(&self) {} method from the Eq trait again now.

@cyrgani

Copy link
Copy Markdown
Contributor

Could you also add a test that something like

fnmain(){X::assert_fields_are_eq();}#[derive(PartialEq,Eq)]structX(u8);

will not compile?

Comment threadcompiler/rustc_builtin_macros/src/deriving/cmp/eq.rs Outdated

@nnethercotennethercote left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now completely different to the original idea of using a const. I'm no lang expert, but injecting X::assert_fields_are_eq doesn't seem acceptable to me.

Beyond that, I've only skimmed the PR so far. I think the 8 commits should be squashed down to 1 or 2. I'm also surprised at how much extra code is required in eq.rs to generate a slight variation on what is currently generated.

View changes since this review

@nnethercotennethercote added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 1, 2026
@KiChjang

Copy link
Copy Markdown
ContributorAuthor

Could you also add a test that something like

fnmain(){X::assert_fields_are_eq();}#[derive(PartialEq,Eq)]structX(u8);

will not compile?

This probably would still compile, since what I essentially did was move the trait method to be under an inherent impl instead, which is probably why it isn't desirable.

Given so, I'd probably need to revert to the original idea of using a const item block, however the const fn would still have to stay the same because I'd still need to somehow bring all lifetime/type parameters into scope, and if we don't use an impl, then a fn is the minimal vehicle to do so.

@nnethercote

Copy link
Copy Markdown
Contributor

This probably would still compile, since what I essentially did was move the trait method to be under an inherent impl instead, which is probably why it isn't desirable.

That's right. Currently we have a hidden method in Eq, which users can (intentionally or unintentionally) interact with. With an inherent method that exposure is increased. With an anonymous const it becomes impossible for users to interact with it.

@rust-timer

Copy link
Copy Markdown
Collaborator

Insufficient permissions to issue commands to rust-timer.

@KiChjang

Copy link
Copy Markdown
ContributorAuthor

Did you explore what I wrote briefly in #149978 (comment), to do something like:

#[derive(PartialEq,Eq)]pubstructA<'a,T>{field1:&'a[T],field2:Option<Box<Self>>,}// Generatesconst _:() = {struct__AssertIsEq<'a,T: core::eq::Eq>{field1: core::eq::AssertParamIsEq<&'a[T]>,field2: core::eq::AssertParamIsEq<Option<Box<Self>>>,}impl<'a,T: core::eq::PartialEq>PartialEqfor__AssertIsEq<'a,T>{#[inline]fneq(&self,other:&Self) -> bool{unimplemented!()}}impl<'a,T: core::eq::Eq>Eqfor__AssertIsEq<'a,T>{}};

Also, this is apparently perf-sensitive, please remember to do a perf-run before merging.

View changes since this review

This seems to emit quite a lot of boilerplate for the purpose of making sure that AssertParamIsEq is being properly typechecked, so I opted for a const fn approach. I may be wrong though -- let's use benchmarking to test out whether I'm putting in more overhead or not.

Would appreciate some help in putting this on the timer queue though as I don't have sufficient permissions.

impl ::core::cmp::Eq for PackedPoint {
#[inline]
impl ::core::cmp::Eq for PackedPoint { }
const {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't even valid syntax, it should be const _: () = {.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the code that gets generated when I tell the compiler to emit a ast::ItemKind::ConstBlock:

/// A module-level const block.
/// Equivalent to `const _: () = const { ... };`.
///
/// E.g., `const { assert!(true) }`.
ConstBlock(ConstBlockItem),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird. Maybe a pretty printing issue? Would be good to understand what's happening here.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, so it's apparently a feature gated behind const_block_items in #149226. I'm also curious as to why it's necessary to generate a new ItemKind specifically for it when it's equivalent to const _: () = const { ... } in all cases and purposes. Will change to use the const item instead.

@rust-log-analyzer

This comment has been minimized.

@KiChjang
KiChjangforce-pushed the derive-eq-const-assertion branch from c694584 to 602529dCompareMarch 2, 2026 12:57
@KiChjang
KiChjangforce-pushed the derive-eq-const-assertion branch from 602529d to c70ca54CompareMarch 2, 2026 13:13
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rustbotrustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Mar 3, 2026
@KiChjang
KiChjang requested a review from cyrganiMarch 3, 2026 08:04
mut_visit::walk_ty(self, ty);
}
}
fn visit_expr(&mut self, expr: &mut ast::Expr) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put blank lines between the methods, here and elsewhere.

use crate::deriving::generic::*;
use crate::deriving::path_std;

struct ReplaceSelfTyVisitor(Box<ast::Ty>);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blank line after the struct, and likewise below.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the three visitors need a brief comment explaining what they are doing and why.

span,
};

cx.item(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A brief example of the kind of code being generated would be helpful here.

#[coverage(off)]
fn assert_fields_are_eq(&self) {
#[inline]
const fn assert_fields_are_eq(_: *const Point) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume the parameter to assert_fields_are_eq is necessary in the generic case to make the trait bounds work? But could the parameter be avoided for non-generic functions like this one?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, why is the parameter (when present) *const T? Could it just be T?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, why is the parameter (when present) *const T? Could it just be T?

Probably to work with ?Sized types?

@nnethercotennethercote added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 7, 2026

@madsmtmmadsmtm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option to make it work properly with Self, while not doing an inherent method:

Add an extra trait with the method on it instead.

// `#[doc(hidden)]` trait (and unstable if we can?)impl::core::cmp::EqHelperTraitfor $ty {#[inline]fnassert_fields_are_eq(){let _:::core::cmp::AssertParamIsEq<$param1>;let _:::core::cmp::AssertParamIsEq<$param2>;// ...}}

View changes since this review

const LEN: usize = 10;
}

// An empty enum.

@madsmtmmadsmtmMar 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a test case like this:

macro_rules! get_self {() => {Self};}#[derive(Clone,Debug,PartialEq,Eq,PartialOrd,Ord,Hash,Default)]structSelfFromMacro{this:Option<Box<get_self!()>>}

(I'm commenting this specifically because I think that any solution that matches on the token Self isn't gonna work).

@nnethercote

Copy link
Copy Markdown
Contributor

Another option to make it work properly with Self, while not doing an inherent method:

Add an extra trait with the method on it instead.

The whole point of this PR is to eliminate a hidden name. Let's not introduce a new hidden name, even if it is unstable.

@madsmtm

Copy link
Copy Markdown
Member

Another option to make it work properly with Self, while not doing an inherent method:
Add an extra trait with the method on it instead.

The whole point of this PR is to eliminate a hidden name. Let's not introduce a new hidden name, even if it is unstable.

Idk., I think there's a difference between a hidden name on Eq, and a hidden type somewhere else. But I agree that if we can find some other solution, then that's preferable.

Details

Ideally we'd be able to do this:

structFoo;implFoo{const _:() = {// reference `Self` here.};}

But that's probably a bit far off, see rust-lang/rfcs#3527.

@KiChjang

KiChjang commented Mar 11, 2026

Copy link
Copy Markdown
ContributorAuthor

Another option to make it work properly with Self, while not doing an inherent method:

Add an extra trait with the method on it instead.

// `#[doc(hidden)]` trait (and unstable if we can?)impl::core::cmp::EqHelperTraitfor $ty {#[inline]fnassert_fields_are_eq(){let _:::core::cmp::AssertParamIsEq<$param1>;let _:::core::cmp::AssertParamIsEq<$param2>;// ...}}

View changes since this review

This was what I had in an earlier revision, but it was not desirable as mentioned by @nnethercote since it still introduces a new name.

I am thinking however that maybe we could instead make all assert bindings be associated const bindings instead? E.g.

structFoo(u32);implFoo{const _:::core::cmp::AssertParamIsEq<u32> = ::core::cmp::AssertParamIsEq{_field:PhantomData};}

@nnethercote

Copy link
Copy Markdown
Contributor

I am thinking however that maybe we could instead make all assert bindings be associated const bindings instead? E.g.

structFoo(u32);implFoo{const _:::core::cmp::AssertParamIsEq<u32> = ::core::cmp::AssertParamIsEq{_field:PhantomData};}

Sounds promising! It's anonymous, and uses impl Foo so you can reference Self which makes things simpler. It's a shame the ::core::cmp::AssertParamIsEq is repeated. I guess that's because consts must be assigned, unlike lets?

(Thanks for your patience on this, BTW. It has turned out more complicated than it first appeared.)

@theemathas

Copy link
Copy Markdown
Contributor

@KiChjang@nnethercote That won't work. It seems like associated consts must have a proper name:

structFoo;implFoo{const _:i32 = 1;}
error: `const` items in this context need a name
--> src/lib.rs:3:11
|
3 | const _: i32 = 1;
| ^ `_` is not a valid name for this `const` item

@KiChjang

Copy link
Copy Markdown
ContributorAuthor

Ah crud, I experimented around and consulted the Rust lang reference a couple of times, and it looks like at this point in time, Rust does not allow ANY associated items in an impl block to be anonymous (i.e. underscore).

This means we can't avoid introducing a new name if we go the impl block route -- we MUST use a const item block instead, but that then makes the Self type unparseable if it is generated from a macro.

I now wonder if there's a way for us to tell the compiler to relax some restrictions around naming for associated items in impl blocks if the code in question is generated from an compiler builtin macro...

But if we are going down this route, I'm also wondering whether it is ok to special case this for the aristocratic and special builtin derive macros? Maybe the better way is to instead open an RFC allowing for anonymous associated items?

@madsmtm

Copy link
Copy Markdown
Member

But if we are going down this route, I'm also wondering whether it is ok to special case this for the aristocratic and special builtin derive macros? Maybe the better way is to instead open an RFC allowing for anonymous associated items?

One exists: rust-lang/rfcs#3527.

@rust-bors

rust-borsBot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

☔ The latest upstream changes (presumably #155796) made this pull request unmergeable. Please resolve the merge conflicts.

@KiChjang

Copy link
Copy Markdown
ContributorAuthor

Now waiting for #158944 to resolve so that we can make use of the language feature for this task.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-authorStatus: This is awaiting some action (such as code changes or more information) from the author.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

update #[derive(Eq)] to not need assert_receiver_is_total_eq

9 participants

@KiChjang@rustbot@rust-log-analyzer@theemathas@Kivooeo@cyrgani@nnethercote@rust-timer@madsmtm