using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
namespace Python.Runtime
{
///
/// Performs data conversions between managed types and Python types.
///
[SuppressUnmanagedCodeSecurity]
internal class Converter
{
private Converter()
{
}
private static readonly Type objectType;
private static readonly Type stringType;
private static readonly Type singleType;
private static readonly Type doubleType;
private static readonly Type int16Type;
private static readonly Type int32Type;
private static readonly Type int64Type;
private static readonly Type boolType;
private static readonly Type typeType;
static Converter()
{
objectType = typeof(Object);
stringType = typeof(String);
int16Type = typeof(Int16);
int32Type = typeof(Int32);
int64Type = typeof(Int64);
singleType = typeof(Single);
doubleType = typeof(Double);
boolType = typeof(Boolean);
typeType = typeof(Type);
}
///
/// Given a builtin Python type, return the corresponding CLR type.
///
internal static Type? GetTypeByAlias(BorrowedReference op)
{
if (op == Runtime.PyStringType)
return stringType;
if (op == Runtime.PyUnicodeType)
return stringType;
if (op == Runtime.PyLongType)
return int32Type;
if (op == Runtime.PyLongType)
return int64Type;
if (op == Runtime.PyFloatType)
return doubleType;
if (op == Runtime.PyBoolType)
return boolType;
return null;
}
internal static BorrowedReference GetPythonTypeByAlias(Type op)
{
if (op == stringType)
return Runtime.PyUnicodeType.Reference;
if (op == int16Type)
return Runtime.PyLongType.Reference;
if (op == int32Type)
return Runtime.PyLongType.Reference;
if (op == int64Type)
return Runtime.PyLongType.Reference;
if (op == doubleType)
return Runtime.PyFloatType.Reference;
if (op == singleType)
return Runtime.PyFloatType.Reference;
if (op == boolType)
return Runtime.PyBoolType.Reference;
return BorrowedReference.Null;
}
internal static NewReference ToPython(T value)
=> ToPython(value, typeof(T));
private static readonly Func IsTransparentProxy = GetIsTransparentProxy();
private static bool Never(object _) => false;
private static Func GetIsTransparentProxy()
{
var remoting = typeof(int).Assembly.GetType("System.Runtime.Remoting.RemotingServices");
if (remoting is null) return Never;
var isProxy = remoting.GetMethod("IsTransparentProxy", new[] { typeof(object) });
if (isProxy is null) return Never;
return (Func)Delegate.CreateDelegate(
typeof(Func), isProxy,
throwOnBindFailure: true);
}
internal static NewReference ToPythonDetectType(object? value)
=> value is null ? new NewReference(Runtime.PyNone) : ToPython(value, value.GetType());
internal static NewReference ToPython(object? value, Type type)
{
if (value is PyObject pyObj)
{
return new NewReference(pyObj);
}
// Null always converts to None in Python.
if (value == null)
{
return new NewReference(Runtime.PyNone);
}
if (EncodableByUser(type, value))
{
var encoded = PyObjectConversions.TryEncode(value, type);
if (encoded != null)
{
return new NewReference(encoded);
}
}
if (type.IsInterface)
{
var ifaceObj = (InterfaceObject)ClassManager.GetClassImpl(type);
return ifaceObj.TryWrapObject(value);
}
if (type.IsArray || type.IsEnum)
{
return CLRObject.GetReference(value, type);
}
// it the type is a python subclass of a managed type then return the
// underlying python object rather than construct a new wrapper object.
if (value is IPythonDerivedType pyderived)
{
if (!IsTransparentProxy(pyderived))
return ClassDerivedObject.ToPython(pyderived);
}
// ModuleObjects are created in a way that their wrapping them as
// a CLRObject fails, the ClassObject has no tpHandle. Return the
// pyHandle as is, do not convert.
if (value is ModuleObject)
{
throw new NotImplementedException();
}
// hmm - from Python, we almost never care what the declared
// type is. we'd rather have the object bound to the actual
// implementing class.
type = value.GetType();
if (type.IsEnum)
{
return CLRObject.GetReference(value, type);
}
TypeCode tc = Type.GetTypeCode(type);
switch (tc)
{
case TypeCode.Object:
return CLRObject.GetReference(value, type);
case TypeCode.String:
return Runtime.PyString_FromString((string)value);
case TypeCode.Int32:
return Runtime.PyInt_FromInt32((int)value);
case TypeCode.Boolean:
if ((bool)value)
{
return new NewReference(Runtime.PyTrue);
}
return new NewReference(Runtime.PyFalse);
case TypeCode.Byte:
return Runtime.PyInt_FromInt32((byte)value);
case TypeCode.Char:
return Runtime.PyUnicode_FromOrdinal((int)((char)value));
case TypeCode.Int16:
return Runtime.PyInt_FromInt32((short)value);
case TypeCode.Int64:
return Runtime.PyLong_FromLongLong((long)value);
case TypeCode.Single:
return Runtime.PyFloat_FromDouble((float)value);
case TypeCode.Double:
return Runtime.PyFloat_FromDouble((double)value);
case TypeCode.SByte:
return Runtime.PyInt_FromInt32((sbyte)value);
case TypeCode.UInt16:
return Runtime.PyInt_FromInt32((ushort)value);
case TypeCode.UInt32:
return Runtime.PyLong_FromUnsignedLongLong((uint)value);
case TypeCode.UInt64:
return Runtime.PyLong_FromUnsignedLongLong((ulong)value);
default:
return CLRObject.GetReference(value, type);
}
}
static bool EncodableByUser(Type type, object value)
{
TypeCode typeCode = Type.GetTypeCode(type);
return type.IsEnum
|| typeCode is TypeCode.DateTime or TypeCode.Decimal
|| typeCode == TypeCode.Object && value.GetType() != typeof(object) && value is not Type;
}
///
/// In a few situations, we don't have any advisory type information
/// when we want to convert an object to Python.
///
internal static NewReference ToPythonImplicit(object? value)
{
if (value == null)
{
return new NewReference(Runtime.PyNone);
}
return ToPython(value, objectType);
}
///
/// Return a managed object for the given Python object, taking funny
/// byref types into account.
///
/// A Python object
/// The desired managed type
/// Receives the managed object
/// If true, call Exceptions.SetError with the reason for failure.
/// True on success
internal static bool ToManaged(BorrowedReference value, Type type,
out object? result, bool setError)
{
if (type.IsByRef)
{
type = type.GetElementType();
}
return Converter.ToManagedValue(value, type, out result, setError);
}
internal static bool ToManagedValue(BorrowedReference value, Type obType,
out object? result, bool setError)
{
if (obType == typeof(PyObject))
{
result = new PyObject(value);
return true;
}
if (obType.IsSubclassOf(typeof(PyObject))
&& !obType.IsAbstract
&& obType.GetConstructor(new[] { typeof(PyObject) }) is { } ctor)
{
var untyped = new PyObject(value);
result = ToPyObjectSubclass(ctor, untyped, setError);
return result is not null;
}
// Common case: if the Python value is a wrapped managed object
// instance, just return the wrapped object.
result = null;
switch (ManagedType.GetManagedObject(value))
{
case CLRObject co:
object tmp = co.inst;
if (obType.IsInstanceOfType(tmp))
{
result = tmp;
return true;
}
if (setError)
{
string typeString = tmp is null ? "null" : tmp.GetType().ToString();
Exceptions.SetError(Exceptions.TypeError, $"{typeString} value cannot be converted to {obType}");
}
return false;
case ClassBase cb:
if (!cb.type.Valid)
{
Exceptions.SetError(Exceptions.TypeError, cb.type.DeletedMessage);
return false;
}
result = cb.type.Value;
return true;
case null:
break;
default:
throw new ArgumentException("We should never receive instances of other managed types");
}
if (value == Runtime.PyNone && !obType.IsValueType)
{
result = null;
return true;
}
if (obType.IsGenericType && obType.GetGenericTypeDefinition() == typeof(Nullable))
{
if (value == Runtime.PyNone)
{
result = null;
return true;
}
// Set type to underlying type
obType = obType.GetGenericArguments()[0];
}
if (obType.ContainsGenericParameters)
{
if (setError)
{
Exceptions.SetError(Exceptions.TypeError, $"Cannot create an instance of the open generic type {obType}");
}
return false;
}
if (obType.IsArray)
{
return ToArray(value, obType, out result, setError);
}
// Conversion to 'Object' is done based on some reasonable default
// conversions (Python string -> managed string).
if (obType == objectType)
{
if (Runtime.PyString_CheckExact(value))
{
return ToPrimitive(value, stringType, out result, setError);
}
if (Runtime.PyBool_CheckExact(value))
{
return ToPrimitive(value, boolType, out result, setError);
}
if (Runtime.PyFloat_CheckExact(value))
{
return ToPrimitive(value, doubleType, out result, setError);
}
// give custom codecs a chance to take over conversion
// of ints, sequences, and types derived from primitives
BorrowedReference pyType = Runtime.PyObject_TYPE(value);
if (PyObjectConversions.TryDecode(value, pyType, obType, out result))
{
return true;
}
if (Runtime.PyString_Check(value))
{
return ToPrimitive(value, stringType, out result, setError);
}
if (Runtime.PyBool_Check(value))
{
return ToPrimitive(value, boolType, out result, setError);
}
if (Runtime.PyFloat_Check(value))
{
return ToPrimitive(value, doubleType, out result, setError);
}
if (Runtime.PyInt_Check(value))
{
result = new PyInt(value);
return true;
}
result = new PyObject(value);
return true;
}
// Conversion to 'Type' is done using the same mappings as above for objects.
if (obType == typeType)
{
if (value == Runtime.PyStringType)
{
result = stringType;
return true;
}
if (value == Runtime.PyBoolType)
{
result = boolType;
return true;
}
if (value == Runtime.PyLongType)
{
result = typeof(PyInt);
return true;
}
if (value == Runtime.PyFloatType)
{
result = doubleType;
return true;
}
if (value == Runtime.PyListType)
{
result = typeof(PyList);
return true;
}
if (value == Runtime.PyTupleType)
{
result = typeof(PyTuple);
return true;
}
if (setError)
{
Exceptions.SetError(Exceptions.TypeError, "value cannot be converted to Type");
}
return false;
}
if (DecodableByUser(obType))
{
BorrowedReference pyType = Runtime.PyObject_TYPE(value);
if (PyObjectConversions.TryDecode(value, pyType, obType, out result))
{
return true;
}
}
if (obType == typeof(System.Numerics.BigInteger)
&& Runtime.PyInt_Check(value))
{
using var pyInt = new PyInt(value);
result = pyInt.ToBigInteger();
return true;
}
return ToPrimitive(value, obType, out result, setError);
}
///
/// Unlike ,
/// this method does not have a setError parameter, because it should
/// only be called after .
///
internal static bool ToManagedExplicit(BorrowedReference value, Type obType,
out object? result)
{
result = null;
// this method would potentially clean any existing error resulting in information loss
Debug.Assert(Runtime.PyErr_Occurred() == null);
string? converterName =
IsInteger(obType) ? "__int__"
: IsFloatingNumber(obType) ? "__float__"
: null;
if (converterName is null) return false;
Debug.Assert(obType.IsPrimitive);
using var converter = Runtime.PyObject_GetAttrString(value, converterName);
if (converter.IsNull())
{
Exceptions.Clear();
return false;
}
using var explicitlyCoerced = Runtime.PyObject_CallObject(converter.Borrow(), BorrowedReference.Null);
if (explicitlyCoerced.IsNull())
{
Exceptions.Clear();
return false;
}
return ToPrimitive(explicitlyCoerced.Borrow(), obType, out result, false);
}
static object? ToPyObjectSubclass(ConstructorInfo ctor, PyObject instance, bool setError)
{
try
{
return ctor.Invoke(new object[] { instance });
}
catch (TargetInvocationException ex)
{
if (setError)
{
Exceptions.SetError(ex.InnerException);
}
return null;
}
catch (SecurityException ex)
{
if (setError)
{
Exceptions.SetError(ex);
}
return null;
}
}
static bool DecodableByUser(Type type)
{
TypeCode typeCode = Type.GetTypeCode(type);
return type.IsEnum
|| typeCode is TypeCode.Object or TypeCode.Decimal or TypeCode.DateTime;
}
internal delegate bool TryConvertFromPythonDelegate(BorrowedReference pyObj, out object? result);
internal static int ToInt32(BorrowedReference value)
{
nint num = Runtime.PyLong_AsSignedSize_t(value);
if (num == -1 && Exceptions.ErrorOccurred())
{
throw PythonException.ThrowLastAsClrException();
}
return checked((int)num);
}
///
/// Convert a Python value to an instance of a primitive managed type.
///
internal static bool ToPrimitive(BorrowedReference value, Type obType, out object? result, bool setError)
{
result = null;
if (obType.IsEnum)
{
if (setError)
{
Exceptions.SetError(Exceptions.TypeError, "since Python.NET 3.0 int can not be converted to Enum implicitly. Use Enum(int_value)");
}
return false;
}
TypeCode tc = Type.GetTypeCode(obType);
switch (tc)
{
case TypeCode.String:
string? st = Runtime.GetManagedString(value);
if (st == null)
{
goto type_error;
}
result = st;
return true;
case TypeCode.Int32:
{
// Python3 always use PyLong API
nint num = Runtime.PyLong_AsSignedSize_t(value);
if (num == -1 && Exceptions.ErrorOccurred())
{
goto convert_error;
}
if (num > Int32.MaxValue || num < Int32.MinValue)
{
goto overflow;
}
result = (int)num;
return true;
}
case TypeCode.Boolean:
if (value == Runtime.PyTrue)
{
result = true;
return true;
}
if (value == Runtime.PyFalse)
{
result = false;
return true;
}
if (setError)
{
goto type_error;
}
return false;
case TypeCode.Byte:
{
if (Runtime.PyObject_TypeCheck(value, Runtime.PyBytesType))
{
if (Runtime.PyBytes_Size(value) == 1)
{
IntPtr bytePtr = Runtime.PyBytes_AsString(value);
result = (byte)Marshal.ReadByte(bytePtr);
return true;
}
goto type_error;
}
nint num = Runtime.PyLong_AsSignedSize_t(value);
if (num == -1 && Exceptions.ErrorOccurred())
{
goto convert_error;
}
if (num > Byte.MaxValue || num < Byte.MinValue)
{
goto overflow;
}
result = (byte)num;
return true;
}
case TypeCode.SByte:
{
if (Runtime.PyObject_TypeCheck(value, Runtime.PyBytesType))
{
if (Runtime.PyBytes_Size(value) == 1)
{
IntPtr bytePtr = Runtime.PyBytes_AsString(value);
result = (sbyte)Marshal.ReadByte(bytePtr);
return true;
}
goto type_error;
}
nint num = Runtime.PyLong_AsSignedSize_t(value);
if (num == -1 && Exceptions.ErrorOccurred())
{
goto convert_error;
}
if (num > SByte.MaxValue || num < SByte.MinValue)
{
goto overflow;
}
result = (sbyte)num;
return true;
}
case TypeCode.Char:
{
if (Runtime.PyObject_TypeCheck(value, Runtime.PyBytesType))
{
if (Runtime.PyBytes_Size(value) == 1)
{
IntPtr bytePtr = Runtime.PyBytes_AsString(value);
result = (char)Marshal.ReadByte(bytePtr);
return true;
}
goto type_error;
}
else if (Runtime.PyObject_TypeCheck(value, Runtime.PyUnicodeType))
{
if (Runtime.PyUnicode_GetLength(value) == 1)
{
int chr = Runtime.PyUnicode_ReadChar(value, 0);
result = (Char)chr;
return true;
}
goto type_error;
}
nint num = Runtime.PyLong_AsSignedSize_t(value);
if (num == -1 && Exceptions.ErrorOccurred())
{
goto convert_error;
}
if (num > Char.MaxValue || num < Char.MinValue)
{
goto overflow;
}
result = (char)num;
return true;
}
case TypeCode.Int16:
{
nint num = Runtime.PyLong_AsSignedSize_t(value);
if (num == -1 && Exceptions.ErrorOccurred())
{
goto convert_error;
}
if (num > Int16.MaxValue || num < Int16.MinValue)
{
goto overflow;
}
result = (short)num;
return true;
}
case TypeCode.Int64:
{
if (Runtime.Is32Bit)
{
if (!Runtime.PyInt_Check(value))
{
goto type_error;
}
long? num = Runtime.PyLong_AsLongLong(value);
if (num is null)
{
goto convert_error;
}
result = num.Value;
return true;
}
else
{
nint num = Runtime.PyLong_AsSignedSize_t(value);
if (num == -1 && Exceptions.ErrorOccurred())
{
goto convert_error;
}
result = (long)num;
return true;
}
}
case TypeCode.UInt16:
{
nint num = Runtime.PyLong_AsSignedSize_t(value);
if (num == -1 && Exceptions.ErrorOccurred())
{
goto convert_error;
}
if (num > UInt16.MaxValue || num < UInt16.MinValue)
{
goto overflow;
}
result = (ushort)num;
return true;
}
case TypeCode.UInt32:
{
nuint num = Runtime.PyLong_AsUnsignedSize_t(value);
if (num == unchecked((nuint)(-1)) && Exceptions.ErrorOccurred())
{
goto convert_error;
}
if (num > UInt32.MaxValue)
{
goto overflow;
}
result = (uint)num;
return true;
}
case TypeCode.UInt64:
{
ulong? num = Runtime.PyLong_AsUnsignedLongLong(value);
if (num is null)
{
goto convert_error;
}
result = num.Value;
return true;
}
case TypeCode.Single:
{
if (!Runtime.PyFloat_Check(value) && !Runtime.PyInt_Check(value))
{
goto type_error;
}
double num = Runtime.PyFloat_AsDouble(value);
if (num == -1.0 && Exceptions.ErrorOccurred())
{
goto convert_error;
}
if (num > Single.MaxValue || num < Single.MinValue)
{
if (!double.IsInfinity(num))
{
goto overflow;
}
}
result = (float)num;
return true;
}
case TypeCode.Double:
{
if (!Runtime.PyFloat_Check(value) && !Runtime.PyInt_Check(value))
{
goto type_error;
}
double num = Runtime.PyFloat_AsDouble(value);
if (num == -1.0 && Exceptions.ErrorOccurred())
{
goto convert_error;
}
result = num;
return true;
}
default:
goto type_error;
}
convert_error:
if (!setError)
{
Exceptions.Clear();
}
return false;
type_error:
if (setError)
{
string tpName = Runtime.PyObject_GetTypeName(value);
Exceptions.SetError(Exceptions.TypeError, $"'{tpName}' value cannot be converted to {obType}");
}
return false;
overflow:
// C# level overflow error
if (setError)
{
Exceptions.SetError(Exceptions.OverflowError, "value too large to convert");
}
return false;
}
private static void SetConversionError(BorrowedReference value, Type target)
{
// PyObject_Repr might clear the error
Runtime.PyErr_Fetch(out var causeType, out var causeVal, out var causeTrace);
var ob = Runtime.PyObject_Repr(value);
string src = "'object has no repr'";
if (ob.IsNull())
{
Exceptions.Clear();
}
else
{
src = Runtime.GetManagedString(ob.Borrow()) ?? src;
}
ob.Dispose();
Runtime.PyErr_Restore(causeType.StealNullable(), causeVal.StealNullable(), causeTrace.StealNullable());
Exceptions.RaiseTypeError($"Cannot convert {src} to {target}");
}
///
/// Convert a Python value to a correctly typed managed array instance.
/// The Python value must support the Python iterator protocol or and the
/// items in the sequence must be convertible to the target array type.
///
private static bool ToArray(BorrowedReference value, Type obType, out object? result, bool setError)
{
Type elementType = obType.GetElementType();
result = null;
using var IterObject = Runtime.PyObject_GetIter(value);
if (IterObject.IsNull())
{
if (setError)
{
SetConversionError(value, obType);
}
else
{
// PyObject_GetIter will have set an error
Exceptions.Clear();
}
return false;
}
IList list;
try
{
// MakeGenericType can throw because elementType may not be a valid generic argument even though elementType[] is a valid array type.
// For example, if elementType is a pointer type.
// See https://docs.microsoft.com/en-us/dotnet/api/system.type.makegenerictype#System_Type_MakeGenericType_System_Type
var constructedListType = typeof(List).MakeGenericType(elementType);
bool IsSeqObj = Runtime.PySequence_Check(value);
object[] constructorArgs = Array.Empty();
if (IsSeqObj)
{
var len = Runtime.PySequence_Size(value);
if (len >= 0)
{
if (len type == typeof(float) || type == typeof(double);
internal static bool IsInteger(Type type)
=> type == typeof(Byte) || type == typeof(SByte)
|| type == typeof(Int16) || type == typeof(UInt16)
|| type == typeof(Int32) || type == typeof(UInt32)
|| type == typeof(Int64) || type == typeof(UInt64);
}
public static class ConverterExtension
{
public static PyObject ToPython(this object? o)
{
if (o is null) return Runtime.None;
return Converter.ToPython(o, o.GetType()).MoveToPyObject();
}
public static PyObject ToPythonAs(this T? o)
{
if (o is null) return Runtime.None;
return Converter.ToPython(o, typeof(T)).MoveToPyObject();
}
}
}