Skip to content

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

Open
KiChjang wants to merge 6 commits intorust-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 intorust-lang:mainfrom
KiChjang:derive-eq-const-assertion

Conversation

@KiChjang
Copy link
Contributor

@KiChjang KiChjang commented Feb 26, 2026

@rustbot
Copy link
Collaborator

rustbot commented Feb 26, 2026

Changes to the code generated for builtin derived traits.

cc @nnethercote

@rustbot rustbot 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.

@theemathas
Copy link
Contributor

A test case that seems like might break with this:

trait Trait<T> {}

#[derive(PartialEq, Eq)]
struct Thing<T: Trait<Self>>(T);

@theemathas
Copy link
Contributor

A less exotic test case:

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

@Kivooeo
Copy link
Member

Kivooeo commented Feb 26, 2026

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

r? nnethercote

@rustbot rustbot assigned nnethercote and unassigned cyrgani Feb 26, 2026
@KiChjang
Copy link
Contributor Author

KiChjang commented Feb 26, 2026

A less exotic test case:

#[derive(PartialEq, Eq)]
struct Thing(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<...> {
    const fn assert_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
Contributor Author

Looks like CI is passing now, r? @nnethercote

@rustbot
Copy link
Collaborator

rustbot commented Feb 28, 2026

Requested reviewer is already assigned to this pull request.

Please choose another assignee.

@KiChjang KiChjang requested a review from nnethercote February 28, 2026 00:45
@cyrgani
Copy link
Contributor

cyrgani commented Feb 28, 2026

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

@cyrgani
Copy link
Contributor

cyrgani commented Feb 28, 2026

Could you also add a test that something like

fn main() {
    X::assert_fields_are_eq();
}
#[derive(PartialEq, Eq)]
struct X(u8);

will not compile?

Copy link
Contributor

@nnethercote nnethercote left a comment

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

@nnethercote nnethercote 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
Contributor Author

KiChjang commented Mar 1, 2026

Could you also add a test that something like

fn main() {
    X::assert_fields_are_eq();
}
#[derive(PartialEq, Eq)]
struct X(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
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-bors

This comment has been minimized.

Copy link
Contributor

@madsmtm madsmtm left a comment

Choose a reason for hiding this comment

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

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

#[derive(PartialEq, Eq)]
pub struct A<'a, T> {
    field1: &'a [T],
    field2: Option<Box<Self>>,
}

// Generates

const _: () = {
    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> PartialEq for __AssertIsEq<'a, T> {
        #[inline]
        fn eq(&self, other: &Self) -> bool { unimplemented!() }
    }
    impl<'a, T: core::eq::Eq> Eq for __AssertIsEq<'a, T> {}
};

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

View changes since this review

@KiChjang KiChjang force-pushed the derive-eq-const-assertion branch from 32d0739 to c9cc7ea Compare March 2, 2026 11:29
@rustbot
Copy link
Collaborator

rustbot commented Mar 2, 2026

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@KiChjang KiChjang force-pushed the derive-eq-const-assertion branch from c9cc7ea to b5a6295 Compare March 2, 2026 11:35
@KiChjang
Copy link
Contributor Author

KiChjang commented Mar 2, 2026

@bors try @rust-timer queue

@rust-bors
Copy link
Contributor

rust-bors bot commented Mar 2, 2026

@KiChjang: 🔑 Insufficient privileges: not in try users

@rust-timer
Copy link
Collaborator

Insufficient permissions to issue commands to rust-timer.

@KiChjang
Copy link
Contributor Author

KiChjang commented Mar 2, 2026

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

#[derive(PartialEq, Eq)]
pub struct A<'a, T> {
    field1: &'a [T],
    field2: Option<Box<Self>>,
}

// Generates

const _: () = {
    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> PartialEq for __AssertIsEq<'a, T> {
        #[inline]
        fn eq(&self, other: &Self) -> bool { unimplemented!() }
    }
    impl<'a, T: core::eq::Eq> Eq for __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
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
Contributor Author

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
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.

@rust-log-analyzer

This comment has been minimized.

@KiChjang KiChjang force-pushed the derive-eq-const-assertion branch from c694584 to 602529d Compare March 2, 2026 12:57
@KiChjang KiChjang force-pushed the derive-eq-const-assertion branch from 602529d to c70ca54 Compare March 2, 2026 13:13
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@KiChjang KiChjang requested review from madsmtm and nnethercote March 3, 2026 08:04
@rustbot rustbot 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 cyrgani March 3, 2026 08:04
mut_visit::walk_ty(self, ty);
}
}
fn visit_expr(&mut self, expr: &mut ast::Expr) {
Copy link
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
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
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
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
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
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
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?

Probably to work with ?Sized types?

@nnethercote nnethercote 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
Copy link
Contributor

@madsmtm madsmtm left a comment

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::EqHelperTrait for $ty {
    #[inline]
    fn assert_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.
Copy link
Contributor

@madsmtm madsmtm Mar 9, 2026

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)]
struct SelfFromMacro {
    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).

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

Labels

S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant 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