diff --git a/AUTHORS.md b/AUTHORS.md index 6cfa216b1..4fa522336 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -29,6 +29,7 @@ - Christoph Gohlke ([@cgohlke](https://github.com/cgohlke)) - Christopher Bremner ([@chrisjbremner](https://github.com/chrisjbremner)) - Christopher Pow ([@christopherpow](https://github.com/christopherpow)) +- Colton Sellers ([@C-SELLERS](https://github.com/C-SELLERS)) - Daniel Abrahamsson ([@danabr](https://github.com/danabr)) - Daniel Fernandez ([@fdanny](https://github.com/fdanny)) - Daniel Santana ([@dgsantana](https://github.com/dgsantana)) @@ -52,6 +53,7 @@ - Luke Stratman ([@lstratman](https://github.com/lstratman)) - Konstantin Posudevskiy ([@konstantin-posudevskiy](https://github.com/konstantin-posudevskiy)) - Matthias Dittrich ([@matthid](https://github.com/matthid)) +- Martin Molinero ([@Martin-Molinero](https://github.com/Martin-Molinero)) - Meinrad Recheis ([@henon](https://github.com/henon)) - Mohamed Koubaa ([@koubaa](https://github.com/koubaa)) - Patrick Stewart ([@patstew](https://github.com/patstew)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bee653e8..305c317a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,11 +21,6 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. details about the cause of the failure - `clr.AddReference` no longer adds ".dll" implicitly - `PyIter(PyObject)` constructor replaced with static `PyIter.GetIter(PyObject)` method -- BREAKING: Return values from .NET methods that return an interface are now automatically - wrapped in that interface. This is a breaking change for users that rely on being - able to access members that are part of the implementation class, but not the - interface. Use the new __implementation__ or __raw_implementation__ properties to - if you need to "downcast" to the implementation class. - BREAKING: Parameters marked with `ParameterAttributes.Out` are no longer returned in addition to the regular method return value (unless they are passed with `ref` or `out` keyword). - BREAKING: Drop support for the long-deprecated CLR.* prefix. diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index 875adf8ef..446710e5b 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -34,6 +34,56 @@ public void Dispose() PythonEngine.Shutdown(); } + [Test] + public void ConvertListRoundTrip() + { + var list = new List { typeof(decimal), typeof(int) }; + var py = list.ToPython(); + object result; + var converted = Converter.ToManaged(py.Handle, typeof(List), out result, false); + + Assert.IsTrue(converted); + Assert.AreEqual(result, list); + } + + [Test] + public void ConvertPyListToArray() + { + var array = new List { typeof(decimal), typeof(int) }; + var py = array.ToPython(); + object result; + var converted = Converter.ToManaged(py.Handle, typeof(Type[]), out result, false); + + Assert.IsTrue(converted); + Assert.AreEqual(result, array); + } + + [Test] + public void ConvertTimeSpanRoundTrip() + { + var timespan = new TimeSpan(0, 1, 0, 0); + var pyTimedelta = timespan.ToPython(); + + object result; + var converted = Converter.ToManaged(pyTimedelta.Handle, typeof(TimeSpan), out result, false); + + Assert.IsTrue(converted); + Assert.AreEqual(result, timespan); + } + + [Test] + public void ConvertDateTimeRoundTrip() + { + var datetime = new DateTime(2000, 1, 1); + var pyDatetime = datetime.ToPython(); + + object result; + var converted = Converter.ToManaged(pyDatetime.Handle, typeof(DateTime), out result, false); + + Assert.IsTrue(converted); + Assert.AreEqual(result, datetime); + } + [Test] public void TestConvertSingleToManaged( [Values(float.PositiveInfinity, float.NegativeInfinity, float.MinValue, float.MaxValue, float.NaN, @@ -136,5 +186,17 @@ public void RawPyObjectProxy() var proxiedHandle = pyObjectProxy.GetAttr("Handle").As(); Assert.AreEqual(pyObject.Handle, proxiedHandle); } + + [Test] + public void PrimitiveIntConversion() + { + decimal value = 10; + var pyValue = value.ToPython(); + + // Try to convert python value to int + var testInt = pyValue.As(); + Assert.AreEqual(testInt , 10); + } + } } diff --git a/src/embed_tests/TestInterfaceClasses.cs b/src/embed_tests/TestInterfaceClasses.cs new file mode 100644 index 000000000..e597d2717 --- /dev/null +++ b/src/embed_tests/TestInterfaceClasses.cs @@ -0,0 +1,76 @@ +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + public class TestInterfaceClasses + { + public string testCode = @" +from clr import AddReference +AddReference(""System"") +AddReference(""Python.EmbeddingTest"") +from Python.EmbeddingTest import * + +testModule = TestInterfaceClasses.GetInstance() +print(testModule.Child.ChildBool) + +"; + + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + [Test] + public void TestInterfaceDerivedClassMembers() + { + // This test gets an instance of the CSharpTestModule in Python + // and then attempts to access it's member "Child"'s bool that is + // not defined in the interface. + PythonEngine.Exec(testCode); + } + + public interface IInterface + { + bool InterfaceBool { get; set; } + } + + public class Parent : IInterface + { + public bool InterfaceBool { get; set; } + public bool ParentBool { get; set; } + } + + public class Child : Parent + { + public bool ChildBool { get; set; } + } + + public class CSharpTestModule + { + public IInterface Child; + + public CSharpTestModule() + { + Child = new Child + { + ChildBool = true, + ParentBool = true, + InterfaceBool = true + }; + } + } + + public static CSharpTestModule GetInstance() + { + return new CSharpTestModule(); + } + } +} diff --git a/src/embed_tests/TestMethodBinder.cs b/src/embed_tests/TestMethodBinder.cs new file mode 100644 index 000000000..010e3d2af --- /dev/null +++ b/src/embed_tests/TestMethodBinder.cs @@ -0,0 +1,121 @@ +using System; + +using NUnit.Framework; + +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + public class TestMethodBinder + { + private static dynamic module; + private static string testModule = @" +from clr import AddReference +AddReference(""System"") +AddReference(""Python.EmbeddingTest"") +from Python.EmbeddingTest import * +class PythonModel(TestMethodBinder.CSharpModel): + def TestA(self): + return self.OnlyString(TestMethodBinder.TestImplicitConversion()) + def TestB(self): + return self.OnlyClass('input string') + def TestC(self): + return self.InvokeModel('input string') + def TestD(self): + return self.InvokeModel(TestMethodBinder.TestImplicitConversion()) + def TestE(self, array): + return array.Length == 2"; + + + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + module = PythonEngine.ModuleFromString("module", testModule).GetAttr("PythonModel").Invoke(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + [Test] + public void ImplicitConversionToString() + { + var data = (string)module.TestA(); + // we assert implicit conversion took place + Assert.AreEqual("OnlyString impl: implicit to string", data); + } + + [Test] + public void ImplicitConversionToClass() + { + var data = (string)module.TestB(); + // we assert implicit conversion took place + Assert.AreEqual("OnlyClass impl", data); + } + + [Test] + public void WillAvoidUsingImplicitConversionIfPossible_String() + { + var data = (string)module.TestC(); + // we assert no implicit conversion took place + Assert.AreEqual("string impl: input string", data); + } + + [Test] + public void WillAvoidUsingImplicitConversionIfPossible_Class() + { + var data = (string)module.TestD(); + // we assert no implicit conversion took place + Assert.AreEqual("TestImplicitConversion impl", data); + + } + + [Test] + public void ArrayLength() + { + var array = new[] { "pepe", "pinocho" }; + var data = (bool)module.TestE(array); + + // Assert it is true + Assert.AreEqual(true, data); + } + + public class CSharpModel + { + public virtual string OnlyClass(TestImplicitConversion data) + { + return "OnlyClass impl"; + } + + public virtual string OnlyString(string data) + { + return "OnlyString impl: " + data; + } + + public virtual string InvokeModel(string data) + { + return "string impl: " + data; + } + + public virtual string InvokeModel(TestImplicitConversion data) + { + return "TestImplicitConversion impl"; + } + } + + public class TestImplicitConversion + { + public static implicit operator string(TestImplicitConversion symbol) + { + return "implicit to string"; + } + public static implicit operator TestImplicitConversion(string symbol) + { + return new TestImplicitConversion(); + } + } + } +} diff --git a/src/embed_tests/TestOperator.cs b/src/embed_tests/TestOperator.cs index 8e9feb241..c90001267 100644 --- a/src/embed_tests/TestOperator.cs +++ b/src/embed_tests/TestOperator.cs @@ -335,6 +335,29 @@ public void SymmetricalOperatorOverloads() "); } + [Test] + public void OperatorInequality() + { + string name = string.Format("{0}.{1}", + typeof(OperableObject).DeclaringType.Name, + typeof(OperableObject).Name); + string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; + + PythonEngine.Exec($@" +from {module} import * +cls = {name} +b = cls(10) +a = cls(2) + + +c = a <= b +assert c == (a.Num <= b.Num) + +c = a >= b +assert c == (a.Num >= b.Num) +"); + } + [Test] public void OperatorOverloadMissingArgument() { diff --git a/src/runtime/arrayobject.cs b/src/runtime/arrayobject.cs index 5c97c6dbf..de4166091 100644 --- a/src/runtime/arrayobject.cs +++ b/src/runtime/arrayobject.cs @@ -137,13 +137,8 @@ static NewReference NewInstance(Type elementType, BorrowedReference arrayPyType, public new static IntPtr mp_subscript(IntPtr ob, IntPtr idx) { var obj = (CLRObject)GetManagedObject(ob); - var arrObj = (ArrayObject)GetManagedObjectType(ob); - if (!arrObj.type.Valid) - { - return Exceptions.RaiseTypeError(arrObj.type.DeletedMessage); - } var items = obj.inst as Array; - Type itemType = arrObj.type.Value.GetElementType(); + Type itemType = obj.inst.GetType().GetElementType(); int rank = items.Rank; int index; object value; diff --git a/src/runtime/assemblymanager.cs b/src/runtime/assemblymanager.cs index 0387d2dfc..165e6d53d 100644 --- a/src/runtime/assemblymanager.cs +++ b/src/runtime/assemblymanager.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Reflection; using System.Threading; +using System.Threading.Tasks; namespace Python.Runtime { @@ -25,18 +26,17 @@ internal class AssemblyManager // than it can end up referring to assemblies that are already unloaded (default behavior after unload appDomain - // unless LoaderOptimization.MultiDomain is used); // So for multidomain support it is better to have the dict. recreated for each app-domain initialization - private static ConcurrentDictionary> namespaces = - new ConcurrentDictionary>(); - //private static Dictionary> generics; - private static AssemblyLoadEventHandler lhandler; - private static ResolveEventHandler rhandler; + private static ConcurrentDictionary> namespaces = + new ConcurrentDictionary>(); + private static ConcurrentDictionary assembliesNamesCache = + new ConcurrentDictionary(); + private static ConcurrentQueue assemblies = new ConcurrentQueue(); + private static int pendingAssemblies; // updated only under GIL? private static Dictionary probed = new Dictionary(32); - - // modified from event handlers below, potentially triggered from different .NET threads - private static ConcurrentQueue assemblies; - internal static List pypath; + private static List pypath = new List(16); + private static Dictionary> filesInPath = new Dictionary>(); private AssemblyManager() { @@ -49,30 +49,34 @@ private AssemblyManager() /// internal static void Initialize() { - assemblies = new ConcurrentQueue(); - pypath = new List(16); - AppDomain domain = AppDomain.CurrentDomain; - lhandler = new AssemblyLoadEventHandler(AssemblyLoadHandler); - domain.AssemblyLoad += lhandler; - - rhandler = new ResolveEventHandler(ResolveHandler); - domain.AssemblyResolve += rhandler; + domain.AssemblyLoad += AssemblyLoadHandler; + domain.AssemblyResolve += ResolveHandler; - Assembly[] items = domain.GetAssemblies(); - foreach (Assembly a in items) + foreach (var assembly in domain.GetAssemblies()) { try { - ScanAssembly(a); - assemblies.Enqueue(a); + LaunchAssemblyLoader(assembly); } catch (Exception ex) { - Debug.WriteLine("Error scanning assembly {0}. {1}", a, ex); + Debug.WriteLine("Error scanning assembly {0}. {1}", assembly, ex); } } + + var safeCount = 0; + // lets wait until all assemblies are loaded + do + { + if (safeCount++ > 200) + { + throw new TimeoutException("Timeout while waiting for assemblies to load"); + } + + Thread.Sleep(50); + } while (pendingAssemblies > 0); } @@ -82,8 +86,8 @@ internal static void Initialize() internal static void Shutdown() { AppDomain domain = AppDomain.CurrentDomain; - domain.AssemblyLoad -= lhandler; - domain.AssemblyResolve -= rhandler; + domain.AssemblyLoad -= AssemblyLoadHandler; + domain.AssemblyResolve -= ResolveHandler; } @@ -97,8 +101,35 @@ internal static void Shutdown() private static void AssemblyLoadHandler(object ob, AssemblyLoadEventArgs args) { Assembly assembly = args.LoadedAssembly; - assemblies.Enqueue(assembly); - ScanAssembly(assembly); + LaunchAssemblyLoader(assembly); + } + + /// + /// Launches a new task that will load the provided assembly + /// + private static void LaunchAssemblyLoader(Assembly assembly) + { + if (assembly != null) + { + Interlocked.Increment(ref pendingAssemblies); + Task.Factory.StartNew(() => + { + try + { + if (assembliesNamesCache.TryAdd(assembly.GetName().Name, assembly)) + { + assemblies.Enqueue(assembly); + ScanAssembly(assembly); + } + } + catch + { + // pass + } + + Interlocked.Decrement(ref pendingAssemblies); + }); + } } @@ -112,12 +143,12 @@ private static void AssemblyLoadHandler(object ob, AssemblyLoadEventArgs args) private static Assembly ResolveHandler(object ob, ResolveEventArgs args) { string name = args.Name.ToLower(); - foreach (Assembly a in assemblies) + foreach (var assembly in assemblies) { - string full = a.FullName.ToLower(); + var full = assembly.FullName.ToLower(); if (full.StartsWith(name)) { - return a; + return assembly; } } return LoadAssemblyPath(args.Name); @@ -139,19 +170,60 @@ internal static void UpdatePath() { BorrowedReference list = Runtime.PySys_GetObject("path"); var count = Runtime.PyList_Size(list); + var sep = Path.DirectorySeparatorChar; + if (count != pypath.Count) { pypath.Clear(); probed.Clear(); + for (var i = 0; i < count; i++) { BorrowedReference item = Runtime.PyList_GetItem(list, i); string path = Runtime.GetManagedString(item); if (path != null) { - pypath.Add(path); + pypath.Add(path == string.Empty ? path : path + sep); } } + + // for performance we will search for all files in each directory in the path once + Parallel.ForEach(pypath.Where(s => + { + try + { + lock (filesInPath) + { + // only search in directory if it exists and we haven't already analyzed it + return Directory.Exists(s) && !filesInPath.ContainsKey(s); + } + } + catch + { + // just in case, file operations can throw + } + return false; + }), path => + { + var container = new HashSet(); + try + { + foreach (var file in Directory.EnumerateFiles(path) + .Where(file => file.EndsWith(".dll") || file.EndsWith(".exe"))) + { + container.Add(Path.GetFileName(file)); + } + } + catch + { + // just in case, file operations can throw + } + + lock (filesInPath) + { + filesInPath[path] = container; + } + }); } } @@ -163,30 +235,18 @@ internal static void UpdatePath() /// public static string FindAssembly(string name) { - char sep = Path.DirectorySeparatorChar; - - foreach (string head in pypath) + foreach (var kvp in filesInPath) { - string path; - if (head == null || head.Length == 0) - { - path = name; - } - else - { - path = head + sep + name; - } - - string temp = path + ".dll"; - if (File.Exists(temp)) + var dll = $"{name}.dll"; + if (kvp.Value.Contains(dll)) { - return temp; + return kvp.Key + dll; } - temp = path + ".exe"; - if (File.Exists(temp)) + var executable = $"{name}.exe"; + if (kvp.Value.Contains(executable)) { - return temp; + return kvp.Key + executable; } } return null; @@ -242,14 +302,8 @@ public static Assembly LoadAssemblyFullPath(string name) /// public static Assembly FindLoadedAssembly(string name) { - foreach (Assembly a in assemblies) - { - if (a.GetName().Name == name) - { - return a; - } - } - return null; + Assembly result; + return assembliesNamesCache.TryGetValue(name, out result) ? result : null; } /// @@ -267,6 +321,7 @@ internal static void ScanAssembly(Assembly assembly) // A couple of things we want to do here: first, we want to // gather a list of all of the namespaces contributed to by // the assembly. + foreach (Type t in GetTypes(assembly)) { string ns = t.Namespace ?? ""; @@ -277,13 +332,13 @@ internal static void ScanAssembly(Assembly assembly) for (var n = 0; n < names.Length; n++) { s = n == 0 ? names[0] : s + "." + names[n]; - namespaces.TryAdd(s, new ConcurrentDictionary()); + namespaces.TryAdd(s, new ConcurrentDictionary()); } } if (ns != null) { - namespaces[ns].TryAdd(assembly, string.Empty); + namespaces[ns].TryAdd(assembly, 1); } if (ns != null && t.IsGenericTypeDefinition) diff --git a/src/runtime/classmanager.cs b/src/runtime/classmanager.cs index 1ee06e682..2de454c66 100644 --- a/src/runtime/classmanager.cs +++ b/src/runtime/classmanager.cs @@ -177,9 +177,8 @@ internal static Dictionary RestoreRuntimeData(R /// A Borrowed reference to the ClassBase object internal static ClassBase GetClass(Type type) { - ClassBase cb = null; - cache.TryGetValue(type, out cb); - if (cb != null) + ClassBase cb; + if (cache.TryGetValue(type, out cb)) { return cb; } @@ -225,6 +224,11 @@ private static ClassBase CreateClass(Type type) impl = new ArrayObject(type); } + else if (type.IsKeyValuePairEnumerable()) + { + impl = new KeyValuePairEnumerableObject(type); + } + else if (type.IsInterface) { impl = new InterfaceObject(type); diff --git a/src/runtime/classobject.cs b/src/runtime/classobject.cs index 4aa97f648..2f8da8a54 100644 --- a/src/runtime/classobject.cs +++ b/src/runtime/classobject.cs @@ -33,15 +33,15 @@ internal ClassObject(Type tp) : base(tp) /// internal NewReference GetDocString() { - MethodBase[] methods = binder.GetMethods(); + var methods = binder.GetMethods(); var str = ""; - foreach (MethodBase t in methods) + foreach (var t in methods) { if (str.Length > 0) { str += Environment.NewLine; } - str += t.ToString(); + str += t.MethodBase.ToString(); } return NewReference.DangerousFromPointer(Runtime.PyString_FromString(str)); } diff --git a/src/runtime/constructorbinding.cs b/src/runtime/constructorbinding.cs index 803823e39..b3c6b655c 100644 --- a/src/runtime/constructorbinding.cs +++ b/src/runtime/constructorbinding.cs @@ -126,7 +126,7 @@ public static IntPtr tp_repr(IntPtr ob) Runtime.XIncref(self.repr); return self.repr; } - MethodBase[] methods = self.ctorBinder.GetMethods(); + var methods = self.ctorBinder.GetMethods(); if (!self.type.Valid) { @@ -134,8 +134,9 @@ public static IntPtr tp_repr(IntPtr ob) } string name = self.type.Value.FullName; var doc = ""; - foreach (MethodBase t in methods) + foreach (var methodInformation in methods) { + var t = methodInformation.MethodBase; if (doc.Length > 0) { doc += "\n"; diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index f3b378113..342687647 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -30,6 +30,9 @@ private Converter() private static Type flagsType; private static Type boolType; private static Type typeType; + private static IntPtr dateTimeCtor; + private static IntPtr timeSpanCtor; + private static IntPtr tzInfoCtor; static Converter() { @@ -45,6 +48,31 @@ static Converter() flagsType = typeof(FlagsAttribute); boolType = typeof(Boolean); typeType = typeof(Type); + + IntPtr dateTimeMod = Runtime.PyImport_ImportModule("datetime"); + if (dateTimeMod == 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 +class GMT(tzinfo): + def __init__(self, hours, minutes): + self.hours = hours + self.minutes = minutes + def utcoffset(self, dt): + return timedelta(hours=self.hours, minutes=self.minutes) + def tzname(self, dt): + return f'GMT {self.hours:00}:{self.minutes:00}' + def dst (self, dt): + return timedelta(0)").Handle; + + tzInfoCtor = Runtime.PyObject_GetAttrString(tzInfoMod, "GMT"); + if (tzInfoCtor == null) throw new PythonException(); } @@ -71,6 +99,9 @@ internal static Type GetTypeByAlias(IntPtr op) if (op == Runtime.PyBoolType) return boolType; + if (op == Runtime.PyDecimalType) + return decimalType; + return null; } @@ -97,6 +128,9 @@ internal static IntPtr GetPythonTypeByAlias(Type op) if (op == boolType) return Runtime.PyBoolType; + if (op == decimalType) + return Runtime.PyDecimalType; + return IntPtr.Zero; } @@ -173,21 +207,6 @@ internal static IntPtr ToPython(object value, Type type) } } - if (type.IsInterface) - { - var ifaceObj = (InterfaceObject)ClassManager.GetClass(type); - return ifaceObj.WrapObject(value); - } - - // We need to special case interface array handling to ensure we - // produce the correct type. Value may be an array of some concrete - // type (FooImpl[]), but we want access to go via the interface type - // (IFoo[]). - if (type.IsArray && type.GetElementType().IsInterface) - { - return CLRObject.GetInstHandle(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. var pyderived = value as IPythonDerivedType; @@ -208,6 +227,17 @@ 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)); + var returnTimeSpan = Runtime.PyObject_CallObject(timeSpanCtor, timeSpanArgs); + // clean up + Runtime.XDecref(timeSpanArgs); + return returnTimeSpan; + } return CLRObject.GetInstHandle(value, type); case TypeCode.String: @@ -260,6 +290,41 @@ internal static IntPtr ToPython(object value, Type type) case TypeCode.UInt64: return Runtime.PyLong_FromUnsignedLongLong((ulong)value); + case TypeCode.Decimal: + // C# decimal to python decimal has a big impact on performance + // so we will use C# double and python float + return Runtime.PyFloat_FromDouble(decimal.ToDouble((decimal)value)); + + case TypeCode.DateTime: + var datetime = (DateTime)value; + + var size = datetime.Kind == DateTimeKind.Unspecified ? 7 : 8; + + IntPtr dateTimeArgs = Runtime.PyTuple_New(size); + 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)); + + // datetime.datetime 6th argument represents micro seconds + var totalSeconds = datetime.TimeOfDay.TotalSeconds; + var microSeconds = Convert.ToInt32((totalSeconds - Math.Truncate(totalSeconds)) * 1000000); + if (microSeconds == 1000000) microSeconds = 999999; + Runtime.PyTuple_SetItem(dateTimeArgs, 6, Runtime.PyInt_FromInt32(microSeconds)); + + if (size == 8) + { + Runtime.PyTuple_SetItem(dateTimeArgs, 7, TzInfo(datetime.Kind)); + } + + var returnDateTime = Runtime.PyObject_CallObject(dateTimeCtor, dateTimeArgs); + // clean up + Runtime.XDecref(dateTimeArgs); + return returnDateTime; + + default: if (value is IEnumerable) { @@ -281,6 +346,17 @@ 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)); + var returnValue = Runtime.PyObject_CallObject(tzInfoCtor, tzInfoArgs); + Runtime.XDecref(tzInfoArgs); + return returnValue; + } /// /// In a few situations, we don't have any advisory type information @@ -299,6 +375,12 @@ internal static IntPtr ToPythonImplicit(object value) } + internal static bool ToManaged(IntPtr value, Type type, + out object result, bool setError) + { + var usedImplicit = false; + return ToManaged(value, type, out result, setError, out usedImplicit); + } /// /// Return a managed object for the given Python object, taking funny /// byref types into account. @@ -309,21 +391,26 @@ internal static IntPtr ToPythonImplicit(object value) /// If true, call Exceptions.SetError with the reason for failure. /// True on success internal static bool ToManaged(IntPtr value, Type type, - out object result, bool setError) + out object result, bool setError, out bool usedImplicit) { if (type.IsByRef) { type = type.GetElementType(); } - return Converter.ToManagedValue(value, type, out result, setError); + return Converter.ToManagedValue(value, type, out result, setError, out usedImplicit); } internal static bool ToManagedValue(BorrowedReference value, Type obType, out object result, bool setError) - => ToManagedValue(value.DangerousGetAddress(), obType, out result, setError); + { + var usedImplicit = false; + return ToManagedValue(value.DangerousGetAddress(), obType, out result, setError, out usedImplicit); + } + internal static bool ToManagedValue(IntPtr value, Type obType, - out object result, bool setError) + out object result, bool setError, out bool usedImplicit) { + usedImplicit = false; if (obType == typeof(PyObject)) { Runtime.XIncref(value); // PyObject() assumes ownership @@ -331,6 +418,15 @@ internal static bool ToManagedValue(IntPtr value, Type obType, return true; } + if (obType.IsGenericType && Runtime.PyObject_TYPE(value) == Runtime.PyListType) + { + var typeDefinition = obType.GetGenericTypeDefinition(); + if (typeDefinition == typeof(List<>)) + { + return ToList(value, obType, out result, setError); + } + } + // Common case: if the Python value is a wrapped managed object // instance, just return the wrapped object. ManagedType mt = ManagedType.GetManagedObject(value); @@ -346,6 +442,18 @@ internal static bool ToManagedValue(IntPtr value, Type obType, result = tmp; return true; } + else + { + var type = tmp.GetType(); + // check implicit conversions that receive tmp type and return obType + var conversionMethod = type.GetMethod("op_Implicit", new[] { type }); + if (conversionMethod != null && conversionMethod.ReturnType == obType) + { + result = conversionMethod.Invoke(null, new[] { tmp }); + usedImplicit = true; + return true; + } + } if (setError) { string typeString = tmp is null ? "null" : tmp.GetType().ToString(); @@ -496,6 +604,12 @@ 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, out usedImplicit); + } + TypeCode typeCode = Type.GetTypeCode(obType); if (typeCode == TypeCode.Object) { @@ -506,6 +620,20 @@ internal static bool ToManagedValue(IntPtr value, Type obType, } } + var opImplicit = obType.GetMethod("op_Implicit", new[] { obType }); + if (opImplicit != null) + { + if (ToManagedValue(value, opImplicit.ReturnType, out result, setError, out usedImplicit)) + { + 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); } @@ -522,6 +650,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) @@ -534,7 +688,12 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo case TypeCode.Int32: { // Python3 always use PyLong API - nint num = Runtime.PyLong_AsSignedSize_t(value); + op = Runtime.PyNumber_Long(value); + if (op == IntPtr.Zero && Exceptions.ErrorOccurred()) + { + goto convert_error; + } + nint num = Runtime.PyLong_AsSignedSize_t(op); if (num == -1 && Exceptions.ErrorOccurred()) { goto convert_error; @@ -642,7 +801,12 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo case TypeCode.Int16: { - nint num = Runtime.PyLong_AsSignedSize_t(value); + op = Runtime.PyNumber_Long(value); + if (op == IntPtr.Zero && Exceptions.ErrorOccurred()) + { + goto convert_error; + } + nint num = Runtime.PyLong_AsSignedSize_t(op); if (num == -1 && Exceptions.ErrorOccurred()) { goto convert_error; @@ -673,7 +837,12 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo } else { - nint num = Runtime.PyLong_AsSignedSize_t(value); + op = Runtime.PyNumber_Long(value); + if (op == IntPtr.Zero && Exceptions.ErrorOccurred()) + { + goto convert_error; + } + nint num = Runtime.PyLong_AsSignedSize_t(op); if (num == -1 && Exceptions.ErrorOccurred()) { goto convert_error; @@ -685,7 +854,12 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo case TypeCode.UInt16: { - nint num = Runtime.PyLong_AsSignedSize_t(value); + op = Runtime.PyNumber_Long(value); + if (op == IntPtr.Zero && Exceptions.ErrorOccurred()) + { + goto convert_error; + } + nint num = Runtime.PyLong_AsSignedSize_t(op); if (num == -1 && Exceptions.ErrorOccurred()) { goto convert_error; @@ -700,7 +874,12 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo case TypeCode.UInt32: { - nuint num = Runtime.PyLong_AsUnsignedSize_t(value); + op = Runtime.PyNumber_Long(value); + if (op == IntPtr.Zero && Exceptions.ErrorOccurred()) + { + goto convert_error; + } + nuint num = Runtime.PyLong_AsUnsignedSize_t(op); if (num == unchecked((nuint)(-1)) && Exceptions.ErrorOccurred()) { goto convert_error; @@ -715,7 +894,12 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo case TypeCode.UInt64: { - ulong num = Runtime.PyLong_AsUnsignedLongLong(value); + op = Runtime.PyNumber_Long(value); + if (op == IntPtr.Zero && Exceptions.ErrorOccurred()) + { + goto convert_error; + } + ulong num = Runtime.PyLong_AsUnsignedLongLong(op); if (num == ulong.MaxValue && Exceptions.ErrorOccurred()) { goto convert_error; @@ -752,6 +936,28 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo result = num; return true; } + case TypeCode.Decimal: + op = Runtime.PyObject_Str(value); + decimal m; + string sm = Runtime.GetManagedString(op); + if (!Decimal.TryParse(sm, NumberStyles.Number | NumberStyles.AllowExponent, 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 = sdt.EndsWith("+00:00") ? dt.ToUniversalTime() : dt; + return true; default: goto type_error; } @@ -809,7 +1015,7 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s result = null; IntPtr IterObject = Runtime.PyObject_GetIter(value); - if (IterObject == IntPtr.Zero) + if (IterObject == IntPtr.Zero || elementType.IsGenericType) { if (setError) { @@ -823,6 +1029,43 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s return false; } + var list = MakeList(value, IterObject, obType, elementType, setError); + if (list == null) + { + return false; + } + + Array items = Array.CreateInstance(elementType, list.Count); + list.CopyTo(items, 0); + + result = items; + return true; + } + + /// + /// Convert a Python value to a correctly typed managed list instance. + /// The Python value must support the Python sequence protocol and the + /// items in the sequence must be convertible to the target list type. + /// + private static bool ToList(IntPtr value, Type obType, out object result, bool setError) + { + var elementType = obType.GetGenericArguments()[0]; + IntPtr IterObject = Runtime.PyObject_GetIter(value); + result = MakeList(value, IterObject, obType, elementType, setError); + return result != null; + } + + /// + /// Helper function for ToArray and ToList that creates a IList out of iterable objects + /// + /// + /// + /// + /// + /// + /// + private static IList MakeList(IntPtr value, IntPtr IterObject, Type obType, Type elementType, bool setError) + { IList list; try { @@ -850,19 +1093,20 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s Exceptions.SetError(e); SetConversionError(value, obType); } - return false; + + return null; } IntPtr item; - + var usedImplicit = false; while ((item = Runtime.PyIter_Next(IterObject)) != IntPtr.Zero) { object obj; - if (!Converter.ToManaged(item, elementType, out obj, setError)) + if (!Converter.ToManaged(item, elementType, out obj, setError, out usedImplicit)) { Runtime.XDecref(item); - return false; + return null; } list.Add(obj); @@ -870,11 +1114,7 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s } Runtime.XDecref(IterObject); - Array items = Array.CreateInstance(elementType, list.Count); - list.CopyTo(items, 0); - - result = items; - return true; + return list; } diff --git a/src/runtime/genericutil.cs b/src/runtime/genericutil.cs index 92b847e98..de00b2550 100644 --- a/src/runtime/genericutil.cs +++ b/src/runtime/genericutil.cs @@ -27,25 +27,30 @@ public static void Reset() /// A generic type definition (t.IsGenericTypeDefinition must be true) internal static void Register(Type t) { - if (null == t.Namespace || null == t.Name) + lock (mapping) { - return; - } + if (null == t.Namespace || null == t.Name) + { + return; + } - Dictionary> nsmap; - if (!mapping.TryGetValue(t.Namespace, out nsmap)) - { - nsmap = new Dictionary>(); - mapping[t.Namespace] = nsmap; - } - string basename = GetBasename(t.Name); - List gnames; - if (!nsmap.TryGetValue(basename, out gnames)) - { - gnames = new List(); - nsmap[basename] = gnames; + Dictionary> nsmap; + if (!mapping.TryGetValue(t.Namespace, out nsmap)) + { + nsmap = new Dictionary>(); + mapping[t.Namespace] = nsmap; + } + + string basename = GetBasename(t.Name); + List gnames; + if (!nsmap.TryGetValue(basename, out gnames)) + { + gnames = new List(); + nsmap[basename] = gnames; + } + + gnames.Add(t.Name); } - gnames.Add(t.Name); } /// @@ -53,17 +58,20 @@ internal static void Register(Type t) /// public static List GetGenericBaseNames(string ns) { - Dictionary> nsmap; - if (!mapping.TryGetValue(ns, out nsmap)) + lock (mapping) { - return null; - } - var names = new List(); - foreach (string key in nsmap.Keys) - { - names.Add(key); + Dictionary> nsmap; + if (!mapping.TryGetValue(ns, out nsmap)) + { + return null; + } + var names = new List(); + foreach (string key in nsmap.Keys) + { + names.Add(key); + } + return names; } - return names; } /// @@ -79,29 +87,32 @@ public static Type GenericForType(Type t, int paramCount) /// public static Type GenericByName(string ns, string basename, int paramCount) { - Dictionary> nsmap; - if (!mapping.TryGetValue(ns, out nsmap)) + lock (mapping) { - return null; - } + Dictionary> nsmap; + if (!mapping.TryGetValue(ns, out nsmap)) + { + return null; + } - List names; - if (!nsmap.TryGetValue(GetBasename(basename), out names)) - { - return null; - } + List names; + if (!nsmap.TryGetValue(GetBasename(basename), out names)) + { + return null; + } - foreach (string name in names) - { - string qname = ns + "." + name; - Type o = AssemblyManager.LookupTypes(qname).FirstOrDefault(); - if (o != null && o.GetGenericArguments().Length == paramCount) + foreach (string name in names) { - return o; + string qname = ns + "." + name; + Type o = AssemblyManager.LookupTypes(qname).FirstOrDefault(); + if (o != null && o.GetGenericArguments().Length == paramCount) + { + return o; + } } - } - return null; + return null; + } } /// @@ -109,17 +120,22 @@ public static Type GenericByName(string ns, string basename, int paramCount) /// public static string GenericNameForBaseName(string ns, string name) { - Dictionary> nsmap; - if (!mapping.TryGetValue(ns, out nsmap)) + lock (mapping) { - return null; - } - List gnames; - nsmap.TryGetValue(name, out gnames); - if (gnames?.Count > 0) - { - return gnames[0]; + Dictionary> nsmap; + if (!mapping.TryGetValue(ns, out nsmap)) + { + return null; + } + + List gnames; + nsmap.TryGetValue(name, out gnames); + if (gnames?.Count > 0) + { + return gnames[0]; + } } + return null; } diff --git a/src/runtime/indexer.cs b/src/runtime/indexer.cs index 0772b57c6..b20143067 100644 --- a/src/runtime/indexer.cs +++ b/src/runtime/indexer.cs @@ -58,13 +58,13 @@ internal void SetItem(IntPtr inst, IntPtr args) internal bool NeedsDefaultArgs(IntPtr args) { var pynargs = Runtime.PyTuple_Size(args); - MethodBase[] methods = SetterBinder.GetMethods(); - if (methods.Length == 0) + var methods = SetterBinder.GetMethods(); + if (methods.Count == 0) { return false; } - MethodBase mi = methods[0]; + var mi = methods[0].MethodBase; ParameterInfo[] pi = mi.GetParameters(); // need to subtract one for the value int clrnargs = pi.Length - 1; @@ -99,8 +99,8 @@ internal IntPtr GetDefaultArgs(IntPtr args) var pynargs = Runtime.PyTuple_Size(args); // Get the default arg tuple - MethodBase[] methods = SetterBinder.GetMethods(); - MethodBase mi = methods[0]; + var methods = SetterBinder.GetMethods(); + var mi = methods[0].MethodBase; ParameterInfo[] pi = mi.GetParameters(); int clrnargs = pi.Length - 1; IntPtr defaultArgs = Runtime.PyTuple_New(clrnargs - pynargs); diff --git a/src/runtime/interfaceobject.cs b/src/runtime/interfaceobject.cs index 976c09be0..30612f051 100644 --- a/src/runtime/interfaceobject.cs +++ b/src/runtime/interfaceobject.cs @@ -76,43 +76,7 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw) return IntPtr.Zero; } - return self.WrapObject(obj); - } - - /// - /// Wrap the given object in an interface object, so that only methods - /// of the interface are available. - /// - public IntPtr WrapObject(object impl) - { - var objPtr = CLRObject.GetInstHandle(impl, pyHandle); - return objPtr; - } - - /// - /// Expose the wrapped implementation through attributes in both - /// converted/encoded (__implementation__) and raw (__raw_implementation__) form. - /// - public static IntPtr tp_getattro(IntPtr ob, IntPtr key) - { - var clrObj = (CLRObject)GetManagedObject(ob); - - if (!Runtime.PyString_Check(key)) - { - return Exceptions.RaiseTypeError("string expected"); - } - - string name = Runtime.GetManagedString(key); - if (name == "__implementation__") - { - return Converter.ToPython(clrObj.inst); - } - else if (name == "__raw_implementation__") - { - return CLRObject.GetInstHandle(clrObj.inst); - } - - return Runtime.PyObject_GenericGetAttr(ob, key); + return CLRObject.GetInstHandle(obj, self.pyHandle); } } } diff --git a/src/runtime/keyvaluepairenumerableobject.cs b/src/runtime/keyvaluepairenumerableobject.cs new file mode 100644 index 000000000..c1644442c --- /dev/null +++ b/src/runtime/keyvaluepairenumerableobject.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Python.Runtime +{ + /// + /// Implements a Python type for managed KeyValuePairEnumerable (dictionaries). + /// This type is essentially the same as a ClassObject, except that it provides + /// sequence semantics to support natural dictionary usage (__contains__ and __len__) + /// from Python. + /// + internal class KeyValuePairEnumerableObject : ClassObject + { + private static Dictionary, MethodInfo> methodsByType = new Dictionary, MethodInfo>(); + private static List requiredMethods = new List { "Count", "ContainsKey" }; + + internal static bool VerifyMethodRequirements(Type type) + { + foreach (var requiredMethod in requiredMethods) + { + var method = type.GetMethod(requiredMethod); + if (method == null) + { + method = type.GetMethod($"get_{requiredMethod}"); + if (method == null) + { + return false; + } + } + + var key = Tuple.Create(type, requiredMethod); + methodsByType.Add(key, method); + } + + return true; + } + + internal KeyValuePairEnumerableObject(Type tp) : base(tp) + { + + } + + internal override bool CanSubclass() => false; + + /// + /// Implements __len__ for dictionary types. + /// + public static int mp_length(IntPtr ob) + { + var obj = (CLRObject)GetManagedObject(ob); + var self = obj.inst; + + var key = Tuple.Create(self.GetType(), "Count"); + var methodInfo = methodsByType[key]; + + return (int)methodInfo.Invoke(self, null); + } + + /// + /// Implements __contains__ for dictionary types. + /// + public static int sq_contains(IntPtr ob, IntPtr v) + { + var obj = (CLRObject)GetManagedObject(ob); + var self = obj.inst; + + var key = Tuple.Create(self.GetType(), "ContainsKey"); + var methodInfo = methodsByType[key]; + + var parameters = methodInfo.GetParameters(); + object arg; + if (!Converter.ToManaged(v, parameters[0].ParameterType, out arg, false)) + { + Exceptions.SetError(Exceptions.TypeError, + $"invalid parameter type for sq_contains: should be {Converter.GetTypeByAlias(v)}, found {parameters[0].ParameterType}"); + } + + return (bool)methodInfo.Invoke(self, new[] { arg }) ? 1 : 0; + } + } + + public static class KeyValuePairEnumerableObjectExtension + { + public static bool IsKeyValuePairEnumerable(this Type type) + { + var iEnumerableType = typeof(IEnumerable<>); + var keyValuePairType = typeof(KeyValuePair<,>); + + var interfaces = type.GetInterfaces(); + foreach (var i in interfaces) + { + if (i.IsGenericType && + i.GetGenericTypeDefinition() == iEnumerableType) + { + var arguments = i.GetGenericArguments(); + if (arguments.Length != 1) continue; + + var a = arguments[0]; + if (a.IsGenericType && + a.GetGenericTypeDefinition() == keyValuePairType && + a.GetGenericArguments().Length == 2) + { + return KeyValuePairEnumerableObject.VerifyMethodRequirements(type); + } + } + } + + return false; + } + } +} diff --git a/src/runtime/managedtype.cs b/src/runtime/managedtype.cs index 09e8a3be2..1708a2da2 100644 --- a/src/runtime/managedtype.cs +++ b/src/runtime/managedtype.cs @@ -109,25 +109,6 @@ internal static ManagedType GetManagedObject(IntPtr ob) return null; } - /// - /// Given a Python object, return the associated managed object type or null. - /// - internal static ManagedType GetManagedObjectType(IntPtr ob) - { - if (ob != IntPtr.Zero) - { - IntPtr tp = Runtime.PyObject_TYPE(ob); - var flags = Util.ReadCLong(tp, TypeOffset.tp_flags); - if ((flags & TypeFlags.Managed) != 0) - { - tp = Marshal.ReadIntPtr(tp, TypeOffset.magic()); - var gc = (GCHandle)tp; - return (ManagedType)gc.Target; - } - } - return null; - } - internal static ManagedType GetManagedObjectErr(IntPtr ob) { diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs index 8f74e0052..02a9074ad 100644 --- a/src/runtime/methodbinder.cs +++ b/src/runtime/methodbinder.cs @@ -1,13 +1,12 @@ using System; using System.Collections; -using System.Reflection; -using System.Text; using System.Collections.Generic; using System.Linq; +using System.Reflection; +using System.Text; namespace Python.Runtime { - using MaybeMethodBase = MaybeMethodBase; /// /// A MethodBinder encapsulates information about a (possibly overloaded) /// managed method, and is responsible for selecting the right method given @@ -17,27 +16,19 @@ namespace Python.Runtime [Serializable] internal class MethodBinder { - /// - /// The overloads of this method - /// - public List list; - - [NonSerialized] - public MethodBase[] methods; - - [NonSerialized] - public bool init = false; + private List list; public const bool DefaultAllowThreads = true; public bool allow_threads = DefaultAllowThreads; + public bool init = false; internal MethodBinder() { - list = new List(); + list = new List(); } internal MethodBinder(MethodInfo mi) { - list = new List { new MaybeMethodBase(mi) }; + list = new List { new MethodInformation(mi, mi.GetParameters()) }; } public int Count @@ -47,7 +38,9 @@ public int Count internal void AddMethod(MethodBase m) { - list.Add(m); + // we added a new method so we have to re sort the method list + init = false; + list.Add(new MethodInformation(m, m.GetParameters())); } /// @@ -86,7 +79,6 @@ internal static MethodInfo MatchSignature(MethodInfo[] mi, Type[] tp) /// /// Given a sequence of MethodInfo and a sequence of type parameters, /// return the MethodInfo that represents the matching closed generic. - /// If unsuccessful, returns null and may set a Python error. /// internal static MethodInfo MatchParameters(MethodInfo[] mi, Type[] tp) { @@ -179,16 +171,15 @@ internal static MethodInfo MatchSignatureAndParameters(MethodInfo[] mi, Type[] g /// is arranged in order of precedence (done lazily to avoid doing it /// at all for methods that are never called). /// - internal MethodBase[] GetMethods() + internal List GetMethods() { if (!init) { // I'm sure this could be made more efficient. list.Sort(new MethodSorter()); - methods = (from method in list where method.Valid select method.Value).ToArray(); init = true; } - return methods; + return list; } /// @@ -199,21 +190,24 @@ internal MethodBase[] GetMethods() /// Based from Jython `org.python.core.ReflectedArgs.precedence` /// See: https://github.com/jythontools/jython/blob/master/src/org/python/core/ReflectedArgs.java#L192 /// - internal static int GetPrecedence(MethodBase mi) + private static int GetPrecedence(MethodInformation methodInformation) { - if (mi == null) - { - return int.MaxValue; - } - - ParameterInfo[] pi = mi.GetParameters(); + ParameterInfo[] pi = methodInformation.ParameterInfo; + var mi = methodInformation.MethodBase; int val = mi.IsStatic ? 3000 : 0; int num = pi.Length; val += mi.IsGenericMethod ? 1 : 0; for (var i = 0; i < num; i++) { - val += ArgPrecedence(pi[i].ParameterType); + val += ArgPrecedence(pi[i].ParameterType, methodInformation); + } + + var info = mi as MethodInfo; + if (info != null) + { + val += ArgPrecedence(info.ReturnType, methodInformation); + val += mi.DeclaringType == mi.ReflectedType ? 0 : 3000; } return val; @@ -222,7 +216,7 @@ internal static int GetPrecedence(MethodBase mi) /// /// Return a precedence value for a particular Type object. /// - internal static int ArgPrecedence(Type t) + internal static int ArgPrecedence(Type t, MethodInformation mi) { Type objectType = typeof(object); if (t == objectType) @@ -230,14 +224,9 @@ internal static int ArgPrecedence(Type t) return 3000; } - if (t.IsArray) + if (t.IsAssignableFrom(typeof(PyObject)) && !OperatorMethod.IsOperatorMethod(mi.MethodBase)) { - Type e = t.GetElementType(); - if (e == objectType) - { - return 2500; - } - return 100 + ArgPrecedence(e); + return -1; } TypeCode tc = Type.GetTypeCode(t); @@ -287,129 +276,58 @@ internal static int ArgPrecedence(Type t) return 40; } + if (t.IsArray) + { + Type e = t.GetElementType(); + if (e == objectType) + { + return 2500; + } + return 100 + ArgPrecedence(e, mi); + } + return 2000; } /// /// Bind the given Python instance and arguments to a particular method - /// overload in and return a structure that contains the converted Python + /// overload and return a structure that contains the converted Python /// instance, converted arguments and the correct method to call. - /// If unsuccessful, may set a Python error. /// - /// The Python target of the method invocation. - /// The Python arguments. - /// The Python keyword arguments. - /// A Binding if successful. Otherwise null. internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw) { return Bind(inst, args, kw, null, null); } - /// - /// Bind the given Python instance and arguments to a particular method - /// overload in and return a structure that contains the converted Python - /// instance, converted arguments and the correct method to call. - /// If unsuccessful, may set a Python error. - /// - /// The Python target of the method invocation. - /// The Python arguments. - /// The Python keyword arguments. - /// If not null, only bind to that method. - /// A Binding if successful. Otherwise null. internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info) { return Bind(inst, args, kw, info, null); } - private readonly struct MatchedMethod - { - public MatchedMethod(int kwargsMatched, int defaultsNeeded, object[] margs, int outs, MethodBase mb) - { - KwargsMatched = kwargsMatched; - DefaultsNeeded = defaultsNeeded; - ManagedArgs = margs; - Outs = outs; - Method = mb; - } - - public int KwargsMatched { get; } - public int DefaultsNeeded { get; } - public object[] ManagedArgs { get; } - public int Outs { get; } - public MethodBase Method { get; } - } - - private readonly struct MismatchedMethod - { - public MismatchedMethod(PythonException exception, MethodBase mb) - { - Exception = exception; - Method = mb; - } - - public PythonException Exception { get; } - public MethodBase Method { get; } - } - - /// - /// Bind the given Python instance and arguments to a particular method - /// overload in and return a structure that contains the converted Python - /// instance, converted arguments and the correct method to call. - /// If unsuccessful, may set a Python error. - /// - /// The Python target of the method invocation. - /// The Python arguments. - /// The Python keyword arguments. - /// If not null, only bind to that method. - /// If not null, additionally attempt to bind to the generic methods in this array by inferring generic type parameters. - /// A Binding if successful. Otherwise null. internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, MethodInfo[] methodinfo) { - // loop to find match, return invoker w/ or w/o error - MethodBase[] _methods = null; - - var kwargDict = new Dictionary(); - if (kw != IntPtr.Zero) - { - var pynkwargs = (int)Runtime.PyDict_Size(kw); - IntPtr keylist = Runtime.PyDict_Keys(kw); - IntPtr valueList = Runtime.PyDict_Values(kw); - for (int i = 0; i < pynkwargs; ++i) - { - var keyStr = Runtime.GetManagedString(Runtime.PyList_GetItem(new BorrowedReference(keylist), i)); - kwargDict[keyStr] = Runtime.PyList_GetItem(new BorrowedReference(valueList), i).DangerousGetAddress(); - } - Runtime.XDecref(keylist); - Runtime.XDecref(valueList); - } - + // loop to find match, return invoker w/ or /wo error var pynargs = (int)Runtime.PyTuple_Size(args); + object arg; var isGeneric = false; - if (info != null) - { - _methods = new MethodBase[1]; - _methods.SetValue(info, 0); - } - else - { - _methods = GetMethods(); - } + ArrayList defaultArgList; + Type clrtype; + Binding bindingUsingImplicitConversion = null; - var argMatchedMethods = new List(_methods.Length); - var mismatchedMethods = new List(); + var methods = info == null ? GetMethods() + : new List(1) { new MethodInformation(info, info.GetParameters()) }; // TODO: Clean up - foreach (MethodBase mi in _methods) + foreach (var methodInformation in methods) { + var mi = methodInformation.MethodBase; + var pi = methodInformation.ParameterInfo; + if (mi.IsGenericMethod) { isGeneric = true; } - ParameterInfo[] pi = mi.GetParameters(); - ArrayList defaultArgList; - bool paramsArray; - int kwargsMatched; - int defaultsNeeded; + bool isOperator = OperatorMethod.IsOperatorMethod(mi); // Binary operator methods will have 2 CLR args but only one Python arg // (unary operators will have 1 less each), since Python operator methods are bound. @@ -417,436 +335,289 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth bool isReverse = isOperator && OperatorMethod.IsReverse((MethodInfo)mi); // Only cast if isOperator. if (isReverse && OperatorMethod.IsComparisonOp((MethodInfo)mi)) continue; // Comparison operators in Python have no reverse mode. - if (!MatchesArgumentCount(pynargs, pi, kwargDict, out paramsArray, out defaultArgList, out kwargsMatched, out defaultsNeeded) && !isOperator) - { - continue; - } // Preprocessing pi to remove either the first or second argument. - if (isOperator && !isReverse) { + if (isOperator && !isReverse) + { // The first Python arg is the right operand, while the bound instance is the left. // We need to skip the first (left operand) CLR argument. pi = pi.Skip(1).ToArray(); } - else if (isOperator && isReverse) { + else if (isOperator && isReverse) + { // The first Python arg is the left operand. // We need to take the first CLR argument. pi = pi.Take(1).ToArray(); } - int outs; - var margs = TryConvertArguments(pi, paramsArray, args, pynargs, kwargDict, defaultArgList, - needsResolution: _methods.Length > 1, // If there's more than one possible match. - outs: out outs); - if (margs == null) - { - mismatchedMethods.Add(new MismatchedMethod(new PythonException(), mi)); - Exceptions.Clear(); - continue; - } - if (isOperator) + + int clrnargs = pi.Length; + int arrayStart; + + if (CheckMethodArgumentsMatch(clrnargs, + pynargs, + pi, + out arrayStart, + out defaultArgList)) { - if (inst != IntPtr.Zero) + var outs = 0; + var margs = new object[clrnargs]; + var usedImplicitConversion = false; + + for (int n = 0; n < clrnargs; n++) { - if (ManagedType.GetManagedObject(inst) is CLRObject co) + IntPtr op; + if (n < pynargs) { - bool isUnary = pynargs == 0; - // Postprocessing to extend margs. - var margsTemp = isUnary ? new object[1] : new object[2]; - // If reverse, the bound instance is the right operand. - int boundOperandIndex = isReverse ? 1 : 0; - // If reverse, the passed instance is the left operand. - int passedOperandIndex = isReverse ? 0 : 1; - margsTemp[boundOperandIndex] = co.inst; - if (!isUnary) + if (arrayStart == n) { - margsTemp[passedOperandIndex] = margs[0]; + // map remaining Python arguments to a tuple since + // the managed function accepts it - hopefully :] + op = Runtime.PyTuple_GetSlice(args, arrayStart, pynargs); + } + else + { + op = Runtime.PyTuple_GetItem(args, n); } - margs = margsTemp; - } - else continue; - } - } + // this logic below handles cases when multiple overloading methods + // are ambiguous, hence comparison between Python and CLR types + // is necessary + clrtype = null; + IntPtr pyoptype; + if (methods.Count > 1) + { + pyoptype = IntPtr.Zero; + pyoptype = Runtime.PyObject_Type(op); + Exceptions.Clear(); + if (pyoptype != IntPtr.Zero) + { + clrtype = Converter.GetTypeByAlias(pyoptype); + } + Runtime.XDecref(pyoptype); + } - var matchedMethod = new MatchedMethod(kwargsMatched, defaultsNeeded, margs, outs, mi); - argMatchedMethods.Add(matchedMethod); - } - if (argMatchedMethods.Count > 0) - { - var bestKwargMatchCount = argMatchedMethods.Max(x => x.KwargsMatched); - var fewestDefaultsRequired = argMatchedMethods.Where(x => x.KwargsMatched == bestKwargMatchCount).Min(x => x.DefaultsNeeded); - int bestCount = 0; - int bestMatchIndex = -1; + if (clrtype != null) + { + var typematch = false; + if ((pi[n].ParameterType != typeof(object)) && (pi[n].ParameterType != clrtype)) + { + IntPtr pytype = Converter.GetPythonTypeByAlias(pi[n].ParameterType); + pyoptype = Runtime.PyObject_Type(op); + Exceptions.Clear(); + if (pyoptype != IntPtr.Zero) + { + if (pytype != pyoptype) + { + typematch = false; + } + else + { + typematch = true; + clrtype = pi[n].ParameterType; + } + } + 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(underlyingType); + TypeCode paramtypecode = Type.GetTypeCode(clrtype); + if (argtypecode == paramtypecode) + { + typematch = true; + clrtype = pi[n].ParameterType; + } + // accepts non-decimal numbers in decimal parameters + if (underlyingType == typeof(decimal)) + { + clrtype = pi[n].ParameterType; + typematch = Converter.ToManaged(op, clrtype, out arg, false); + } + // this takes care of implicit conversions + var opImplicit = pi[n].ParameterType.GetMethod("op_Implicit", new[] { clrtype }); + if (opImplicit != null) + { + usedImplicitConversion = typematch = opImplicit.ReturnType == pi[n].ParameterType; + clrtype = pi[n].ParameterType; + } + } + Runtime.XDecref(pyoptype); + if (!typematch) + { + margs = null; + break; + } + } + else + { + typematch = true; + clrtype = pi[n].ParameterType; + } + } + else + { + clrtype = pi[n].ParameterType; + } - for (int index = 0; index < argMatchedMethods.Count; index++) - { - var testMatch = argMatchedMethods[index]; - if (testMatch.DefaultsNeeded == fewestDefaultsRequired && testMatch.KwargsMatched == bestKwargMatchCount) - { - bestCount++; - if (bestMatchIndex == -1) - bestMatchIndex = index; + if (pi[n].IsOut || clrtype.IsByRef) + { + outs++; + } + + if (!Converter.ToManaged(op, clrtype, out arg, false)) + { + Exceptions.Clear(); + margs = null; + break; + } + if (arrayStart == n) + { + // GetSlice() creates a new reference but GetItem() + // returns only a borrow reference. + Runtime.XDecref(op); + } + margs[n] = arg; + } + else + { + if (defaultArgList != null) + { + margs[n] = defaultArgList[n - pynargs]; + } + } } - } - if (bestCount > 1 && fewestDefaultsRequired > 0) - { - // Best effort for determining method to match on gives multiple possible - // matches and we need at least one default argument - bail from this point - StringBuilder stringBuilder = new StringBuilder("Not enough arguments provided to disambiguate the method. Found:"); - foreach (var matchedMethod in argMatchedMethods) + if (margs == null) { - stringBuilder.AppendLine(); - stringBuilder.Append(matchedMethod.Method.ToString()); + continue; } - Exceptions.SetError(Exceptions.TypeError, stringBuilder.ToString()); - return null; - } - // If we're here either: - // (a) There is only one best match - // (b) There are multiple best matches but none of them require - // default arguments - // in the case of (a) we're done by default. For (b) regardless of which - // method we choose, all arguments are specified _and_ can be converted - // from python to C# so picking any will suffice - MatchedMethod bestMatch = argMatchedMethods[bestMatchIndex]; - var margs = bestMatch.ManagedArgs; - var outs = bestMatch.Outs; - var mi = bestMatch.Method; - - object target = null; - if (!mi.IsStatic && inst != IntPtr.Zero) - { - //CLRObject co = (CLRObject)ManagedType.GetManagedObject(inst); - // InvalidCastException: Unable to cast object of type - // 'Python.Runtime.ClassObject' to type 'Python.Runtime.CLRObject' - var co = ManagedType.GetManagedObject(inst) as CLRObject; - - // Sanity check: this ensures a graceful exit if someone does - // something intentionally wrong like call a non-static method - // on the class rather than on an instance of the class. - // XXX maybe better to do this before all the other rigmarole. - if (co == null) + if (isOperator) { - Exceptions.SetError(Exceptions.TypeError, "Invoked a non-static method with an invalid instance"); - return null; + if (inst != IntPtr.Zero) + { + if (ManagedType.GetManagedObject(inst) is CLRObject co) + { + bool isUnary = pynargs == 0; + // Postprocessing to extend margs. + var margsTemp = isUnary ? new object[1] : new object[2]; + // If reverse, the bound instance is the right operand. + int boundOperandIndex = isReverse ? 1 : 0; + // If reverse, the passed instance is the left operand. + int passedOperandIndex = isReverse ? 0 : 1; + margsTemp[boundOperandIndex] = co.inst; + if (!isUnary) + { + margsTemp[passedOperandIndex] = margs[0]; + } + margs = margsTemp; + } + else continue; + } } - target = co.inst; - } - - return new Binding(mi, target, margs, outs); - } - else if (isGeneric && info == null && methodinfo != null) - { - // We weren't able to find a matching method but at least one - // is a generic method and info is null. That happens when a generic - // method was not called using the [] syntax. Let's introspect the - // type of the arguments and use it to construct the correct method. - Type[] types = Runtime.PythonArgsToTypeArray(args, true); - MethodInfo mi = MatchParameters(methodinfo, types); - if (mi != null) - { - return Bind(inst, args, kw, mi, null); - } - } - if (mismatchedMethods.Count > 0) - { - var aggregateException = GetAggregateException(mismatchedMethods); - Exceptions.SetError(aggregateException); - } - return null; - } - static AggregateException GetAggregateException(IEnumerable mismatchedMethods) - { - return new AggregateException(mismatchedMethods.Select(m => new ArgumentException($"{m.Exception.Message} in method {m.Method}", m.Exception))); - } - - static IntPtr HandleParamsArray(IntPtr args, int arrayStart, int pyArgCount, out bool isNewReference) - { - isNewReference = false; - IntPtr op; - // for a params method, we may have a sequence or single/multiple items - // here we look to see if the item at the paramIndex is there or not - // and then if it is a sequence itself. - if ((pyArgCount - arrayStart) == 1) - { - // we only have one argument left, so we need to check it - // to see if it is a sequence or a single item - IntPtr item = Runtime.PyTuple_GetItem(args, arrayStart); - if (!Runtime.PyString_Check(item) && Runtime.PySequence_Check(item)) - { - // it's a sequence (and not a string), so we use it as the op - op = item; - } - else - { - isNewReference = true; - op = Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount); - } - } - else - { - isNewReference = true; - op = Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount); - } - return op; - } - - /// - /// Attempts to convert Python positional argument tuple and keyword argument table - /// into an array of managed objects, that can be passed to a method. - /// If unsuccessful, returns null and may set a Python error. - /// - /// Information about expected parameters - /// true, if the last parameter is a params array. - /// A pointer to the Python argument tuple - /// Number of arguments, passed by Python - /// Dictionary of keyword argument name to python object pointer - /// A list of default values for omitted parameters - /// true, if overloading resolution is required - /// Returns number of output parameters - /// If successful, an array of .NET arguments that can be passed to the method. Otherwise null. - static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, - IntPtr args, int pyArgCount, - Dictionary kwargDict, - ArrayList defaultArgList, - bool needsResolution, - out int outs) - { - outs = 0; - var margs = new object[pi.Length]; - int arrayStart = paramsArray ? pi.Length - 1 : -1; - - for (int paramIndex = 0; paramIndex < pi.Length; paramIndex++) - { - var parameter = pi[paramIndex]; - bool hasNamedParam = parameter.Name != null ? kwargDict.ContainsKey(parameter.Name) : false; - bool isNewReference = false; - - if (paramIndex >= pyArgCount && !(hasNamedParam || (paramsArray && paramIndex == arrayStart))) - { - if (defaultArgList != null) + object target = null; + if (!mi.IsStatic && inst != IntPtr.Zero) { - margs[paramIndex] = defaultArgList[paramIndex - pyArgCount]; + //CLRObject co = (CLRObject)ManagedType.GetManagedObject(inst); + // InvalidCastException: Unable to cast object of type + // 'Python.Runtime.ClassObject' to type 'Python.Runtime.CLRObject' + var co = ManagedType.GetManagedObject(inst) as CLRObject; + + // Sanity check: this ensures a graceful exit if someone does + // something intentionally wrong like call a non-static method + // on the class rather than on an instance of the class. + // XXX maybe better to do this before all the other rigmarole. + if (co == null) + { + return null; + } + target = co.inst; } - continue; - } - - IntPtr op; - if (hasNamedParam) - { - op = kwargDict[parameter.Name]; - } - else - { - if(arrayStart == paramIndex) + var binding = new Binding(mi, target, margs, outs); + if (usedImplicitConversion) { - op = HandleParamsArray(args, arrayStart, pyArgCount, out isNewReference); + // lets just keep the first binding using implicit conversion + // this is to respect method order/precedence + if (bindingUsingImplicitConversion == null) + { + // in this case we will not return the binding yet in case there is a match + // which does not use implicit conversions, which will return directly + bindingUsingImplicitConversion = binding; + } } else { - op = Runtime.PyTuple_GetItem(args, paramIndex); + return binding; } } - - bool isOut; - if (!TryConvertArgument(op, parameter.ParameterType, needsResolution, out margs[paramIndex], out isOut)) - { - return null; - } - - if (isNewReference) - { - // TODO: is this a bug? Should this happen even if the conversion fails? - // GetSlice() creates a new reference but GetItem() - // returns only a borrow reference. - Runtime.XDecref(op); - } - - if (isOut) - { - outs++; - } } - return margs; - } - - /// - /// Try to convert a Python argument object to a managed CLR type. - /// If unsuccessful, may set a Python error. - /// - /// Pointer to the Python argument object. - /// That parameter's managed type. - /// If true, there are multiple overloading methods that need resolution. - /// Converted argument. - /// Whether the CLR type is passed by reference. - /// true on success - static bool TryConvertArgument(IntPtr op, Type parameterType, bool needsResolution, - out object arg, out bool isOut) - { - arg = null; - isOut = false; - var clrtype = TryComputeClrArgumentType(parameterType, op, needsResolution: needsResolution); - if (clrtype == null) + // if we generated a binding using implicit conversion return it + if (bindingUsingImplicitConversion != null) { - return false; + return bindingUsingImplicitConversion; } - if (!Converter.ToManaged(op, clrtype, out arg, true)) + // We weren't able to find a matching method but at least one + // is a generic method and info is null. That happens when a generic + // method was not called using the [] syntax. Let's introspect the + // type of the arguments and use it to construct the correct method. + if (isGeneric && info == null && methodinfo != null) { - return false; + Type[] types = Runtime.PythonArgsToTypeArray(args, true); + MethodInfo mi = MatchParameters(methodinfo, types); + return Bind(inst, args, kw, mi, null); } - - isOut = clrtype.IsByRef; - return true; + return null; } /// - /// Determine the managed type that a Python argument object needs to be converted into. + /// This helper method will perform an initial check to determine if we found a matching + /// method based on its parameters count and type /// - /// The parameter's managed type. - /// Pointer to the Python argument object. - /// If true, there are multiple overloading methods that need resolution. - /// null if conversion is not possible - static Type TryComputeClrArgumentType(Type parameterType, IntPtr argument, bool needsResolution) - { - // this logic below handles cases when multiple overloading methods - // are ambiguous, hence comparison between Python and CLR types - // is necessary - Type clrtype = null; - IntPtr pyoptype; - if (needsResolution) - { - // HACK: each overload should be weighted in some way instead - pyoptype = Runtime.PyObject_Type(argument); - if (pyoptype != IntPtr.Zero) - { - clrtype = Converter.GetTypeByAlias(pyoptype); - } - Runtime.XDecref(pyoptype); - } - - if (clrtype != null) - { - if ((parameterType != typeof(object)) && (parameterType != clrtype)) - { - IntPtr pytype = Converter.GetPythonTypeByAlias(parameterType); - pyoptype = Runtime.PyObject_Type(argument); - var typematch = false; - if (pyoptype != IntPtr.Zero) - { - if (pytype != pyoptype) - { - typematch = false; - } - else - { - typematch = true; - clrtype = parameterType; - } - } - if (!typematch) - { - // this takes care of enum values - TypeCode parameterTypeCode = Type.GetTypeCode(parameterType); - TypeCode clrTypeCode = Type.GetTypeCode(clrtype); - if (parameterTypeCode == clrTypeCode) - { - typematch = true; - clrtype = parameterType; - } - else - { - Exceptions.RaiseTypeError($"Expected {parameterTypeCode}, got {clrTypeCode}"); - } - } - Runtime.XDecref(pyoptype); - if (!typematch) - { - return null; - } - } - else - { - clrtype = parameterType; - } - } - else - { - clrtype = parameterType; - } - - return clrtype; - } - /// - /// Check whether the number of Python and .NET arguments match, and compute additional arg information. - /// - /// Number of positional args passed from Python. - /// Parameters of the specified .NET method. - /// Keyword args passed from Python. - /// True if the final param of the .NET method is an array (`params` keyword). - /// List of default values for arguments. - /// Number of kwargs from Python that are also present in the .NET method. - /// Number of non-null defaultsArgs. - /// - static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] parameters, - Dictionary kwargDict, - out bool paramsArray, - out ArrayList defaultArgList, - out int kwargsMatched, - out int defaultsNeeded) + private bool CheckMethodArgumentsMatch(int clrnargs, + nint pynargs, + ParameterInfo[] parameterInfo, + out int arrayStart, + out ArrayList defaultArgList) { + arrayStart = -1; defaultArgList = null; + var match = false; - paramsArray = parameters.Length > 0 ? Attribute.IsDefined(parameters[parameters.Length - 1], typeof(ParamArrayAttribute)) : false; - kwargsMatched = 0; - defaultsNeeded = 0; - if (positionalArgumentCount == parameters.Length && kwargDict.Count == 0) + if (pynargs == clrnargs) { match = true; } - else if (positionalArgumentCount < parameters.Length && (!paramsArray || positionalArgumentCount == parameters.Length - 1)) + else if (pynargs < clrnargs) { match = true; - // every parameter past 'positionalArgumentCount' must have either - // a corresponding keyword arg or a default param, unless the method - // method accepts a params array (which cannot have a default value) defaultArgList = new ArrayList(); - for (var v = positionalArgumentCount; v < parameters.Length; v++) + for (var v = pynargs; v < clrnargs && match; v++) { - if (kwargDict.ContainsKey(parameters[v].Name)) + if (parameterInfo[v].DefaultValue == DBNull.Value) { - // we have a keyword argument for this parameter, - // no need to check for a default parameter, but put a null - // placeholder in defaultArgList - defaultArgList.Add(null); - kwargsMatched++; - } - else if (parameters[v].IsOptional) - { - // IsOptional will be true if the parameter has a default value, - // or if the parameter has the [Optional] attribute specified. - // The GetDefaultValue() extension method will return the value - // to be passed in as the parameter value - defaultArgList.Add(parameters[v].GetDefaultValue()); - defaultsNeeded++; + match = false; } - else if (!paramsArray) + else { - match = false; + defaultArgList.Add(parameterInfo[v].DefaultValue); } } } - else if (positionalArgumentCount > parameters.Length && parameters.Length > 0 && - Attribute.IsDefined(parameters[parameters.Length - 1], typeof(ParamArrayAttribute))) + else if (pynargs > clrnargs && clrnargs > 0 && + Attribute.IsDefined(parameterInfo[clrnargs - 1], typeof(ParamArrayAttribute))) { // This is a `foo(params object[] bar)` style method match = true; - paramsArray = true; + arrayStart = clrnargs - 1; } return match; @@ -862,53 +633,8 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i return Invoke(inst, args, kw, info, null); } - protected static void AppendArgumentTypes(StringBuilder to, IntPtr args) - { - long argCount = Runtime.PyTuple_Size(args); - to.Append("("); - for (long argIndex = 0; argIndex < argCount; argIndex++) - { - var arg = Runtime.PyTuple_GetItem(args, argIndex); - if (arg != IntPtr.Zero) - { - var type = Runtime.PyObject_Type(arg); - if (type != IntPtr.Zero) - { - try - { - var description = Runtime.PyObject_Unicode(type); - if (description != IntPtr.Zero) - { - to.Append(Runtime.GetManagedString(description)); - Runtime.XDecref(description); - } - } - finally - { - Runtime.XDecref(type); - } - } - } - - if (argIndex + 1 < argCount) - to.Append(", "); - } - to.Append(')'); - } - internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, MethodInfo[] methodinfo) { - // No valid methods, nothing to bind. - if (GetMethods().Length == 0) - { - var msg = new StringBuilder("The underlying C# method(s) have been deleted"); - if (list.Count > 0 && list[0].Name != null) - { - msg.Append($": {list[0]}"); - } - return Exceptions.RaiseTypeError(msg.ToString()); - } - Binding binding = Bind(inst, args, kw, info, methodinfo); object result; IntPtr ts = IntPtr.Zero; @@ -920,9 +646,9 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i { value.Append($" for {methodinfo[0].Name}"); } - else if (list.Count > 0 && list[0].Valid) + else if (list.Count > 0) { - value.Append($" for {list[0].Value.Name}"); + value.Append($" for {list[0].MethodBase.Name}"); } value.Append(": "); @@ -960,7 +686,7 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i } // If there are out parameters, we return a tuple containing - // the result, if any, followed by the out parameters. If there is only + // the result followed by the out parameters. If there is only // one out parameter and the return type of the method is void, // we return the out parameter as the result to Python (for // code compatibility with ironpython). @@ -973,22 +699,17 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i int c = pi.Length; var n = 0; - bool isVoid = mi.ReturnType == typeof(void); - int tupleSize = binding.outs + (isVoid ? 0 : 1); - IntPtr t = Runtime.PyTuple_New(tupleSize); - if (!isVoid) - { - IntPtr v = Converter.ToPython(result, mi.ReturnType); - Runtime.PyTuple_SetItem(t, n, v); - n++; - } + IntPtr t = Runtime.PyTuple_New(binding.outs + 1); + IntPtr v = Converter.ToPython(result, mi.ReturnType); + Runtime.PyTuple_SetItem(t, n, v); + n++; for (var i = 0; i < c; i++) { Type pt = pi[i].ParameterType; - if (pt.IsByRef) + if (pi[i].IsOut || pt.IsByRef) { - IntPtr v = Converter.ToPython(binding.args[i], pt.GetElementType()); + v = Converter.ToPython(binding.args[i], pt); Runtime.PyTuple_SetItem(t, n, v); n++; } @@ -996,7 +717,7 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i if (binding.outs == 1 && mi.ReturnType == typeof(void)) { - IntPtr v = Runtime.PyTuple_GetItem(t, 0); + v = Runtime.PyTuple_GetItem(t, 1); Runtime.XIncref(v); Runtime.XDecref(t); return v; @@ -1007,53 +728,81 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i return Converter.ToPython(result, mi.ReturnType); } - } - - /// - /// Utility class to sort method info by parameter type precedence. - /// - internal class MethodSorter : IComparer - { - int IComparer.Compare(MaybeMethodBase m1, MaybeMethodBase m2) + /// + /// Utility class to store the information about a + /// + [Serializable] + internal class MethodInformation { - MethodBase me1 = m1.UnsafeValue; - MethodBase me2 = m2.UnsafeValue; - if (me1 == null && me2 == null) - { - return 0; - } - else if (me1 == null) - { - return -1; - } - else if (me2 == null) + public MethodBase MethodBase { get; } + + public ParameterInfo[] ParameterInfo { get; } + + public MethodInformation(MethodBase methodBase, ParameterInfo[] parameterInfo) { - return 1; + MethodBase = methodBase; + ParameterInfo = parameterInfo; } - if (me1.DeclaringType != me2.DeclaringType) + public override string ToString() { - // m2's type derives from m1's type, favor m2 - if (me1.DeclaringType.IsAssignableFrom(me2.DeclaringType)) - return 1; - - // m1's type derives from m2's type, favor m1 - if (me2.DeclaringType.IsAssignableFrom(me1.DeclaringType)) - return -1; + return MethodBase.ToString(); } + } - int p1 = MethodBinder.GetPrecedence(me1); - int p2 = MethodBinder.GetPrecedence(me2); - if (p1 < p2) + /// + /// Utility class to sort method info by parameter type precedence. + /// + private class MethodSorter : IComparer + { + public int Compare(MethodInformation x, MethodInformation y) { - return -1; + int p1 = GetPrecedence(x); + int p2 = GetPrecedence(y); + if (p1 < p2) + { + return -1; + } + if (p1 > p2) + { + return 1; + } + return 0; } - if (p1 > p2) + } + protected static void AppendArgumentTypes(StringBuilder to, IntPtr args) + { + long argCount = Runtime.PyTuple_Size(args); + to.Append("("); + for (long argIndex = 0; argIndex < argCount; argIndex++) { - return 1; + var arg = Runtime.PyTuple_GetItem(args, argIndex); + if (arg != IntPtr.Zero) + { + var type = Runtime.PyObject_Type(arg); + if (type != IntPtr.Zero) + { + try + { + var description = Runtime.PyObject_Unicode(type); + if (description != IntPtr.Zero) + { + to.Append(Runtime.GetManagedString(description)); + Runtime.XDecref(description); + } + } + finally + { + Runtime.XDecref(type); + } + } + } + + if (argIndex + 1 < argCount) + to.Append(", "); } - return 0; + to.Append(')'); } } @@ -1078,33 +827,4 @@ internal Binding(MethodBase info, object inst, object[] args, int outs) this.outs = outs; } } - - - static internal class ParameterInfoExtensions - { - public static object GetDefaultValue(this ParameterInfo parameterInfo) - { - // parameterInfo.HasDefaultValue is preferable but doesn't exist in .NET 4.0 - bool hasDefaultValue = (parameterInfo.Attributes & ParameterAttributes.HasDefault) == - ParameterAttributes.HasDefault; - - if (hasDefaultValue) - { - return parameterInfo.DefaultValue; - } - else - { - // [OptionalAttribute] was specified for the parameter. - // See https://stackoverflow.com/questions/3416216/optionalattribute-parameters-default-value - // for rules on determining the value to pass to the parameter - var type = parameterInfo.ParameterType; - if (type == typeof(object)) - return Type.Missing; - else if (type.IsValueType) - return Activator.CreateInstance(type); - else - return null; - } - } - } } diff --git a/src/runtime/methodobject.cs b/src/runtime/methodobject.cs index 37c01f5c5..66ee65293 100644 --- a/src/runtime/methodobject.cs +++ b/src/runtime/methodobject.cs @@ -79,14 +79,14 @@ internal IntPtr GetDocString() } var str = ""; Type marker = typeof(DocStringAttribute); - MethodBase[] methods = binder.GetMethods(); - foreach (MethodBase method in methods) + var methods = binder.GetMethods(); + foreach (var method in methods) { if (str.Length > 0) { str += Environment.NewLine; } - var attrs = (Attribute[])method.GetCustomAttributes(marker, false); + var attrs = (Attribute[])method.MethodBase.GetCustomAttributes(marker, false); if (attrs.Length == 0) { str += method.ToString(); diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 35ea3f6d2..c08ff863e 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -182,6 +182,7 @@ public static void Initialize(IEnumerable args, bool setSysArgv = true, // during an initial "import clr", and the world ends shortly thereafter. // This is probably masking some bad mojo happening somewhere in Runtime.Initialize(). delegateManager = new DelegateManager(); + Console.WriteLine("PythonEngine.Initialize(): Runtime.Initialize()..."); Runtime.Initialize(initSigs, mode); initialized = true; Exceptions.Clear(); @@ -199,6 +200,7 @@ public static void Initialize(IEnumerable args, bool setSysArgv = true, } // Load the clr.py resource into the clr module + Console.WriteLine("PythonEngine.Initialize(): GetCLRModule()..."); NewReference clr = Python.Runtime.ImportHook.GetCLRModule(); BorrowedReference clr_dict = Runtime.PyModule_GetDict(clr); @@ -210,6 +212,7 @@ public static void Initialize(IEnumerable args, bool setSysArgv = true, BorrowedReference builtins = Runtime.PyEval_GetBuiltins(); Runtime.PyDict_SetItemString(module_globals, "__builtins__", builtins); + Console.WriteLine("PythonEngine.Initialize(): clr GetManifestResourceStream..."); Assembly assembly = Assembly.GetExecutingAssembly(); using (Stream stream = assembly.GetManifestResourceStream("clr.py")) using (var reader = new StreamReader(stream)) diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index ec7f5e446..068fb1c6a 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading; using System.Collections.Generic; +using System.IO; using Python.Runtime.Native; using Python.Runtime.Platform; using System.Linq; @@ -116,9 +117,11 @@ internal static void Initialize(bool initSigs = false, ShutdownMode mode = Shutd if (Py_IsInitialized() == 0) { + Console.WriteLine("Runtime.Initialize(): Py_Initialize..."); Py_InitializeEx(initSigs ? 1 : 0); if (PyEval_ThreadsInitialized() == 0) { + Console.WriteLine("Runtime.Initialize(): PyEval_InitThreads..."); PyEval_InitThreads(); } // XXX: Reload mode may reduct to Soft mode, @@ -143,7 +146,9 @@ internal static void Initialize(bool initSigs = false, ShutdownMode mode = Shutd IsFinalizing = false; InternString.Initialize(); + Console.WriteLine("Runtime.Initialize(): Initialize types..."); InitPyMembers(); + Console.WriteLine("Runtime.Initialize(): Initialize types end."); ABI.Initialize(PyVersion, pyType: new BorrowedReference(PyTypeType)); @@ -155,6 +160,7 @@ internal static void Initialize(bool initSigs = false, ShutdownMode mode = Shutd TypeManager.Initialize(); // Initialize modules that depend on the runtime class. + Console.WriteLine("Runtime.Initialize(): AssemblyManager.Initialize()..."); AssemblyManager.Initialize(); OperatorMethod.Initialize(); if (mode == ShutdownMode.Reload && RuntimeData.HasStashData()) @@ -170,15 +176,28 @@ internal static void Initialize(bool initSigs = false, ShutdownMode mode = Shutd // Need to add the runtime directory to sys.path so that we // can find built-in assemblies like System.Data, et. al. - string rtdir = RuntimeEnvironment.GetRuntimeDirectory(); + AddToPyPath(RuntimeEnvironment.GetRuntimeDirectory()); + AddToPyPath(Directory.GetCurrentDirectory()); + + Console.WriteLine("Runtime.Initialize(): AssemblyManager.UpdatePath()..."); + AssemblyManager.UpdatePath(); + } + + private static void AddToPyPath(string directory) + { + if (!Directory.Exists(directory)) + { + return; + } + IntPtr path = PySys_GetObject("path").DangerousGetAddress(); - IntPtr item = PyString_FromString(rtdir); + IntPtr item = PyString_FromString(directory); if (PySequence_Contains(path, item) == 0) { PyList_Append(new BorrowedReference(path), item); } + XDecref(item); - AssemblyManager.UpdatePath(); } private static void InitPyMembers() @@ -271,6 +290,14 @@ private static void InitPyMembers() () => PyFloatType = IntPtr.Zero); XDecref(op); + IntPtr decimalMod = PyImport_ImportModule("_pydecimal"); + IntPtr decimalCtor = PyObject_GetAttrString(decimalMod, "Decimal"); + op = PyObject_CallObject(decimalCtor, IntPtr.Zero); + PyDecimalType = PyObject_Type(op); + XDecref(op); + XDecref(decimalMod); + XDecref(decimalCtor); + PyClassType = IntPtr.Zero; PyInstanceType = IntPtr.Zero; @@ -551,6 +578,7 @@ private static void MoveClrInstancesOnwershipToPython() internal static IntPtr PyBoolType; internal static IntPtr PyNoneType; internal static IntPtr PyTypeType; + internal static IntPtr PyDecimalType; internal static IntPtr Py_NoSiteFlag; diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index e0564b243..7f5351ed6 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -251,8 +251,10 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType) InitializeSlot(type, TypeOffset.mp_length, mp_length_slot.Method, slotsHolder); } - if (!typeof(IEnumerable).IsAssignableFrom(clrType) && - !typeof(IEnumerator).IsAssignableFrom(clrType)) + // we want to do this after the slot stuff above in case the class itself implements a slot method + InitializeSlots(type, impl.GetType()); + + if (!clrType.GetInterfaces().Any(ifc => ifc == typeof(IEnumerable) || ifc == typeof(IEnumerator))) { // The tp_iter slot should only be set for enumerable types. Marshal.WriteIntPtr(type, TypeOffset.tp_iter, IntPtr.Zero); diff --git a/src/testing/conversiontest.cs b/src/testing/conversiontest.cs index 1f9d64e1b..58ee61a20 100644 --- a/src/testing/conversiontest.cs +++ b/src/testing/conversiontest.cs @@ -1,3 +1,5 @@ +using System; + namespace Python.Test { using System.Collections.Generic; @@ -31,6 +33,8 @@ public ConversionTest() public ShortEnum EnumField; public object ObjectField = null; public ISpam SpamField; + public DateTime DateTimeField; + public TimeSpan TimeSpanField; public byte[] ByteArrayField; public sbyte[] SByteArrayField; diff --git a/src/testing/dictionarytest.cs b/src/testing/dictionarytest.cs new file mode 100644 index 000000000..a7fa3497d --- /dev/null +++ b/src/testing/dictionarytest.cs @@ -0,0 +1,106 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Python.Test +{ + /// + /// Supports units tests for dictionary __contains__ and __len__ + /// + public class PublicDictionaryTest + { + public IDictionary items; + + public PublicDictionaryTest() + { + items = new int[5] { 0, 1, 2, 3, 4 } + .ToDictionary(k => k.ToString(), v => v); + } + } + + + public class ProtectedDictionaryTest + { + protected IDictionary items; + + public ProtectedDictionaryTest() + { + items = new int[5] { 0, 1, 2, 3, 4 } + .ToDictionary(k => k.ToString(), v => v); + } + } + + + public class InternalDictionaryTest + { + internal IDictionary items; + + public InternalDictionaryTest() + { + items = new int[5] { 0, 1, 2, 3, 4 } + .ToDictionary(k => k.ToString(), v => v); + } + } + + + public class PrivateDictionaryTest + { + private IDictionary items; + + public PrivateDictionaryTest() + { + items = new int[5] { 0, 1, 2, 3, 4 } + .ToDictionary(k => k.ToString(), v => v); + } + } + + public class InheritedDictionaryTest : IDictionary + { + private readonly IDictionary items; + + public InheritedDictionaryTest() + { + items = new int[5] { 0, 1, 2, 3, 4 } + .ToDictionary(k => k.ToString(), v => v); + } + + public int this[string key] + { + get { return items[key]; } + set { items[key] = value; } + } + + public ICollection Keys => items.Keys; + + public ICollection Values => items.Values; + + public int Count => items.Count; + + public bool IsReadOnly => false; + + public void Add(string key, int value) => items.Add(key, value); + + public void Add(KeyValuePair item) => items.Add(item); + + public void Clear() => items.Clear(); + + public bool Contains(KeyValuePair item) => items.Contains(item); + + public bool ContainsKey(string key) => items.ContainsKey(key); + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + items.CopyTo(array, arrayIndex); + } + + public IEnumerator> GetEnumerator() => items.GetEnumerator(); + + public bool Remove(string key) => items.Remove(key); + + public bool Remove(KeyValuePair item) => items.Remove(item); + + public bool TryGetValue(string key, out int value) => items.TryGetValue(key, out value); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/testing/interfacetest.cs b/src/testing/interfacetest.cs index 0158d64da..2c24596bc 100644 --- a/src/testing/interfacetest.cs +++ b/src/testing/interfacetest.cs @@ -11,6 +11,7 @@ internal interface IInternalInterface { } + public interface ISayHello1 { string SayHello(); @@ -42,27 +43,6 @@ string ISayHello2.SayHello() return "hello 2"; } - public ISayHello1 GetISayHello1() - { - return this; - } - - public void GetISayHello2(out ISayHello2 hello2) - { - hello2 = this; - } - - public ISayHello1 GetNoSayHello(out ISayHello2 hello2) - { - hello2 = null; - return null; - } - - public ISayHello1 [] GetISayHello1Array() - { - return new[] { this }; - } - public interface IPublic { } diff --git a/src/testing/subclasstest.cs b/src/testing/subclasstest.cs index ab0b73368..9817d865e 100644 --- a/src/testing/subclasstest.cs +++ b/src/testing/subclasstest.cs @@ -89,24 +89,13 @@ public static string test_bar(IInterfaceTest x, string s, int i) } // test instances can be constructed in managed code - public static SubClassTest create_instance(Type t) - { - return (SubClassTest)t.GetConstructor(new Type[] { }).Invoke(new object[] { }); - } - - public static IInterfaceTest create_instance_interface(Type t) + public static IInterfaceTest create_instance(Type t) { return (IInterfaceTest)t.GetConstructor(new Type[] { }).Invoke(new object[] { }); } - // test instances pass through managed code unchanged ... - public static SubClassTest pass_through(SubClassTest s) - { - return s; - } - - // ... but the return type is an interface type, objects get wrapped - public static IInterfaceTest pass_through_interface(IInterfaceTest s) + // test instances pass through managed code unchanged + public static IInterfaceTest pass_through(IInterfaceTest s) { return s; } diff --git a/src/tests/test_dictionary.py b/src/tests/test_dictionary.py new file mode 100644 index 000000000..1532c9b15 --- /dev/null +++ b/src/tests/test_dictionary.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- + +"""Test support for managed dictionaries.""" + +import Python.Test as Test +import System +import pytest + + +def test_public_dict(): + """Test public dict.""" + ob = Test.PublicDictionaryTest() + items = ob.items + + assert len(items) == 5 + + assert items['0'] == 0 + assert items['4'] == 4 + + items['0'] = 8 + assert items['0'] == 8 + + items['4'] = 9 + assert items['4'] == 9 + + items['-4'] = 0 + assert items['-4'] == 0 + + items['-1'] = 4 + assert items['-1'] == 4 + +def test_protected_dict(): + """Test protected dict.""" + ob = Test.ProtectedDictionaryTest() + items = ob.items + + assert len(items) == 5 + + assert items['0'] == 0 + assert items['4'] == 4 + + items['0'] = 8 + assert items['0'] == 8 + + items['4'] = 9 + assert items['4'] == 9 + + items['-4'] = 0 + assert items['-4'] == 0 + + items['-1'] = 4 + assert items['-1'] == 4 + +def test_internal_dict(): + """Test internal dict.""" + + with pytest.raises(AttributeError): + ob = Test.InternalDictionaryTest() + _ = ob.items + +def test_private_dict(): + """Test private dict.""" + + with pytest.raises(AttributeError): + ob = Test.PrivateDictionaryTest() + _ = ob.items + +def test_dict_contains(): + """Test dict support for __contains__.""" + + ob = Test.PublicDictionaryTest() + keys = ob.items.Keys + + assert '0' in keys + assert '1' in keys + assert '2' in keys + assert '3' in keys + assert '4' in keys + + assert not ('5' in keys) + assert not ('-1' in keys) + +def test_dict_abuse(): + """Test dict abuse.""" + _class = Test.PublicDictionaryTest + ob = Test.PublicDictionaryTest() + + with pytest.raises(AttributeError): + del _class.__getitem__ + + with pytest.raises(AttributeError): + del ob.__getitem__ + + with pytest.raises(AttributeError): + del _class.__setitem__ + + with pytest.raises(AttributeError): + del ob.__setitem__ + + with pytest.raises(TypeError): + Test.PublicArrayTest.__getitem__(0, 0) + +def test_InheritedDictionary(): + """Test class that inherited from IDictionary.""" + items = Test.InheritedDictionaryTest() + + assert len(items) == 5 + + assert items['0'] == 0 + assert items['4'] == 4 + + items['0'] = 8 + assert items['0'] == 8 + + items['4'] = 9 + assert items['4'] == 9 + + items['-4'] = 0 + assert items['-4'] == 0 + + items['-1'] = 4 + assert items['-1'] == 4 + +def test_InheritedDictionary_contains(): + """Test dict support for __contains__ in class that inherited from IDictionary""" + items = Test.InheritedDictionaryTest() + + assert '0' in items + assert '1' in items + assert '2' in items + assert '3' in items + assert '4' in items + + assert not ('5' in items) + assert not ('-1' in items) diff --git a/tests/test_array.py b/tests/test_array.py index 2b1a289ad..cc154394b 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -1334,10 +1334,9 @@ def test_special_array_creation(): assert value[1].__class__ == inst.__class__ assert value.Length == 2 - iface_class = ISayHello1(inst).__class__ value = Array[ISayHello1]([inst, inst]) - assert value[0].__class__ == iface_class - assert value[1].__class__ == iface_class + assert value[0].__class__ == inst.__class__ + assert value[1].__class__ == inst.__class__ assert value.Length == 2 inst = System.Exception("badness") diff --git a/tests/test_conversion.py b/tests/test_conversion.py index aea95e164..1980e49c1 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -475,9 +475,6 @@ def test_decimal_conversion(): """Test decimal conversion.""" from System import Decimal - max_d = Decimal.Parse("79228162514264337593543950335") - min_d = Decimal.Parse("-79228162514264337593543950335") - assert Decimal.ToInt64(Decimal(10)) == 10 ob = ConversionTest() @@ -492,21 +489,45 @@ def test_decimal_conversion(): ob.DecimalField = Decimal.Zero assert ob.DecimalField == Decimal.Zero - ob.DecimalField = max_d - assert ob.DecimalField == max_d - - ob.DecimalField = min_d - assert ob.DecimalField == min_d - with pytest.raises(TypeError): ConversionTest().DecimalField = None with pytest.raises(TypeError): ConversionTest().DecimalField = "spam" +def test_timedelta_conversion(): + import datetime + + ob = ConversionTest() + assert type(ob.TimeSpanField) is type(datetime.timedelta(0)) + assert ob.TimeSpanField.days == 0 + + ob.TimeSpanField = datetime.timedelta(days=1) + assert ob.TimeSpanField.days == 1 + + with pytest.raises(TypeError): + ConversionTest().TimeSpanField = None + with pytest.raises(TypeError): - ConversionTest().DecimalField = 1 + ConversionTest().TimeSpanField = "spam" +def test_datetime_conversion(): + from datetime import datetime + + ob = ConversionTest() + assert type(ob.DateTimeField) is type(datetime(1,1,1)) + assert ob.DateTimeField.day == 1 + + ob.DateTimeField = datetime(2000,1,2) + assert ob.DateTimeField.day == 2 + assert ob.DateTimeField.month == 1 + assert ob.DateTimeField.year == 2000 + + with pytest.raises(TypeError): + ConversionTest().DateTimeField = None + + with pytest.raises(TypeError): + ConversionTest().DateTimeField = "spam" def test_string_conversion(): """Test string / unicode conversion.""" @@ -716,3 +737,5 @@ def CanEncode(self, clr_type): l = ob.ListField l.Add(42) assert ob.ListField.Count == 1 + + diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 7fafeebcb..fc631a2eb 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -120,14 +120,12 @@ def test_raise_instance_exception_with_args(): assert isinstance(exc, NullReferenceException) assert exc.Message == 'Aiiieee!' - def test_managed_exception_propagation(): """Test propagation of exceptions raised in managed code.""" - from System import Decimal, OverflowException - - with pytest.raises(OverflowException): - Decimal.ToInt64(Decimal.MaxValue) + from System import Decimal, DivideByZeroException + with pytest.raises(DivideByZeroException): + Decimal.Divide(1, 0) def test_managed_exception_conversion(): """Test conversion of managed exceptions.""" diff --git a/tests/test_generic.py b/tests/test_generic.py index 248303179..9c2e298e7 100644 --- a/tests/test_generic.py +++ b/tests/test_generic.py @@ -318,6 +318,7 @@ def test_generic_method_type_handling(): assert_generic_method_by_type(ShortEnum, ShortEnum.Zero) assert_generic_method_by_type(System.Object, InterfaceTest()) assert_generic_method_by_type(InterfaceTest, InterfaceTest(), 1) + assert_generic_method_by_type(ISayHello1, InterfaceTest(), 1) def test_correct_overload_selection(): @@ -546,11 +547,10 @@ def test_method_overload_selection_with_generic_types(): value = MethodTest.Overloaded.__overloads__[vtype](input_) assert value.value.__class__ == inst.__class__ - iface_class = ISayHello1(inst).__class__ vtype = GenericWrapper[ISayHello1] input_ = vtype(inst) value = MethodTest.Overloaded.__overloads__[vtype](input_) - assert value.value.__class__ == iface_class + assert value.value.__class__ == inst.__class__ vtype = System.Array[GenericWrapper[int]] input_ = vtype([GenericWrapper[int](0), GenericWrapper[int](1)]) @@ -725,12 +725,11 @@ def test_overload_selection_with_arrays_of_generic_types(): assert value[0].value.__class__ == inst.__class__ assert value.Length == 2 - iface_class = ISayHello1(inst).__class__ gtype = GenericWrapper[ISayHello1] vtype = System.Array[gtype] input_ = vtype([gtype(inst), gtype(inst)]) value = MethodTest.Overloaded.__overloads__[vtype](input_) - assert value[0].value.__class__ == iface_class + assert value[0].value.__class__ == inst.__class__ assert value.Length == 2 diff --git a/tests/test_indexer.py b/tests/test_indexer.py index 7992f76b0..535048ac3 100644 --- a/tests/test_indexer.py +++ b/tests/test_indexer.py @@ -333,31 +333,6 @@ def test_double_indexer(): ob["wrong"] = "wrong" -def test_decimal_indexer(): - """Test Decimal indexers.""" - ob = Test.DecimalIndexerTest() - - from System import Decimal - max_d = Decimal.Parse("79228162514264337593543950335") - min_d = Decimal.Parse("-79228162514264337593543950335") - - assert ob[max_d] is None - - ob[max_d] = "max_" - assert ob[max_d] == "max_" - - ob[min_d] = "min_" - assert ob[min_d] == "min_" - - with pytest.raises(TypeError): - ob = Test.DecimalIndexerTest() - ob["wrong"] - - with pytest.raises(TypeError): - ob = Test.DecimalIndexerTest() - ob["wrong"] = "wrong" - - def test_string_indexer(): """Test String indexers.""" ob = Test.StringIndexerTest() diff --git a/tests/test_interface.py b/tests/test_interface.py index 130bd71c1..7e50aaac5 100644 --- a/tests/test_interface.py +++ b/tests/test_interface.py @@ -61,8 +61,6 @@ def test_explicit_cast_to_interface(): assert hasattr(i1, 'SayHello') assert i1.SayHello() == 'hello 1' assert not hasattr(i1, 'HelloProperty') - assert i1.__implementation__ == ob - assert i1.__raw_implementation__ == ob i2 = Test.ISayHello2(ob) assert type(i2).__name__ == 'ISayHello2' diff --git a/tests/test_method.py b/tests/test_method.py index 9bdb571c0..0b374208d 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -564,10 +564,8 @@ def test_explicit_overload_selection(): value = MethodTest.Overloaded.__overloads__[InterfaceTest](inst) assert value.__class__ == inst.__class__ - iface_class = ISayHello1(InterfaceTest()).__class__ value = MethodTest.Overloaded.__overloads__[ISayHello1](inst) - assert value.__class__ != inst.__class__ - assert value.__class__ == iface_class + assert value.__class__ == inst.__class__ atype = Array[System.Object] value = MethodTest.Overloaded.__overloads__[str, int, atype]( @@ -720,12 +718,11 @@ def test_overload_selection_with_array_types(): assert value[0].__class__ == inst.__class__ assert value[1].__class__ == inst.__class__ - iface_class = ISayHello1(inst).__class__ vtype = Array[ISayHello1] input_ = vtype([inst, inst]) value = MethodTest.Overloaded.__overloads__[vtype](input_) - assert value[0].__class__ == iface_class - assert value[1].__class__ == iface_class + assert value[0].__class__ == inst.__class__ + assert value[1].__class__ == inst.__class__ def test_explicit_overload_selection_failure(): diff --git a/tests/test_subclass.py b/tests/test_subclass.py index 4f3180480..bcb2e7cbe 100644 --- a/tests/test_subclass.py +++ b/tests/test_subclass.py @@ -112,10 +112,8 @@ def test_interface(): assert ob.bar("bar", 2) == "bar/bar" assert FunctionsTest.test_bar(ob, "bar", 2) == "bar/bar" - # pass_through will convert from InterfaceTestClass -> IInterfaceTest, - # causing a new wrapper object to be created. Hence id will differ. - x = FunctionsTest.pass_through_interface(ob) - assert id(x) != id(ob) + x = FunctionsTest.pass_through(ob) + assert id(x) == id(ob) def test_derived_class(): @@ -188,14 +186,14 @@ def test_create_instance(): assert id(x) == id(ob) InterfaceTestClass = interface_test_class_fixture(test_create_instance.__name__) - ob2 = FunctionsTest.create_instance_interface(InterfaceTestClass) + ob2 = FunctionsTest.create_instance(InterfaceTestClass) assert ob2.foo() == "InterfaceTestClass" assert FunctionsTest.test_foo(ob2) == "InterfaceTestClass" assert ob2.bar("bar", 2) == "bar/bar" assert FunctionsTest.test_bar(ob2, "bar", 2) == "bar/bar" - y = FunctionsTest.pass_through_interface(ob2) - assert id(y) != id(ob2) + y = FunctionsTest.pass_through(ob2) + assert id(y) == id(ob2) def test_events(): pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy