ironpython2 icon indicating copy to clipboard operation
ironpython2 copied to clipboard

Make CLR enum behave more like python IntEnum

Open kayoub5 opened this issue 6 years ago • 8 comments

Description

The CLR enum have interesting characteristics that makes it too similar to python IntEnum and IntFlag. however not all of those characteristics are supported in IronPython. Examples: given the following DaysInt:

[Flags]
public enum DaysInt : int {
    Mon = 0x01,
    Tue = 0x02,
    Wed = 0x04,
    Thu = 0x08,
    Fri = 0x10,
    Sat = 0x20,
    Sun = 0x40,
    Weekdays = Mon | Tue | Wed | Thu | Fri,
    Weekend = Sat | Sun,
    All = Weekdays | Weekend
  }

the below C# code will work just fine:

Console.WriteLine((DaysInt) 8); // Thu
Console.WriteLine((DaysInt) 0x100); // 256
Console.WriteLine(DaysInt.Tue < DaysInt.Wed); // 6

if DaysInt was implemented purely in python, it would look something like this:

from enum import IntFlag

class DaysInt(IntFlag):
    Mon = 0x01,
    Tue = 0x02,
    Wed = 0x04,
    Thu = 0x08,
    Fri = 0x10,
    Sat = 0x20,
    Sun = 0x40,
    Weekdays = 0x1F,
    Weekend = 0x20 | 0x40,
    All = 0x7F,

print(DaysInt(8)) # DaysInt.Thu
print(DaysInt(0x100)) # DaysInt.256
print(DaysInt.Tue < DaysInt.Wed) # True

Steps to Reproduce

Now if we take CLR implimentation of DaysInt, and import it to IronPython

print(DaysInt(8)) # throws
print(DaysInt(0x100)) # throws
print(DaysInt.Tue < DaysInt.Wed) # throws

After some digging, I noticed that none of the methods to support this is implemented (__new__, __gt___,...) in EnumOps.cs.

I am open to creating the PR myself, if the idea itself is accepted.

Versions

IronPython 2.7.8

kayoub5 avatar Jun 14 '18 12:06 kayoub5

I know that IntEnum is for Python 3.4+ but it was back-ported to Python 2 as enum34 and it's widely adopted by the Python community.

kayoub5 avatar Jun 14 '18 12:06 kayoub5

We would definitely be open to a PR, please make sure there are tests for the behavior when submitting the PR.

slide avatar Jun 14 '18 13:06 slide

@slide after some digging, I found that patching EnumOps was not getting me anywhere, the methods defined there are not called automatically but rather only explicitly. for example when doing EnumType.__call__(1)

I found that making the changes at the DLR level are much more easier: adding EnumType(int number) constructor to CompilerHelpers.GetConstructors(...). however I am not sure if this is a desired change at the DLR level (possible side effects for IronRuby?)

for other changes (like changing the str(), repr(), and __eq__), a small push in the right direction may help. debugging the code got me nowhere, due the noise caused by stepping through Microsoft.Scripting.Interpreter classes, plus the documentation is nonexistent.

kayoub5 avatar Jun 16 '18 07:06 kayoub5

@kayoub5 You could try modifying IsStandardDotNetType and add something like !typeof(Enum).IsAssignableFrom(Value.UnderlyingSystemType) to the list of checks. That way the __new__ methods defined in EnumOps would be picked up. I'm not sure if this will cause issues elsewhere but it's a good place to start.

A side note: it looks like the comparison operators already work for enums? DaysInt.Tue < DaysInt.Wed works for me.

slozier avatar Jun 16 '18 18:06 slozier

@slozier

You could try modifying IsStandardDotNetType and add something like !typeof(Enum).IsAssignableFrom(Value.UnderlyingSystemType) to the list of checks. That way the new methods defined in EnumOps would be picked up. I'm not sure if this will cause issues elsewhere but it's a good place to start.

Good tip


A side note: it looks like the comparison operators already work for enums? DaysInt.Tue < DaysInt.Wed works for me.

I double checked, yes it works for DaysInt.Tue < DaysInt.Wed but not 2 < DaysInt.Wed (returns False). Any ideas on how to override this?


I am getting a strange behavior, where DaysInt.Mon == DaysInt.Mon returns False (on master). I don't seem to be able to debug it, any idea what method is being used for equality checking?

kayoub5 avatar Jun 17 '18 14:06 kayoub5

For 2 < DaysInt.Wed I'm not sure what comparison method it's currently calling, I'm not sure what sort of comparison would decide to return False. I'm guessing you could probably fix it with some overloads in EnumOps but there may be some better way.

As for DaysInt.Mon == DaysInt.Mon returning False I'm not seeing this behavior over here so I'm not sure...

slozier avatar Jun 17 '18 17:06 slozier

We can close this via #424 correct?

slide avatar Jul 06 '18 17:07 slide

@slide the PR was just about constructing enum from their integer value. but there is more to python enum compatibility, for example:

# Get enum by str name
color = Color['RED']

# enum member properties
member = Color.RED
print member.name # RED
print member.value # 1

# listing enum members
print list(Shape) # [<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>]

Those points could be addressed in separate issues, or in this one.

kayoub5 avatar Jul 08 '18 10:07 kayoub5