Skip to content

Commit 1d03ccc

Browse files
committed
Replace DictionaryObject with LookUpObject
1 parent aa2a1d6 commit 1d03ccc

File tree

6 files changed

+211
-114
lines changed

6 files changed

+211
-114
lines changed

src/embed_tests/ClassManagerTests.cs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,11 +1005,13 @@ def call(instance):
10051005

10061006
#endregion
10071007

1008-
private static TestCaseData[] IDictionaryContainsTestCases => new TestCaseData[]
1009-
{
1008+
private static TestCaseData[] IDictionaryContainsTestCases =>
1009+
[
10101010
new(typeof(TestDictionary<string, string>)),
10111011
new(typeof(Dictionary<string, string>)),
1012-
};
1012+
new(typeof(TestKeyValueContainer<string, string>)),
1013+
new(typeof(DynamicClassDictionary<string, string>)),
1014+
];
10131015

10141016
[TestCaseSource(nameof(IDictionaryContainsTestCases))]
10151017
public void IDictionaryContainsMethodIsBound(Type dictType)
@@ -1028,9 +1030,9 @@ def contains(dictionary, key):
10281030

10291031
using var contains = module.GetAttr("contains");
10301032

1031-
var dictionary = (Activator.CreateInstance(dictType) as IDictionary)!;
1033+
var dictionary = Convert.ChangeType(Activator.CreateInstance(dictType), dictType);
10321034
var key1 = "key1";
1033-
dictionary.Add(key1, "value1");
1035+
(dictionary as dynamic).Add(key1, "value1");
10341036

10351037
using var pyDictionary = dictionary.ToPython();
10361038
using var pyKey1 = key1.ToPython();
@@ -1060,8 +1062,8 @@ def contains(dictionary, key):
10601062

10611063
using var contains = module.GetAttr("contains");
10621064

1063-
var dictionary = (Activator.CreateInstance(dictType) as IDictionary)!;
1064-
dictionary.Add("key1", "value1");
1065+
var dictionary = Convert.ChangeType(Activator.CreateInstance(dictType), dictType);
1066+
(dictionary as dynamic).Add("key1", "value1");
10651067

10661068
using var pyDictionary = dictionary.ToPython();
10671069

@@ -1070,9 +1072,9 @@ def contains(dictionary, key):
10701072
Assert.IsFalse(result);
10711073
}
10721074

1073-
public class TestDictionary<TValue, TKey> : IDictionary
1075+
public class TestDictionary<TKey, TValue> : IDictionary
10741076
{
1075-
private readonly Dictionary<TValue, TKey> _data = new();
1077+
private readonly Dictionary<TKey, TValue> _data = new();
10761078

10771079
public object this[object key] { get => ((IDictionary)_data)[key]; set => ((IDictionary)_data)[key] = value; }
10781080

@@ -1130,6 +1132,36 @@ public bool ContainsKey(TKey key)
11301132
return Contains(key);
11311133
}
11321134
}
1135+
1136+
public class TestKeyValueContainer<TKey, TValue>
1137+
where TKey: class
1138+
where TValue: class
1139+
{
1140+
private readonly Dictionary<TKey, TValue> _data = new();
1141+
public int Count => _data.Count;
1142+
public bool ContainsKey(TKey key)
1143+
{
1144+
return _data.ContainsKey(key);
1145+
}
1146+
public void Add(TKey key, TValue value)
1147+
{
1148+
_data.Add(key, value);
1149+
}
1150+
}
1151+
1152+
public class DynamicClassDictionary<TKey, TValue> : TestPropertyAccess.DynamicFixture
1153+
{
1154+
private readonly Dictionary<TKey, TValue> _data = new();
1155+
public int Count => _data.Count;
1156+
public bool ContainsKey(TKey key)
1157+
{
1158+
return _data.ContainsKey(key);
1159+
}
1160+
public void Add(TKey key, TValue value)
1161+
{
1162+
_data.Add(key, value);
1163+
}
1164+
}
11331165
}
11341166

11351167
public class NestedTestParent

src/runtime/ClassManager.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,6 @@ internal static ClassBase CreateClass(Type type)
180180
impl = new ArrayObject(type);
181181
}
182182

183-
else if (type.IsDictionary())
184-
{
185-
impl = new DictionaryObject(type);
186-
}
187-
188183
else if (type.IsKeyValuePairEnumerable())
189184
{
190185
impl = new KeyValuePairEnumerableObject(type);
@@ -210,7 +205,19 @@ internal static ClassBase CreateClass(Type type)
210205

211206
else if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(type))
212207
{
213-
impl = new DynamicClassObject(type);
208+
if (type.IsLookUp())
209+
{
210+
impl = new DynamicClassLookUpObject(type);
211+
}
212+
else
213+
{
214+
impl = new DynamicClassObject(type);
215+
}
216+
}
217+
218+
else if (type.IsLookUp())
219+
{
220+
impl = new LookUpObject(type);
214221
}
215222

216223
else

src/runtime/Types/DictionaryObject.cs

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
3+
namespace Python.Runtime
4+
{
5+
/// <summary>
6+
/// Implements a Python type for managed DynamicClass objects that support look up (dictionaries),
7+
/// that is, they implement ContainsKey().
8+
/// This type is essentially the same as a ClassObject, except that it provides
9+
/// sequence semantics to support natural dictionary usage (__contains__ and __len__)
10+
/// from Python.
11+
/// </summary>
12+
internal class DynamicClassLookUpObject : DynamicClassObject
13+
{
14+
internal DynamicClassLookUpObject(Type tp) : base(tp)
15+
{
16+
}
17+
18+
/// <summary>
19+
/// Implements __len__ for dictionary types.
20+
/// </summary>
21+
public static int mp_length(BorrowedReference ob)
22+
{
23+
return LookUpObject.mp_length(ob);
24+
}
25+
26+
/// <summary>
27+
/// Implements __contains__ for dictionary types.
28+
/// </summary>
29+
public static int sq_contains(BorrowedReference ob, BorrowedReference v)
30+
{
31+
return LookUpObject.sq_contains(ob, v);
32+
}
33+
}
34+
}

src/runtime/Types/KeyValuePairEnumerableObject.cs

Lines changed: 2 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Reflection;
43

54
namespace Python.Runtime
65
{
@@ -10,82 +9,14 @@ namespace Python.Runtime
109
/// sequence semantics to support natural dictionary usage (__contains__ and __len__)
1110
/// from Python.
1211
/// </summary>
13-
internal class KeyValuePairEnumerableObject : ClassObject
12+
internal class KeyValuePairEnumerableObject : LookUpObject
1413
{
15-
[NonSerialized]
16-
private static Dictionary<Tuple<Type, string>, MethodInfo> methodsByType = new Dictionary<Tuple<Type, string>, MethodInfo>();
17-
private static List<string> requiredMethods = new List<string> { "Count", "ContainsKey" };
18-
19-
internal static bool VerifyMethodRequirements(Type type)
20-
{
21-
foreach (var requiredMethod in requiredMethods)
22-
{
23-
var method = type.GetMethod(requiredMethod);
24-
if (method == null)
25-
{
26-
method = type.GetMethod($"get_{requiredMethod}");
27-
if (method == null)
28-
{
29-
return false;
30-
}
31-
}
32-
33-
var key = Tuple.Create(type, requiredMethod);
34-
methodsByType.Add(key, method);
35-
}
36-
37-
return true;
38-
}
39-
4014
internal KeyValuePairEnumerableObject(Type tp) : base(tp)
4115
{
4216

4317
}
4418

4519
internal override bool CanSubclass() => false;
46-
47-
/// <summary>
48-
/// Implements __len__ for dictionary types.
49-
/// </summary>
50-
public static int mp_length(BorrowedReference ob)
51-
{
52-
var obj = (CLRObject)GetManagedObject(ob);
53-
var self = obj.inst;
54-
55-
var key = Tuple.Create(self.GetType(), "Count");
56-
var methodInfo = methodsByType[key];
57-
58-
return (int)methodInfo.Invoke(self, null);
59-
}
60-
61-
/// <summary>
62-
/// Implements __contains__ for dictionary types.
63-
/// </summary>
64-
public static int sq_contains(BorrowedReference ob, BorrowedReference v)
65-
{
66-
var obj = (CLRObject)GetManagedObject(ob);
67-
var self = obj.inst;
68-
69-
var key = Tuple.Create(self.GetType(), "ContainsKey");
70-
var methodInfo = methodsByType[key];
71-
72-
var parameters = methodInfo.GetParameters();
73-
object arg;
74-
if (!Converter.ToManaged(v, parameters[0].ParameterType, out arg, false))
75-
{
76-
Exceptions.SetError(Exceptions.TypeError,
77-
$"invalid parameter type for sq_contains: should be {Converter.GetTypeByAlias(v)}, found {parameters[0].ParameterType}");
78-
}
79-
80-
// If the argument is None, we return false. Python allows using None as key,
81-
// but C# doesn't and will throw, so we shortcut here
82-
if (arg == null)
83-
{
84-
return 0;
85-
}
86-
87-
return (bool)methodInfo.Invoke(self, new[] { arg }) ? 1 : 0;
88-
}
8920
}
9021

9122
public static class KeyValuePairEnumerableObjectExtension
@@ -109,7 +40,7 @@ public static bool IsKeyValuePairEnumerable(this Type type)
10940
a.GetGenericTypeDefinition() == keyValuePairType &&
11041
a.GetGenericArguments().Length == 2)
11142
{
112-
return KeyValuePairEnumerableObject.VerifyMethodRequirements(type);
43+
return LookUpObject.VerifyMethodRequirements(type);
11344
}
11445
}
11546
}

0 commit comments

Comments
 (0)