Skip to content

Commit 26a6dcb

Browse files
committed
Finish comparison operator impl and add tests
1 parent d19e1bd commit 26a6dcb

File tree

4 files changed

+77
-1
lines changed

4 files changed

+77
-1
lines changed

src/embed_tests/TestOperator.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,12 @@ public void SymmetricalOperatorOverloads()
270270
271271
c = a > b
272272
assert c == (a.Num > b.Num)
273+
274+
c = a == b
275+
assert c == (a.Num == b.Num)
276+
277+
c = a != b
278+
assert c == (a.Num != b.Num)
273279
");
274280
}
275281

@@ -339,6 +345,12 @@ public void ForwardOperatorOverloads()
339345
340346
c = a > b
341347
assert c == (a.Num > b)
348+
349+
c = a == b
350+
assert c == (a.Num == b)
351+
352+
c = a != b
353+
assert c == (a.Num != b)
342354
");
343355
}
344356

@@ -392,6 +404,12 @@ public void ReverseOperatorOverloads()
392404
393405
c = a > b
394406
assert c == (a > b.Num)
407+
408+
c = a == b
409+
assert c == (a == b.Num)
410+
411+
c = a != b
412+
assert c == (a != b.Num)
395413
");
396414

397415
}

src/runtime/classbase.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ internal class ClassBase : ManagedType
2121
[NonSerialized]
2222
internal List<string> dotNetMembers;
2323
internal Indexer indexer;
24+
internal Hashtable richcompare;
2425
internal MaybeType type;
2526

2627
internal ClassBase(Type tp)
@@ -35,6 +36,15 @@ internal virtual bool CanSubclass()
3536
return !type.Value.IsEnum;
3637
}
3738

39+
public readonly static Dictionary<int, string> PyToCilOpMap = new Dictionary<int, string>
40+
{
41+
[Runtime.Py_EQ] = "op_Equality",
42+
[Runtime.Py_NE] = "op_Inequality",
43+
[Runtime.Py_GT] = "op_GreaterThan",
44+
[Runtime.Py_GE] = "op_GreaterThanOrEqual",
45+
[Runtime.Py_LT] = "op_LessThan",
46+
[Runtime.Py_LE] = "op_LessThanOrEqual",
47+
};
3848

3949
/// <summary>
4050
/// Default implementation of [] semantics for reflected types.
@@ -72,6 +82,42 @@ public static IntPtr tp_richcompare(IntPtr ob, IntPtr other, int op)
7282
{
7383
CLRObject co1;
7484
CLRObject co2;
85+
IntPtr tp = Runtime.PyObject_TYPE(ob);
86+
var cls = (ClassBase)GetManagedObject(tp);
87+
// C# operator methods take precedence over IComparable.
88+
// We first check if there's a comparison operator by looking up the richcompare table,
89+
// otherwise fallback to checking if an IComparable interface is handled.
90+
if (PyToCilOpMap.ContainsKey(op)) {
91+
string CilOp = PyToCilOpMap[op];
92+
if (cls.richcompare.Contains(CilOp)) {
93+
var methodObject = (MethodObject)cls.richcompare[CilOp];
94+
IntPtr args = other;
95+
var free = false;
96+
if (!Runtime.PyTuple_Check(other))
97+
{
98+
// Wrap the `other` argument of a binary comparison operator in a PyTuple.
99+
args = Runtime.PyTuple_New(1);
100+
Runtime.XIncref(other);
101+
Runtime.PyTuple_SetItem(args, 0, other);
102+
free = true;
103+
}
104+
105+
IntPtr value;
106+
try
107+
{
108+
value = methodObject.Invoke(ob, args, IntPtr.Zero);
109+
}
110+
finally
111+
{
112+
if (free)
113+
{
114+
Runtime.XDecref(args); // Free args pytuple
115+
}
116+
}
117+
return value;
118+
}
119+
}
120+
75121
switch (op)
76122
{
77123
case Runtime.Py_EQ:

src/runtime/classmanager.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ private static void InitClassBase(Type type, ClassBase impl)
259259
ClassInfo info = GetClassInfo(type);
260260

261261
impl.indexer = info.indexer;
262+
impl.richcompare = new Hashtable();
262263

263264
// Now we allocate the Python type object to reflect the given
264265
// managed type, filling the Python type slots with thunks that
@@ -284,6 +285,9 @@ private static void InitClassBase(Type type, ClassBase impl)
284285
Runtime.PyDict_SetItemString(dict, name, item.pyHandle);
285286
// Decref the item now that it's been used.
286287
item.DecrRefCount();
288+
if (ClassBase.PyToCilOpMap.ContainsValue(name)) {
289+
impl.richcompare.Add(name, iter.Value);
290+
}
287291
}
288292

289293
// If class has constructors, generate an __doc__ attribute.

src/runtime/operatormethod.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ static OperatorMethod()
5050
["op_UnaryNegation"] = new SlotDefinition("__neg__", TypeOffset.nb_negative),
5151
["op_UnaryPlus"] = new SlotDefinition("__pos__", TypeOffset.nb_positive),
5252
["op_OneComplement"] = new SlotDefinition("__invert__", TypeOffset.nb_invert),
53+
["op_Equality"] = new SlotDefinition("__eq__", TypeOffset.tp_richcompare),
54+
["op_Inequality"] = new SlotDefinition("__ne__", TypeOffset.tp_richcompare),
5355
["op_GreaterThan"] = new SlotDefinition("__gt__", TypeOffset.tp_richcompare),
5456
["op_GreaterThanOrEqual"] = new SlotDefinition("__ge__", TypeOffset.tp_richcompare),
5557
["op_LessThan"] = new SlotDefinition("__lt__", TypeOffset.tp_richcompare),
@@ -79,6 +81,12 @@ public static bool IsOperatorMethod(MethodBase method)
7981
}
8082
return OpMethodMap.ContainsKey(method.Name);
8183
}
84+
85+
public static bool IsComparisonOp(MethodInfo method)
86+
{
87+
return OpMethodMap[method.Name].TypeOffset == TypeOffset.tp_richcompare;
88+
}
89+
8290
/// <summary>
8391
/// For the operator methods of a CLR type, set the special slots of the
8492
/// corresponding Python type's operator methods.
@@ -91,7 +99,7 @@ public static void FixupSlots(IntPtr pyType, Type clrType)
9199
Debug.Assert(_opType != null);
92100
foreach (var method in clrType.GetMethods(flags))
93101
{
94-
if (!IsOperatorMethod(method))
102+
if (!IsOperatorMethod(method) || IsComparisonOp(method)) // We don't want to override ClassBase.tp_richcompare.
95103
{
96104
continue;
97105
}

0 commit comments

Comments
 (0)