FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

array.c: take an `Array` element for equal to itself in `#==` by takumin · Pull Request #7359 · mruby/mruby · GitHub

/ mruby Public

array.c: take an Array element for equal to itself in #== - #7359

Open
takumin wants to merge 1 commit into
mruby:masterfrom
takumin:array-eq-mrb-equal
Open

array.c: take an Array element for equal to itself in #==#7359
takumin wants to merge 1 commit into
mruby:masterfrom
takumin:array-eq-mrb-equal

Conversation

takumin commented Aug 25, 2026
edited by coderabbitai Bot
Loading

Copy link
Copy Markdown
Contributor

Summary

a == b is answered by OP_EQ, which takes two values for equal where they are the same object and dispatches == only after. mrb_equal() is that same test made from C, and it is what Array#index and #delete search a pair with. Array#== dispatched == at each element instead, going around it, so an object was equal to itself outside an array and not inside one.

Changes

File What
src/array.c mrb_ary_eq() compares its elements with mrb_equal() rather than dispatching == at each one
test/t/array.rb Array#== over an object whose == answers false to everything

The one comparison that asks nothing of the object

mrb_equal() looks for the object first and asks == after. Every other search an array makes reads a pair that way, and OP_EQ reads one that way before it dispatches anything; mrb_ary_eq() was the exception.

class Never
  def ==(other); false; end
end
n = Never.new

n == n         # true, OP_EQ having answered it from the object
[n].index(n)   # 0
[n] == [n]     # before false, CRuby true

CRuby compares elements here the same way it compares them in #index, and so does this now. An element whose == answers as == should is found by == as before, one comparison earlier.

Behaviour

n is an object whose == answers false to everything; a and b are two NaNs built at run time as z / z from z = [0.0][0], so that nothing folds them into a single literal. Bold marks a row that differs from CRuby.

expression master this PR CRuby
[n] == [n] false true true
[1.0, a] == [1.0, a] false true true
[1.0, a] == [1.0, b] false true false
[a].index(b) 0 0 nil

The last two rows are one answer. A NaN is equal to no value, its own operand included, so == can never find one and only the object is left to go by; where a boxing keeps a Float in the value, every NaN is normalized to one representation, so two NaNs made apart are one object. #index has been saying so on master all along, and #== says it now too. Making a NaN an object of its own is a change to what a Float is rather than to how an array compares, and it is not in this PR.

Speed

x = (1..200).to_a
y = (1..200).to_a
i = 0
while i < 20000 do x == y; i += 1 end

Best of 11 interleaved runs against master, with a master-vs-master control to read the noise by, and the instruction counts callgrind reads beside them:

benchmark master this PR control Ir
Array#== over 200 elements, 20000 times 85.6 ms 11.0 ms (0.13x) +0.4% 1,441,020,104 -> 180,820,104

mrb_equal() in place of a method dispatch at each of the 200 elements is what #index already paid for the same walk.

Size

bin/mruby .text for every build in build_config/ci/gcc-clang.rb:

build master this PR delta
ascii-ctype 1,293,718 1,293,638 -80
bintest 1,307,110 1,307,030 -80
byte-string 1,271,494 1,271,414 -80
cxx_abi 1,334,041 1,333,993 -48
full-debug (-O0) 1,913,478 1,913,462 -16

The whole of it is src/array.o, which shrinks by 80 bytes: a call to mrb_funcall_argv1() with a symbol and an argument array goes away, and mrb_equal() takes its place.

Testing

rake test passes for the default build, for build_config/host-nofloat.rb, for every build in build_config/ci/gcc-clang.rb including the C++ ABI one, and for a MRB_NO_BOXING build of the full-core gembox, with no compiler warnings beyond the one -Wmaybe-uninitialized about eq in hash.c that master already emits in a MRB_NO_BOXING build. The last of those is there because the rows about a NaN in the table above are ones a boxing decides.

Environment

Machine, toolchain and the compile lines these numbers were taken with
OS Ubuntu 24.04.4 LTS
Kernel Linux 7.0.0-30-generic x86_64
CPU AMD Ryzen 9 5950X (16 cores)
C compiler gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
binutils GNU ld (GNU Binutils) 2.47.20260726
valgrind valgrind-3.27.1 (callgrind)
CRuby 4.0.6 (2026-07-14 revision 03b6d3f889)

The wall clock and instruction counts were taken with the default build:

gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DMRB_USE_COMPLEX -DMRB_USE_BIGINT -DMRB_USE_DEBUG_HOOK

The ## Size table was taken with build_config/ci/gcc-clang.rb:

bintest      gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -DMRB_GC_FIXED_ARENA -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER -DMRB_USE_DEBUG_HOOK
ascii-ctype  gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -DMRB_USE_ASCII_CTYPE -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER
byte-string  gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER
cxx_abi      gcc -g -O3 -Wall -Wundef -Wwrite-strings -x c++ -std=gnu++03 -DMRB_GC_FIXED_ARENA -DMRB_USE_CXX_EXCEPTION -DMRB_USE_CXX_ABI -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER
full-debug   gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -g3 -O0 -DMRB_GC_STRESS -DMRB_USE_DEBUG_HOOK -DMRB_DEBUG -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER

cxx_abi compiles mruby as C++ with gcc -x c++ -std=gnu++03; g++ only links. full-debug is the one build at -O0, enable_debug putting -g3 -O0 after the toolchain default of -g -O3.

Summary by CodeRabbit

  • Bug Fixes

    • Updated array equality to consistently recognize an object as equal to itself, even when its custom equality method returns false.
    • Aligned Array#== behavior with standard Ruby behavior and related array operations.
  • Tests

    • Added regression coverage for array comparisons involving custom equality methods.

`a == b` is answered by `OP_EQ`, which takes two values for equal where they
are the same object and dispatches `==` only after. `mrb_equal()` is that same
test made from C, and it is what `Array#index` and `#delete` search a pair
with. `Array#==` dispatched `==` per element instead, going around it, so an
object whose `==` answers false to everything was not equal to itself inside an
array while it was equal to itself outside one.

```ruby
class Never
  def ==(other); false; end
end
n = Never.new

[n].index(n)   # 0
[n] == [n]     # before false, CRuby true
```

CRuby compares the elements here the same way `#index` does, and so does this
now.
takumin requested a review from matz as a code owner August 25, 2026 14:45
github-actions Bot added the core label Aug 25, 2026

coderabbitai Bot commented Aug 25, 2026
edited
Loading

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info ⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 18fbca10-1022-4df9-ab4c-e24a575db076

📥 Commits

Reviewing files that changed from the base of the PR and between 44ab336 and 8b0c2ef.

📒 Files selected for processing (2)
  • src/array.c
  • test/t/array.rb

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

Array#== now compares elements with mrb_equal(). Tests cover identical objects whose explicit == method returns false, including Array#index.

Changes

Array equality behavior

Layer / File(s) Summary
Element equality and regression coverage
src/array.c, test/t/array.rb
mrb_ary_eq now uses mrb_equal() for element comparisons. Regression tests verify self-equality in Array#== and Array#index when explicit == returns false.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 8b0c2

This localized change aligns Array#== with the existing identity-first equality behavior used by other array searches and adds coverage; no actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: matz, leviongit

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: Array#== now treats an array element as equal to itself. The wording is somewhat awkward, but it is specific and related to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant


Back | FazBrowse Home | New Git URL