From da5b844c63f6f4b29a9f6921fc3256c2674d677a Mon Sep 17 00:00:00 2001 From: "adam.hutchison" Date: Fri, 9 Nov 2018 12:52:02 -0700 Subject: [PATCH] Fix enum hash equality Protobuf::Enum delegates methods to Fixnum, which has a custom hash equality method (`eql?`). This causes enum values to be equivalent when using `==`, `===`, `equals?`, but not `eql?`**: 2.3.7 :002 > Test::EnumTestType::ZERO.eql?(::Test::EnumTestType::ZERO) => false However, they are equilvalent to their tag value: 2.3.7 :002 > Test::EnumTestType::ZERO.eql?(::Test::EnumTestType::ZERO.tag) => true Use the hash equality implementation from Object#eql?, which is equivalent to == instead. **The implementation changed in Ruby 2.5, so this only affects Ruby versions less than v2.5. --- lib/protobuf/enum.rb | 19 +++++++++++++++++++ spec/lib/protobuf/enum_spec.rb | 14 ++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/lib/protobuf/enum.rb b/lib/protobuf/enum.rb index a50ebc54..f9d5d4de 100644 --- a/lib/protobuf/enum.rb +++ b/lib/protobuf/enum.rb @@ -271,6 +271,25 @@ def class tag.class end + # Protobuf::Enum delegates methods to Fixnum, which has a custom hash equality method (`eql?`) + # This causes enum values to be equivalent when using `==`, `===`, `equals?`, but not `eql?`**: + # + # 2.3.7 :002 > ::Test::EnumTestType::ZERO.eql?(::Test::EnumTestType::ZERO) + # => false + # + # However, they are equilvalent to their tag value: + # + # 2.3.7 :002 > ::Test::EnumTestType::ZERO.eql?(::Test::EnumTestType::ZERO.tag) + # => true + # + # **The implementation changed in Ruby 2.5, so this only affects Ruby versions less than v2.4. + # + # Use the hash equality implementation from Object#eql?, which is equivalent to == instead. + # + def eql?(other) + self == other + end + def inspect "\#" end diff --git a/spec/lib/protobuf/enum_spec.rb b/spec/lib/protobuf/enum_spec.rb index a2bebb8d..49b9602b 100644 --- a/spec/lib/protobuf/enum_spec.rb +++ b/spec/lib/protobuf/enum_spec.rb @@ -268,6 +268,20 @@ specify { subject.try { |yielded| expect(yielded).to eq(subject) } } end + describe '#eql?' do + it "is equal to itself" do + expect(::Test::EnumTestType::ZERO.eql?(::Test::EnumTestType::ZERO)).to be(true) + end + + it "is equal to it's tag" do + expect(::Test::EnumTestType::ZERO.eql?(::Test::EnumTestType::ZERO.tag)).to be(true) + end + + it "is not equal to it's name" do + expect(::Test::EnumTestType::ZERO.eql?(::Test::EnumTestType::ZERO.name)).to be(false) + end + end + context 'when coercing from enum' do subject { Test::StatusType::PENDING } it { is_expected.to eq(0) }