Skip to content

Commit 87d4db9

Browse files
authored
Merge pull request #1786 from losttech/bugs/KeyCollection
Multiple fixes related to Dictionary.Keys bug
2 parents a80c685 + d854332 commit 87d4db9

10 files changed

+91
-7
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
2121
- `__name__` and `__signature__` to reflected .NET methods
2222
- .NET collection types now implement standard Python collection interfaces from `collections.abc`.
2323
See [Mixins/collections.py](src/runtime/Mixins/collections.py).
24+
- you can cast objects to generic .NET interfaces without specifying generic arguments as long as there is no ambiguity.
2425
- .NET arrays implement Python buffer protocol
2526
- Python integer interoperability with `System.Numerics.BigInteger`
2627
- Python.NET will correctly resolve .NET methods, that accept `PyList`, `PyInt`,

src/runtime/ClassManager.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ internal static void RestoreRuntimeData(ClassManagerState storage)
133133
/// Return the ClassBase-derived instance that implements a particular
134134
/// reflected managed type, creating it if it doesn't yet exist.
135135
/// </summary>
136-
internal static ReflectedClrType GetClass(Type type) => ReflectedClrType.GetOrCreate(type);
136+
internal static BorrowedReference GetClass(Type type) => ReflectedClrType.GetOrCreate(type);
137137

138138
internal static ClassBase GetClassImpl(Type type)
139139
{

src/runtime/Mixins/collections.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77

88
class IteratorMixin(col.Iterator):
99
def close(self):
10-
self.Dispose()
10+
if hasattr(self, 'Dispose'):
11+
self.Dispose()
12+
else:
13+
from System import IDisposable
14+
IDisposable(self).Dispose()
1115

1216
class IterableMixin(col.Iterable):
1317
pass
@@ -16,7 +20,12 @@ class SizedMixin(col.Sized):
1620
def __len__(self): return self.Count
1721

1822
class ContainerMixin(col.Container):
19-
def __contains__(self, item): return self.Contains(item)
23+
def __contains__(self, item):
24+
if hasattr('self', 'Contains'):
25+
return self.Contains(item)
26+
else:
27+
from System.Collections.Generic import ICollection
28+
return ICollection(self).Contains(item)
2029

2130
try:
2231
abc_Collection = col.Collection

src/runtime/TypeManager.cs

+17
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,23 @@ static void GetPythonTypeName(Type clrType, System.Text.StringBuilder target)
217217
}
218218

219219
target.Append(']');
220+
221+
int nestedStart = fullName.IndexOf('+');
222+
while (nestedStart >= 0)
223+
{
224+
target.Append('.');
225+
int nextNested = fullName.IndexOf('+', nestedStart + 1);
226+
if (nextNested < 0)
227+
{
228+
target.Append(fullName.Substring(nestedStart + 1));
229+
}
230+
else
231+
{
232+
target.Append(fullName.Substring(nestedStart + 1, length: nextNested - nestedStart - 1));
233+
}
234+
nestedStart = nextNested;
235+
}
236+
220237
return;
221238
}
222239
}

src/runtime/Types/ClassObject.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ public override NewReference type_subscript(BorrowedReference idx)
236236
return Exceptions.RaiseTypeError("type expected");
237237
}
238238
Type a = t.MakeArrayType();
239-
PyType o = ClassManager.GetClass(a);
239+
BorrowedReference o = ClassManager.GetClass(a);
240240
return new NewReference(o);
241241
}
242242

src/runtime/Types/ClrObject.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ internal static NewReference GetReference(object ob, BorrowedReference pyType)
4343

4444
internal static NewReference GetReference(object ob, Type type)
4545
{
46-
PyType cc = ClassManager.GetClass(type);
46+
BorrowedReference cc = ClassManager.GetClass(type);
4747
return Create(ob, cc);
4848
}
4949

5050
internal static NewReference GetReference(object ob)
5151
{
52-
PyType cc = ClassManager.GetClass(ob.GetType());
52+
BorrowedReference cc = ClassManager.GetClass(ob.GetType());
5353
return Create(ob, cc);
5454
}
5555

src/runtime/Types/GenericType.cs

+49
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Linq;
23

34
namespace Python.Runtime
45
{
@@ -20,10 +21,58 @@ internal GenericType(Type tp) : base(tp)
2021
/// </summary>
2122
public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, BorrowedReference kw)
2223
{
24+
var self = (GenericType)GetManagedObject(tp)!;
25+
if (!self.type.Valid)
26+
{
27+
return Exceptions.RaiseTypeError(self.type.DeletedMessage);
28+
}
29+
var type = self.type.Value;
30+
31+
if (type.IsInterface && !type.IsConstructedGenericType)
32+
{
33+
var nargs = Runtime.PyTuple_Size(args);
34+
if (nargs == 1)
35+
{
36+
var instance = Runtime.PyTuple_GetItem(args, 0);
37+
return AsGenericInterface(instance, type);
38+
}
39+
}
40+
2341
Exceptions.SetError(Exceptions.TypeError, "cannot instantiate an open generic type");
42+
2443
return default;
2544
}
2645

46+
static NewReference AsGenericInterface(BorrowedReference instance, Type targetType)
47+
{
48+
if (GetManagedObject(instance) is not CLRObject obj)
49+
{
50+
return Exceptions.RaiseTypeError("only .NET objects can be cast to .NET interfaces");
51+
}
52+
53+
Type[] supportedInterfaces = obj.inst.GetType().GetInterfaces();
54+
Type[] constructedInterfaces = supportedInterfaces
55+
.Where(i => i.IsConstructedGenericType && i.GetGenericTypeDefinition() == targetType)
56+
.ToArray();
57+
58+
if (constructedInterfaces.Length == 1)
59+
{
60+
BorrowedReference pythonic = ClassManager.GetClass(constructedInterfaces[0]);
61+
using var args = Runtime.PyTuple_New(1);
62+
Runtime.PyTuple_SetItem(args.Borrow(), 0, instance);
63+
return Runtime.PyObject_CallObject(pythonic, args.Borrow());
64+
}
65+
66+
if (constructedInterfaces.Length > 1)
67+
{
68+
string interfaces = string.Join(", ", constructedInterfaces.Select(TypeManager.GetPythonTypeName));
69+
return Exceptions.RaiseTypeError("Ambiguous cast to .NET interface. "
70+
+ $"Object implements: {interfaces}");
71+
}
72+
73+
return Exceptions.RaiseTypeError("object does not implement "
74+
+ TypeManager.GetPythonTypeName(targetType));
75+
}
2776

2877
/// <summary>
2978
/// Implements __call__ for reflected generic types.

src/runtime/Types/MethodObject.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ public static NewReference tp_descr_get(BorrowedReference ds, BorrowedReference
191191
&& obj.inst is IPythonDerivedType
192192
&& self.type.Value.IsInstanceOfType(obj.inst))
193193
{
194-
var basecls = ClassManager.GetClass(self.type.Value);
194+
var basecls = ReflectedClrType.GetOrCreate(self.type.Value);
195195
return new MethodBinding(self, new PyObject(ob), basecls).Alloc();
196196
}
197197

src/runtime/Types/ReflectedClrType.cs

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ internal sealed class ReflectedClrType : PyType
1212
{
1313
private ReflectedClrType(StolenReference reference) : base(reference, prevalidated: true) { }
1414
internal ReflectedClrType(ReflectedClrType original) : base(original, prevalidated: true) { }
15+
internal ReflectedClrType(BorrowedReference original) : base(original) { }
1516
ReflectedClrType(SerializationInfo info, StreamingContext context) : base(info, context) { }
1617

1718
internal ClassBase Impl => (ClassBase)ManagedType.GetManagedObject(this)!;

tests/test_collection_mixins.py

+7
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,10 @@ def test_dict_items():
1414
k,v = items[0]
1515
assert k == 42
1616
assert v == "a"
17+
18+
# regression test for https://github.com/pythonnet/pythonnet/issues/1785
19+
def test_dict_in_keys():
20+
d = C.Dictionary[str, int]()
21+
d["a"] = 42
22+
assert "a" in d.Keys
23+
assert "b" not in d.Keys

0 commit comments

Comments
 (0)