Project

General

Profile

Actions

Bug #14437

closed

Integer == doesn't work with coerce since 2.4 (and != since 1.9). Should it?

Added by taw (Tomasz Wegrzanowski) about 6 years ago. Updated over 3 years ago.

Status:
Closed
Assignee:
-
Target version:
-
[ruby-core:85366]
Tags:

Description

Here's extracted test sample:

class Item
  def initialize(value)
    @value = value
  end

  def coerce(other)
    [Item.new(other), self]
  end

  def ==(other)
    Item.new("#{inspect} == #{other.inspect}")
  end

  def !=(other)
    Item.new("#{inspect} != #{other.inspect}")
  end

  def inspect
    "(#{@value})"
  end
end

a = Item.new("a")
p [RUBY_VERSION, 42 == a, 42 != a, a == 42, a == a, a != 42, a != a]

I'd expect it to print: ["2.x.x", ((a) == 42), ((a) != 42), ((a) == 42), ((a) == (a)), ((a) != 42), ((a) != (a))]

What happens instead is:

["1.9.3", ((a) == 42), false, ((a) == 42), ((a) == (a)), ((a) != 42), ((a) != (a))]
["2.2.0", ((a) == 42), false, ((a) == 42), ((a) == (a)), ((a) != 42), ((a) != (a))]
["2.3.3", ((a) == 42), false, ((a) == 42), ((a) == (a)), ((a) != 42), ((a) != (a))]
["2.4.1", true, false, ((a) == 42), ((a) == (a)), ((a) != 42), ((a) != (a))]
["2.5.0", true, false, ((a) == 42), ((a) == (a)), ((a) != 42), ((a) != (a))]

So != never used coerce, and now == doesn't use coerce either.
Using Bignum value instead of 42 in this example breaks it even pre-2.4.

So, the question is:

  • is using coerce like this not supported, and it was just an accident that it used to work? (and I should redefine Integer#== and Integer#!=)
  • or is it meant to work, and it's a ruby bug?

For context, it's a problem for z3 gem, which builds big mathematical expressions like Z3.Int("a")+Z3.Int("b") == 4
and then uses Microsoft Z3 solver to solve them. Not being able to use == / != because of this issue would really reduce its usability.

Using coerce this way works just fine with +, -, *, >=, etc., it's just == and != which don't work.

Updated by nobu (Nobuyoshi Nakada) about 6 years ago

  • Description updated (diff)

== and != have never called coerce.
Only +, -, <, > and so on.

Updated by taw (Tomasz Wegrzanowski) about 6 years ago

nobu (Nobuyoshi Nakada) wrote:

== and != have never called coerce.
Only +, -, <, > and so on.

You're right about coerce not being involved here, my theory was wrong.

Looking at the code fix_equal(x,y) when y was of non-core class used to call num_equal,(x,y) which then did reverse call for y==x,
so it was possible to have custom classes, which have meaningful interaction with 42 == obj

It still does this, except now it casts the result to true/false.

static VALUE
num_equal(VALUE x, VALUE y)
{
    VALUE result;
    if (x == y) return Qtrue;
    result = num_funcall1(y, id_eq, x);
    if (RTEST(result)) return Qtrue;
    return Qfalse;
}

I can see it comes from this change https://github.com/ruby/ruby/commit/ffa371d9aa1af1f22c41063add9af3e4922f2f12

Why was this changed? It's messing up with my use case here, and there's a bunch of other ruby code (like rspec)
which make == return something else than true/false.

Updated by jeremyevans0 (Jeremy Evans) over 3 years ago

  • Status changed from Open to Closed

taw (Tomasz Wegrzanowski) wrote in #note-2:

Looking at the code fix_equal(x,y) when y was of non-core class used to call num_equal,(x,y) which then did reverse call for y==x,
so it was possible to have custom classes, which have meaningful interaction with 42 == obj

It still does this, except now it casts the result to true/false.

static VALUE
num_equal(VALUE x, VALUE y)
{
    VALUE result;
    if (x == y) return Qtrue;
    result = num_funcall1(y, id_eq, x);
    if (RTEST(result)) return Qtrue;
    return Qfalse;
}

I can see it comes from this change https://github.com/ruby/ruby/commit/ffa371d9aa1af1f22c41063add9af3e4922f2f12

Why was this changed? It's messing up with my use case here, and there's a bunch of other ruby code (like rspec)
which make == return something else than true/false.

The Integer#== method is documented to only return true or false (see https://docs.ruby-lang.org/en/master/Integer.html#method-i-3D-3D), so returning other values would be considered a bug. Code that wants == to return a custom value should now use obj == integer instead of integer == obj.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0