Skip to content

Commit 9bff356

Browse files
committed
reworked Enum marshaling
- enums are no longer converted to and from PyLong automatically #1220 - one can construct an instance of MyEnum from Python using MyEnum(numeric_val), e.g. MyEnum(10) - in the above, if MyEnum does not have [Flags] and does not have value 10 defined, to create MyEnum with value 10 one must call MyEnum(10, True). Here True is an unnamed parameter, that allows unchecked conversion - legacy behavior has been moved to a codec (EnumPyLongCodec); enums can now be encoded by codecs
1 parent 707ef36 commit 9bff356

16 files changed

+314
-147
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ when .NET expects an integer [#1342][i1342]
3636
- BREAKING: Methods with `ref` or `out` parameters and void return type return a tuple of only the `ref` and `out` parameters.
3737
- BREAKING: to call Python from .NET `Runtime.PythonDLL` property must be set to Python DLL name
3838
or the DLL must be loaded in advance. This must be done before calling any other Python.NET functions.
39+
- BREAKING: disabled implicit conversion from C# enums to Python `int` and back.
40+
One must now either use enum members (e.g. `MyEnum.Option`), or use enum constructor
41+
(e.g. `MyEnum(42)` or `MyEnum(42, True)` when `MyEnum` does not have a member with value 42).
3942
- Sign Runtime DLL with a strong name
4043
- Implement loading through `clr_loader` instead of the included `ClrModule`, enables
4144
support for .NET Core

src/embed_tests/TestOperator.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,14 @@ public void SymmetricalOperatorOverloads()
335335
");
336336
}
337337

338+
[Test]
339+
public void EnumOperator()
340+
{
341+
PythonEngine.Exec($@"
342+
from System.IO import FileAccess
343+
c = FileAccess.Read | FileAccess.Write");
344+
}
345+
338346
[Test]
339347
public void OperatorOverloadMissingArgument()
340348
{

src/runtime/Codecs/EnumPyLongCodec.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using System;
2+
3+
namespace Python.Runtime.Codecs
4+
{
5+
[Obsolete]
6+
public sealed class EnumPyLongCodec: IPyObjectEncoder, IPyObjectDecoder
7+
{
8+
public static EnumPyLongCodec Instance { get; } = new EnumPyLongCodec();
9+
10+
public bool CanDecode(PyObject objectType, Type targetType)
11+
{
12+
return targetType.IsEnum
13+
&& objectType.IsSubclass(new BorrowedReference(Runtime.PyLongType));
14+
}
15+
16+
public bool CanEncode(Type type)
17+
{
18+
return type == typeof(object) || type == typeof(ValueType) || type.IsEnum;
19+
}
20+
21+
public bool TryDecode<T>(PyObject pyObj, out T value)
22+
{
23+
value = default;
24+
if (!typeof(T).IsEnum) return false;
25+
26+
Type etype = Enum.GetUnderlyingType(typeof(T));
27+
28+
if (!PyLong.IsLongType(pyObj)) return false;
29+
30+
object result;
31+
try
32+
{
33+
result = pyObj.AsManagedObject(etype);
34+
}
35+
catch (InvalidCastException)
36+
{
37+
return false;
38+
}
39+
40+
if (Enum.IsDefined(typeof(T), result)
41+
|| typeof(T).GetCustomAttributes(typeof(FlagsAttribute), true).Length > 0)
42+
{
43+
value = (T)Enum.ToObject(typeof(T), result);
44+
return true;
45+
}
46+
47+
return false;
48+
}
49+
50+
public PyObject TryEncode(object value)
51+
{
52+
if (value is null) return null;
53+
54+
var enumType = value.GetType();
55+
if (!enumType.IsEnum) return null;
56+
57+
try
58+
{
59+
return new PyLong((long)value);
60+
}
61+
catch (InvalidCastException)
62+
{
63+
return new PyLong((ulong)value);
64+
}
65+
}
66+
67+
private EnumPyLongCodec() { }
68+
}
69+
}

src/runtime/classmanager.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,16 @@ private static ClassInfo GetClassInfo(Type type)
403403
}
404404
}
405405

406+
if (type.IsEnum)
407+
{
408+
var opsImpl = typeof(EnumOps<>).MakeGenericType(type);
409+
foreach (var op in opsImpl.GetMethods(OpsHelper.BindingFlags))
410+
{
411+
local[op.Name] = 1;
412+
}
413+
info = info.Concat(opsImpl.GetMethods(OpsHelper.BindingFlags)).ToArray();
414+
}
415+
406416
// Now again to filter w/o losing overloaded member info
407417
for (i = 0; i < info.Length; i++)
408418
{

src/runtime/classobject.cs

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ internal NewReference GetDocString()
5050
/// <summary>
5151
/// Implements __new__ for reflected classes and value types.
5252
/// </summary>
53-
public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw)
53+
public static IntPtr tp_new(IntPtr tpRaw, IntPtr args, IntPtr kw)
5454
{
55+
var tp = new BorrowedReference(tpRaw);
5556
var self = GetManagedObject(tp) as ClassObject;
5657

5758
// Sanity check: this ensures a graceful error if someone does
@@ -87,7 +88,7 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw)
8788
return IntPtr.Zero;
8889
}
8990

90-
return CLRObject.GetInstHandle(result, tp);
91+
return CLRObject.GetInstHandle(result, tp).DangerousMoveToPointerOrNull();
9192
}
9293

9394
if (type.IsAbstract)
@@ -98,8 +99,7 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw)
9899

99100
if (type.IsEnum)
100101
{
101-
Exceptions.SetError(Exceptions.TypeError, "cannot instantiate enumeration");
102-
return IntPtr.Zero;
102+
return NewEnum(type, new BorrowedReference(args), tp).DangerousMoveToPointerOrNull();
103103
}
104104

105105
object obj = self.binder.InvokeRaw(IntPtr.Zero, args, kw);
@@ -108,7 +108,45 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw)
108108
return IntPtr.Zero;
109109
}
110110

111-
return CLRObject.GetInstHandle(obj, tp);
111+
return CLRObject.GetInstHandle(obj, tp).DangerousMoveToPointerOrNull();
112+
}
113+
114+
private static NewReference NewEnum(Type type, BorrowedReference args, BorrowedReference tp)
115+
{
116+
nint argCount = Runtime.PyTuple_Size(args);
117+
bool allowUnchecked = false;
118+
if (argCount == 2)
119+
{
120+
var allow = Runtime.PyTuple_GetItem(args, 1);
121+
if (!Converter.ToManaged(allow, typeof(bool), out var allowObj, true) || allowObj is null)
122+
{
123+
Exceptions.RaiseTypeError("second argument to enum constructor must be a boolean");
124+
return default;
125+
}
126+
allowUnchecked |= (bool)allowObj;
127+
}
128+
129+
if (argCount < 1 || argCount > 2)
130+
{
131+
Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments");
132+
return default;
133+
}
134+
135+
var op = Runtime.PyTuple_GetItem(args, 0);
136+
if (!Converter.ToManaged(op, type.GetEnumUnderlyingType(), out object result, true))
137+
{
138+
return default;
139+
}
140+
141+
if (!allowUnchecked && !Enum.IsDefined(type, result)
142+
&& type.GetCustomAttributes(typeof(FlagsAttribute), true).Length == 0)
143+
{
144+
Exceptions.SetError(Exceptions.ValueError, "Invalid enumeration value. Pass True as the second argument if unchecked conversion is desired");
145+
return default;
146+
}
147+
148+
object enumValue = Enum.ToObject(type, result);
149+
return CLRObject.GetInstHandle(enumValue, tp);
112150
}
113151

114152

src/runtime/converter.cs

Lines changed: 30 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ private Converter()
2727
private static Type int16Type;
2828
private static Type int32Type;
2929
private static Type int64Type;
30-
private static Type flagsType;
3130
private static Type boolType;
3231
private static Type typeType;
3332

@@ -42,7 +41,6 @@ static Converter()
4241
singleType = typeof(Single);
4342
doubleType = typeof(Double);
4443
decimalType = typeof(Decimal);
45-
flagsType = typeof(FlagsAttribute);
4644
boolType = typeof(Boolean);
4745
typeType = typeof(Type);
4846
}
@@ -148,7 +146,8 @@ internal static IntPtr ToPython(object value, Type type)
148146
return result;
149147
}
150148

151-
if (Type.GetTypeCode(type) == TypeCode.Object && value.GetType() != typeof(object)) {
149+
if (Type.GetTypeCode(type) == TypeCode.Object && value.GetType() != typeof(object)
150+
|| type.IsEnum) {
152151
var encoded = PyObjectConversions.TryEncode(value, type);
153152
if (encoded != null) {
154153
result = encoded.Handle;
@@ -203,6 +202,11 @@ internal static IntPtr ToPython(object value, Type type)
203202

204203
type = value.GetType();
205204

205+
if (type.IsEnum)
206+
{
207+
return CLRObject.GetInstHandle(value, type);
208+
}
209+
206210
TypeCode tc = Type.GetTypeCode(type);
207211

208212
switch (tc)
@@ -317,6 +321,18 @@ internal static bool ToManaged(IntPtr value, Type type,
317321
}
318322
return Converter.ToManagedValue(value, type, out result, setError);
319323
}
324+
/// <summary>
325+
/// Return a managed object for the given Python object, taking funny
326+
/// byref types into account.
327+
/// </summary>
328+
/// <param name="value">A Python object</param>
329+
/// <param name="type">The desired managed type</param>
330+
/// <param name="result">Receives the managed object</param>
331+
/// <param name="setError">If true, call <c>Exceptions.SetError</c> with the reason for failure.</param>
332+
/// <returns>True on success</returns>
333+
internal static bool ToManaged(BorrowedReference value, Type type,
334+
out object result, bool setError)
335+
=> ToManaged(value.DangerousGetAddress(), type, out result, setError);
320336

321337
internal static bool ToManagedValue(BorrowedReference value, Type obType,
322338
out object result, bool setError)
@@ -398,11 +414,6 @@ internal static bool ToManagedValue(IntPtr value, Type obType,
398414
return ToArray(value, obType, out result, setError);
399415
}
400416

401-
if (obType.IsEnum)
402-
{
403-
return ToEnum(value, obType, out result, setError);
404-
}
405-
406417
// Conversion to 'Object' is done based on some reasonable default
407418
// conversions (Python string -> managed string, Python int -> Int32 etc.).
408419
if (obType == objectType)
@@ -497,7 +508,7 @@ internal static bool ToManagedValue(IntPtr value, Type obType,
497508
}
498509

499510
TypeCode typeCode = Type.GetTypeCode(obType);
500-
if (typeCode == TypeCode.Object)
511+
if (typeCode == TypeCode.Object || obType.IsEnum)
501512
{
502513
IntPtr pyType = Runtime.PyObject_TYPE(value);
503514
if (PyObjectConversions.TryDecode(value, pyType, obType, out result))
@@ -516,8 +527,17 @@ internal static bool ToManagedValue(IntPtr value, Type obType,
516527
/// </summary>
517528
private static bool ToPrimitive(IntPtr value, Type obType, out object result, bool setError)
518529
{
519-
TypeCode tc = Type.GetTypeCode(obType);
520530
result = null;
531+
if (obType.IsEnum)
532+
{
533+
if (setError)
534+
{
535+
Exceptions.SetError(Exceptions.TypeError, "since Python.NET 3.0 int can not be converted to Enum implicitly. Use Enum(int_value)");
536+
}
537+
return false;
538+
}
539+
540+
TypeCode tc = Type.GetTypeCode(obType);
521541
IntPtr op = IntPtr.Zero;
522542

523543
switch (tc)
@@ -876,40 +896,6 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s
876896
result = items;
877897
return true;
878898
}
879-
880-
881-
/// <summary>
882-
/// Convert a Python value to a correctly typed managed enum instance.
883-
/// </summary>
884-
private static bool ToEnum(IntPtr value, Type obType, out object result, bool setError)
885-
{
886-
Type etype = Enum.GetUnderlyingType(obType);
887-
result = null;
888-
889-
if (!ToPrimitive(value, etype, out result, setError))
890-
{
891-
return false;
892-
}
893-
894-
if (Enum.IsDefined(obType, result))
895-
{
896-
result = Enum.ToObject(obType, result);
897-
return true;
898-
}
899-
900-
if (obType.GetCustomAttributes(flagsType, true).Length > 0)
901-
{
902-
result = Enum.ToObject(obType, result);
903-
return true;
904-
}
905-
906-
if (setError)
907-
{
908-
Exceptions.SetError(Exceptions.ValueError, "invalid enumeration value");
909-
}
910-
911-
return false;
912-
}
913899
}
914900

915901
public static class ConverterExtension

src/runtime/exceptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ public static void Clear()
343343
public static void warn(string message, IntPtr exception, int stacklevel)
344344
{
345345
if (exception == IntPtr.Zero ||
346-
(Runtime.PyObject_IsSubclass(exception, Exceptions.Warning) != 1))
346+
(Runtime.PyObject_IsSubclass(new BorrowedReference(exception), new BorrowedReference(Exceptions.Warning)) != 1))
347347
{
348348
Exceptions.RaiseTypeError("Invalid exception");
349349
}

src/runtime/operatormethod.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ static OperatorMethod()
5151
["op_OnesComplement"] = new SlotDefinition("__invert__", TypeOffset.nb_invert),
5252
["op_UnaryNegation"] = new SlotDefinition("__neg__", TypeOffset.nb_negative),
5353
["op_UnaryPlus"] = new SlotDefinition("__pos__", TypeOffset.nb_positive),
54-
["op_OneComplement"] = new SlotDefinition("__invert__", TypeOffset.nb_invert),
5554
};
5655
ComparisonOpMap = new Dictionary<string, string>
5756
{
@@ -80,7 +79,7 @@ public static void Shutdown()
8079

8180
public static bool IsOperatorMethod(MethodBase method)
8281
{
83-
if (!method.IsSpecialName)
82+
if (!method.IsSpecialName && !method.IsOpsHelper())
8483
{
8584
return false;
8685
}
@@ -102,7 +101,12 @@ public static void FixupSlots(IntPtr pyType, Type clrType)
102101
{
103102
const BindingFlags flags = BindingFlags.Public | BindingFlags.Static;
104103
Debug.Assert(_opType != null);
105-
foreach (var method in clrType.GetMethods(flags))
104+
105+
var staticMethods =
106+
clrType.IsEnum ? typeof(EnumOps<>).MakeGenericType(clrType).GetMethods(flags)
107+
: clrType.GetMethods(flags);
108+
109+
foreach (var method in staticMethods)
106110
{
107111
// We only want to override slots for operators excluding
108112
// comparison operators, which are handled by ClassBase.tp_richcompare.
@@ -170,9 +174,11 @@ public static string ReversePyMethodName(string pyName)
170174
/// <returns></returns>
171175
public static bool IsReverse(MethodInfo method)
172176
{
173-
Type declaringType = method.DeclaringType;
177+
Type primaryType = method.IsOpsHelper()
178+
? method.DeclaringType.GetGenericArguments()[0]
179+
: method.DeclaringType;
174180
Type leftOperandType = method.GetParameters()[0].ParameterType;
175-
return leftOperandType != declaringType;
181+
return leftOperandType != primaryType;
176182
}
177183

178184
public static void FilterMethods(MethodInfo[] methods, out MethodInfo[] forwardMethods, out MethodInfo[] reverseMethods)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy