Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compiler conversions #171

Merged
merged 22 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1676815
Create an overload for generic collections to let the compiler do all…
tonybaloney Aug 27, 2024
1bb34a6
Generic dictionaries
tonybaloney Aug 27, 2024
a3ca0e8
Undo program changes for profiling
tonybaloney Aug 27, 2024
79ed334
Add tests
tonybaloney Aug 27, 2024
c5ab40b
Add missing GIL lock
tonybaloney Aug 27, 2024
fce0fcc
Reducing down the number of public methods for to/from PyObject
aaronpowell Aug 27, 2024
3719404
Merge branch 'compiler_conversions' of https://github.com/tonybaloney…
aaronpowell Aug 27, 2024
9c9ca30
Make sure all references to the python exception factory throw
tonybaloney Aug 27, 2024
f502984
Don't hard code python paths Anthony
aaronpowell Aug 27, 2024
fa174a2
Do error checks inside Python interop classes
tonybaloney Aug 27, 2024
97b34c0
Merge branch 'compiler_conversions' of https://github.com/tonybaloney…
tonybaloney Aug 27, 2024
768da2b
Fixing tests by going back via the PyObject.As method
aaronpowell Aug 27, 2024
91c763a
Merge branch 'compiler_conversions' of https://github.com/tonybaloney…
aaronpowell Aug 27, 2024
7591b66
Add missing throw
tonybaloney Aug 27, 2024
7057490
Add more missing throws
tonybaloney Aug 27, 2024
a65481c
Merge branch 'compiler_conversions' of https://github.com/tonybaloney…
tonybaloney Aug 27, 2024
9031345
Fixed some tuple tests
aaronpowell Aug 27, 2024
53134a6
Check if something is a tuple or generator before converting
tonybaloney Aug 27, 2024
074a727
Merge branch 'compiler_conversions' of https://github.com/tonybaloney…
tonybaloney Aug 27, 2024
05ad7ca
Fixing first-level tuple nesting
aaronpowell Aug 27, 2024
7e9e299
Move the PyTuple_Size call back to after the type check
tonybaloney Aug 27, 2024
2fc6027
Put GIL dispose requests in a queue instead of running them in the fi…
tonybaloney Aug 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/CSnakes.Runtime.Tests/Python/PyObjectTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CSnakes.Runtime.Python;
using System.Collections;

namespace CSnakes.Runtime.Tests.Python;
public class PyObjectTests : RuntimeTestBase
Expand Down Expand Up @@ -61,7 +62,8 @@ public void TestObjectIsNone()
}

[Fact]
public void TestObjectIsSmallIntegers() {
public void TestObjectIsSmallIntegers()
{
// Small numbers are the same object in Python, weird implementation detail.
var obj1 = PyObject.From(42);
var obj2 = PyObject.From(42);
Expand Down Expand Up @@ -157,4 +159,22 @@ public void TestObjectNotStrictInequality(object? o1, object? o2, bool expectedL
Assert.Equal(expectedLT, obj1 <= obj2);
Assert.Equal(expectedGT, obj1 >= obj2);
}

[Fact]
public void TestAsCollection()
{
using PyObject o = PyObject.From<IEnumerable<string>>(new[] { "Hello", "World" })!;
var collection = o.AsCollection<IReadOnlyCollection<string>, string>();
Assert.NotNull(collection);
Assert.Equal(2, collection!.Count());
}

[Fact]
public void TestAsDictionary()
{
using PyObject o = PyObject.From<IDictionary<string, string>>(new Dictionary<string, string> { { "Hello", "World" } })!;
var dictionary = o.AsDictionary<IReadOnlyDictionary<string, string>, string, string>();
Assert.NotNull(dictionary);
Assert.Equal("World", dictionary!["Hello"]);
}
}
3 changes: 2 additions & 1 deletion src/CSnakes.Runtime.Tests/RuntimeTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public RuntimeTestBase()
var pb = services.WithPython();
pb.WithHome(Environment.CurrentDirectory);

pb.FromNuGet(pythonVersionWindows)
pb
.FromNuGet(pythonVersionWindows)
.FromMacOSInstallerLocator(pythonVersionMacOS)
.FromEnvironmentVariable("Python3_ROOT_DIR", pythonVersionLinux); // This last one is for GitHub Actions

Expand Down
4 changes: 2 additions & 2 deletions src/CSnakes.Runtime/CPython/Dict.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal static nint PackDict(Span<string> kwnames, Span<IntPtr> kwvalues)
int result = PyDict_SetItemRaw(dict, keyObj, kwvalues[i]);
if (result == -1)
{
PyObject.ThrowPythonExceptionAsClrException();
throw PyObject.ThrowPythonExceptionAsClrException();
}
Py_DecRefRaw(keyObj);
}
Expand All @@ -55,7 +55,7 @@ internal static nint PyDict_GetItem(PyObject dict, PyObject key)
var result = PyDict_GetItem_(dict, key);
if (result == IntPtr.Zero)
{
PyObject.ThrowPythonExceptionAsClrException();
throw PyObject.ThrowPythonExceptionAsClrException();
}
Py_IncRefRaw(result);
return result;
Expand Down
14 changes: 12 additions & 2 deletions src/CSnakes.Runtime/CPython/Float.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,23 @@ internal unsafe partial class CPythonAPI
[LibraryImport(PythonLibraryName)]
internal static partial nint PyFloat_FromDouble(double value);

internal static double PyFloat_AsDouble(PyObject p)
{
double result = PyFloat_AsDouble_(p);
if (result == -1 && PyErr_Occurred())
{
throw PyObject.ThrowPythonExceptionAsClrException("Error converting Python object to double, check that the object was a Python float. See InnerException for details.");
}
return result;
}

/// <summary>
/// Convery a PyFloat to a C double
/// </summary>
/// <param name="p"></param>
/// <returns>The double value</returns>
[LibraryImport(PythonLibraryName)]
internal static partial double PyFloat_AsDouble(PyObject obj);
[LibraryImport(PythonLibraryName, EntryPoint = "PyFloat_AsDouble")]
private static partial double PyFloat_AsDouble_(PyObject obj);

internal static bool IsPyFloat(PyObject p)
{
Expand Down
2 changes: 1 addition & 1 deletion src/CSnakes.Runtime/CPython/Import.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ protected static nint GetBuiltin(string name)
nint attr = PyObject_GetAttrRaw(module, pyAttrName);
if (attr == IntPtr.Zero)
{
PyObject.ThrowPythonExceptionAsClrException();
throw PyObject.ThrowPythonExceptionAsClrException();
}
Py_DecRefRaw(pyName);
Py_DecRefRaw(pyAttrName);
Expand Down
2 changes: 1 addition & 1 deletion src/CSnakes.Runtime/CPython/List.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ internal static nint PyList_GetItem(PyObject obj, nint pos)
nint item = PyList_GetItem_(obj, pos);
if (item == IntPtr.Zero)
{
PyObject.ThrowPythonExceptionAsClrException();
throw PyObject.ThrowPythonExceptionAsClrException();
}
Py_IncRefRaw(item);
return item;
Expand Down
38 changes: 34 additions & 4 deletions src/CSnakes.Runtime/CPython/Long.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,41 @@ internal unsafe partial class CPythonAPI
[LibraryImport(PythonLibraryName)]
internal static partial nint PyLong_FromLongLong(long v);

[LibraryImport(PythonLibraryName)]
internal static partial long PyLong_AsLongLong(PyObject p);
/// <summary>
/// Calls PyLong_AsLongLong and throws a Python Exception if an error occurs.
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
internal static long PyLong_AsLongLong(PyObject p)
{
long result = PyLong_AsLongLong_(p);
if (result == -1 && PyErr_Occurred())
{
throw PyObject.ThrowPythonExceptionAsClrException("Error converting Python object to int, check that the object was a Python int or that the value wasn't too large. See InnerException for details.");
}
return result;
}

[LibraryImport(PythonLibraryName)]
internal static partial int PyLong_AsLong(PyObject p);
[LibraryImport(PythonLibraryName, EntryPoint = "PyLong_AsLongLong")]
private static partial long PyLong_AsLongLong_(PyObject p);

/// <summary>
/// Calls PyLong_AsLong and throws a Python Exception if an error occurs.
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
internal static long PyLong_AsLong(PyObject p)
{
long result = PyLong_AsLong_(p);
if (result == -1 && PyErr_Occurred())
{
throw PyObject.ThrowPythonExceptionAsClrException("Error converting Python object to int, check that the object was a Python int or that the value wasn't too large. See InnerException for details.");
}
return result;
}

[LibraryImport(PythonLibraryName, EntryPoint = "PyLong_AsLong")]
private static partial int PyLong_AsLong_(PyObject p);

internal static bool IsPyLong(PyObject p)
{
Expand Down
4 changes: 2 additions & 2 deletions src/CSnakes.Runtime/CPython/Object.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ internal static bool PyObject_IsInstance(PyObject ob, IntPtr type)
int result = PyObject_IsInstance_(ob, type);
if (result == -1)
{
PyObject.ThrowPythonExceptionAsClrException();
throw PyObject.ThrowPythonExceptionAsClrException();
}
return result == 1;
}
Expand Down Expand Up @@ -173,7 +173,7 @@ internal static bool PyObject_RichCompare(PyObject ob1, PyObject ob2, RichCompar
int result = PyObject_RichCompareBool(ob1, ob2, comparisonType);
if (result == -1)
{
PyObject.ThrowPythonExceptionAsClrException();
throw PyObject.ThrowPythonExceptionAsClrException();
}
return result == 1;
}
Expand Down
4 changes: 2 additions & 2 deletions src/CSnakes.Runtime/CPython/Tuple.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ internal static nint PyTuple_GetItemWithNewRef(PyObject ob, nint pos)
nint item = PyTuple_GetItem(ob, pos);
if (item == IntPtr.Zero)
{
PyObject.ThrowPythonExceptionAsClrException();
throw PyObject.ThrowPythonExceptionAsClrException();
}
Py_IncRefRaw(item);
return item;
Expand All @@ -83,7 +83,7 @@ internal static nint PyTuple_GetItemWithNewRefRaw(nint ob, nint pos)
nint item = PyTuple_GetItemRaw(ob, pos);
if (item == IntPtr.Zero)
{
PyObject.ThrowPythonExceptionAsClrException();
throw PyObject.ThrowPythonExceptionAsClrException();
}
Py_IncRefRaw(item);
return item;
Expand Down
15 changes: 13 additions & 2 deletions src/CSnakes.Runtime/CPython/Unicode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,25 @@ internal static nint AsPyUnicodeObject(string s)
[LibraryImport(PythonLibraryName)]
internal static partial nint PyUnicode_DecodeUTF16(char* str, nint size, IntPtr errors, IntPtr byteorder);

/// <summary>
/// Calls PyUnicode_AsUTF8 and throws a Python Exception if an error occurs.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
internal static string PyUnicode_AsUTF8(PyObject s)
{
var result = PyUnicode_AsUTF8_(s);
return result is null ? throw PyObject.ThrowPythonExceptionAsClrException() : result;
}

/// <summary>
/// Convert the string object to a UTF-8 encoded string and return a pointer to the internal buffer.
/// This function does a type check and returns null if the object is not a string.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
[LibraryImport(PythonLibraryName, StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(NonFreeUtf8StringMarshaller))]
internal static partial string? PyUnicode_AsUTF8(PyObject s);
[LibraryImport(PythonLibraryName, StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(NonFreeUtf8StringMarshaller), EntryPoint = "PyUnicode_AsUTF8")]
private static partial string? PyUnicode_AsUTF8_(PyObject s);

[LibraryImport(PythonLibraryName, StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(NonFreeUtf8StringMarshaller), EntryPoint = "PyUnicode_AsUTF8")]
internal static partial string? PyUnicode_AsUTF8Raw(nint s);
Expand Down
4 changes: 2 additions & 2 deletions src/CSnakes.Runtime/PyObjectTypeConverter.BigInteger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
namespace CSnakes.Runtime;
internal partial class PyObjectTypeConverter
{
private static object? ConvertToBigInteger(PyObject pyObject, Type destinationType) =>
internal static BigInteger ConvertToBigInteger(PyObject pyObject, Type destinationType) =>
// There is no practical API for this in CPython. Use str() instead.
BigInteger.Parse(pyObject.ToString());

private static PyObject ConvertFromBigInteger(BigInteger integer)
internal static PyObject ConvertFromBigInteger(BigInteger integer)
{
using PyObject pyUnicode = PyObject.Create(CPythonAPI.AsPyUnicodeObject(integer.ToString()));
return PyObject.Create(CPythonAPI.PyLong_FromUnicodeObject(pyUnicode, 10));
Expand Down
32 changes: 27 additions & 5 deletions src/CSnakes.Runtime/PyObjectTypeConverter.Dictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace CSnakes.Runtime;
internal partial class PyObjectTypeConverter
{
private object? ConvertToDictionary(PyObject pyObject, Type destinationType, bool useMappingProtocol = false)
private object ConvertToDictionary(PyObject pyObject, Type destinationType, bool useMappingProtocol = false)
{
using PyObject items = useMappingProtocol ?
PyObject.Create(CPythonAPI.PyMapping_Items(pyObject)) :
Expand Down Expand Up @@ -45,9 +45,9 @@ internal partial class PyObjectTypeConverter
if (keyAsString is null)
{
CPythonAPI.Py_DecRefRaw(itemKey);
PyObject.ThrowPythonExceptionAsClrException();
throw PyObject.ThrowPythonExceptionAsClrException();
}
object? convertedValue = ConvertTo(value, valueType);
object? convertedValue = value.As(valueType);

dict.Add(keyAsString!, convertedValue);
CPythonAPI.Py_DecRefRaw(itemKey);
Expand All @@ -56,8 +56,8 @@ internal partial class PyObjectTypeConverter
using PyObject key = PyObject.Create(CPythonAPI.PyTuple_GetItemWithNewRefRaw(kvpTuple, 0));
using PyObject value = PyObject.Create(CPythonAPI.PyTuple_GetItemWithNewRefRaw(kvpTuple, 1));

object? convertedKey = ConvertTo(key, keyType);
object? convertedValue = ConvertTo(value, valueType);
object? convertedKey = key.As(keyType);
object? convertedValue = value.As(valueType);

dict.Add(convertedKey!, convertedValue);
}
Expand All @@ -67,6 +67,28 @@ internal partial class PyObjectTypeConverter
return typeInfo.ReturnTypeConstructor.Invoke([dict]);
}

internal IReadOnlyDictionary<TKey, TValue> ConvertToDictionary<TKey, TValue>(PyObject pyObject) where TKey : notnull
{
using PyObject items = PyObject.Create(CPythonAPI.PyMapping_Items(pyObject));

var dict = new Dictionary<TKey, TValue>();
nint itemsLength = CPythonAPI.PyList_Size(items);
for (nint i = 0; i < itemsLength; i++)
{
nint kvpTuple = CPythonAPI.PyList_GetItem(items, i);
using PyObject key = PyObject.Create(CPythonAPI.PyTuple_GetItemWithNewRefRaw(kvpTuple, 0));
using PyObject value = PyObject.Create(CPythonAPI.PyTuple_GetItemWithNewRefRaw(kvpTuple, 1));

TKey convertedKey = key.As<TKey>();
TValue convertedValue = value.As<TValue>();

dict.Add(convertedKey, convertedValue);
CPythonAPI.Py_DecRefRaw(kvpTuple);
}

return dict;
}

private PyObject ConvertFromDictionary(IDictionary dictionary)
{
int len = dictionary.Keys.Count;
Expand Down
8 changes: 7 additions & 1 deletion src/CSnakes.Runtime/PyObjectTypeConverter.Generator.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
using CSnakes.Runtime.CPython;
using CSnakes.Runtime.Python;
using System.Reflection;

namespace CSnakes.Runtime;
internal partial class PyObjectTypeConverter
{
private object? ConvertToGeneratorIterator(PyObject pyObject, Type destinationType)
internal object ConvertToGeneratorIterator(PyObject pyObject, Type destinationType)
{
if (!CPythonAPI.IsPyGenerator(pyObject))
{
throw new InvalidCastException($"Cannot convert {pyObject.GetPythonType()} to a generator.");
}

if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo))
{
Type item1Type = destinationType.GetGenericArguments()[0];
Expand Down
Loading