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.

@cyrgani
Copy link
Contributor

cyrgani commented Mar 1, 2026

I've built the compiler with this PR's changes applied and the code I posted above still (correctly) does not compile.(probably due to some hygiene rules that make the function name unaccessible outside the #[derive(Eq)] invocation). So this does not seem to be a problem?

@KiChjang
Copy link
Contributor Author

KiChjang commented Mar 1, 2026

Ah, it's likely due to visibility issues -- it's not accessible publicly, but if you call it within an impl block for the type, then it would compile. Example:

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

I have local changes that revert the emitted code to be using a const impl block with a const fn inside of it, however some UI test expectations need to be updated as a result of that change. Will push as soon as I get around to fix them.

@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),

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

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