Skip to content

Disable implicit conversions that might lose information #1568

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
- .NET collection types now implement standard Python collection interfaces from `collections.abc`.
See [Mixins/collections.py](src/runtime/Mixins/collections.py).
- .NET arrays implement Python buffer protocol
- Python.NET will correctly resolve .NET methods, that accept `PyList`, `PyInt`,
and other `PyObject` derived types when called from Python.
- `PyIterable` type, that wraps any iterable object in Python


### Changed
Expand Down Expand Up @@ -51,13 +54,22 @@ One must now either use enum members (e.g. `MyEnum.Option`), or use enum constru
- Sign Runtime DLL with a strong name
- Implement loading through `clr_loader` instead of the included `ClrModule`, enables
support for .NET Core
- .NET and Python exceptions are preserved when crossing Python/.NET boundary
- BREAKING: .NET and Python exceptions are preserved when crossing Python/.NET boundary
- BREAKING: custom encoders are no longer called for instances of `System.Type`
- `PythonException.Restore` no longer clears `PythonException` instance.
- Replaced the old `__import__` hook hack with a PEP302-style Meta Path Loader
- BREAKING: Names of .NET types (e.g. `str(__class__)`) changed to better support generic types
- BREAKING: overload resolution will no longer prefer basic types. Instead, first matching overload will
be chosen.
- BREAKING: .NET collections and arrays are no longer automatically converted to
Python collections. Instead, they implement standard Python
collection interfaces from `collections.abc`.
See [Mixins/collections.py](src/runtime/Mixins/collections.py).
- BREAKING: When trying to convert Python `int` to `System.Object`, result will
be of type `PyInt` instead of `System.Int32` due to possible loss of information.
Python `float` will continue to be converted to `System.Double`.
- BREAKING: `PyObject` no longer implements `IEnumerable<PyObject>`.
Instead, `PyIterable` does that.

### Fixed

Expand Down
12 changes: 6 additions & 6 deletions src/embed_tests/Codecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ static void TupleConversionsGeneric<T, TTuple>()
[Test]
public void TupleConversionsObject()
{
TupleConversionsObject<ValueTuple<int, string, object>, ValueTuple>();
TupleConversionsObject<ValueTuple<double, string, object>, ValueTuple>();
}
static void TupleConversionsObject<T, TTuple>()
{
TupleCodec<TTuple>.Register();
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
var tuple = Activator.CreateInstance(typeof(T), 42.0, "42", new object());
T restored = default;
using (var scope = Py.CreateScope())
{
Expand All @@ -66,11 +66,11 @@ static void TupleConversionsObject<T, TTuple>()
[Test]
public void TupleRoundtripObject()
{
TupleRoundtripObject<ValueTuple<int, string, object>, ValueTuple>();
TupleRoundtripObject<ValueTuple<double, string, object>, ValueTuple>();
}
static void TupleRoundtripObject<T, TTuple>()
{
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
var tuple = Activator.CreateInstance(typeof(T), 42.0, "42", new object());
var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple);
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out object restored));
Assert.AreEqual(expected: tuple, actual: restored);
Expand Down Expand Up @@ -231,7 +231,7 @@ public void IterableDecoderTest()
//ensure a PyList can be converted to a plain IEnumerable
System.Collections.IEnumerable plainEnumerable1 = null;
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out plainEnumerable1); });
CollectionAssert.AreEqual(plainEnumerable1, new List<object> { 1, 2, 3 });
CollectionAssert.AreEqual(plainEnumerable1.Cast<PyInt>().Select(i => i.ToInt32()), new List<object> { 1, 2, 3 });

//can convert to any generic ienumerable. If the type is not assignable from the python element
//it will lead to an empty iterable when decoding. TODO - should it throw?
Expand Down Expand Up @@ -271,7 +271,7 @@ public void IterableDecoderTest()
var fooType = foo.GetPythonType();
System.Collections.IEnumerable plainEnumerable2 = null;
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out plainEnumerable2); });
CollectionAssert.AreEqual(plainEnumerable2, new List<object> { 1, 2, 3 });
CollectionAssert.AreEqual(plainEnumerable2.Cast<PyInt>().Select(i => i.ToInt32()), new List<object> { 1, 2, 3 });

//can convert to any generic ienumerable. If the type is not assignable from the python element
//it will be an exception during TryDecode
Expand Down
19 changes: 19 additions & 0 deletions src/embed_tests/TestConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,25 @@ public void ConvertOverflow()
}
}

[Test]
public void ToNullable()
{
const int Const = 42;
var i = new PyInt(Const);
var ni = i.As<int?>();
Assert.AreEqual(Const, ni);
}

[Test]
public void ToPyList()
{
var list = new PyList();
list.Append("hello".ToPython());
list.Append("world".ToPython());
var back = list.ToPython().As<PyList>();
Assert.AreEqual(list.Length(), back.Length());
}

[Test]
public void RawListProxy()
{
Expand Down
3 changes: 3 additions & 0 deletions src/runtime/Util.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;

Expand Down Expand Up @@ -68,5 +69,7 @@ internal static string ReadStringResource(this System.Reflection.Assembly assemb
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
}

public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> enumerator) => enumerator;
}
}
11 changes: 3 additions & 8 deletions src/runtime/classderived.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ internal static Type CreateDerivedType(string name,
{
Runtime.XIncref(py_dict);
using (var dict = new PyDict(py_dict))
using (PyObject keys = dict.Keys())
using (PyIterable keys = dict.Keys())
{
foreach (PyObject pyKey in keys)
{
Expand Down Expand Up @@ -223,7 +223,7 @@ internal static Type CreateDerivedType(string name,
{
Runtime.XIncref(py_dict);
using (var dict = new PyDict(py_dict))
using (PyObject keys = dict.Keys())
using (PyIterable keys = dict.Keys())
{
foreach (PyObject pyKey in keys)
{
Expand Down Expand Up @@ -439,19 +439,14 @@ private static void AddPythonMethod(string methodName, PyObject func, TypeBuilde
}

using (PyObject pyReturnType = func.GetAttr("_clr_return_type_"))
using (PyObject pyArgTypes = func.GetAttr("_clr_arg_types_"))
using (var pyArgTypes = PyIter.GetIter(func.GetAttr("_clr_arg_types_")))
{
var returnType = pyReturnType.AsManagedObject(typeof(Type)) as Type;
if (returnType == null)
{
returnType = typeof(void);
}

if (!pyArgTypes.IsIterable())
{
throw new ArgumentException("_clr_arg_types_ must be a list or tuple of CLR types");
}

var argTypes = new List<Type>();
foreach (PyObject pyArgType in pyArgTypes)
{
Expand Down
Loading