-
Notifications
You must be signed in to change notification settings - Fork 762
Support comparison operators #1347
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
9a8cb96
bcbace3
31f3ed1
ae7e774
5685727
03a73c0
67928e1
6a64678
9dd9162
a8c6591
6d4dec7
7a2b5e2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -21,6 +21,7 @@ internal class ClassBase : ManagedType | |||
[NonSerialized] | ||||
internal List<string> dotNetMembers; | ||||
internal Indexer indexer; | ||||
internal Hashtable richcompare; | ||||
internal MaybeType type; | ||||
|
||||
internal ClassBase(Type tp) | ||||
|
@@ -35,6 +36,15 @@ internal virtual bool CanSubclass() | |||
return !type.Value.IsEnum; | ||||
} | ||||
|
||||
public readonly static Dictionary<int, string> PyToCilOpMap = new Dictionary<int, string> | ||||
{ | ||||
[Runtime.Py_EQ] = "op_Equality", | ||||
[Runtime.Py_NE] = "op_Inequality", | ||||
[Runtime.Py_GT] = "op_GreaterThan", | ||||
[Runtime.Py_GE] = "op_GreaterThanOrEqual", | ||||
[Runtime.Py_LT] = "op_LessThan", | ||||
[Runtime.Py_LE] = "op_LessThanOrEqual", | ||||
}; | ||||
|
||||
/// <summary> | ||||
/// Default implementation of [] semantics for reflected types. | ||||
|
@@ -72,6 +82,42 @@ public static IntPtr tp_richcompare(IntPtr ob, IntPtr other, int op) | |||
{ | ||||
CLRObject co1; | ||||
CLRObject co2; | ||||
IntPtr tp = Runtime.PyObject_TYPE(ob); | ||||
var cls = (ClassBase)GetManagedObject(tp); | ||||
// C# operator methods take precedence over IComparable. | ||||
// We first check if there's a comparison operator by looking up the richcompare table, | ||||
// otherwise fallback to checking if an IComparable interface is handled. | ||||
if (PyToCilOpMap.ContainsKey(op)) { | ||||
string CilOp = PyToCilOpMap[op]; | ||||
if (cls.richcompare.Contains(CilOp)) { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Generally, you'd do |
||||
var methodObject = (MethodObject)cls.richcompare[CilOp]; | ||||
IntPtr args = other; | ||||
var free = false; | ||||
if (!Runtime.PyTuple_Check(other)) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the code inside this Consequently, Probably a good idea to add a test. I believe you'd need an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that error message might have been a flaky bug, because now the variable states are the same but the error is:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is because to work with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, I didn't know about that. I put the I added 2/6 of the tuple comparison tests but it's done in a bit of a strange way. In the test I'm assuming that the PyObject is a tuple; is that okay? (Below is resolved)
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think you need to add a test with a tuple for each comparison operator. One is enough. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The failure on the screenshot is only happening because you have debugger attached. It tries to call |
||||
{ | ||||
// Wrap the `other` argument of a binary comparison operator in a PyTuple. | ||||
args = Runtime.PyTuple_New(1); | ||||
Runtime.XIncref(other); | ||||
Runtime.PyTuple_SetItem(args, 0, other); | ||||
free = true; | ||||
} | ||||
|
||||
IntPtr value; | ||||
try | ||||
{ | ||||
value = methodObject.Invoke(ob, args, IntPtr.Zero); | ||||
} | ||||
finally | ||||
{ | ||||
if (free) | ||||
{ | ||||
Runtime.XDecref(args); // Free args pytuple | ||||
} | ||||
} | ||||
return value; | ||||
} | ||||
} | ||||
|
||||
switch (op) | ||||
{ | ||||
case Runtime.Py_EQ: | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,6 +50,8 @@ static OperatorMethod() | |
["op_UnaryNegation"] = new SlotDefinition("__neg__", TypeOffset.nb_negative), | ||
["op_UnaryPlus"] = new SlotDefinition("__pos__", TypeOffset.nb_positive), | ||
["op_OneComplement"] = new SlotDefinition("__invert__", TypeOffset.nb_invert), | ||
["op_Equality"] = new SlotDefinition("__eq__", TypeOffset.tp_richcompare), | ||
["op_Inequality"] = new SlotDefinition("__ne__", TypeOffset.tp_richcompare), | ||
["op_GreaterThan"] = new SlotDefinition("__gt__", TypeOffset.tp_richcompare), | ||
["op_GreaterThanOrEqual"] = new SlotDefinition("__ge__", TypeOffset.tp_richcompare), | ||
["op_LessThan"] = new SlotDefinition("__lt__", TypeOffset.tp_richcompare), | ||
|
@@ -79,6 +81,12 @@ public static bool IsOperatorMethod(MethodBase method) | |
} | ||
return OpMethodMap.ContainsKey(method.Name); | ||
} | ||
|
||
public static bool IsComparisonOp(MethodInfo method) | ||
{ | ||
return OpMethodMap[method.Name].TypeOffset == TypeOffset.tp_richcompare; | ||
} | ||
|
||
/// <summary> | ||
/// For the operator methods of a CLR type, set the special slots of the | ||
/// corresponding Python type's operator methods. | ||
|
@@ -91,7 +99,7 @@ public static void FixupSlots(IntPtr pyType, Type clrType) | |
Debug.Assert(_opType != null); | ||
foreach (var method in clrType.GetMethods(flags)) | ||
{ | ||
if (!IsOperatorMethod(method)) | ||
if (!IsOperatorMethod(method) || IsComparisonOp(method)) // We don't want to override ClassBase.tp_richcompare. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Direct comment to the reason why we don't want to override, otherwise it does not explain anything and simply says what code in this method does (which is generally not very useful). Something like "comparison operators are handled by |
||
{ | ||
continue; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not have it as
Dictionary<int, MethodObject>
, mapping directly from{Py_EQ, ...}
to corresponding operator implementation?