diff --git a/CHANGELOG.md b/CHANGELOG.md index 52ee08484..9b5dd1816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,8 @@ details about the cause of the failure able to access members that are part of the implementation class, but not the interface. Use the new `__implementation__` or `__raw_implementation__` properties to if you need to "downcast" to the implementation class. +- BREAKING: `==` and `!=` operators on `PyObject` instances now use Python comparison + (previously was equivalent to `object.ReferenceEquals(,)`) - BREAKING: Parameters marked with `ParameterAttributes.Out` are no longer returned in addition to the regular method return value (unless they are passed with `ref` or `out` keyword). - BREAKING: Drop support for the long-deprecated CLR.* prefix. diff --git a/MANIFEST.in b/MANIFEST.in index 4763ae70f..6458d5778 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ -recursive-include src/ * +graft src/runtime +prune src/runtime/obj +prune src/runtime/bin include Directory.Build.* include pythonnet.sln include version.txt -global-exclude **/obj/* **/bin/* diff --git a/pythonnet/__init__.py b/pythonnet/__init__.py index fa6ed45cf..9876a0bec 100644 --- a/pythonnet/__init__.py +++ b/pythonnet/__init__.py @@ -3,7 +3,7 @@ from typing import Dict, Optional, Union import clr_loader -__all__ = ["set_runtime", "set_default_runtime", "load"] +__all__ = ["set_runtime", "set_runtime_from_env", "load"] _RUNTIME: Optional[clr_loader.Runtime] = None _LOADER_ASSEMBLY: Optional[clr_loader.wrappers.Assembly] = None @@ -30,7 +30,7 @@ def set_runtime(runtime: Union[clr_loader.Runtime, str], **params: str) -> None: def _get_params_from_env(prefix: str) -> Dict[str, str]: from os import environ - full_prefix = f"PYTHONNET_{prefix.upper()}" + full_prefix = f"PYTHONNET_{prefix.upper()}_" len_ = len(full_prefix) env_vars = { @@ -63,8 +63,8 @@ def _create_runtime_from_spec( raise RuntimeError(f"Invalid runtime name: '{spec}'") -def set_default_runtime() -> None: - """Set up the default runtime +def set_runtime_from_env() -> None: + """Set up the runtime using the environment This will use the environment variable PYTHONNET_RUNTIME to decide the runtime to use, which may be one of netfx, coreclr or mono. The parameters @@ -80,16 +80,13 @@ def set_default_runtime() -> None: """ from os import environ - print("Set default RUNTIME") - raise RuntimeError("Shouldn't be called here") - spec = environ.get("PYTHONNET_RUNTIME", "default") runtime = _create_runtime_from_spec(spec) set_runtime(runtime) def load( - runtime: Union[clr_loader.Runtime, str] = "default", **params: Dict[str, str] + runtime: Union[clr_loader.Runtime, str, None] = None, **params: str ) -> None: """Load Python.NET in the specified runtime @@ -102,7 +99,10 @@ def load( return if _RUNTIME is None: - set_runtime(runtime, **params) + if runtime is None: + set_runtime_from_env() + else: + set_runtime(runtime, **params) if _RUNTIME is None: raise RuntimeError("No valid runtime selected") diff --git a/src/embed_tests/dynamic.cs b/src/embed_tests/dynamic.cs index 0a181231c..6e3bfc4cb 100644 --- a/src/embed_tests/dynamic.cs +++ b/src/embed_tests/dynamic.cs @@ -128,6 +128,28 @@ public void PassPyObjectInNet() Assert.IsTrue(sys.testattr1.Equals(sys.testattr2)); } + // regression test for https://github.com/pythonnet/pythonnet/issues/1848 + [Test] + public void EnumEquality() + { + using var scope = Py.CreateScope(); + scope.Exec(@" +import enum + +class MyEnum(enum.IntEnum): + OK = 1 + ERROR = 2 + +def get_status(): + return MyEnum.OK +" +); + + dynamic MyEnum = scope.Get("MyEnum"); + dynamic status = scope.Get("get_status").Invoke(); + Assert.IsTrue(status == MyEnum.OK); + } + // regression test for https://github.com/pythonnet/pythonnet/issues/1680 [Test] public void ForEach() diff --git a/src/embed_tests/packages.config b/src/embed_tests/packages.config deleted file mode 100644 index 590eaef8c..000000000 --- a/src/embed_tests/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/runtime/AssemblyManager.cs b/src/runtime/AssemblyManager.cs index d09d2d76e..a8bbd1f6c 100644 --- a/src/runtime/AssemblyManager.cs +++ b/src/runtime/AssemblyManager.cs @@ -334,7 +334,7 @@ public static bool IsValidNamespace(string name) } /// - /// Returns an IEnumerable containing the namepsaces exported + /// Returns an enumerable collection containing the namepsaces exported /// by loaded assemblies in the current app domain. /// public static IEnumerable GetNamespaces () diff --git a/src/runtime/ClassManager.cs b/src/runtime/ClassManager.cs index 6c5558f3a..79ab20e82 100644 --- a/src/runtime/ClassManager.cs +++ b/src/runtime/ClassManager.cs @@ -87,7 +87,7 @@ internal static ClassManagerState SaveRuntimeData() if ((Runtime.PyDict_DelItemString(dict.Borrow(), member) == -1) && (Exceptions.ExceptionMatches(Exceptions.KeyError))) { - // Trying to remove a key that's not in the dictionary + // Trying to remove a key that's not in the dictionary // raises an error. We don't care about it. Runtime.PyErr_Clear(); } @@ -215,7 +215,7 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p impl.indexer = info.indexer; impl.richcompare.Clear(); - + // Finally, initialize the class __dict__ and return the object. using var newDict = Runtime.PyObject_GenericGetDict(pyType.Reference); BorrowedReference dict = newDict.Borrow(); @@ -271,6 +271,15 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, doc.Borrow()); } } + + if (Runtime.PySequence_Contains(dict, PyIdentifier.__doc__) != 1) + { + // Ensure that at least some doc string is set + using var fallbackDoc = Runtime.PyString_FromString( + $"Python wrapper for .NET type {type}" + ); + Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, fallbackDoc.Borrow()); + } } doc.Dispose(); @@ -562,7 +571,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl) return ci; } - + /// /// This class owns references to PyObjects in the `members` member. /// The caller has responsibility to DECREF them. diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index f86ba7900..e1820f05b 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -361,7 +361,7 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType, // conversions (Python string -> managed string). if (obType == objectType) { - if (Runtime.IsStringType(value)) + if (Runtime.PyString_Check(value)) { return ToPrimitive(value, stringType, out result, setError); } diff --git a/src/runtime/Finalizer.cs b/src/runtime/Finalizer.cs index e796cacd1..f4b465ecb 100644 --- a/src/runtime/Finalizer.cs +++ b/src/runtime/Finalizer.cs @@ -106,6 +106,7 @@ internal IncorrectRefCountException(IntPtr ptr) #endregion + [ForbidPythonThreads] public void Collect() => this.DisposeAll(); internal void ThrottledCollect() diff --git a/src/runtime/Native/NewReference.cs b/src/runtime/Native/NewReference.cs index 25145fc4f..f7a030818 100644 --- a/src/runtime/Native/NewReference.cs +++ b/src/runtime/Native/NewReference.cs @@ -47,7 +47,7 @@ public PyObject MoveToPyObject() /// public NewReference Move() { - var result = new NewReference(this); + var result = DangerousFromPointer(this.DangerousGetAddress()); this.pointer = default; return result; } diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index fad5b9da8..5072f23cd 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -23,6 +23,8 @@ true snupkg + True + ..\pythonnet.snk true diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs index 6e9c4d1f1..e5879ae67 100644 --- a/src/runtime/PythonEngine.cs +++ b/src/runtime/PythonEngine.cs @@ -228,6 +228,7 @@ public static void Initialize(IEnumerable args, bool setSysArgv = true, Assembly assembly = Assembly.GetExecutingAssembly(); // add the contents of clr.py to the module string clr_py = assembly.ReadStringResource("clr.py"); + Exec(clr_py, module_globals, locals.Reference); LoadSubmodule(module_globals, "clr.interop", "interop.py"); @@ -237,14 +238,22 @@ public static void Initialize(IEnumerable args, bool setSysArgv = true, // add the imported module to the clr module, and copy the API functions // and decorators into the main clr module. Runtime.PyDict_SetItemString(clr_dict, "_extras", module); + + // append version + var version = typeof(PythonEngine) + .Assembly + .GetCustomAttribute() + .InformationalVersion; + using var versionObj = Runtime.PyString_FromString(version); + Runtime.PyDict_SetItemString(clr_dict, "__version__", versionObj.Borrow()); + using var keys = locals.Keys(); foreach (PyObject key in keys) { - if (!key.ToString()!.StartsWith("_") || key.ToString()!.Equals("__version__")) + if (!key.ToString()!.StartsWith("_")) { - PyObject value = locals[key]; + using PyObject value = locals[key]; Runtime.PyDict_SetItem(clr_dict, key.Reference, value.Reference); - value.Dispose(); } key.Dispose(); } diff --git a/src/runtime/PythonTypes/PyObject.cs b/src/runtime/PythonTypes/PyObject.cs index cfd3e7158..3d48e22ed 100644 --- a/src/runtime/PythonTypes/PyObject.cs +++ b/src/runtime/PythonTypes/PyObject.cs @@ -1075,12 +1075,9 @@ public virtual bool Equals(PyObject? other) { return true; } - int r = Runtime.PyObject_Compare(this, other); - if (Exceptions.ErrorOccurred()) - { - throw PythonException.ThrowLastAsClrException(); - } - return r == 0; + int result = Runtime.PyObject_RichCompareBool(obj, other.obj, Runtime.Py_EQ); + if (result < 0) throw PythonException.ThrowLastAsClrException(); + return result != 0; } @@ -1304,6 +1301,18 @@ public override bool TryConvert(ConvertBinder binder, out object? result) return false; } + private bool TryCompare(PyObject arg, int op, out object @out) + { + int result = Runtime.PyObject_RichCompareBool(this.obj, arg.obj, op); + @out = result != 0; + if (result < 0) + { + Exceptions.Clear(); + return false; + } + return true; + } + public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object? result) { using var _ = Py.GIL(); @@ -1352,11 +1361,9 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg res = Runtime.PyNumber_InPlaceXor(this.obj, ((PyObject)arg).obj); break; case ExpressionType.GreaterThan: - result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) > 0; - return true; + return this.TryCompare((PyObject)arg, Runtime.Py_GT, out result); case ExpressionType.GreaterThanOrEqual: - result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) >= 0; - return true; + return this.TryCompare((PyObject)arg, Runtime.Py_GE, out result); case ExpressionType.LeftShift: res = Runtime.PyNumber_Lshift(this.obj, ((PyObject)arg).obj); break; @@ -1364,11 +1371,9 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg res = Runtime.PyNumber_InPlaceLshift(this.obj, ((PyObject)arg).obj); break; case ExpressionType.LessThan: - result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) < 0; - return true; + return this.TryCompare((PyObject)arg, Runtime.Py_LT, out result); case ExpressionType.LessThanOrEqual: - result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) <= 0; - return true; + return this.TryCompare((PyObject)arg, Runtime.Py_LE, out result); case ExpressionType.Modulo: res = Runtime.PyNumber_Remainder(this.obj, ((PyObject)arg).obj); break; @@ -1376,8 +1381,9 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg res = Runtime.PyNumber_InPlaceRemainder(this.obj, ((PyObject)arg).obj); break; case ExpressionType.NotEqual: - result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) != 0; - return true; + return this.TryCompare((PyObject)arg, Runtime.Py_NE, out result); + case ExpressionType.Equal: + return this.TryCompare((PyObject)arg, Runtime.Py_EQ, out result); case ExpressionType.Or: res = Runtime.PyNumber_Or(this.obj, ((PyObject)arg).obj); break; @@ -1402,6 +1408,40 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg return true; } + public static bool operator ==(PyObject? a, PyObject? b) + { + if (a is null && b is null) + { + return true; + } + if (a is null || b is null) + { + return false; + } + + using var _ = Py.GIL(); + int result = Runtime.PyObject_RichCompareBool(a.obj, b.obj, Runtime.Py_EQ); + if (result < 0) throw PythonException.ThrowLastAsClrException(); + return result != 0; + } + + public static bool operator !=(PyObject? a, PyObject? b) + { + if (a is null && b is null) + { + return false; + } + if (a is null || b is null) + { + return true; + } + + using var _ = Py.GIL(); + int result = Runtime.PyObject_RichCompareBool(a.obj, b.obj, Runtime.Py_NE); + if (result < 0) throw PythonException.ThrowLastAsClrException(); + return result != 0; + } + // Workaround for https://bugzilla.xamarin.com/show_bug.cgi?id=41509 // See https://github.com/pythonnet/pythonnet/pull/219 internal static object? CheckNone(PyObject pyObj) @@ -1436,14 +1476,17 @@ public override bool TryUnaryOperation(UnaryOperationBinder binder, out object? case ExpressionType.Not: r = Runtime.PyObject_Not(this.obj); result = r == 1; + if (r == -1) Exceptions.Clear(); return r != -1; case ExpressionType.IsFalse: r = Runtime.PyObject_IsTrue(this.obj); result = r == 0; + if (r == -1) Exceptions.Clear(); return r != -1; case ExpressionType.IsTrue: r = Runtime.PyObject_IsTrue(this.obj); result = r == 1; + if (r == -1) Exceptions.Clear(); return r != -1; case ExpressionType.Decrement: case ExpressionType.Increment: diff --git a/src/runtime/Resources/clr.py b/src/runtime/Resources/clr.py index 2254e7430..d4330a4d5 100644 --- a/src/runtime/Resources/clr.py +++ b/src/runtime/Resources/clr.py @@ -2,8 +2,6 @@ Code in this module gets loaded into the main clr module. """ -__version__ = "3.0.0dev" - class clrproperty(object): """ diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 6ad1d459f..4dc904f43 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -59,6 +59,11 @@ private static string GetDefaultDllName(Version version) internal static bool TypeManagerInitialized => _typesInitialized; internal static readonly bool Is32Bit = IntPtr.Size == 4; + // Available in newer .NET Core versions (>= 5) as IntPtr.MaxValue etc. + internal static readonly long IntPtrMaxValue = Is32Bit ? Int32.MaxValue : Int64.MaxValue; + internal static readonly long IntPtrMinValue = Is32Bit ? Int32.MinValue : Int64.MinValue; + internal static readonly ulong UIntPtrMaxValue = Is32Bit ? UInt32.MaxValue : UInt64.MaxValue; + // .NET core: System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows) internal static bool IsWindows = Environment.OSVersion.Platform == PlatformID.Win32NT; @@ -94,6 +99,7 @@ internal static int GetRun() internal static bool HostedInPython; internal static bool ProcessIsTerminating; + /// /// Initialize the runtime... /// /// Always call this method from the Main thread. After the @@ -354,6 +360,7 @@ static bool TryCollectingGarbage(int runs, bool forceBreakLoops) /// /// Total number of GC loops to run /// true if a steady state was reached upon the requested number of tries (e.g. on the last try no objects were collected). + [ForbidPythonThreads] public static bool TryCollectingGarbage(int runs) => TryCollectingGarbage(runs, forceBreakLoops: false); @@ -962,31 +969,6 @@ internal static IntPtr PyObject_CallObject(IntPtr pointer, IntPtr args) internal static int PyObject_RichCompareBool(BorrowedReference value1, BorrowedReference value2, int opid) => Delegates.PyObject_RichCompareBool(value1, value2, opid); - internal static int PyObject_Compare(BorrowedReference value1, BorrowedReference value2) - { - int res; - res = PyObject_RichCompareBool(value1, value2, Py_LT); - if (-1 == res) - return -1; - else if (1 == res) - return -1; - - res = PyObject_RichCompareBool(value1, value2, Py_EQ); - if (-1 == res) - return -1; - else if (1 == res) - return 0; - - res = PyObject_RichCompareBool(value1, value2, Py_GT); - if (-1 == res) - return -1; - else if (1 == res) - return 1; - - Exceptions.SetError(Exceptions.SystemError, "Error comparing objects"); - return -1; - } - internal static int PyObject_IsInstance(BorrowedReference ob, BorrowedReference type) => Delegates.PyObject_IsInstance(ob, type); @@ -1306,13 +1288,6 @@ internal static bool PyFloat_Check(BorrowedReference ob) //==================================================================== // Python string API //==================================================================== - internal static bool IsStringType(BorrowedReference op) - { - BorrowedReference t = PyObject_TYPE(op); - return (t == PyStringType) - || (t == PyUnicodeType); - } - internal static bool PyString_Check(BorrowedReference ob) { return PyObject_TYPE(ob) == PyStringType; diff --git a/src/runtime/Types/ArrayObject.cs b/src/runtime/Types/ArrayObject.cs index bda717e56..b95934baf 100644 --- a/src/runtime/Types/ArrayObject.cs +++ b/src/runtime/Types/ArrayObject.cs @@ -520,7 +520,7 @@ static IntPtr AllocateBufferProcs() #endregion /// - /// + /// /// public static void InitializeSlots(PyType type, ISet initialized, SlotsHolder slotsHolder) { diff --git a/src/runtime/Types/ClassObject.cs b/src/runtime/Types/ClassObject.cs index 474e9dd7b..cc42039e8 100644 --- a/src/runtime/Types/ClassObject.cs +++ b/src/runtime/Types/ClassObject.cs @@ -70,22 +70,9 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo // Primitive types do not have constructors, but they look like // they do from Python. If the ClassObject represents one of the // convertible primitive types, just convert the arg directly. - if (type.IsPrimitive || type == typeof(string)) + if (type.IsPrimitive) { - if (Runtime.PyTuple_Size(args) != 1) - { - Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments"); - return default; - } - - BorrowedReference op = Runtime.PyTuple_GetItem(args, 0); - - if (!Converter.ToManaged(op, type, out var result, true)) - { - return default; - } - - return CLRObject.GetReference(result!, tp); + return NewPrimitive(tp, args, type); } if (type.IsAbstract) @@ -99,6 +86,11 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo return NewEnum(type, args, tp); } + if (type == typeof(string)) + { + return NewString(args, tp); + } + if (IsGenericNullable(type)) { // Nullable has special handling in .NET runtime. @@ -112,6 +104,166 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo return self.NewObjectToPython(obj, tp); } + /// + /// Construct a new .NET String object from Python args + /// + /// This manual implementation of all individual relevant constructors + /// is required because System.String can't be allocated uninitialized. + /// + /// Additionally, it implements `String(pythonStr)` + /// + private static NewReference NewString(BorrowedReference args, BorrowedReference tp) + { + var argCount = Runtime.PyTuple_Size(args); + + string? result = null; + if (argCount == 1) + { + BorrowedReference ob = Runtime.PyTuple_GetItem(args, 0); + if (Runtime.PyString_Check(ob)) + { + if (Runtime.GetManagedString(ob) is string val) + result = val; + } + else if (Converter.ToManagedValue(ob, typeof(char[]), out object? arr, false)) + { + result = new String((char[])arr!); + } + } + else if (argCount == 2) + { + BorrowedReference p1 = Runtime.PyTuple_GetItem(args, 0); + BorrowedReference p2 = Runtime.PyTuple_GetItem(args, 1); + + if ( + Converter.ToManagedValue(p1, typeof(char), out object? chr, false) && + Converter.ToManagedValue(p2, typeof(int), out object? count, false) + ) + { + result = new String((char)chr!, (int)count!); + } + } + else if (argCount == 3) + { + BorrowedReference p1 = Runtime.PyTuple_GetItem(args, 0); + BorrowedReference p2 = Runtime.PyTuple_GetItem(args, 1); + BorrowedReference p3 = Runtime.PyTuple_GetItem(args, 2); + + if ( + Converter.ToManagedValue(p1, typeof(char[]), out object? arr, false) && + Converter.ToManagedValue(p2, typeof(int), out object? offset, false) && + Converter.ToManagedValue(p3, typeof(int), out object? length, false) + ) + { + result = new String((char[])arr!, (int)offset!, (int)length!); + } + } + + if (result != null) + return CLRObject.GetReference(result!, tp); + + Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments"); + return default; + } + + /// + /// Create a new Python object for a primitive type + /// + /// The primitive types are Boolean, Byte, SByte, Int16, UInt16, + /// Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, + /// and Single. + /// + /// All numeric types and Boolean can be handled by a simple + /// conversion, (U)IntPtr has to be handled separately as we + /// do not want to convert them automically to/from integers. + /// + /// .NET type to construct + /// Corresponding Python type + /// Constructor arguments + private static NewReference NewPrimitive(BorrowedReference tp, BorrowedReference args, Type type) + { + // TODO: Handle IntPtr + if (Runtime.PyTuple_Size(args) != 1) + { + Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments"); + return default; + } + + BorrowedReference op = Runtime.PyTuple_GetItem(args, 0); + object? result = null; + + if (type == typeof(IntPtr)) + { + if (ManagedType.GetManagedObject(op) is CLRObject clrObject) + { + switch (clrObject.inst) + { + case nint val: + result = new IntPtr(val); + break; + case Int64 val: + result = new IntPtr(val); + break; + case Int32 val: + result = new IntPtr(val); + break; + } + } + else if (Runtime.PyInt_Check(op)) + { + long? num = Runtime.PyLong_AsLongLong(op); + if (num is long n && n >= Runtime.IntPtrMinValue && n <= Runtime.IntPtrMaxValue) + { + result = new IntPtr(n); + } + else + { + Exceptions.SetError(Exceptions.OverflowError, "value not in range for IntPtr"); + return default; + } + } + } + + if (type == typeof(UIntPtr)) + { + if (ManagedType.GetManagedObject(op) is CLRObject clrObject) + { + switch (clrObject.inst) + { + case nuint val: + result = new UIntPtr(val); + break; + case UInt64 val: + result = new UIntPtr(val); + break; + case UInt32 val: + result = new UIntPtr(val); + break; + } + } + else if (Runtime.PyInt_Check(op)) + { + ulong? num = Runtime.PyLong_AsUnsignedLongLong(op); + if (num is ulong n && n <= Runtime.UIntPtrMaxValue) + { + result = new UIntPtr(n); + } + else + { + Exceptions.SetError(Exceptions.OverflowError, "value not in range for UIntPtr"); + return default; + } + } + } + + if (result == null && !Converter.ToManaged(op, type, out result, true)) + { + return default; + } + + return CLRObject.GetReference(result!, tp); + } + protected virtual void SetTypeNewSlot(BorrowedReference pyType, SlotsHolder slotsHolder) { TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.tp_new, new Interop.BBB_N(tp_new_impl), slotsHolder); diff --git a/src/runtime/Types/EventBinding.cs b/src/runtime/Types/EventBinding.cs index 9eb2382ec..5c47d4aab 100644 --- a/src/runtime/Types/EventBinding.cs +++ b/src/runtime/Types/EventBinding.cs @@ -70,7 +70,6 @@ public static NewReference nb_inplace_subtract(BorrowedReference ob, BorrowedRef return new NewReference(ob); } - /// public static int tp_descr_set(BorrowedReference ds, BorrowedReference ob, BorrowedReference val) => EventObject.tp_descr_set(ds, ob, val); diff --git a/src/runtime/Types/ReflectedClrType.cs b/src/runtime/Types/ReflectedClrType.cs index 2e8f95924..b787939be 100644 --- a/src/runtime/Types/ReflectedClrType.cs +++ b/src/runtime/Types/ReflectedClrType.cs @@ -22,7 +22,6 @@ internal ReflectedClrType(BorrowedReference original) : base(original) { } /// /// /// Returned might be partially initialized. - /// If you need fully initialized type, use /// public static ReflectedClrType GetOrCreate(Type type) { diff --git a/src/testing/conversiontest.cs b/src/testing/conversiontest.cs index 7a00f139e..272bb74c2 100644 --- a/src/testing/conversiontest.cs +++ b/src/testing/conversiontest.cs @@ -1,5 +1,6 @@ namespace Python.Test { + using System; using System.Collections.Generic; /// @@ -26,6 +27,8 @@ public ConversionTest() public ulong UInt64Field = 0; public float SingleField = 0.0F; public double DoubleField = 0.0; + public IntPtr IntPtrField = IntPtr.Zero; + public UIntPtr UIntPtrField = UIntPtr.Zero; public decimal DecimalField = 0; public string StringField; public ShortEnum EnumField; @@ -42,7 +45,7 @@ public ConversionTest() } - + public interface ISpam { @@ -63,7 +66,7 @@ public string GetValue() return value; } } - + public class UnicodeString { public string value = "안녕"; diff --git a/tests/test_constructors.py b/tests/test_constructors.py index 8e7ef2794..3e0b1bb93 100644 --- a/tests/test_constructors.py +++ b/tests/test_constructors.py @@ -3,6 +3,7 @@ """Test CLR class constructor support.""" import pytest +import sys import System @@ -69,3 +70,33 @@ def test_default_constructor_fallback(): with pytest.raises(TypeError): ob = DefaultConstructorMatching("2") + + +def test_constructor_leak(): + from System import Uri + from Python.Runtime import Runtime + + uri = Uri("http://www.python.org") + Runtime.TryCollectingGarbage(20) + ref_count = sys.getrefcount(uri) + + # check disabled due to GC uncertainty + # assert ref_count == 1 + + + +def test_string_constructor(): + from System import String, Char, Array + + ob = String('A', 10) + assert ob == 'A' * 10 + + arr = Array[Char](10) + for i in range(10): + arr[i] = Char(str(i)) + + ob = String(arr) + assert ob == "0123456789" + + ob = String(arr, 5, 4) + assert ob == "5678" diff --git a/tests/test_conversion.py b/tests/test_conversion.py index 6693d8000..a5b4c6fd9 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -25,7 +25,7 @@ def test_bool_conversion(): with pytest.raises(TypeError): ob.BooleanField = 1 - + with pytest.raises(TypeError): ob.BooleanField = 0 @@ -679,3 +679,37 @@ def test_iconvertible_conversion(): assert 1024 == change_type(1024, System.Int32) assert 1024 == change_type(1024, System.Int64) assert 1024 == change_type(1024, System.Int16) + +def test_intptr_construction(): + from System import IntPtr, UIntPtr, Int64, UInt64 + from ctypes import sizeof, c_void_p + + ptr_size = sizeof(c_void_p) + max_intptr = 2 ** (ptr_size * 8 - 1) - 1 + min_intptr = -max_intptr - 1 + max_uintptr = 2 ** (ptr_size * 8) - 1 + min_uintptr = 0 + + ob = ConversionTest() + + assert ob.IntPtrField == IntPtr.Zero + assert ob.UIntPtrField == UIntPtr.Zero + + for v in [0, -1, 1024, max_intptr, min_intptr]: + ob.IntPtrField = IntPtr(Int64(v)) + assert ob.IntPtrField == IntPtr(v) + assert ob.IntPtrField.ToInt64() == v + + for v in [min_intptr - 1, max_intptr + 1]: + with pytest.raises(OverflowError): + IntPtr(v) + + for v in [0, 1024, min_uintptr, max_uintptr, max_intptr]: + ob.UIntPtrField = UIntPtr(UInt64(v)) + assert ob.UIntPtrField == UIntPtr(v) + assert ob.UIntPtrField.ToUInt64() == v + + for v in [min_uintptr - 1, max_uintptr + 1, min_intptr]: + with pytest.raises(OverflowError): + UIntPtr(v) + diff --git a/tests/test_docstring.py b/tests/test_docstring.py index 640a61915..36c925a74 100644 --- a/tests/test_docstring.py +++ b/tests/test_docstring.py @@ -25,3 +25,10 @@ def test_doc_without_ctor(): assert DocWithoutCtorTest.__doc__ == 'DocWithoutCtorTest Class' assert DocWithoutCtorTest.TestMethod.__doc__ == 'DocWithoutCtorTest TestMethod' assert DocWithoutCtorTest.StaticTestMethod.__doc__ == 'DocWithoutCtorTest StaticTestMethod' + + +def test_doc_primitve(): + from System import Int64, String + + assert Int64.__doc__ is not None + assert String.__doc__ is not None diff --git a/tests/test_enum.py b/tests/test_enum.py index 981fb735c..f24f95b36 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -15,7 +15,6 @@ def test_enum_standard_attrs(): assert DayOfWeek.__name__ == 'DayOfWeek' assert DayOfWeek.__module__ == 'System' assert isinstance(DayOfWeek.__dict__, DictProxyType) - assert DayOfWeek.__doc__ is None def test_enum_get_member(): @@ -139,7 +138,7 @@ def test_enum_undefined_value(): # This should fail because our test enum doesn't have it. with pytest.raises(ValueError): Test.FieldTest().EnumField = Test.ShortEnum(20) - + # explicitly permit undefined values Test.FieldTest().EnumField = Test.ShortEnum(20, True) @@ -157,6 +156,6 @@ def test_enum_conversion(): with pytest.raises(TypeError): Test.FieldTest().EnumField = "str" - + with pytest.raises(TypeError): Test.FieldTest().EnumField = 1 diff --git a/version.txt b/version.txt index e16bcc8be..dc72b3783 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.0-dev1 +3.0.0-rc4