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/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/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/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/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 1eeb96b54..20bef23d4 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;
@@ -1281,13 +1286,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/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/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..6d8f00c12 100644
--- a/tests/test_constructors.py
+++ b/tests/test_constructors.py
@@ -69,3 +69,20 @@ def test_default_constructor_fallback():
with pytest.raises(TypeError):
ob = DefaultConstructorMatching("2")
+
+
+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 d15be84db..b9eb748c0 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-3.0.0-rc2
+3.0.0-rc3