Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ba5127a

Browse files
slidefilmor
authored andcommitted
Add mp_length slot for .NET classes implementing ICollection/ICollection<T> (#994)
- Add mp_length slot implementation for .NET types - Check if the object implement ICollection or ICollection<T> - Add tests for explicit and non-explicit interface implementation
1 parent 5f56ebc commit ba5127a

File tree

9 files changed

+289
-11
lines changed

9 files changed

+289
-11
lines changed

AUTHORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
## Contributors
1414

15+
- Alex Earl ([@slide](https://github.com/slide))
1516
- Alex Helms ([@alexhelms](https://github.com/alexhelms))
1617
- Alexandre Catarino([@AlexCatarino](https://github.com/AlexCatarino))
1718
- Arvid JB ([@ArvidJB](https://github.com/ArvidJB))

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1212
- Added automatic NuGet package generation in appveyor and local builds
1313
- Added function that sets Py_NoSiteFlag to 1.
1414
- Added support for Jetson Nano.
15+
- Added support for __len__ for .NET classes that implement ICollection
1516

1617
### Changed
1718

src/runtime/Python.Runtime.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@
142142
<Compile Include="Util.cs" />
143143
<Compile Include="platform\Types.cs" />
144144
<Compile Include="platform\LibraryLoader.cs" />
145+
<Compile Include="slots\mp_length.cs" />
145146
</ItemGroup>
146147
<ItemGroup Condition=" '$(PythonInteropFile)' != '' ">
147148
<Compile Include="$(PythonInteropFile)" />

src/runtime/arrayobject.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -244,16 +244,5 @@ public static int sq_contains(IntPtr ob, IntPtr v)
244244

245245
return 0;
246246
}
247-
248-
249-
/// <summary>
250-
/// Implements __len__ for array types.
251-
/// </summary>
252-
public static int mp_length(IntPtr ob)
253-
{
254-
var self = (CLRObject)GetManagedObject(ob);
255-
var items = self.inst as Array;
256-
return items.Length;
257-
}
258247
}
259248
}

src/runtime/slots/mp_length.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Reflection;
6+
7+
namespace Python.Runtime.Slots
8+
{
9+
internal static class mp_length_slot
10+
{
11+
/// <summary>
12+
/// Implements __len__ for classes that implement ICollection
13+
/// (this includes any IList implementer or Array subclass)
14+
/// </summary>
15+
public static int mp_length(IntPtr ob)
16+
{
17+
var co = ManagedType.GetManagedObject(ob) as CLRObject;
18+
if (co == null)
19+
{
20+
Exceptions.RaiseTypeError("invalid object");
21+
}
22+
23+
// first look for ICollection implementation directly
24+
if (co.inst is ICollection c)
25+
{
26+
return c.Count;
27+
}
28+
29+
Type clrType = co.inst.GetType();
30+
31+
// now look for things that implement ICollection<T> directly (non-explicitly)
32+
PropertyInfo p = clrType.GetProperty("Count");
33+
if (p != null && clrType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>)))
34+
{
35+
return (int)p.GetValue(co.inst, null);
36+
}
37+
38+
// finally look for things that implement the interface explicitly
39+
var iface = clrType.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>));
40+
if (iface != null)
41+
{
42+
p = iface.GetProperty(nameof(ICollection<int>.Count));
43+
return (int)p.GetValue(co.inst, null);
44+
}
45+
46+
Exceptions.SetError(Exceptions.TypeError, $"object of type '{clrType.Name}' has no len()");
47+
return -1;
48+
}
49+
}
50+
}

src/runtime/typemanager.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4+
using System.Linq;
45
using System.Reflection;
56
using System.Runtime.InteropServices;
67
using Python.Runtime.Platform;
8+
using Python.Runtime.Slots;
79

810
namespace Python.Runtime
911
{
@@ -153,6 +155,13 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType)
153155
Marshal.WriteIntPtr(type, TypeOffset.tp_itemsize, IntPtr.Zero);
154156
Marshal.WriteIntPtr(type, TypeOffset.tp_dictoffset, (IntPtr)tp_dictoffset);
155157

158+
// add a __len__ slot for inheritors of ICollection and ICollection<>
159+
if (typeof(ICollection).IsAssignableFrom(clrType) || clrType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>)))
160+
{
161+
InitializeSlot(type, TypeOffset.mp_length, typeof(mp_length_slot).GetMethod(nameof(mp_length_slot.mp_length)));
162+
}
163+
164+
// we want to do this after the slot stuff above in case the class itself implements a slot method
156165
InitializeSlots(type, impl.GetType());
157166

158167
if (base_ != IntPtr.Zero)
@@ -193,6 +202,12 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType)
193202
return type;
194203
}
195204

205+
static void InitializeSlot(IntPtr type, int slotOffset, MethodInfo method)
206+
{
207+
IntPtr thunk = Interop.GetThunk(method);
208+
Marshal.WriteIntPtr(type, slotOffset, thunk);
209+
}
210+
196211
internal static IntPtr CreateSubType(IntPtr py_name, IntPtr py_base_type, IntPtr py_dict)
197212
{
198213
// Utility to create a subtype of a managed type with the ability for the

src/testing/Python.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
<Compile Include="doctest.cs" />
9393
<Compile Include="subclasstest.cs" />
9494
<Compile Include="ReprTest.cs" />
95+
<Compile Include="mp_lengthtest.cs" />
9596
</ItemGroup>
9697
<ItemGroup>
9798
<Reference Include="Microsoft.CSharp" />

src/testing/mp_lengthtest.cs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
5+
namespace Python.Test
6+
{
7+
public class MpLengthCollectionTest : ICollection
8+
{
9+
private readonly List<int> items;
10+
11+
public MpLengthCollectionTest()
12+
{
13+
SyncRoot = new object();
14+
items = new List<int>
15+
{
16+
1,
17+
2,
18+
3
19+
};
20+
}
21+
22+
public int Count => items.Count;
23+
24+
public object SyncRoot { get; private set; }
25+
26+
public bool IsSynchronized => false;
27+
28+
public void CopyTo(Array array, int index)
29+
{
30+
throw new NotImplementedException();
31+
}
32+
33+
public IEnumerator GetEnumerator()
34+
{
35+
throw new NotImplementedException();
36+
}
37+
}
38+
39+
public class MpLengthExplicitCollectionTest : ICollection
40+
{
41+
private readonly List<int> items;
42+
private readonly object syncRoot;
43+
44+
public MpLengthExplicitCollectionTest()
45+
{
46+
syncRoot = new object();
47+
items = new List<int>
48+
{
49+
9,
50+
10
51+
};
52+
}
53+
int ICollection.Count => items.Count;
54+
55+
object ICollection.SyncRoot => syncRoot;
56+
57+
bool ICollection.IsSynchronized => false;
58+
59+
void ICollection.CopyTo(Array array, int index)
60+
{
61+
throw new NotImplementedException();
62+
}
63+
64+
IEnumerator IEnumerable.GetEnumerator()
65+
{
66+
throw new NotImplementedException();
67+
}
68+
}
69+
70+
public class MpLengthGenericCollectionTest<T> : ICollection<T>
71+
{
72+
private readonly List<T> items;
73+
74+
public MpLengthGenericCollectionTest() {
75+
SyncRoot = new object();
76+
items = new List<T>();
77+
}
78+
79+
public int Count => items.Count;
80+
81+
public object SyncRoot { get; private set; }
82+
83+
public bool IsSynchronized => false;
84+
85+
public bool IsReadOnly => false;
86+
87+
public void Add(T item)
88+
{
89+
items.Add(item);
90+
}
91+
92+
public void Clear()
93+
{
94+
items.Clear();
95+
}
96+
97+
public bool Contains(T item)
98+
{
99+
return items.Contains(item);
100+
}
101+
102+
public void CopyTo(T[] array, int arrayIndex)
103+
{
104+
items.CopyTo(array, arrayIndex);
105+
}
106+
107+
public IEnumerator GetEnumerator()
108+
{
109+
return ((IEnumerable)items).GetEnumerator();
110+
}
111+
112+
public bool Remove(T item)
113+
{
114+
return items.Remove(item);
115+
}
116+
117+
IEnumerator<T> IEnumerable<T>.GetEnumerator()
118+
{
119+
return items.GetEnumerator();
120+
}
121+
}
122+
123+
public class MpLengthExplicitGenericCollectionTest<T> : ICollection<T>
124+
{
125+
private readonly List<T> items;
126+
127+
public MpLengthExplicitGenericCollectionTest()
128+
{
129+
items = new List<T>();
130+
}
131+
132+
int ICollection<T>.Count => items.Count;
133+
134+
bool ICollection<T>.IsReadOnly => false;
135+
136+
public void Add(T item)
137+
{
138+
items.Add(item);
139+
}
140+
141+
void ICollection<T>.Clear()
142+
{
143+
items.Clear();
144+
}
145+
146+
bool ICollection<T>.Contains(T item)
147+
{
148+
return items.Contains(item);
149+
}
150+
151+
void ICollection<T>.CopyTo(T[] array, int arrayIndex)
152+
{
153+
items.CopyTo(array, arrayIndex);
154+
}
155+
156+
IEnumerator<T> IEnumerable<T>.GetEnumerator()
157+
{
158+
return items.GetEnumerator();
159+
}
160+
161+
IEnumerator IEnumerable.GetEnumerator()
162+
{
163+
return ((IEnumerable)items).GetEnumerator();
164+
}
165+
166+
bool ICollection<T>.Remove(T item)
167+
{
168+
return items.Remove(item);
169+
}
170+
}
171+
}

src/tests/test_mp_length.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""Test __len__ for .NET classes implementing ICollection/ICollection<T>."""
4+
5+
import System
6+
import pytest
7+
from Python.Test import MpLengthCollectionTest, MpLengthExplicitCollectionTest, MpLengthGenericCollectionTest, MpLengthExplicitGenericCollectionTest
8+
9+
def test_simple___len__():
10+
"""Test __len__ for simple ICollection implementers"""
11+
import System
12+
import System.Collections.Generic
13+
l = System.Collections.Generic.List[int]()
14+
assert len(l) == 0
15+
l.Add(5)
16+
l.Add(6)
17+
assert len(l) == 2
18+
19+
d = System.Collections.Generic.Dictionary[int, int]()
20+
assert len(d) == 0
21+
d.Add(4, 5)
22+
assert len(d) == 1
23+
24+
a = System.Array[int]([0,1,2,3])
25+
assert len(a) == 4
26+
27+
def test_custom_collection___len__():
28+
"""Test __len__ for custom collection class"""
29+
s = MpLengthCollectionTest()
30+
assert len(s) == 3
31+
32+
def test_custom_collection_explicit___len__():
33+
"""Test __len__ for custom collection class that explicitly implements ICollection"""
34+
s = MpLengthExplicitCollectionTest()
35+
assert len(s) == 2
36+
37+
def test_custom_generic_collection___len__():
38+
"""Test __len__ for custom generic collection class"""
39+
s = MpLengthGenericCollectionTest[int]()
40+
s.Add(1)
41+
s.Add(2)
42+
assert len(s) == 2
43+
44+
def test_custom_generic_collection_explicit___len__():
45+
"""Test __len__ for custom generic collection that explicity implements ICollection<T>"""
46+
s = MpLengthExplicitGenericCollectionTest[int]()
47+
s.Add(1)
48+
s.Add(10)
49+
assert len(s) == 2

0 commit comments

Comments
 (0)