Skip to content

Commit 755d867

Browse files
committed
Implements DictionaryObject
Enables `__contains__` and `__len`__ for CLR classes that implements a dictionary.
1 parent 39774f5 commit 755d867

File tree

6 files changed

+402
-0
lines changed

6 files changed

+402
-0
lines changed

src/runtime/Python.Runtime.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
<Reference Include="System" />
7777
</ItemGroup>
7878
<ItemGroup>
79+
<Compile Include="dictionaryobject.cs" />
7980
<Compile Include="finalizer.cs" />
8081
<Compile Include="Properties\AssemblyInfo.cs" />
8182
<Compile Include="..\SharedAssemblyInfo.cs">

src/runtime/classmanager.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ private static ClassBase CreateClass(Type type)
8787
impl = new ArrayObject(type);
8888
}
8989

90+
else if (type.IsDictionary())
91+
{
92+
impl = new DictionaryObject(type);
93+
}
94+
9095
else if (type.IsInterface)
9196
{
9297
impl = new InterfaceObject(type);

src/runtime/dictionaryobject.cs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Reflection;
4+
5+
namespace Python.Runtime
6+
{
7+
/// <summary>
8+
/// Implements a Python type for managed dictionaries. This type is essentially
9+
/// the same as a ClassObject, except that it provides sequence semantics
10+
/// to support natural dictionary usage (__contains__ and __len__) from Python.
11+
/// </summary>
12+
internal class DictionaryObject : ClassObject
13+
{
14+
private static Dictionary<Tuple<Type, string>, MethodInfo> methodsByType = new Dictionary<Tuple<Type, string>, MethodInfo>();
15+
private static Dictionary<string, string> methodMap = new Dictionary<string, string>
16+
{
17+
{ "mp_length", "Count" },
18+
{ "sq_contains", "ContainsKey" }
19+
};
20+
21+
public List<string> MappedMethods { get; } = new List<string>();
22+
23+
internal DictionaryObject(Type tp) : base(tp)
24+
{
25+
if (!tp.IsDictionary())
26+
{
27+
throw new ArgumentException("object is not a dict");
28+
}
29+
30+
foreach (var name in methodMap)
31+
{
32+
var key = Tuple.Create(type, name.Value);
33+
MethodInfo method;
34+
if (!methodsByType.TryGetValue(key, out method))
35+
{
36+
method = tp.GetMethod(name.Value);
37+
if (method == null)
38+
{
39+
method = tp.GetMethod($"get_{name.Value}");
40+
}
41+
if (method == null)
42+
{
43+
continue;
44+
}
45+
methodsByType.Add(key, method);
46+
}
47+
48+
MappedMethods.Add(name.Key);
49+
}
50+
}
51+
52+
internal override bool CanSubclass() => false;
53+
54+
/// <summary>
55+
/// Implements __len__ for dictionary types.
56+
/// </summary>
57+
public static int mp_length(IntPtr ob)
58+
{
59+
var obj = (CLRObject)GetManagedObject(ob);
60+
var self = obj.inst;
61+
62+
MethodInfo methodInfo;
63+
if (!TryGetMethodInfo(self.GetType(), "Count", out methodInfo))
64+
{
65+
return 0;
66+
}
67+
68+
return (int)methodInfo.Invoke(self, null);
69+
}
70+
71+
/// <summary>
72+
/// Implements __contains__ for dictionary types.
73+
/// </summary>
74+
public static int sq_contains(IntPtr ob, IntPtr v)
75+
{
76+
var obj = (CLRObject)GetManagedObject(ob);
77+
var self = obj.inst;
78+
79+
MethodInfo methodInfo;
80+
if (!TryGetMethodInfo(self.GetType(), "ContainsKey", out methodInfo))
81+
{
82+
return 0;
83+
}
84+
85+
var parameters = methodInfo.GetParameters();
86+
object arg;
87+
if (!Converter.ToManaged(v, parameters[0].ParameterType, out arg, false))
88+
{
89+
Exceptions.SetError(Exceptions.TypeError,
90+
$"invalid parameter type for sq_contains: should be {Converter.GetTypeByAlias(v)}, found {parameters[0].ParameterType}");
91+
}
92+
93+
return (bool)methodInfo.Invoke(self, new[] { arg }) ? 1 : 0;
94+
}
95+
96+
private static bool TryGetMethodInfo(Type type, string alias, out MethodInfo methodInfo)
97+
{
98+
var key = Tuple.Create(type, alias);
99+
100+
if (!methodsByType.TryGetValue(key, out methodInfo))
101+
{
102+
Exceptions.SetError(Exceptions.TypeError,
103+
$"{nameof(type)} does not define {alias} method");
104+
105+
return false;
106+
}
107+
108+
return true;
109+
}
110+
}
111+
112+
public static class DictionaryObjectExtension
113+
{
114+
public static bool IsDictionary(this Type type)
115+
{
116+
var iEnumerableType = typeof(IEnumerable<>);
117+
var keyValuePairType = typeof(KeyValuePair<,>);
118+
119+
var interfaces = type.GetInterfaces();
120+
foreach (var i in interfaces)
121+
{
122+
if (i.IsGenericType &&
123+
i.GetGenericTypeDefinition() == iEnumerableType)
124+
{
125+
var arguments = i.GetGenericArguments();
126+
if (arguments.Length != 1) continue;
127+
128+
var a = arguments[0];
129+
if (a.IsGenericType &&
130+
a.GetGenericTypeDefinition() == keyValuePairType &&
131+
a.GetGenericArguments().Length == 2)
132+
{
133+
return true;
134+
}
135+
}
136+
}
137+
138+
return false;
139+
}
140+
}
141+
}

src/testing/Python.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
<DebugType>pdbonly</DebugType>
7171
</PropertyGroup>
7272
<ItemGroup>
73+
<Compile Include="dictionarytest.cs" />
7374
<Compile Include="arraytest.cs" />
7475
<Compile Include="callbacktest.cs" />
7576
<Compile Include="classtest.cs" />

src/testing/dictionarytest.cs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace Python.Test
6+
{
7+
/// <summary>
8+
/// Supports units tests for dictionary __contains__ and __len__
9+
/// </summary>
10+
public class PublicDictionaryTest
11+
{
12+
public IDictionary<string, int> items;
13+
14+
public PublicDictionaryTest()
15+
{
16+
items = new int[5] { 0, 1, 2, 3, 4 }
17+
.ToDictionary(k => k.ToString(), v => v);
18+
}
19+
}
20+
21+
22+
public class ProtectedDictionaryTest
23+
{
24+
protected IDictionary<string, int> items;
25+
26+
public ProtectedDictionaryTest()
27+
{
28+
items = new int[5] { 0, 1, 2, 3, 4 }
29+
.ToDictionary(k => k.ToString(), v => v);
30+
}
31+
}
32+
33+
34+
public class InternalDictionaryTest
35+
{
36+
internal IDictionary<string, int> items;
37+
38+
public InternalDictionaryTest()
39+
{
40+
items = new int[5] { 0, 1, 2, 3, 4 }
41+
.ToDictionary(k => k.ToString(), v => v);
42+
}
43+
}
44+
45+
46+
public class PrivateDictionaryTest
47+
{
48+
private IDictionary<string, int> items;
49+
50+
public PrivateDictionaryTest()
51+
{
52+
items = new int[5] { 0, 1, 2, 3, 4 }
53+
.ToDictionary(k => k.ToString(), v => v);
54+
}
55+
}
56+
57+
public class InheritedDictionaryTest : IDictionary<string, int>
58+
{
59+
private readonly IDictionary<string, int> items;
60+
61+
public InheritedDictionaryTest()
62+
{
63+
items = new int[5] { 0, 1, 2, 3, 4 }
64+
.ToDictionary(k => k.ToString(), v => v);
65+
}
66+
67+
public int this[string key]
68+
{
69+
get { return items[key]; }
70+
set { items[key] = value; }
71+
}
72+
73+
public ICollection<string> Keys => items.Keys;
74+
75+
public ICollection<int> Values => items.Values;
76+
77+
public int Count => items.Count;
78+
79+
public bool IsReadOnly => false;
80+
81+
public void Add(string key, int value) => items.Add(key, value);
82+
83+
public void Add(KeyValuePair<string, int> item) => items.Add(item);
84+
85+
public void Clear() => items.Clear();
86+
87+
public bool Contains(KeyValuePair<string, int> item) => items.Contains(item);
88+
89+
public bool ContainsKey(string key) => items.ContainsKey(key);
90+
91+
public void CopyTo(KeyValuePair<string, int>[] array, int arrayIndex)
92+
{
93+
items.CopyTo(array, arrayIndex);
94+
}
95+
96+
public IEnumerator<KeyValuePair<string, int>> GetEnumerator() => items.GetEnumerator();
97+
98+
public bool Remove(string key) => items.Remove(key);
99+
100+
public bool Remove(KeyValuePair<string, int> item) => items.Remove(item);
101+
102+
public bool TryGetValue(string key, out int value) => items.TryGetValue(key, out value);
103+
104+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
105+
}
106+
}

0 commit comments

Comments
 (0)