diff --git a/src/embed_tests/TestMethodBinder.cs b/src/embed_tests/TestMethodBinder.cs index e377b5f83..355a96c3f 100644 --- a/src/embed_tests/TestMethodBinder.cs +++ b/src/embed_tests/TestMethodBinder.cs @@ -805,6 +805,34 @@ public string ImplicitConversionSameArgumentCount2(string symbol, decimal quanti { return "ImplicitConversionSameArgumentCount2 2"; } + + // ---- + + public string VariableArgumentsMethod(params CSharpModel[] paramsParams) + { + return "VariableArgumentsMethod(CSharpModel[])"; + } + + public string VariableArgumentsMethod(params PyObject[] paramsParams) + { + return "VariableArgumentsMethod(PyObject[])"; + } + + public string ConstructorMessage { get; set; } + + public OverloadsTestClass(params CSharpModel[] paramsParams) + { + ConstructorMessage = "OverloadsTestClass(CSharpModel[])"; + } + + public OverloadsTestClass(params PyObject[] paramsParams) + { + ConstructorMessage = "OverloadsTestClass(PyObject[])"; + } + + public OverloadsTestClass() + { + } } [TestCase("Method1('abc', namedArg1=10, namedArg2=321)", "Method1 Overload 1")] @@ -898,6 +926,75 @@ def call_method(instance): Assert.IsFalse(Exceptions.ErrorOccurred()); } + [Test] + public void BindsConstructorToSnakeCasedArgumentsVersion([Values] bool useCamelCase, [Values] bool passOptionalArgument) + { + using var _ = Py.GIL(); + + var argument1Name = useCamelCase ? "someArgument" : "some_argument"; + var argument2Name = useCamelCase ? "anotherArgument" : "another_argument"; + var argument2Code = passOptionalArgument ? $", {argument2Name}=\"another argument value\"" : ""; + + var module = PyModule.FromString("BindsConstructorToSnakeCasedArgumentsVersion", @$" +from clr import AddReference +AddReference(""System"") +from Python.EmbeddingTest import * + +def create_instance(): + return TestMethodBinder.CSharpModel({argument1Name}=1{argument2Code}) +"); + var exception = Assert.Throws(() => module.GetAttr("create_instance").Invoke()); + var sourceException = exception.InnerException; + Assert.IsInstanceOf(sourceException); + + var expectedMessage = passOptionalArgument + ? "Constructor with arguments: someArgument=1. anotherArgument=\"another argument value\"" + : "Constructor with arguments: someArgument=1. anotherArgument=\"another argument default value\""; + Assert.AreEqual(expectedMessage, sourceException.Message); + } + + [Test] + public void PyObjectArrayHasPrecedenceOverOtherTypeArrays() + { + using var _ = Py.GIL(); + + var module = PyModule.FromString("PyObjectArrayHasPrecedenceOverOtherTypeArrays", @$" +from clr import AddReference +AddReference(""System"") +from Python.EmbeddingTest import * + +class PythonModel(TestMethodBinder.CSharpModel): + pass + +def call_method(): + return TestMethodBinder.OverloadsTestClass().VariableArgumentsMethod(PythonModel(), PythonModel()) +"); + + var result = module.GetAttr("call_method").Invoke().As(); + Assert.AreEqual("VariableArgumentsMethod(PyObject[])", result); + } + + [Test] + public void PyObjectArrayHasPrecedenceOverOtherTypeArraysInConstructors() + { + using var _ = Py.GIL(); + + var module = PyModule.FromString("PyObjectArrayHasPrecedenceOverOtherTypeArrays", @$" +from clr import AddReference +AddReference(""System"") +from Python.EmbeddingTest import * + +class PythonModel(TestMethodBinder.CSharpModel): + pass + +def get_instance(): + return TestMethodBinder.OverloadsTestClass(PythonModel(), PythonModel()) +"); + + var instance = module.GetAttr("get_instance").Invoke(); + Assert.AreEqual("OverloadsTestClass(PyObject[])", instance.GetAttr("ConstructorMessage").As()); + } + // Used to test that we match this function with Py DateTime & Date Objects public static int GetMonth(DateTime test) @@ -918,6 +1015,12 @@ public CSharpModel() new TestImplicitConversion() }; } + + public CSharpModel(int someArgument, string anotherArgument = "another argument default value") + { + throw new NotImplementedException($"Constructor with arguments: someArgument={someArgument}. anotherArgument=\"{anotherArgument}\""); + } + public void TestList(List conversions) { if (!conversions.Any()) diff --git a/src/perf_tests/Python.PerformanceTests.csproj b/src/perf_tests/Python.PerformanceTests.csproj index c0990cf9b..2ef942f0d 100644 --- a/src/perf_tests/Python.PerformanceTests.csproj +++ b/src/perf_tests/Python.PerformanceTests.csproj @@ -13,7 +13,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + compile @@ -25,7 +25,7 @@ - + diff --git a/src/runtime/ClassManager.cs b/src/runtime/ClassManager.cs index e5d31f4f1..58f80ce30 100644 --- a/src/runtime/ClassManager.cs +++ b/src/runtime/ClassManager.cs @@ -552,6 +552,11 @@ void AddMember(string name, string snakeCasedName, bool isStaticReadonlyCallable methodList = methods[name] = new (); } methodList.Add(ctor, true); + // Same constructor, but with snake-cased arguments + if (ctor.GetParameters().Any(pi => pi.Name?.ToSnakeCase() != pi.Name)) + { + methodList.Add(ctor, false); + } continue; case MemberTypes.Property: diff --git a/src/runtime/MethodBinder.cs b/src/runtime/MethodBinder.cs index bef394ba7..f598da499 100644 --- a/src/runtime/MethodBinder.cs +++ b/src/runtime/MethodBinder.cs @@ -365,6 +365,16 @@ internal static int ArgPrecedence(Type t, MethodInformation mi) return -1; } + if (t.IsArray) + { + Type e = t.GetElementType(); + if (e == objectType) + { + return 2500; + } + return 100 + ArgPrecedence(e, mi); + } + TypeCode tc = Type.GetTypeCode(t); // TODO: Clean up switch (tc) @@ -406,16 +416,6 @@ internal static int ArgPrecedence(Type t, MethodInformation mi) return 40; } - if (t.IsArray) - { - Type e = t.GetElementType(); - if (e == objectType) - { - return 2500; - } - return 100 + ArgPrecedence(e, mi); - } - return 2000; } diff --git a/src/runtime/Properties/AssemblyInfo.cs b/src/runtime/Properties/AssemblyInfo.cs index 0c15263c9..3b88a6eb5 100644 --- a/src/runtime/Properties/AssemblyInfo.cs +++ b/src/runtime/Properties/AssemblyInfo.cs @@ -4,5 +4,5 @@ [assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")] [assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")] -[assembly: AssemblyVersion("2.0.37")] -[assembly: AssemblyFileVersion("2.0.37")] +[assembly: AssemblyVersion("2.0.38")] +[assembly: AssemblyFileVersion("2.0.38")] diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 5b3276dbe..7c25c9219 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -5,7 +5,7 @@ Python.Runtime Python.Runtime QuantConnect.pythonnet - 2.0.37 + 2.0.38 false LICENSE https://github.com/pythonnet/pythonnet