Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e7da0bc

Browse files
committed
Support reverse binary operations
1 parent 5855a1b commit e7da0bc

File tree

4 files changed

+101
-12
lines changed

4 files changed

+101
-12
lines changed

src/embed_tests/TestOperator.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,42 @@ public void ForwardOperatorOverloads()
202202
assert c.Num == a.Num ^ b
203203
");
204204
}
205+
206+
207+
[Test]
208+
public void ReverseOperatorOverloads()
209+
{
210+
string name = string.Format("{0}.{1}",
211+
typeof(OperableObject).DeclaringType.Name,
212+
typeof(OperableObject).Name);
213+
string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace;
214+
215+
PythonEngine.Exec($@"
216+
from {module} import *
217+
cls = {name}
218+
a = 2
219+
b = cls(10)
220+
221+
c = a + b
222+
assert c.Num == a + b.Num
223+
224+
c = a - b
225+
assert c.Num == a - b.Num
226+
227+
c = a * b
228+
assert c.Num == a * b.Num
229+
230+
c = a & b
231+
assert c.Num == a & b.Num
232+
233+
c = a | b
234+
assert c.Num == a | b.Num
235+
236+
c = a ^ b
237+
assert c.Num == a ^ b.Num
238+
");
239+
240+
}
205241
[Test]
206242
public void ShiftOperatorOverloads()
207243
{

src/runtime/classmanager.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,14 @@ private static ClassInfo GetClassInfo(Type type)
472472
ci.members[name] = ob;
473473
if (mlist.Any(OperatorMethod.IsOperatorMethod))
474474
{
475-
ci.members[OperatorMethod.GetPyMethodName(name)] = ob;
475+
string pyName = OperatorMethod.GetPyMethodName(name);
476+
string pyNameReverse = pyName.Insert(2, "r");
477+
MethodInfo[] forwardMethods, reverseMethods;
478+
OperatorMethod.FilterMethods(mlist, out forwardMethods, out reverseMethods);
479+
// Only methods where the left operand is the declaring type.
480+
ci.members[pyName] = new MethodObject(type, name, forwardMethods);
481+
// Only methods where only the right operand is the declaring type.
482+
ci.members[pyNameReverse] = new MethodObject(type, name, reverseMethods);
476483
}
477484
}
478485

src/runtime/methodbinder.cs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -350,9 +350,20 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
350350
var outs = 0;
351351
int clrnargs = pi.Length;
352352
isOperator = isOperator && pynargs == clrnargs - 1; // Handle mismatched arg numbers due to Python operator being bound.
353+
// Preprocessing pi to remove either the first or second argument.
354+
bool isForward = isOperator && OperatorMethod.IsForward((MethodInfo)mi); // Only cast if isOperator.
355+
if (isOperator && isForward) {
356+
// The first Python arg is the right operand, while the bound instance is the left.
357+
// We need to skip the first (left operand) CLR argument.
358+
pi = pi.Skip(1).Take(1).ToArray();
359+
}
360+
else if (isOperator && !isForward) {
361+
// The first Python arg is the left operand.
362+
// We need to take the first CLR argument.
363+
pi = pi.Take(1).ToArray();
364+
}
353365
var margs = TryConvertArguments(pi, paramsArray, args, pynargs, kwargDict, defaultArgList,
354366
needsResolution: _methods.Length > 1, // If there's more than one possible match.
355-
isOperator: isOperator,
356367
outs: out outs);
357368
if (margs == null)
358369
{
@@ -364,7 +375,15 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
364375
{
365376
if (ManagedType.GetManagedObject(inst) is CLRObject co)
366377
{
367-
margs[0] = co.inst;
378+
// Postprocessing to extend margs.
379+
var margsTemp = new object[2];
380+
// If forward, the bound instance is the left operand.
381+
int boundOperandIndex = isForward ? 0 : 1;
382+
// If forward, the passed instance is the right operand.
383+
int passedOperandIndex = isForward ? 1 : 0;
384+
margsTemp[boundOperandIndex] = co.inst;
385+
margsTemp[passedOperandIndex] = margs[0];
386+
margs = margsTemp;
368387
}
369388
else { break; }
370389
}
@@ -488,15 +507,13 @@ static IntPtr HandleParamsArray(IntPtr args, int arrayStart, int pyArgCount, out
488507
/// <param name="kwargDict">Dictionary of keyword argument name to python object pointer</param>
489508
/// <param name="defaultArgList">A list of default values for omitted parameters</param>
490509
/// <param name="needsResolution"><c>true</c>, if overloading resolution is required</param>
491-
/// <param name="isOperator"><c>true</c>, if is operator method</param>
492510
/// <param name="outs">Returns number of output parameters</param>
493511
/// <returns>An array of .NET arguments, that can be passed to a method.</returns>
494512
static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
495513
IntPtr args, int pyArgCount,
496514
Dictionary<string, IntPtr> kwargDict,
497515
ArrayList defaultArgList,
498516
bool needsResolution,
499-
bool isOperator,
500517
out int outs)
501518
{
502519
outs = 0;
@@ -535,13 +552,6 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
535552
op = Runtime.PyTuple_GetItem(args, paramIndex);
536553
}
537554
}
538-
if (isOperator && paramIndex == 0)
539-
{
540-
// After we've obtained the first argument from Python, we need to skip the first argument of the CLR
541-
// because operator method is a bound method in Python
542-
paramIndex++; // Leave the first .NET param as null (margs).
543-
parameter = pi[paramIndex];
544-
}
545555

546556
bool isOut;
547557
if (!TryConvertArgument(op, parameter.ParameterType, needsResolution, out margs[paramIndex], out isOut))

src/runtime/operatormethod.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,5 +130,41 @@ private static PyObject GetOperatorType()
130130
return locals.GetItem("OperatorMethod");
131131
}
132132
}
133+
134+
public static string ReversePyMethodName(string pyName)
135+
{
136+
return pyName.Insert(2, "r");
137+
}
138+
139+
/// <summary>
140+
/// Check if the method is performing a forward or reverse operation.
141+
/// </summary>
142+
/// <param name="method">The operator method.</param>
143+
/// <returns></returns>
144+
public static bool IsForward(MethodInfo method)
145+
{
146+
Type declaringType = method.DeclaringType;
147+
Type leftOperandType = method.GetParameters()[0].ParameterType;
148+
return leftOperandType == declaringType;
149+
}
150+
151+
public static void FilterMethods(MethodInfo[] methods, out MethodInfo[] forwardMethods, out MethodInfo[] reverseMethods)
152+
{
153+
List<MethodInfo> forwardMethodsList = new List<MethodInfo>();
154+
List<MethodInfo> reverseMethodsList = new List<MethodInfo>();
155+
foreach (var method in methods)
156+
{
157+
if (IsForward(method))
158+
{
159+
forwardMethodsList.Add(method);
160+
} else
161+
{
162+
reverseMethodsList.Add(method);
163+
}
164+
165+
}
166+
forwardMethods = forwardMethodsList.ToArray();
167+
reverseMethods = reverseMethodsList.ToArray();
168+
}
133169
}
134170
}

0 commit comments

Comments
 (0)