Skip to content

Commit

Permalink
Merge pull request #92 from jhonabreul/bug-error-set-even-with-matche…
Browse files Browse the repository at this point in the history
…d-overload

Fix type instance conversion
  • Loading branch information
jhonabreul authored May 14, 2024
2 parents 72d052e + 65ad1b9 commit 8fb227a
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 64 deletions.
25 changes: 25 additions & 0 deletions src/embed_tests/TestConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,27 @@ public void PrimitiveIntConversion()
var testInt = pyValue.As<int>();
Assert.AreEqual(testInt , 10);
}

[TestCase(typeof(Type), true)]
[TestCase(typeof(string), false)]
[TestCase(typeof(TestCSharpModel), false)]
public void NoErrorSetWhenFailingToConvertClassType(Type type, bool shouldConvert)
{
using var _ = Py.GIL();

var module = PyModule.FromString("CallsCorrectOverloadWithoutErrors", @"
from clr import AddReference
AddReference(""System"")
AddReference(""Python.EmbeddingTest"")
from Python.EmbeddingTest import *
class TestPythonModel(TestCSharpModel):
pass
");
var testPythonModelClass = module.GetAttr("TestPythonModel");
Assert.AreEqual(shouldConvert, Converter.ToManaged(testPythonModelClass, type, out var result, setError: false));
Assert.IsFalse(Exceptions.ErrorOccurred());
}
}

public interface IGetList
Expand All @@ -461,4 +482,8 @@ public class GetListImpl : IGetList
{
public List<string> GetList() => new() { "testing" };
}

public class TestCSharpModel
{
}
}
158 changes: 158 additions & 0 deletions src/embed_tests/TestMethodBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,164 @@ from Python.EmbeddingTest import *
"));
}

public class OverloadsTestClass
{

public string Method1(string positionalArg, decimal namedArg1 = 1.2m, int namedArg2 = 123)
{
Console.WriteLine("1");
return "Method1 Overload 1";
}

public string Method1(decimal namedArg1 = 1.2m, int namedArg2 = 123)
{
Console.WriteLine("2");
return "Method1 Overload 2";
}

// ----

public string Method2(string arg1, int arg2, decimal arg3, decimal kwarg1 = 1.1m, bool kwarg2 = false, string kwarg3 = "")
{
return "Method2 Overload 1";
}

public string Method2(string arg1, int arg2, decimal kwarg1 = 1.1m, bool kwarg2 = false, string kwarg3 = "")
{
return "Method2 Overload 2";
}

// ----

public string Method3(string arg1, int arg2, float arg3, float kwarg1 = 1.1f, bool kwarg2 = false, string kwarg3 = "")
{
return "Method3 Overload 1";
}

public string Method3(string arg1, int arg2, float kwarg1 = 1.1f, bool kwarg2 = false, string kwarg3 = "")
{
return "Method3 Overload 2";
}

// ----

public string ImplicitConversionSameArgumentCount(string symbol, int quantity, float trailingAmount, bool trailingAsPercentage, string tag = "")
{
return "ImplicitConversionSameArgumentCount 1";
}

public string ImplicitConversionSameArgumentCount(string symbol, decimal quantity, decimal trailingAmount, bool trailingAsPercentage, string tag = "")
{
return "ImplicitConversionSameArgumentCount 2";
}

public string ImplicitConversionSameArgumentCount2(string symbol, int quantity, float trailingAmount, bool trailingAsPercentage, string tag = "")
{
return "ImplicitConversionSameArgumentCount2 1";
}

public string ImplicitConversionSameArgumentCount2(string symbol, float quantity, float trailingAmount, bool trailingAsPercentage, string tag = "")
{
return "ImplicitConversionSameArgumentCount2 2";
}

public string ImplicitConversionSameArgumentCount2(string symbol, decimal quantity, float trailingAmount, bool trailingAsPercentage, string tag = "")
{
return "ImplicitConversionSameArgumentCount2 2";
}
}

[TestCase("Method1('abc', namedArg1=10, namedArg2=321)", "Method1 Overload 1")]
[TestCase("Method1('abc', namedArg1=12.34, namedArg2=321)", "Method1 Overload 1")]
[TestCase("Method2(\"SPY\", 10, 123, kwarg1=1, kwarg2=True)", "Method2 Overload 1")]
[TestCase("Method2(\"SPY\", 10, 123.34, kwarg1=1.23, kwarg2=True)", "Method2 Overload 1")]
[TestCase("Method3(\"SPY\", 10, 123.34, kwarg1=1.23, kwarg2=True)", "Method3 Overload 1")]
public void SelectsRightOverloadWithNamedParameters(string methodCallCode, string expectedResult)
{
using var _ = Py.GIL();

dynamic module = PyModule.FromString("SelectsRightOverloadWithNamedParameters", @$"
def call_method(instance):
return instance.{methodCallCode}
");

var instance = new OverloadsTestClass();
var result = module.call_method(instance).As<string>();

Assert.AreEqual(expectedResult, result);
}

[TestCase("ImplicitConversionSameArgumentCount", "10", "ImplicitConversionSameArgumentCount 1")]
[TestCase("ImplicitConversionSameArgumentCount", "10.1", "ImplicitConversionSameArgumentCount 2")]
[TestCase("ImplicitConversionSameArgumentCount2", "10", "ImplicitConversionSameArgumentCount2 1")]
[TestCase("ImplicitConversionSameArgumentCount2", "10.1", "ImplicitConversionSameArgumentCount2 2")]
public void DisambiguatesOverloadWithSameArgumentCountAndImplicitConversion(string methodName, string quantity, string expectedResult)
{
using var _ = Py.GIL();

dynamic module = PyModule.FromString("DisambiguatesOverloadWithSameArgumentCountAndImplicitConversion", @$"
def call_method(instance):
return instance.{methodName}(""SPY"", {quantity}, 123.4, trailingAsPercentage=True)
");

var instance = new OverloadsTestClass();
var result = module.call_method(instance).As<string>();

Assert.AreEqual(expectedResult, result);
}

public class CSharpClass
{
public string CalledMethodMessage { get; private set; }

public void Method()
{
CalledMethodMessage = "Overload 1";
}

public void Method(string stringArgument, decimal decimalArgument = 1.2m)
{
CalledMethodMessage = "Overload 2";
}

public void Method(PyObject typeArgument, decimal decimalArgument = 1.2m)
{
CalledMethodMessage = "Overload 3";
}
}

[Test]
public void CallsCorrectOverloadWithoutErrors()
{
using var _ = Py.GIL();

var module = PyModule.FromString("CallsCorrectOverloadWithoutErrors", @"
from clr import AddReference
AddReference(""System"")
AddReference(""Python.EmbeddingTest"")
from Python.EmbeddingTest import *
class PythonModel(TestMethodBinder.CSharpModel):
pass
def call_method(instance):
instance.Method(PythonModel, decimalArgument=1.234)
");

var instance = new CSharpClass();
using var pyInstance = instance.ToPython();

Assert.DoesNotThrow(() =>
{
module.GetAttr("call_method").Invoke(pyInstance);
});

Assert.AreEqual("Overload 3", instance.CalledMethodMessage);

Assert.IsFalse(Exceptions.ErrorOccurred());
}


// Used to test that we match this function with Py DateTime & Date Objects
public static int GetMonth(DateTime test)
Expand Down
4 changes: 2 additions & 2 deletions src/perf_tests/Python.PerformanceTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
<PackageReference Include="quantconnect.pythonnet" Version="2.0.36" GeneratePathProperty="true">
<PackageReference Include="quantconnect.pythonnet" Version="2.0.37" GeneratePathProperty="true">
<IncludeAssets>compile</IncludeAssets>
</PackageReference>
</ItemGroup>
Expand All @@ -25,7 +25,7 @@
</Target>

<Target Name="CopyBaseline" AfterTargets="Build">
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.36\lib\net6.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.37\lib\net6.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
</Target>

<Target Name="CopyNewBuild" AfterTargets="Build">
Expand Down
23 changes: 21 additions & 2 deletions src/runtime/Converter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,13 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType,
}
if (mt is ClassBase cb)
{
if (!cb.type.Valid || !obType.IsInstanceOfType(cb.type.Value))
// The value being converted is a class type, so it will only succeed if it's being converted into a Type
if (obType != typeof(Type))
{
return false;
}

if (!cb.type.Valid)
{
Exceptions.SetError(Exceptions.TypeError, cb.type.DeletedMessage);
return false;
Expand Down Expand Up @@ -845,8 +851,21 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec
}

case TypeCode.Boolean:
result = Runtime.PyObject_IsTrue(value) != 0;
if (value == Runtime.PyTrue)
{
result = true;
return true;
}
if (value == Runtime.PyFalse)
{
result = false;
return true;
}
if (setError)
{
goto type_error;
}
return false;

case TypeCode.Byte:
{
Expand Down
Loading

0 comments on commit 8fb227a

Please sign in to comment.