Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Src/IronRuby.Tests/RubyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,7 @@ public Tests(Driver/*!*/ driver) {
Integer_ToStringRadix1,
Pack_BerTruncated1,
Integer_Int64Shifts1,
BasicObject_EqualIdentity1,
};
}
}
Expand Down
39 changes: 39 additions & 0 deletions Src/IronRuby.Tests/Runtime/MiscTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
");
}

/// <summary>
/// BasicObject#equal? is identity, separate from #== - a class whose == calls equal?
/// (Set#==) must not recurse through BaseEquals into its own ==.
/// </summary>
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
");
}
}
Expand Down
19 changes: 15 additions & 4 deletions Src/Libraries/Builtins/BasicObjectOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,16 +153,27 @@ public static object SendMessage(SendSiteStorage<Func<CallSite, RubyScope, objec

#region ==, !=, !, equal?

// equal? is an alias of == in BasicObject, so both names sit on both overloads (an
// UnboundMethod for one is == to one for the other).
// BasicObject#== and #equal? are both rb_obj_equal (identity) in CRuby, but they are two
// methods: a class that overrides == (Set, a String subclass) must not change equal?. For
// a Ruby-defined class BaseEquals ends in RubyObject.Equals - which calls the Ruby == - or
// in a CLR base's Equals (MutableString compares contents), so equal? must not use it:
// a == that starts with `equal?(other)` would recurse until the stack overflows.
[RubyMethod("==")]
[RubyMethod("equal?")]
public static bool ValueEquals([NotNull]IRubyObject/*!*/ self, object other) {
return self.BaseEquals(other);
}

[RubyMethod("==")]
[RubyMethod("equal?")]
public static bool IsEqual(object self, object other) {
// Integer, Float, true/false etc. are CLR value types boxed per use: their identity is
// their value (CRuby's immediates). Everything else is identical only to itself.
if (self is ValueType) {
return Object.Equals(self, other);
}
return ReferenceEquals(self, other);
}

[RubyMethod("==")]
public static bool ValueEquals(object self, object other) {
// a String, Regexp or Time compares its contents in Equals; for them this is identity
if (self is MutableString || self is RubyRegex || self is RubyTime) {
Expand Down
5 changes: 2 additions & 3 deletions Src/Libraries/Initializers.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -631,9 +631,8 @@ private static void LoadBasicObject_Instance(IronRuby.Builtins.RubyModule/*!*/ m
);

DefineLibraryMethod(module, "equal?", 0x51,
0x00000001U, 0x00000000U,
new Func<IronRuby.Runtime.IRubyObject, System.Object, System.Boolean>(IronRuby.Builtins.BasicObjectOps.ValueEquals),
new Func<System.Object, System.Object, System.Boolean>(IronRuby.Builtins.BasicObjectOps.ValueEquals)
0x00000000U,
new Func<System.Object, System.Object, System.Boolean>(IronRuby.Builtins.BasicObjectOps.IsEqual)
);

DefineLibraryMethod(module, "initialize", 0x5a,
Expand Down
Loading