From 3e00a201fa6b0a75ecdb0f2667fa8a17f4f8fc6a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Nov 2026 13:51:02 +0000 Subject: [PATCH] BasicObject#equal? is identity, its own method apart from #== equal? sat on both BasicObject#== overloads, so for a Ruby object it went through IRubyObject.BaseEquals - RubyObject.Equals, which calls the Ruby ==, or a CLR base's Equals. A String subclass was equal? to another with the same contents, and a == that starts with equal?(other), as Set#== does, could recurse into itself. equal? now compares references, and value equality for boxed CLR value types (Integer, Float, true/false), which are CRuby's immediates. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KPwZf7PaEzcpcESWtdCoKs --- Src/IronRuby.Tests/RubyTests.cs | 1 + Src/IronRuby.Tests/Runtime/MiscTests.cs | 39 ++++++++++++++++++++++++ Src/Libraries/Builtins/BasicObjectOps.cs | 19 +++++++++--- Src/Libraries/Initializers.Generated.cs | 5 ++- 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/Src/IronRuby.Tests/RubyTests.cs b/Src/IronRuby.Tests/RubyTests.cs index 28b825ee7..432145f95 100644 --- a/Src/IronRuby.Tests/RubyTests.cs +++ b/Src/IronRuby.Tests/RubyTests.cs @@ -1006,6 +1006,7 @@ public Tests(Driver/*!*/ driver) { Integer_ToStringRadix1, Pack_BerTruncated1, Integer_Int64Shifts1, + BasicObject_EqualIdentity1, }; } } diff --git a/Src/IronRuby.Tests/Runtime/MiscTests.cs b/Src/IronRuby.Tests/Runtime/MiscTests.cs index 4e41ae55f..4cb16d5e5 100644 --- a/Src/IronRuby.Tests/Runtime/MiscTests.cs +++ b/Src/IronRuby.Tests/Runtime/MiscTests.cs @@ -4245,6 +4245,45 @@ struct ivar: nil ivar on integer receiver via instance_exec: nil autoload const: ""LoadError: cannot load such file -- /nonexistent/thing"" tracepoint ctor: [[:call, :initialize], [:return, :initialize], [:call, :x], [:return, :x]] +"); + } + + /// + /// BasicObject#equal? is identity, separate from #== - a class whose == calls equal? + /// (Set#==) must not recurse through BaseEquals into its own ==. + /// + public void BasicObject_EqualIdentity1() { + TestOutput(@" +# Set#== in set.rb (not loadable here: no prelude) has this shape +class MySet + attr_reader :h + def initialize(*a); @h = {}; a.each { |x| @h[x] = true }; end + def ==(other); if self.equal?(other) then true elsif other.instance_of?(self.class) then @h == other.h else false end; end +end +class Eq; def ==(o); equal?(o); end; end +class Str < String; def ==(o); equal?(o); end; end +p MySet.new(1).equal?(MySet.new(1)), (s = MySet.new(1)).equal?(s), s == MySet.new(1) +e = Eq.new; p e.equal?(e), e.equal?(Eq.new), e == e, e == Eq.new +t = Str.new('a'); p t.equal?(t), t.equal?(Str.new('a')), 'a'.equal?('a') +p 1.equal?(1), 1.5.equal?(1.5), :a.equal?(:a), nil.equal?(nil), true.equal?(true) +p BasicObject.new.equal?(BasicObject.new) +", @" +false +true +true +true +false +true +false +true +false +false +true +true +true +true +true +false "); } } diff --git a/Src/Libraries/Builtins/BasicObjectOps.cs b/Src/Libraries/Builtins/BasicObjectOps.cs index c4f8eba9d..9ddc0fb96 100644 --- a/Src/Libraries/Builtins/BasicObjectOps.cs +++ b/Src/Libraries/Builtins/BasicObjectOps.cs @@ -153,16 +153,27 @@ public static object SendMessage(SendSiteStorage(IronRuby.Builtins.BasicObjectOps.ValueEquals), - new Func(IronRuby.Builtins.BasicObjectOps.ValueEquals) + 0x00000000U, + new Func(IronRuby.Builtins.BasicObjectOps.IsEqual) ); DefineLibraryMethod(module, "initialize", 0x5a,