diff --git a/src/CSnakes.Runtime/CPython/Import.cs b/src/CSnakes.Runtime/CPython/Import.cs index ff8f4f0c..e8389179 100644 --- a/src/CSnakes.Runtime/CPython/Import.cs +++ b/src/CSnakes.Runtime/CPython/Import.cs @@ -15,9 +15,24 @@ internal static PyObject Import(string name) nint pyName = AsPyUnicodeObject(name); nint module = PyImport_Import(pyName); Py_DecRefRaw(pyName); - return new(module); + return PyObject.Create(module); } + protected static nint GetBuiltin(string name) + { + nint pyName = AsPyUnicodeObject("builtins"); + nint pyAttrName = AsPyUnicodeObject(name); + nint module = PyImport_Import(pyName); + nint attr = PyObject_GetAttrRaw(module, pyAttrName); + if (attr == IntPtr.Zero) + { + PyObject.ThrowPythonExceptionAsClrException(); + } + Py_DecRefRaw(pyName); + Py_DecRefRaw(pyAttrName); + return attr; + } + /// /// Import and return a reference to the module `name` /// diff --git a/src/CSnakes.Runtime/CPython/Init.cs b/src/CSnakes.Runtime/CPython/Init.cs index c32d54e6..3119a98f 100644 --- a/src/CSnakes.Runtime/CPython/Init.cs +++ b/src/CSnakes.Runtime/CPython/Init.cs @@ -1,4 +1,5 @@ using CSnakes.Runtime.Python; +using CSnakes.Runtime.Python.Interns; using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; @@ -79,11 +80,7 @@ private void InitializeEmbeddedPython() PyDictType = GetTypeRaw(PyDict_New()); PyBytesType = GetTypeRaw(PyBytes_FromByteSpan(new byte[] { })); ItemsStrIntern = AsPyUnicodeObject("items"); - - // Import builtins module - var builtinsMod = Import("builtins"); - PyNone = GetAttr(builtinsMod, "None"); - Py_DecRef(builtinsMod); + PyNone = GetBuiltin("None"); } PyEval_SaveThread(); } diff --git a/src/CSnakes.Runtime/CPython/None.cs b/src/CSnakes.Runtime/CPython/None.cs index eb3fb93c..a56ebac1 100644 --- a/src/CSnakes.Runtime/CPython/None.cs +++ b/src/CSnakes.Runtime/CPython/None.cs @@ -1,4 +1,5 @@ -namespace CSnakes.Runtime.CPython; +using CSnakes.Runtime.Python; +namespace CSnakes.Runtime.CPython; internal unsafe partial class CPythonAPI { @@ -10,7 +11,16 @@ internal unsafe partial class CPythonAPI /// A new reference to None. In newer versions of Python, None is immortal anyway. internal static nint GetNone() { + if (PyNone == IntPtr.Zero) + { + throw new InvalidOperationException("Python is not initialized. You cannot call this method outside of a Python Environment context."); + } Py_IncRefRaw(PyNone); return PyNone; } + + internal static bool IsNone(PyObject o) + { + return PyNone == o.DangerousGetHandle(); + } } diff --git a/src/CSnakes.Runtime/CPython/Object.cs b/src/CSnakes.Runtime/CPython/Object.cs index d4c59056..3fcc9a6e 100644 --- a/src/CSnakes.Runtime/CPython/Object.cs +++ b/src/CSnakes.Runtime/CPython/Object.cs @@ -106,6 +106,9 @@ internal static bool HasAttr(PyObject ob, string name) [LibraryImport(PythonLibraryName)] internal static partial IntPtr PyObject_GetAttr(PyObject ob, IntPtr attr); + [LibraryImport(PythonLibraryName, EntryPoint = "PyObject_GetAttr")] + private static partial IntPtr PyObject_GetAttrRaw(IntPtr ob, IntPtr attr); + /// /// Does the object ob have the attr `attr`? /// diff --git a/src/CSnakes.Runtime/PyObjectTypeConverter.BigInteger.cs b/src/CSnakes.Runtime/PyObjectTypeConverter.BigInteger.cs index f015442d..d532eca6 100644 --- a/src/CSnakes.Runtime/PyObjectTypeConverter.BigInteger.cs +++ b/src/CSnakes.Runtime/PyObjectTypeConverter.BigInteger.cs @@ -15,7 +15,7 @@ internal partial class PyObjectTypeConverter private PyObject ConvertFromBigInteger(ITypeDescriptorContext? context, CultureInfo? culture, BigInteger integer) { - using PyObject pyUnicode = new PyObject(CPythonAPI.AsPyUnicodeObject(integer.ToString())); - return new PyObject(CPythonAPI.PyLong_FromUnicodeObject(pyUnicode, 10)); + using PyObject pyUnicode = PyObject.Create(CPythonAPI.AsPyUnicodeObject(integer.ToString())); + return PyObject.Create(CPythonAPI.PyLong_FromUnicodeObject(pyUnicode, 10)); } } \ No newline at end of file diff --git a/src/CSnakes.Runtime/PyObjectTypeConverter.Dictionary.cs b/src/CSnakes.Runtime/PyObjectTypeConverter.Dictionary.cs index d9d9caab..f737e059 100644 --- a/src/CSnakes.Runtime/PyObjectTypeConverter.Dictionary.cs +++ b/src/CSnakes.Runtime/PyObjectTypeConverter.Dictionary.cs @@ -11,42 +11,42 @@ internal partial class PyObjectTypeConverter { private object? ConvertToDictionary(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture, bool useMappingProtocol = false) { - using PyObject items = useMappingProtocol ? new(CPythonAPI.PyMapping_Items(pyObject)) : new(CPythonAPI.PyDict_Items(pyObject)); - - Type item1Type = destinationType.GetGenericArguments()[0]; - Type item2Type = destinationType.GetGenericArguments()[1]; - - if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo)) - { - Type dictType = typeof(Dictionary<,>).MakeGenericType(item1Type, item2Type); + using PyObject items = useMappingProtocol ? PyObject.Create(CPythonAPI.PyMapping_Items(pyObject)) : PyObject.Create(CPythonAPI.PyDict_Items(pyObject)); + + Type item1Type = destinationType.GetGenericArguments()[0]; + Type item2Type = destinationType.GetGenericArguments()[1]; + + if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo)) + { + Type dictType = typeof(Dictionary<,>).MakeGenericType(item1Type, item2Type); Type returnType = typeof(ReadOnlyDictionary<,>).MakeGenericType(item1Type, item2Type); - - typeInfo = new(returnType.GetConstructor([dictType])!, dictType.GetConstructor([])!); - knownDynamicTypes[destinationType] = typeInfo; - } + + typeInfo = new(returnType.GetConstructor([dictType])!, dictType.GetConstructor([])!); + knownDynamicTypes[destinationType] = typeInfo; + } IDictionary dict = (IDictionary)typeInfo.TransientTypeConstructor!.Invoke([]); nint itemsLength = CPythonAPI.PyList_Size(items); for (nint i = 0; i < itemsLength; i++) { - using PyObject item = new(CPythonAPI.PyList_GetItem(items, i)); + using PyObject item = PyObject.Create(CPythonAPI.PyList_GetItem(items, i)); - using PyObject item1 = new(CPythonAPI.PyTuple_GetItem(item, 0)); - using PyObject item2 = new(CPythonAPI.PyTuple_GetItem(item, 1)); + using PyObject item1 = PyObject.Create(CPythonAPI.PyTuple_GetItem(item, 0)); + using PyObject item2 = PyObject.Create(CPythonAPI.PyTuple_GetItem(item, 1)); object? convertedItem1 = ConvertTo(context, culture, item1, item1Type); object? convertedItem2 = ConvertTo(context, culture, item2, item2Type); dict.Add(convertedItem1!, convertedItem2); - } - + } + return typeInfo.ReturnTypeConstructor.Invoke([dict]); } private PyObject ConvertFromDictionary(ITypeDescriptorContext? context, CultureInfo? culture, IDictionary dictionary) { - PyObject pyDict = new(CPythonAPI.PyDict_New()); + PyObject pyDict = PyObject.Create(CPythonAPI.PyDict_New()); foreach (DictionaryEntry kvp in dictionary) { diff --git a/src/CSnakes.Runtime/PyObjectTypeConverter.List.cs b/src/CSnakes.Runtime/PyObjectTypeConverter.List.cs index 958f6581..b9ab3978 100644 --- a/src/CSnakes.Runtime/PyObjectTypeConverter.List.cs +++ b/src/CSnakes.Runtime/PyObjectTypeConverter.List.cs @@ -9,19 +9,19 @@ internal partial class PyObjectTypeConverter { private object? ConvertToList(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture) { - Type genericArgument = destinationType.GetGenericArguments()[0]; - - if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo)) - { - Type listType = typeof(List<>).MakeGenericType(genericArgument); - typeInfo = new(listType.GetConstructor([])!); - knownDynamicTypes[destinationType] = typeInfo; - } + Type genericArgument = destinationType.GetGenericArguments()[0]; + + if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo)) + { + Type listType = typeof(List<>).MakeGenericType(genericArgument); + typeInfo = new(listType.GetConstructor([])!); + knownDynamicTypes[destinationType] = typeInfo; + } IList list = (IList)typeInfo.ReturnTypeConstructor.Invoke([]); for (var i = 0; i < CPythonAPI.PyList_Size(pyObject); i++) { - using PyObject item = new(CPythonAPI.PyList_GetItem(pyObject, i)); + using PyObject item = PyObject.Create(CPythonAPI.PyList_GetItem(pyObject, i)); list.Add(ConvertTo(context, culture, item, genericArgument)); } @@ -30,20 +30,20 @@ internal partial class PyObjectTypeConverter private object? ConvertToListFromSequence(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture) { - Type genericArgument = destinationType.GetGenericArguments()[0]; - - if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo)) - { - Type listType = typeof(List<>).MakeGenericType(genericArgument); - typeInfo = new(listType.GetConstructor([])!); - knownDynamicTypes[destinationType] = typeInfo; - } + Type genericArgument = destinationType.GetGenericArguments()[0]; - IList list = (IList)typeInfo.ReturnTypeConstructor.Invoke([]); + if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo)) + { + Type listType = typeof(List<>).MakeGenericType(genericArgument); + typeInfo = new(listType.GetConstructor([])!); + knownDynamicTypes[destinationType] = typeInfo; + } + + IList list = (IList)typeInfo.ReturnTypeConstructor.Invoke([]); for (var i = 0; i < CPythonAPI.PySequence_Size(pyObject); i++) { - using PyObject item = new(CPythonAPI.PySequence_GetItem(pyObject, i)); + using PyObject item = PyObject.Create(CPythonAPI.PySequence_GetItem(pyObject, i)); list.Add(ConvertTo(context, culture, item, genericArgument)); } @@ -52,7 +52,7 @@ internal partial class PyObjectTypeConverter private PyObject ConvertFromList(ITypeDescriptorContext? context, CultureInfo? culture, IEnumerable e) { - PyObject pyList = new(CPythonAPI.PyList_New(0)); + PyObject pyList = PyObject.Create(CPythonAPI.PyList_New(0)); foreach (var item in e) { diff --git a/src/CSnakes.Runtime/PyObjectTypeConverter.Tuple.cs b/src/CSnakes.Runtime/PyObjectTypeConverter.Tuple.cs index ab552015..1460037c 100644 --- a/src/CSnakes.Runtime/PyObjectTypeConverter.Tuple.cs +++ b/src/CSnakes.Runtime/PyObjectTypeConverter.Tuple.cs @@ -35,7 +35,7 @@ private PyObject ConvertFromTuple(ITypeDescriptorContext? context, CultureInfo? var tupleValues = new List(); for (nint i = 0; i < CPythonAPI.PyTuple_Size(pyObj); i++) { - PyObject value = new(CPythonAPI.PyTuple_GetItem(pyObj, i)); + PyObject value = PyObject.Create(CPythonAPI.PyTuple_GetItem(pyObj, i)); tupleValues.Add(value); } diff --git a/src/CSnakes.Runtime/PyObjectTypeConverter.cs b/src/CSnakes.Runtime/PyObjectTypeConverter.cs index 54ffb3b5..42c3e92f 100644 --- a/src/CSnakes.Runtime/PyObjectTypeConverter.cs +++ b/src/CSnakes.Runtime/PyObjectTypeConverter.cs @@ -124,17 +124,18 @@ internal partial class PyObjectTypeConverter : TypeConverter public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) => value switch { - string str => new PyObject(CPythonAPI.AsPyUnicodeObject(str)), - byte[] bytes => new PyObject(CPythonAPI.PyBytes_FromByteSpan(bytes.AsSpan())), - long l => new PyObject(CPythonAPI.PyLong_FromLongLong(l)), - int i => new PyObject(CPythonAPI.PyLong_FromLong(i)), - bool b => new PyObject(CPythonAPI.PyBool_FromLong(b ? 1 : 0)), - double d => new PyObject(CPythonAPI.PyFloat_FromDouble(d)), + string str => PyObject.Create(CPythonAPI.AsPyUnicodeObject(str)), + byte[] bytes => PyObject.Create(CPythonAPI.PyBytes_FromByteSpan(bytes.AsSpan())), + long l => PyObject.Create(CPythonAPI.PyLong_FromLongLong(l)), + int i => PyObject.Create(CPythonAPI.PyLong_FromLong(i)), + bool b => PyObject.Create(CPythonAPI.PyBool_FromLong(b ? 1 : 0)), + double d => PyObject.Create(CPythonAPI.PyFloat_FromDouble(d)), IDictionary dictionary => ConvertFromDictionary(context, culture, dictionary), ITuple t => ConvertFromTuple(context, culture, t), IEnumerable e => ConvertFromList(context, culture, e), BigInteger b => ConvertFromBigInteger(context, culture, b), - null => new PyObject(CPythonAPI.GetNone()), + PyObject pyObject => pyObject.Clone(), + null => PyObject.None, _ => base.ConvertFrom(context, culture, value) }; @@ -177,7 +178,7 @@ private PyObject ToPython(object? o, ITypeDescriptorContext? context, CultureInf { if (o is null) { - return new PyObject(CPythonAPI.GetNone()); + return PyObject.None; } var result = ConvertFrom(context, culture, o); diff --git a/src/CSnakes.Runtime/Python/Interns/ImmortalPyObject.cs b/src/CSnakes.Runtime/Python/Interns/ImmortalPyObject.cs new file mode 100644 index 00000000..013b9bfa --- /dev/null +++ b/src/CSnakes.Runtime/Python/Interns/ImmortalPyObject.cs @@ -0,0 +1,16 @@ +namespace CSnakes.Runtime.Python.Interns; + +internal class ImmortalPyObject : PyObject +{ + internal ImmortalPyObject(nint handle) : base(handle) + { + } + + protected override bool ReleaseHandle() => true; + + + protected override void Dispose(bool disposing) + { + // I am immortal!! + } +} diff --git a/src/CSnakes.Runtime/Python/Interns/PyNoneObject.cs b/src/CSnakes.Runtime/Python/Interns/PyNoneObject.cs new file mode 100644 index 00000000..967e2d7a --- /dev/null +++ b/src/CSnakes.Runtime/Python/Interns/PyNoneObject.cs @@ -0,0 +1,18 @@ +using CSnakes.Runtime.CPython; + +namespace CSnakes.Runtime.Python.Interns; + +internal sealed class PyNoneObject : ImmortalPyObject +{ + public PyNoneObject() : base(CPythonAPI.GetNone()) + { + } + + public override bool IsNone() => true; + + public override string GetRepr() => ToString(); + + public override string ToString() => "None"; + + internal override PyObject Clone() => this; +} diff --git a/src/CSnakes.Runtime/Python/PyObject.cs b/src/CSnakes.Runtime/Python/PyObject.cs index d9cf334f..1f5e26dc 100644 --- a/src/CSnakes.Runtime/Python/PyObject.cs +++ b/src/CSnakes.Runtime/Python/PyObject.cs @@ -1,4 +1,5 @@ using CSnakes.Runtime.CPython; +using CSnakes.Runtime.Python.Interns; using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; @@ -12,7 +13,7 @@ public class PyObject : SafeHandle { private static readonly TypeConverter td = TypeDescriptor.GetConverter(typeof(PyObject)); - internal PyObject(IntPtr pyObject, bool ownsHandle = true) : base(pyObject, ownsHandle) + protected PyObject(IntPtr pyObject, bool ownsHandle = true) : base(pyObject, ownsHandle) { if (pyObject == IntPtr.Zero) { @@ -20,6 +21,13 @@ internal PyObject(IntPtr pyObject, bool ownsHandle = true) : base(pyObject, owns } } + internal static PyObject Create(IntPtr ptr) + { + if (None.DangerousGetHandle() == ptr) + return None; + return new PyObject(ptr); + } + public override bool IsInvalid => handle == IntPtr.Zero; protected override bool ReleaseHandle() @@ -63,13 +71,13 @@ internal static void ThrowPythonExceptionAsClrException() throw new InvalidDataException("An error occurred in Python, but no exception was set."); } - using var pyExceptionType = new PyObject(excType); + using var pyExceptionType = Create(excType); PyObject? pyExceptionTraceback = excTraceback == IntPtr.Zero ? null : new PyObject(excTraceback); var pyExceptionStr = string.Empty; if (excValue != IntPtr.Zero) { - using PyObject pyExceptionValue = new PyObject(excValue); + using PyObject pyExceptionValue = Create(excValue); pyExceptionStr = pyExceptionValue.ToString(); } ; @@ -97,7 +105,7 @@ private static void RaiseOnPythonNotInitialized() /// Get the type for the object. /// /// A new reference to the type field. - public PyObject GetPythonType() + public virtual PyObject GetPythonType() { RaiseOnPythonNotInitialized(); using (GIL.Acquire()) @@ -111,16 +119,16 @@ public PyObject GetPythonType() /// /// /// Attribute object (new ref) - public PyObject GetAttr(string name) + public virtual PyObject GetAttr(string name) { RaiseOnPythonNotInitialized(); using (GIL.Acquire()) { - return new PyObject(CPythonAPI.GetAttr(this, name)); + return Create(CPythonAPI.GetAttr(this, name)); } } - public bool HasAttr(string name) + public virtual bool HasAttr(string name) { RaiseOnPythonNotInitialized(); using (GIL.Acquire()) @@ -133,12 +141,12 @@ public bool HasAttr(string name) /// Get the iterator for the object. This is equivalent to iter(obj) in Python. /// /// The iterator object (new ref) - public PyObject GetIter() + public virtual PyObject GetIter() { RaiseOnPythonNotInitialized(); using (GIL.Acquire()) { - return new PyObject(CPythonAPI.PyObject_GetIter(this)); + return Create(CPythonAPI.PyObject_GetIter(this)); } } @@ -146,7 +154,7 @@ public PyObject GetIter() /// Get the results of the repr() function on the object. /// /// - public string GetRepr() + public virtual string GetRepr() { RaiseOnPythonNotInitialized(); using (GIL.Acquire()) @@ -157,6 +165,14 @@ public string GetRepr() } } + /// + /// Is the Python object None? + /// + /// true if None, else false + public virtual bool IsNone() => CPythonAPI.IsNone(this); + + public static PyObject None { get; } = new PyNoneObject(); + /// /// Call the object. Equivalent to (__call__)(args) /// All arguments are treated as positional. @@ -188,7 +204,7 @@ public PyObject CallWithArgs(PyObject[]? args = null) { using (GIL.Acquire()) { - return new PyObject(CPythonAPI.Call(this, argHandles)); + return Create(CPythonAPI.Call(this, argHandles)); } } finally { @@ -234,7 +250,7 @@ public PyObject CallWithKeywordArguments(PyObject[]? args = null, string[]? kwna { using (GIL.Acquire()) { - return new PyObject(CPythonAPI.Call(this, argHandles, kwnames, kwargHandles)); + return Create(CPythonAPI.Call(this, argHandles, kwnames, kwargHandles)); } } finally @@ -292,12 +308,12 @@ public T As() using (GIL.Acquire()) { return value is null ? - new PyObject(CPythonAPI.GetNone()) : + PyObject.None : (PyObject?)td.ConvertFrom(value); } } - internal PyObject Clone() + internal virtual PyObject Clone() { CPythonAPI.Py_IncRefRaw(handle); return new PyObject(handle); diff --git a/src/CSnakes.Runtime/Python/PyTuple.cs b/src/CSnakes.Runtime/Python/PyTuple.cs index 5d9fd633..c7484a47 100644 --- a/src/CSnakes.Runtime/Python/PyTuple.cs +++ b/src/CSnakes.Runtime/Python/PyTuple.cs @@ -18,7 +18,7 @@ public static PyObject CreateTuple(IEnumerable items) marshallers.Add(m); handles.Add(m.ToUnmanaged()); } - return new(CPythonAPI.PackTuple(handles.ToArray())); + return PyObject.Create(CPythonAPI.PackTuple(handles.ToArray())); } finally { diff --git a/src/CSnakes/Reflection/MethodReflection.cs b/src/CSnakes/Reflection/MethodReflection.cs index 58244ff5..a2fbb3b1 100644 --- a/src/CSnakes/Reflection/MethodReflection.cs +++ b/src/CSnakes/Reflection/MethodReflection.cs @@ -51,6 +51,21 @@ public static MethodDefinition FromMethod(PythonFunctionDefinition function, str { continue; } + bool needsConversion = true; // TODO: Skip .From for PyObject arguments. + ExpressionSyntax rhs = IdentifierName(parameter.cSharpParameter.Identifier); + if (needsConversion) + rhs = + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("PyObject"), + IdentifierName("From"))) + .WithArgumentList( + ArgumentList( + SingletonSeparatedList( + Argument( + IdentifierName(parameter.cSharpParameter.Identifier))))); + pythonConversionStatements.Add( LocalDeclarationStatement( VariableDeclaration( @@ -63,16 +78,8 @@ public static MethodDefinition FromMethod(PythonFunctionDefinition function, str EqualsValueClause( PostfixUnaryExpression( SyntaxKind.SuppressNullableWarningExpression, - InvocationExpression( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName("PyObject"), - IdentifierName("From"))) - .WithArgumentList( - ArgumentList( - SingletonSeparatedList( - Argument( - IdentifierName(parameter.cSharpParameter.Identifier))))))))))) + rhs + )))))) .WithUsingKeyword( Token(SyntaxKind.UsingKeyword))); } diff --git a/src/CSnakes/Reflection/TypeReflection.cs b/src/CSnakes/Reflection/TypeReflection.cs index 4941737a..36b6610f 100644 --- a/src/CSnakes/Reflection/TypeReflection.cs +++ b/src/CSnakes/Reflection/TypeReflection.cs @@ -26,7 +26,7 @@ public static TypeSyntax AsPredefinedType(PythonTypeSpec pythonType) "Optional" => AsPredefinedType(pythonType.Arguments[0]), "Generator" => CreateGeneratorType(pythonType.Arguments[0], pythonType.Arguments[1], pythonType.Arguments[2]), // Todo more types... see https://docs.python.org/3/library/stdtypes.html#standard-generic-classes - _ => SyntaxFactory.ParseTypeName("PyObject"),// TODO : Should be nullable? + _ => SyntaxFactory.ParseTypeName("PyObject"), }; } return pythonType.Name switch @@ -36,8 +36,7 @@ public static TypeSyntax AsPredefinedType(PythonTypeSpec pythonType) "float" => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.DoubleKeyword)), "bool" => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.BoolKeyword)), "bytes" => SyntaxFactory.ParseTypeName("byte[]"), - // Todo more types... - _ => SyntaxFactory.ParseTypeName("PyObject"),// TODO : Should be nullable? + _ => SyntaxFactory.ParseTypeName("PyObject"), }; } diff --git a/src/Integration.Tests/Integration.Tests.csproj b/src/Integration.Tests/Integration.Tests.csproj index 83421d99..8ede156a 100644 --- a/src/Integration.Tests/Integration.Tests.csproj +++ b/src/Integration.Tests/Integration.Tests.csproj @@ -17,6 +17,7 @@ + @@ -49,6 +50,9 @@ Always + + Always + Always diff --git a/src/Integration.Tests/NoneTests.cs b/src/Integration.Tests/NoneTests.cs new file mode 100644 index 00000000..9a5b13fc --- /dev/null +++ b/src/Integration.Tests/NoneTests.cs @@ -0,0 +1,23 @@ +using CSnakes.Runtime.Python; + +namespace Integration.Tests; +public class NoneTests : IntegrationTestBase +{ + [Fact] + public void TestReturnsNoneIsNone() + { + var mod = Env.TestNone(); + using PyObject result = mod.ReturnsNone(); + Assert.True(result.IsNone()); + } + + [Fact] + public void TestNullArgAsNone() + { + PyObject none = PyObject.None; + Assert.True(none.IsNone()); + // Give to function + var mod = Env.TestNone(); + Assert.True(mod.TestNoneResult(none)); + } +} diff --git a/src/Integration.Tests/python/test_none.py b/src/Integration.Tests/python/test_none.py new file mode 100644 index 00000000..3806a627 --- /dev/null +++ b/src/Integration.Tests/python/test_none.py @@ -0,0 +1,5 @@ +def returns_none(): + return None + +def test_none_result(arg) -> bool: + return arg is None