Skip to content

Commit 429cc3b

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 429cc3b

File tree

9 files changed

+196
-104
lines changed

9 files changed

+196
-104
lines changed

CHANGELOG.md

+3
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/runtime/Codecs/EnumPyLongCodec.cs

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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+
return new PyLong((long)value);
58+
}
59+
60+
private EnumPyLongCodec() { }
61+
}
62+
}

src/runtime/classobject.cs

+43-5
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

+30-44
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

+1-1
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/pylong.cs

+1-4
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,8 @@ public PyLong(string value) : base(FromString(value))
188188

189189

190190
/// <summary>
191-
/// IsLongType Method
192-
/// </summary>
193-
/// <remarks>
194191
/// Returns true if the given object is a Python long.
195-
/// </remarks>
192+
/// </summary>
196193
public static bool IsLongType(PyObject value)
197194
{
198195
return Runtime.PyLong_Check(value.obj);

src/runtime/pyobject.cs

+10-6
Original file line numberDiff line numberDiff line change
@@ -934,17 +934,21 @@ public bool IsInstance(PyObject typeOrClass)
934934

935935

936936
/// <summary>
937-
/// IsSubclass Method
938-
/// </summary>
939-
/// <remarks>
940-
/// Return true if the object is identical to or derived from the
937+
/// Return <c>true</c> if the object is identical to or derived from the
941938
/// given Python type or class. This method always succeeds.
942-
/// </remarks>
939+
/// </summary>
943940
public bool IsSubclass(PyObject typeOrClass)
944941
{
945942
if (typeOrClass == null) throw new ArgumentNullException(nameof(typeOrClass));
946943

947-
int r = Runtime.PyObject_IsSubclass(obj, typeOrClass.obj);
944+
return IsSubclass(typeOrClass.Reference);
945+
}
946+
947+
internal bool IsSubclass(BorrowedReference typeOrClass)
948+
{
949+
if (typeOrClass.IsNull) throw new ArgumentNullException(nameof(typeOrClass));
950+
951+
int r = Runtime.PyObject_IsSubclass(Reference, typeOrClass);
948952
if (r < 0)
949953
{
950954
Runtime.PyErr_Clear();

src/runtime/runtime.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1103,7 +1103,7 @@ internal static int PyObject_Compare(IntPtr value1, IntPtr value2)
11031103
internal static int PyObject_IsInstance(IntPtr ob, IntPtr type) => Delegates.PyObject_IsInstance(ob, type);
11041104

11051105

1106-
internal static int PyObject_IsSubclass(IntPtr ob, IntPtr type) => Delegates.PyObject_IsSubclass(ob, type);
1106+
internal static int PyObject_IsSubclass(BorrowedReference ob, BorrowedReference type) => Delegates.PyObject_IsSubclass(ob, type);
11071107

11081108

11091109
internal static int PyCallable_Check(IntPtr pointer) => Delegates.PyCallable_Check(pointer);
@@ -2313,7 +2313,7 @@ static Delegates()
23132313
PyObject_CallObject = (delegate* unmanaged[Cdecl]<IntPtr, IntPtr, IntPtr>)GetFunctionByName(nameof(PyObject_CallObject), GetUnmanagedDll(_PythonDll));
23142314
PyObject_RichCompareBool = (delegate* unmanaged[Cdecl]<IntPtr, IntPtr, int, int>)GetFunctionByName(nameof(PyObject_RichCompareBool), GetUnmanagedDll(_PythonDll));
23152315
PyObject_IsInstance = (delegate* unmanaged[Cdecl]<IntPtr, IntPtr, int>)GetFunctionByName(nameof(PyObject_IsInstance), GetUnmanagedDll(_PythonDll));
2316-
PyObject_IsSubclass = (delegate* unmanaged[Cdecl]<IntPtr, IntPtr, int>)GetFunctionByName(nameof(PyObject_IsSubclass), GetUnmanagedDll(_PythonDll));
2316+
PyObject_IsSubclass = (delegate* unmanaged[Cdecl]<BorrowedReference, BorrowedReference, int>)GetFunctionByName(nameof(PyObject_IsSubclass), GetUnmanagedDll(_PythonDll));
23172317
PyCallable_Check = (delegate* unmanaged[Cdecl]<IntPtr, int>)GetFunctionByName(nameof(PyCallable_Check), GetUnmanagedDll(_PythonDll));
23182318
PyObject_IsTrue = (delegate* unmanaged[Cdecl]<BorrowedReference, int>)GetFunctionByName(nameof(PyObject_IsTrue), GetUnmanagedDll(_PythonDll));
23192319
PyObject_Not = (delegate* unmanaged[Cdecl]<IntPtr, int>)GetFunctionByName(nameof(PyObject_Not), GetUnmanagedDll(_PythonDll));
@@ -2585,7 +2585,7 @@ static Delegates()
25852585
internal static delegate* unmanaged[Cdecl]<IntPtr, IntPtr, IntPtr> PyObject_CallObject { get; }
25862586
internal static delegate* unmanaged[Cdecl]<IntPtr, IntPtr, int, int> PyObject_RichCompareBool { get; }
25872587
internal static delegate* unmanaged[Cdecl]<IntPtr, IntPtr, int> PyObject_IsInstance { get; }
2588-
internal static delegate* unmanaged[Cdecl]<IntPtr, IntPtr, int> PyObject_IsSubclass { get; }
2588+
internal static delegate* unmanaged[Cdecl]<BorrowedReference, BorrowedReference, int> PyObject_IsSubclass { get; }
25892589
internal static delegate* unmanaged[Cdecl]<IntPtr, int> PyCallable_Check { get; }
25902590
internal static delegate* unmanaged[Cdecl]<BorrowedReference, int> PyObject_IsTrue { get; }
25912591
internal static delegate* unmanaged[Cdecl]<IntPtr, int> PyObject_Not { get; }

0 commit comments

Comments
 (0)