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

Reducing the calls to ConvertTo/ConvertFrom so we can refactor them easier #174

Merged
merged 2 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 5 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 static object ConvertToDictionary(PyObject pyObject, Type destinationType, bool useMappingProtocol = false)
{
using PyObject items = useMappingProtocol ?
PyObject.Create(CPythonAPI.PyMapping_Items(pyObject)) :
Expand Down Expand Up @@ -67,7 +67,7 @@ private object ConvertToDictionary(PyObject pyObject, Type destinationType, bool
return typeInfo.ReturnTypeConstructor.Invoke([dict]);
}

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

Expand All @@ -89,7 +89,7 @@ internal IReadOnlyDictionary<TKey, TValue> ConvertToDictionary<TKey, TValue>(PyO
return dict;
}

private PyObject ConvertFromDictionary(IDictionary dictionary)
internal static PyObject ConvertFromDictionary(IDictionary dictionary)
{
int len = dictionary.Keys.Count;
PyObject[] keys = new PyObject[len];
Expand All @@ -98,8 +98,8 @@ private PyObject ConvertFromDictionary(IDictionary dictionary)
int i = 0;
foreach (DictionaryEntry kvp in dictionary)
{
keys[i] = ToPython(kvp.Key);
values[i] = ToPython(kvp.Value);
keys[i] = PyObject.From(kvp.Key);
values[i] = PyObject.From(kvp.Value);
i++;
}

Expand Down
2 changes: 1 addition & 1 deletion src/CSnakes.Runtime/PyObjectTypeConverter.Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace CSnakes.Runtime;
internal partial class PyObjectTypeConverter
{
internal object ConvertToGeneratorIterator(PyObject pyObject, Type destinationType)
internal static object ConvertToGeneratorIterator(PyObject pyObject, Type destinationType)
{
if (!CPythonAPI.IsPyGenerator(pyObject))
{
Expand Down
16 changes: 8 additions & 8 deletions src/CSnakes.Runtime/PyObjectTypeConverter.List.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 ICollection ConvertToList(PyObject pyObject, Type destinationType)
private static ICollection ConvertToList(PyObject pyObject, Type destinationType)
{
Type genericArgument = destinationType.GetGenericArguments()[0];

Expand All @@ -29,7 +29,7 @@ private ICollection ConvertToList(PyObject pyObject, Type destinationType)
return list;
}

private ICollection ConvertToListFromSequence(PyObject pyObject, Type destinationType)
private static ICollection ConvertToListFromSequence(PyObject pyObject, Type destinationType)
{
Type genericArgument = destinationType.GetGenericArguments()[0];

Expand All @@ -46,13 +46,13 @@ private ICollection ConvertToListFromSequence(PyObject pyObject, Type destinatio
for (var i = 0; i < listSize; i++)
{
using PyObject item = PyObject.Create(CPythonAPI.PySequence_GetItem(pyObject, i));
list.Add(ConvertTo(item, genericArgument));
list.Add(item.As(genericArgument));
}

return list;
}

internal IReadOnlyCollection<TItem> ConvertToCollection<TItem>(PyObject pyObject)
internal static IReadOnlyCollection<TItem> ConvertToCollection<TItem>(PyObject pyObject)
{
nint listSize = CPythonAPI.PySequence_Size(pyObject);
var list = new List<TItem>((int)listSize);
Expand All @@ -65,25 +65,25 @@ internal IReadOnlyCollection<TItem> ConvertToCollection<TItem>(PyObject pyObject
return list;
}

private PyObject ConvertFromList(ICollection e)
internal static PyObject ConvertFromList(ICollection e)
{
List<PyObject> pyObjects = new(e.Count);

foreach (object? item in e)
{
pyObjects.Add(ConvertFrom(item));
pyObjects.Add(PyObject.From(item));
}

return Pack.CreateList(CollectionsMarshal.AsSpan(pyObjects));
}

private PyObject ConvertFromList(IEnumerable e)
internal static PyObject ConvertFromList(IEnumerable e)
{
List<PyObject> pyObjects = [];

foreach (object? item in e)
{
pyObjects.Add(ConvertFrom(item));
pyObjects.Add(PyObject.From(item));
}

return Pack.CreateList(CollectionsMarshal.AsSpan(pyObjects));
Expand Down
6 changes: 3 additions & 3 deletions src/CSnakes.Runtime/PyObjectTypeConverter.Tuple.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@
namespace CSnakes.Runtime;
internal partial class PyObjectTypeConverter
{
private PyObject ConvertFromTuple(ITuple t)
internal static PyObject ConvertFromTuple(ITuple t)
{
PyObject[] pyObjects = new PyObject[t.Length];

for (var i = 0; i < t.Length; i++)
{
pyObjects[i] = ConvertFrom(t[i]!); // NULL->PyNone
pyObjects[i] = PyObject.From(t[i]); // NULL->PyNone
}

return Pack.CreateTuple(pyObjects);
}

internal ITuple ConvertToTuple(PyObject pyObj, Type destinationType)
internal static ITuple ConvertToTuple(PyObject pyObj, Type destinationType)
{
if (!CPythonAPI.IsPyTuple(pyObj))
{
Expand Down
3 changes: 1 addition & 2 deletions src/CSnakes.Runtime/PyObjectTypeConverter.Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
namespace CSnakes.Runtime;
internal partial class PyObjectTypeConverter
{
private static ConcurrentDictionary<(Type, Type), bool> assignableGenericsMap = [];

private static readonly ConcurrentDictionary<(Type, Type), bool> assignableGenericsMap = [];

public static bool IsAssignableToGenericType(Type givenType, Type genericType)
{
Expand Down
144 changes: 11 additions & 133 deletions src/CSnakes.Runtime/PyObjectTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,155 +9,33 @@
namespace CSnakes.Runtime;
internal partial class PyObjectTypeConverter
{
private readonly ConcurrentDictionary<Type, DynamicTypeInfo> knownDynamicTypes = [];
private static readonly ConcurrentDictionary<Type, DynamicTypeInfo> knownDynamicTypes = [];

/// <summary>
/// Convert a Python object to a CLR managed object.
/// Caller should already hold the GIL because this function uses the Python runtime for some conversions.
/// </summary>
/// <param name="context"></param>
/// <param name="culture"></param>
/// <param name="value"></param>
/// <param name="destinationType"></param>
/// <returns></returns>
/// <exception cref="NotSupportedException">Passed object is not a PyObject</exception>
/// <exception cref="InvalidCastException">Source/Target types do not match</exception>
public object ConvertTo(object? value, Type destinationType)
public static object PyObjectToManagedType(PyObject pyObject, Type destinationType)
{
if (value is not PyObject pyObject)
if (CPythonAPI.IsPyDict(pyObject) && IsAssignableToGenericType(destinationType, dictionaryType))
{
throw new NotSupportedException();
return ConvertToDictionary(pyObject, destinationType);
}

if (destinationType == pyObjectType)
if (CPythonAPI.IsPyList(pyObject) && IsAssignableToGenericType(destinationType, listType))
{
return pyObject.Clone();
return ConvertToList(pyObject, destinationType);
}

if (destinationType == typeof(byte[]) && CPythonAPI.IsBytes(pyObject))
// This needs to come after lists, because sequences are also maps
if (CPythonAPI.IsPyMappingWithItems(pyObject) && destinationType.IsAssignableTo(collectionType))
{
return CPythonAPI.PyBytes_AsByteArray(pyObject);
return ConvertToDictionary(pyObject, destinationType, useMappingProtocol: true);
}

if (destinationType == typeof(long))
if (CPythonAPI.IsPySequence(pyObject) && IsAssignableToGenericType(destinationType, listType))
{
long result = CPythonAPI.PyLong_AsLongLong(pyObject);
if (result == -1 && CPythonAPI.PyErr_Occurred())
{
throw PyObject.ThrowPythonExceptionAsClrException("Error converting Python object to long, check that the object was a Python long or that the value wasn't too large. See InnerException for details.");
}
return result;
}

if (destinationType == typeof(BigInteger) && CPythonAPI.IsPyLong(pyObject))
{
return ConvertToBigInteger(pyObject, destinationType);
}

if (destinationType == typeof(int))
{
var result = CPythonAPI.PyLong_AsLong(pyObject);
if (result == -1 && CPythonAPI.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;
}

if (destinationType == typeof(bool) && CPythonAPI.IsPyBool(pyObject))
{
return CPythonAPI.IsPyTrue(pyObject);
}

if (destinationType == typeof(double))
{
var result = CPythonAPI.PyFloat_AsDouble(pyObject);
if (result == -1 && CPythonAPI.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;
}

if (destinationType.IsAssignableTo(typeof(ITuple)))
{
if (CPythonAPI.IsPyTuple(pyObject))
{
return ConvertToTuple(pyObject, destinationType);
}

var tupleTypes = destinationType.GetGenericArguments();
if (tupleTypes.Length > 1)
{
throw new InvalidCastException($"The type is a tuple with more than one generic argument, but the underlying Python type is not a tuple. Destination Type: {destinationType}");
}

var convertedValue = ConvertTo(pyObject, tupleTypes[0]);
return Activator.CreateInstance(destinationType, convertedValue)!;
}

if (destinationType.IsGenericType)
{
if (IsAssignableToGenericType(destinationType, dictionaryType) && CPythonAPI.IsPyDict(pyObject))
{
return ConvertToDictionary(pyObject, destinationType);
}

if (IsAssignableToGenericType(destinationType, listType) && CPythonAPI.IsPyList(pyObject))
{
return ConvertToList(pyObject, destinationType);
}

if (IsAssignableToGenericType(destinationType, generatorIteratorType) && CPythonAPI.IsPyGenerator(pyObject))
{
return ConvertToGeneratorIterator(pyObject, destinationType);
}

// This needs to come after lists, because sequences are also maps
if (destinationType.IsAssignableTo(collectionType) && CPythonAPI.IsPyMappingWithItems(pyObject))
{
return ConvertToDictionary(pyObject, destinationType, useMappingProtocol: true);
}

if (IsAssignableToGenericType(destinationType, listType) && CPythonAPI.IsPySequence(pyObject))
{
return ConvertToListFromSequence(pyObject, destinationType);
}
return ConvertToListFromSequence(pyObject, destinationType);
}

throw new InvalidCastException($"Attempting to cast {destinationType} from {pyObject.GetPythonType()}");
}

public PyObject ConvertFrom(object value) =>
value switch
{
string str => PyObject.Create(CPythonAPI.AsPyUnicodeObject(str)),
byte[] bytes => PyObject.Create(CPythonAPI.PyBytes_FromByteSpan(bytes.AsSpan())),
long l => PyObject.From(l),
int i => PyObject.From(i),
bool b => PyObject.From(b),
double d => PyObject.From(d),
IDictionary dictionary => ConvertFromDictionary(dictionary),
ITuple t => ConvertFromTuple(t),
ICollection l => ConvertFromList(l),
IEnumerable e => ConvertFromList(e),
BigInteger b => ConvertFromBigInteger(b),
PyObject pyObject => pyObject.Clone(),
null => PyObject.None,
_ => throw new InvalidCastException($"Cannot convert {value} to PyObject")
};

private PyObject ToPython(object? o)
{
if (o is null)
{
return PyObject.None;
}

var result = ConvertFrom(o);

return result is null ? throw new NotImplementedException() : result;
}

record DynamicTypeInfo(ConstructorInfo ReturnTypeConstructor, ConstructorInfo? TransientTypeConstructor = null);
}
26 changes: 15 additions & 11 deletions src/CSnakes.Runtime/Python/PyObject.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using CSnakes.Runtime.CPython;
using CSnakes.Runtime.Python.Interns;
using System.Collections;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
Expand All @@ -11,8 +12,6 @@ namespace CSnakes.Runtime.Python;
[DebuggerDisplay("PyObject: repr={GetRepr()}, type={GetPythonType().ToString()}")]
public class PyObject : SafeHandle
{
private static readonly PyObjectTypeConverter td = new();

protected PyObject(IntPtr pyObject, bool ownsHandle = true) : base(pyObject, ownsHandle)
{
if (pyObject == IntPtr.Zero)
Expand Down Expand Up @@ -454,9 +453,9 @@ internal object As(Type type)
var t when t == typeof(string) => CPythonAPI.PyUnicode_AsUTF8(this),
var t when t == typeof(BigInteger) => PyObjectTypeConverter.ConvertToBigInteger(this, t),
var t when t == typeof(byte[]) => CPythonAPI.PyBytes_AsByteArray(this),
var t when t.IsAssignableTo(typeof(ITuple)) => td.ConvertToTuple(this, t),
var t when t.IsAssignableTo(typeof(IGeneratorIterator)) => td.ConvertToGeneratorIterator(this, t),
var t => td.ConvertTo(this, t),
var t when t.IsAssignableTo(typeof(ITuple)) => PyObjectTypeConverter.ConvertToTuple(this, t),
var t when t.IsAssignableTo(typeof(IGeneratorIterator)) => PyObjectTypeConverter.ConvertToGeneratorIterator(this, t),
var t => PyObjectTypeConverter.PyObjectToManagedType(this, t),
};
}
}
Expand All @@ -465,21 +464,21 @@ public IReadOnlyCollection<TItem> AsCollection<TCollection, TItem>() where TColl
{
using (GIL.Acquire())
{
return td.ConvertToCollection<TItem>(this);
return PyObjectTypeConverter.ConvertToCollection<TItem>(this);
}
}
public IReadOnlyDictionary<TKey, TValue> AsDictionary<TDict, TKey, TValue>() where TDict : IReadOnlyDictionary<TKey, TValue> where TKey : notnull
{
using (GIL.Acquire())
{
return td.ConvertToDictionary<TKey, TValue>(this);
return PyObjectTypeConverter.ConvertToDictionary<TKey, TValue>(this);
}
}

public IReadOnlyCollection<TItem> As<TCollection, TItem>() where TCollection : IReadOnlyCollection<TItem> =>
td.ConvertToCollection<TItem>(this);
PyObjectTypeConverter.ConvertToCollection<TItem>(this);
public IReadOnlyDictionary<TKey, TValue> As<TDict, TKey, TValue>() where TDict : IReadOnlyDictionary<TKey, TValue> where TKey : notnull =>
td.ConvertToDictionary<TKey, TValue>(this);
PyObjectTypeConverter.ConvertToDictionary<TKey, TValue>(this);

public static PyObject From<T>(T value)
{
Expand All @@ -496,8 +495,13 @@ public static PyObject From<T>(T value)
long l => Create(CPythonAPI.PyLong_FromLongLong(l)),
double d => Create(CPythonAPI.PyFloat_FromDouble(d)),
string s => Create(CPythonAPI.AsPyUnicodeObject(s)),
BigInteger bi => PyObjectTypeConverter.ConvertFromBigInteger(bi),
_ => td.ConvertFrom(value),
byte[] bytes => PyObject.Create(CPythonAPI.PyBytes_FromByteSpan(bytes.AsSpan())),
IDictionary dictionary => PyObjectTypeConverter.ConvertFromDictionary(dictionary),
ITuple t => PyObjectTypeConverter.ConvertFromTuple(t),
ICollection l => PyObjectTypeConverter.ConvertFromList(l),
IEnumerable e => PyObjectTypeConverter.ConvertFromList(e),
BigInteger b => PyObjectTypeConverter.ConvertFromBigInteger(b),
_ => throw new InvalidCastException($"Cannot convert {value} to PyObject"),
};
}
}
Expand Down
Loading