Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
5 changes: 5 additions & 0 deletions src/runtime/ClassManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@ internal static ClassBase CreateClass(Type type)
impl = new LookUpObject(type);
}

else if (type.IsEnum)
{
impl = new EnumObject(type);
}

else
{
impl = new ClassObject(type);
Expand Down
85 changes: 48 additions & 37 deletions src/runtime/Types/ClassBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,42 +156,7 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc
try
{
int cmp = co1Comp.CompareTo(co2Inst);

BorrowedReference pyCmp;
if (cmp < 0)
{
if (op == Runtime.Py_LT || op == Runtime.Py_LE)
{
pyCmp = Runtime.PyTrue;
}
else
{
pyCmp = Runtime.PyFalse;
}
}
else if (cmp == 0)
{
if (op == Runtime.Py_LE || op == Runtime.Py_GE)
{
pyCmp = Runtime.PyTrue;
}
else
{
pyCmp = Runtime.PyFalse;
}
}
else
{
if (op == Runtime.Py_GE || op == Runtime.Py_GT)
{
pyCmp = Runtime.PyTrue;
}
else
{
pyCmp = Runtime.PyFalse;
}
}
return new NewReference(pyCmp);
return new NewReference(GetComparisonResult(op, cmp));
}
catch (ArgumentException e)
{
Expand All @@ -202,7 +167,53 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc
}
}

private static bool TryGetSecondCompareOperandInstance(BorrowedReference left, BorrowedReference right, out CLRObject co1, out object co2Inst)
/// <summary>
/// Get the result of a comparison operation based on the operator and the comparison result.
/// </summary>
/// <remarks>
/// This method is used to determine the result of a comparison operation, excluding equality and inequality.
/// </remarks>
protected static BorrowedReference GetComparisonResult(int op, int comparisonResult)
{
BorrowedReference pyCmp;
if (comparisonResult < 0)
{
if (op == Runtime.Py_LT || op == Runtime.Py_LE)
{
pyCmp = Runtime.PyTrue;
}
else
{
pyCmp = Runtime.PyFalse;
}
}
else if (comparisonResult == 0)
{
if (op == Runtime.Py_LE || op == Runtime.Py_GE)
{
pyCmp = Runtime.PyTrue;
}
else
{
pyCmp = Runtime.PyFalse;
}
}
else
{
if (op == Runtime.Py_GE || op == Runtime.Py_GT)
{
pyCmp = Runtime.PyTrue;
}
else
{
pyCmp = Runtime.PyFalse;
}
}

return pyCmp;
}

protected static bool TryGetSecondCompareOperandInstance(BorrowedReference left, BorrowedReference right, out CLRObject co1, out object co2Inst)
{
co2Inst = null;

Expand Down
2 changes: 1 addition & 1 deletion src/runtime/Types/DelegateObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public static NewReference tp_call(BorrowedReference ob, BorrowedReference args,
/// <summary>
/// Implements __cmp__ for reflected delegate types.
/// </summary>
public new static NewReference tp_richcompare(BorrowedReference ob, BorrowedReference other, int op)
public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReference other, int op)
{
if (op != Runtime.Py_EQ && op != Runtime.Py_NE)
{
Expand Down
210 changes: 210 additions & 0 deletions src/runtime/Types/EnumObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
using System;
using System.Runtime.CompilerServices;

namespace Python.Runtime
{
/// <summary>
/// Managed class that provides the implementation for reflected enum types.
/// </summary>
[Serializable]
internal class EnumObject : ClassBase
{
internal EnumObject(Type type) : base(type)
{
}

/// <summary>
/// Standard comparison implementation for instances of enum types.
/// </summary>
public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReference other, int op)
{
object rightInstance;
CLRObject leftClrObject;
int comparisonResult;

switch (op)
{
case Runtime.Py_EQ:
case Runtime.Py_NE:
var pytrue = Runtime.PyTrue;
var pyfalse = Runtime.PyFalse;

// swap true and false for NE
if (op != Runtime.Py_EQ)
{
pytrue = Runtime.PyFalse;
pyfalse = Runtime.PyTrue;
}

if (ob == other)
{
return new NewReference(pytrue);
}

if (!TryGetSecondCompareOperandInstance(ob, other, out leftClrObject, out rightInstance))
{
return new NewReference(pyfalse);
}

if (rightInstance != null &&
TryCompare(leftClrObject.inst as Enum, rightInstance, out comparisonResult) &&
comparisonResult == 0)
{
return new NewReference(pytrue);
}
else
{
return new NewReference(pyfalse);
}

case Runtime.Py_LT:
case Runtime.Py_LE:
case Runtime.Py_GT:
case Runtime.Py_GE:
if (!TryGetSecondCompareOperandInstance(ob, other, out leftClrObject, out rightInstance))
{
return Exceptions.RaiseTypeError("Cannot get managed object");
}

if (rightInstance == null)
{
return Exceptions.RaiseTypeError($"Cannot compare {leftClrObject.inst.GetType()} to None");
}

try
{
if (!TryCompare(leftClrObject.inst as Enum, rightInstance, out comparisonResult))
{
return Exceptions.RaiseTypeError($"Cannot compare {leftClrObject.inst.GetType()} with {rightInstance.GetType()}");
}

return new NewReference(GetComparisonResult(op, comparisonResult));
}
catch (ArgumentException e)
{
return Exceptions.RaiseTypeError(e.Message);
}

default:
return new NewReference(Runtime.PyNotImplemented);
}
}

/// <summary>
/// Tries comparing the give enum to the right operand by converting it to the appropriate type if possible
/// </summary>
/// <returns>True if the right operand was converted to a supported type and the comparison was performed successfully</returns>
private static bool TryCompare(Enum left, object right, out int result)
{
result = int.MinValue;
var conversionSuccessful = true;
var leftType = left.GetType();
var leftIsUnsigned = () => leftType.GetEnumUnderlyingType() == typeof(UInt64);

switch (right)
{
case Enum when leftType == right.GetType():
// Same enum type
result = left.CompareTo(right);
break;
case Enum rightEnum:
// Different enum type
result = Compare(left, rightEnum, leftIsUnsigned());
break;
case string rightString:
result = left.ToString().CompareTo(rightString);
break;
case double rightDouble:
result = Compare(left, rightDouble, leftIsUnsigned());
break;
case long rightLong:
result = Compare(left, rightLong, leftIsUnsigned());
break;
case ulong rightULong:
result = Compare(left, rightULong, leftIsUnsigned());
break;
case int rightInt:
result = Compare(left, (long)rightInt, leftIsUnsigned());
break;
case uint rightUInt:
result = Compare(left, (ulong)rightUInt, leftIsUnsigned());
break;
default:
conversionSuccessful = false;
break;
}

return conversionSuccessful;
}

#region Comparison against integers

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Compare(long a, ulong b)
{
if (a < 0) return -1;
return ((ulong)a).CompareTo(b);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Compare(Enum a, long b, bool isUnsigned)
{

if (isUnsigned)
{
return -Compare(b, Convert.ToUInt64(a));
}
return Convert.ToInt64(a).CompareTo(b);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Compare(Enum a, ulong b, bool inUnsigned)
{
if (inUnsigned)
{
return Convert.ToUInt64(a).CompareTo(b);
}
return Compare(Convert.ToInt64(a), b);
}

#endregion

#region Comparison against doubles

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Compare(Enum a, double b, bool isUnsigned)
{
if (isUnsigned)
{
var uIntA = Convert.ToUInt64(a);
if (uIntA < b) return -1;
if (uIntA > b) return 1;
return 0;
}

var intA = Convert.ToInt64(a);
if (intA < b) return -1;
if (intA > b) return 1;
return 0;
}

#endregion

#region Comparison against other enum types

/// <summary>
/// We support comparing enums of different types by comparing their underlying values.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Compare(Enum a, Enum b, bool isUnsigned)
{
if (b.GetType().GetEnumUnderlyingType() == typeof(UInt64))
{
return Compare(a, Convert.ToUInt64(b), isUnsigned);
}
return Compare(a, Convert.ToInt64(b), isUnsigned);
}

#endregion
}
}
Loading
Loading