diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index 5179c849b..18c9ce0dd 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -30,6 +30,10 @@ private Converter() private static Type flagsType; private static Type boolType; private static Type typeType; + private static IntPtr decimalCtor; + private static IntPtr dateTimeCtor; + private static IntPtr timeSpanCtor; + private static IntPtr tzInfoCtor; static Converter() { @@ -45,6 +49,37 @@ static Converter() flagsType = typeof(FlagsAttribute); boolType = typeof(Boolean); typeType = typeof(Type); + + IntPtr decimalMod = Runtime.PyImport_ImportModule("decimal"); + if (decimalMod == null) throw new PythonException(); + + IntPtr dateTimeMod = Runtime.PyImport_ImportModule("datetime"); + if (dateTimeMod == null) throw new PythonException(); + + decimalCtor = Runtime.PyObject_GetAttrString(decimalMod, "Decimal"); + if (decimalCtor == null) throw new PythonException(); + + dateTimeCtor = Runtime.PyObject_GetAttrString(dateTimeMod, "datetime"); + if (dateTimeCtor == null) throw new PythonException(); + + timeSpanCtor = Runtime.PyObject_GetAttrString(dateTimeMod, "timedelta"); + if (timeSpanCtor == null) throw new PythonException(); + + IntPtr tzInfoMod = PythonEngine.ModuleFromString("custom_tzinfo", + "from datetime import timedelta, tzinfo\n" + + "class GMT(tzinfo):\n" + + " def __init__(self, hours, minutes):\n" + + " self.hours = hours\n" + + " self.minutes = minutes\n" + + " def utcoffset(self, dt):\n" + + " return timedelta(hours=self.hours, minutes=self.minutes)\n" + + " def tzname(self, dt):\n" + + " return \"GMT {0:00}:{1:00}\".format(self.hours, self.minutes)\n" + + " def dst (self, dt):\n" + + " return timedelta(0)\n").Handle; + + tzInfoCtor = Runtime.PyObject_GetAttrString(tzInfoMod, "GMT"); + if (tzInfoCtor == null) throw new PythonException(); } @@ -100,6 +135,9 @@ internal static IntPtr GetPythonTypeByAlias(Type op) if (op == boolType) return Runtime.PyBoolType; + if (op == decimalType) + return Runtime.PyFloatType; + return IntPtr.Zero; } @@ -169,6 +207,14 @@ internal static IntPtr ToPython(object value, Type type) switch (tc) { case TypeCode.Object: + if (value is TimeSpan) + { + var timespan = (TimeSpan)value; + + IntPtr timeSpanArgs = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(timeSpanArgs, 0, Runtime.PyFloat_FromDouble(timespan.TotalDays)); + return Runtime.PyObject_CallObject(timeSpanCtor, timeSpanArgs); + } return CLRObject.GetInstHandle(value, type); case TypeCode.String: @@ -221,6 +267,29 @@ internal static IntPtr ToPython(object value, Type type) case TypeCode.UInt64: return Runtime.PyLong_FromUnsignedLongLong((ulong)value); + case TypeCode.Decimal: + string d2s = ((decimal)value).ToString(nfi); + IntPtr d2p = Runtime.PyString_FromString(d2s); + IntPtr decimalArgs = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(decimalArgs, 0, d2p); + + return Runtime.PyObject_CallObject(decimalCtor, decimalArgs); + + case TypeCode.DateTime: + var datetime = (DateTime)value; + + IntPtr dateTimeArgs = Runtime.PyTuple_New(8); + Runtime.PyTuple_SetItem(dateTimeArgs, 0, Runtime.PyInt_FromInt32(datetime.Year)); + Runtime.PyTuple_SetItem(dateTimeArgs, 1, Runtime.PyInt_FromInt32(datetime.Month)); + Runtime.PyTuple_SetItem(dateTimeArgs, 2, Runtime.PyInt_FromInt32(datetime.Day)); + Runtime.PyTuple_SetItem(dateTimeArgs, 3, Runtime.PyInt_FromInt32(datetime.Hour)); + Runtime.PyTuple_SetItem(dateTimeArgs, 4, Runtime.PyInt_FromInt32(datetime.Minute)); + Runtime.PyTuple_SetItem(dateTimeArgs, 5, Runtime.PyInt_FromInt32(datetime.Second)); + Runtime.PyTuple_SetItem(dateTimeArgs, 6, Runtime.PyInt_FromInt32(datetime.Millisecond)); + Runtime.PyTuple_SetItem(dateTimeArgs, 7, TzInfo(datetime.Kind)); + + return Runtime.PyObject_CallObject(dateTimeCtor, dateTimeArgs); + default: if (value is IEnumerable) { @@ -242,6 +311,16 @@ internal static IntPtr ToPython(object value, Type type) } } + private static IntPtr TzInfo(DateTimeKind kind) + { + if (kind == DateTimeKind.Unspecified) return Runtime.PyNone; + var offset = kind == DateTimeKind.Local ? DateTimeOffset.Now.Offset : TimeSpan.Zero; + IntPtr tzInfoArgs = Runtime.PyTuple_New(2); + Runtime.PyTuple_SetItem(tzInfoArgs, 0, Runtime.PyFloat_FromDouble(offset.Hours)); + Runtime.PyTuple_SetItem(tzInfoArgs, 1, Runtime.PyFloat_FromDouble(offset.Minutes)); + return Runtime.PyObject_CallObject(tzInfoCtor, tzInfoArgs); + } + /// /// In a few situations, we don't have any advisory type information @@ -428,6 +507,26 @@ internal static bool ToManagedValue(IntPtr value, Type obType, return false; } + var underlyingType = Nullable.GetUnderlyingType(obType); + if (underlyingType != null) + { + return ToManagedValue(value, underlyingType, out result, setError); + } + + var opImplicit = obType.GetMethod("op_Implicit", new[] { obType }); + if (opImplicit != null) + { + if (ToManagedValue(value, opImplicit.ReturnType, out result, setError)) + { + opImplicit = obType.GetMethod("op_Implicit", new[] { result.GetType() }); + if (opImplicit != null) + { + result = opImplicit.Invoke(null, new[] { result }); + } + return opImplicit != null; + } + } + return ToPrimitive(value, obType, out result, setError); } @@ -444,6 +543,32 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo switch (tc) { + case TypeCode.Object: + if (obType == typeof(TimeSpan)) + { + op = Runtime.PyObject_Str(value); + TimeSpan ts; + var arr = Runtime.GetManagedString(op).Split(','); + string sts = arr.Length == 1 ? arr[0] : arr[1]; + if (!TimeSpan.TryParse(sts, out ts)) + { + goto type_error; + } + Runtime.XDecref(op); + + int days = 0; + if (arr.Length > 1) + { + if (!int.TryParse(arr[0].Split(' ')[0].Trim(), out days)) + { + goto type_error; + } + } + result = ts.Add(TimeSpan.FromDays(days)); + return true; + } + break; + case TypeCode.String: string st = Runtime.GetManagedString(value); if (st == null) @@ -793,6 +918,30 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo Runtime.XDecref(op); result = d; return true; + + case TypeCode.Decimal: + op = Runtime.PyObject_Str(value); + decimal m; + string sm = Runtime.GetManagedString(op); + if (!Decimal.TryParse(sm, NumberStyles.Number, nfi, out m)) + { + goto type_error; + } + Runtime.XDecref(op); + result = m; + return true; + + case TypeCode.DateTime: + op = Runtime.PyObject_Str(value); + DateTime dt; + string sdt = Runtime.GetManagedString(op); + if (!DateTime.TryParse(sdt, out dt)) + { + goto type_error; + } + Runtime.XDecref(op); + result = dt; + return true; } @@ -837,7 +986,7 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s int size = Runtime.PySequence_Size(value); result = null; - if (size < 0) + if (size < 0 || elementType.IsGenericType) { if (setError) { diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs index f0c58f34f..b01480473 100644 --- a/src/runtime/methodbinder.cs +++ b/src/runtime/methodbinder.cs @@ -394,14 +394,27 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth } if (!typematch) { + // this takes care of nullables + var underlyingType = Nullable.GetUnderlyingType(pi[n].ParameterType); + if (underlyingType == null) + { + underlyingType = pi[n].ParameterType; + } // this takes care of enum values - TypeCode argtypecode = Type.GetTypeCode(pi[n].ParameterType); + TypeCode argtypecode = Type.GetTypeCode(underlyingType); TypeCode paramtypecode = Type.GetTypeCode(clrtype); if (argtypecode == paramtypecode) { typematch = true; clrtype = pi[n].ParameterType; } + // this takes care of implicit conversions + var opImplicit = pi[n].ParameterType.GetMethod("op_Implicit", new[] { clrtype }); + if (opImplicit != null) + { + typematch = opImplicit.ReturnType == pi[n].ParameterType; + clrtype = pi[n].ParameterType; + } } Runtime.XDecref(pyoptype); if (!typematch)