Skip to content

Illegal Eq derive in an enum with Option<f64> #103157

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
stepantubanov opened this issue Oct 17, 2022 · 10 comments · Fixed by #103176
Closed

Illegal Eq derive in an enum with Option<f64> #103157

stepantubanov opened this issue Oct 17, 2022 · 10 comments · Fixed by #103176
Assignees
Labels
C-bug Category: This is a bug. P-high High priority regression-from-stable-to-stable Performance or correctness regression from one stable version to another.

Comments

@stepantubanov
Copy link

stepantubanov commented Oct 17, 2022

I tried this code:

#[derive(PartialEq, Eq)]
pub enum Value {
    Boolean(Option<bool>),
    Float(Option<f64>),
}

fn main() {
    let a = Value::Float(Some(f64::NAN));
    
    // Eq should always have a == a
    // https://doc.rust-lang.org/std/cmp/trait.Eq.html
    assert!(a == a);
}

I expected to see this happen: code fails to compile.
Instead, this happened: code compiles, and assertion fails.

Changing order of enum variants to make Float go first fixes the issue:

// error[E0277] the trait bound `f64: Eq` is not satisfied
#[derive(PartialEq, Eq)]
                    -- in this derive macro expansion
pub enum Value {
    Float(Option<f64>),
    Boolean(Option<bool>),
}

Meta

rustc --version --verbose:

rustc 1.64.0 (a55dd71d5 2022-09-19)
binary: rustc
commit-hash: a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52
commit-date: 2022-09-19
host: aarch64-apple-darwin
release: 1.64.0
LLVM version: 14.0.6

It's reproducible in Playground in Stable, Beta and Nightly.

@stepantubanov stepantubanov added the C-bug Category: This is a bug. label Oct 17, 2022
@fuzzypixelz
Copy link
Contributor

fuzzypixelz commented Oct 17, 2022

I have played around a bit with the example and got rustc to compile an even bigger class of bad programs:

pub struct Wrap<T> {
    inner: T,
}

impl<T> PartialEq for Wrap<T>
where
    T: PartialEq,
{
    fn eq(&self, other: &Self) -> bool {
        self.inner.eq(&other.inner)
    }
}

impl<T> Eq for Wrap<T> where T: Eq {}

#[derive(PartialEq, Eq)]
pub struct SuperWrap<T> {
    thing: Wrap<T>,
    float: Wrap<f64>
}

fn reflexivity<T>(a: &T) where T: Eq {
    assert!(a == a)
}

fn main() {
    let a = SuperWrap {
        thing: Wrap { inner: 42_u64 },
        float: Wrap { inner: f64::NAN },
    };
    reflexivity(&a);
}

You can reproduce this in the Playground.

As you can see, the compiler is tricked into thinking that SuperWrap<u32> implements Eq.
This works with sum and product types, as long as:

  • Wrap<T> is the first field (or variant)
  • T and f64 are "wrapped" with the same type, i.e Box, Vec or Option.

I have little experience with rustc internals. Maybe someone more experienced could direct us to where this magic happens?

@Noratrieb
Copy link
Member

Noratrieb commented Oct 17, 2022

Expanding the macro yields a

impl ::core::cmp::Eq for Value {
    #[inline]
    #[doc(hidden)]
    #[no_coverage]
    fn assert_receiver_is_total_eq(&self) -> () {
        let _: ::core::cmp::AssertParamIsEq<Option<bool>>;
    }
}

looks like it only asserts Eqness for the first field - whoops!

@rustbot claim

@Noratrieb
Copy link
Member

Gonna hand it over to you @fuzzypixelz as discussed on zulip
@rustbot release

@fuzzypixelz
Copy link
Contributor

@rustbot claim

@rustbot rustbot assigned fuzzypixelz and unassigned Noratrieb Oct 17, 2022
@SNCPlay42
Copy link
Contributor

On 1.63.0 this failed to compile, as expected (godbolt):

error[E0277]: the trait bound `f64: Eq` is not satisfied
 --> <source>:4:11
  |
1 | #[derive(PartialEq, Eq)]
  |                     -- in this derive macro expansion
...
4 |     Float(Option<f64>),
  |           ^^^^^^^^^^^ the trait `Eq` is not implemented for `f64`
  |
  = help: the following other types implement trait `Eq`:
            i128
            i16
            i32
            i64
            i8
            isize
            u128
            u16
          and 4 others
  = note: required because of the requirements on the impl of `Eq` for `Option<f64>`
note: required by a bound in `AssertParamIsEq`
  = note: this error originates in the derive macro `Eq` (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to previous error

@rustbot label regression-from-stable-to-stable

@rustbot rustbot added regression-from-stable-to-stable Performance or correctness regression from one stable version to another. I-prioritize Issue: Indicates that prioritization has been requested for this issue. labels Oct 17, 2022
@Noratrieb
Copy link
Member

this was recently introduced when @nnethercote was doing derive optimizations.

@nnethercote nnethercote self-assigned this Oct 17, 2022
@nnethercote
Copy link
Contributor

I'll take a look today, thanks for the report.

@Noratrieb
Copy link
Member

Don't worry, we've found the issue already, just wanted to let you know.

@nnethercote
Copy link
Contributor

See #103176 for the fix.

@apiraino
Copy link
Contributor

WG-prioritization assigning priority a posteriori (Zulip discussion) mostly to help tracking the fix progress.

@rustbot label -I-prioritize +P-high

@rustbot rustbot added P-high High priority and removed I-prioritize Issue: Indicates that prioritization has been requested for this issue. labels Oct 18, 2022
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Oct 19, 2022
…ath, r=spastorino

Fix `TyKind::is_simple_path`

Fixes rust-lang#103157.

r? `@spastorino`
@bors bors closed this as completed in 9a23f60 Oct 19, 2022
cuviper pushed a commit to cuviper/rust that referenced this issue Oct 20, 2022
PR rust-lang#98758 introduced code to avoid redundant assertions in derived code
like this:
```
let _: ::core::clone::AssertParamIsClone<u32>;
let _: ::core::clone::AssertParamIsClone<u32>;
```
But the predicate `is_simple_path` introduced as part of this failed to
account for generic arguments. Therefore the deriving code erroneously
considers types like `Option<bool>` and `Option<f32>` to be the same.

This commit fixes `is_simple_path`.

Fixes rust-lang#103157.

(cherry picked from commit 9a23f60)
lyming2007 pushed a commit to lyming2007/rust that referenced this issue Oct 21, 2022
PR rust-lang#98758 introduced code to avoid redundant assertions in derived code
like this:
```
let _: ::core::clone::AssertParamIsClone<u32>;
let _: ::core::clone::AssertParamIsClone<u32>;
```
But the predicate `is_simple_path` introduced as part of this failed to
account for generic arguments. Therefore the deriving code erroneously
considers types like `Option<bool>` and `Option<f32>` to be the same.

This commit fixes `is_simple_path`.

Fixes rust-lang#103157.
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
C-bug Category: This is a bug. P-high High priority regression-from-stable-to-stable Performance or correctness regression from one stable version to another.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants