Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

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

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in 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