Skip to content

Commit 3ac46de

Browse files
filmorCopilot
andcommitted
Fix ClassDerived issues, at support for deletions, add more tests
Co-authored-by: Copilot <copilot@github.com>
1 parent f6a6968 commit 3ac46de

6 files changed

Lines changed: 413 additions & 19 deletions

File tree

src/runtime/Mixins/dlr.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ class DynamicMetaObjectProviderMixin:
66
def __dir__(self):
77
names = set(super().__dir__())
88

9-
get_names = getattr(self, "GetDynamicMemberNames", None)
10-
if callable(get_names):
9+
get_dynamic_member_names = getattr(self, "GetDynamicMemberNames", None)
10+
if callable(get_dynamic_member_names):
1111
try:
12-
names.update(get_names())
12+
for name in get_dynamic_member_names():
13+
if isinstance(name, str):
14+
names.add(name)
1315
except Exception:
1416
pass
1517

src/runtime/TypeManager.cs

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Dynamic;
34
using System.Linq;
45
using System.Reflection;
56
using System.Runtime.InteropServices;
@@ -37,6 +38,182 @@ internal class TypeManager
3738
"tp_clear",
3839
};
3940

41+
static readonly DynamicObjectMemberAccessor dynamicMemberAccessor = new();
42+
43+
static bool HasClrMember(object instance, string memberName) =>
44+
instance.GetType().GetMember(memberName, BindingFlags.Public | BindingFlags.Instance).Length > 0;
45+
46+
static bool IsPythonSpecialAttributeName(string memberName) =>
47+
memberName.Length > 4 && memberName.StartsWith("__") && memberName.EndsWith("__");
48+
49+
static bool TryGetDynamicInstance(BorrowedReference ob, out object instance, out IDynamicMetaObjectProvider dynamicObject)
50+
{
51+
if (ManagedType.GetManagedObject(ob) is CLRObject co && co.inst is IDynamicMetaObjectProvider coDynamic)
52+
{
53+
instance = co.inst;
54+
dynamicObject = coDynamic;
55+
return true;
56+
}
57+
58+
if (Converter.ToManaged(ob, typeof(IDynamicMetaObjectProvider), out object? managedDynamic, false)
59+
&& managedDynamic is IDynamicMetaObjectProvider convertedDynamic)
60+
{
61+
instance = managedDynamic;
62+
dynamicObject = convertedDynamic;
63+
return true;
64+
}
65+
66+
if (Converter.ToManaged(ob, typeof(object), out object? managedInstance, false)
67+
&& managedInstance is IDynamicMetaObjectProvider boxedDynamic)
68+
{
69+
instance = managedInstance;
70+
dynamicObject = boxedDynamic;
71+
return true;
72+
}
73+
74+
instance = null!;
75+
dynamicObject = null!;
76+
return false;
77+
}
78+
79+
static bool TryGetManagedDynamicInstance(BorrowedReference ob, out object instance, out IDynamicMetaObjectProvider dynamicObject)
80+
{
81+
if (ManagedType.GetManagedObject(ob) is CLRObject co && co.inst is IDynamicMetaObjectProvider coDynamic)
82+
{
83+
instance = co.inst;
84+
dynamicObject = coDynamic;
85+
return true;
86+
}
87+
88+
instance = null!;
89+
dynamicObject = null!;
90+
return false;
91+
}
92+
93+
public static NewReference tp_getattro_dlr_proxy(BorrowedReference ob, BorrowedReference key)
94+
{
95+
bool hasStringKey = Runtime.PyString_Check(key);
96+
string? memberName = hasStringKey ? Runtime.GetManagedString(key) : null;
97+
98+
if (memberName is not null && TryGetManagedDynamicInstance(ob, out object preInstance, out var preDynamicObject))
99+
{
100+
if (memberName == nameof(DynamicObjectMemberAccessor.GetDynamicMemberNames)
101+
&& !HasClrMember(preInstance, memberName))
102+
{
103+
using var pyMemberNames = new Func<IReadOnlyCollection<string>>(
104+
() => dynamicMemberAccessor.GetDynamicMemberNames(preDynamicObject)).ToPython();
105+
return pyMemberNames.NewReferenceOrNull();
106+
}
107+
}
108+
109+
var attr = Runtime.PyObject_GenericGetAttr(ob, key);
110+
if (!attr.IsNull())
111+
{
112+
return attr;
113+
}
114+
115+
if (memberName == null)
116+
{
117+
return default;
118+
}
119+
120+
if (Runtime.PyErr_ExceptionMatches(Exceptions.AttributeError) == 0)
121+
{
122+
return default;
123+
}
124+
125+
if (!TryGetManagedDynamicInstance(ob, out object instance, out var dynamicObject))
126+
{
127+
return default;
128+
}
129+
130+
if (memberName is null)
131+
{
132+
return default;
133+
}
134+
135+
if (HasClrMember(instance, memberName))
136+
{
137+
return default;
138+
}
139+
140+
if (IsPythonSpecialAttributeName(memberName))
141+
{
142+
return default;
143+
}
144+
145+
bool resolved;
146+
object? value;
147+
try
148+
{
149+
resolved = dynamicMemberAccessor.TryGetMember(dynamicObject, memberName, out value);
150+
}
151+
catch
152+
{
153+
return default;
154+
}
155+
156+
if (!resolved)
157+
{
158+
return default;
159+
}
160+
161+
Runtime.PyErr_Clear();
162+
163+
using var pyValue = value.ToPython();
164+
return pyValue.NewReferenceOrNull();
165+
}
166+
167+
public static int tp_setattro_dlr_proxy(BorrowedReference ob, BorrowedReference key, BorrowedReference val)
168+
{
169+
string? memberName = Runtime.PyString_Check(key) ? Runtime.GetManagedString(key) : null;
170+
bool hasDynamicInstance = TryGetDynamicInstance(ob, out object instance, out var dynamicObject);
171+
bool canUseDynamic = hasDynamicInstance
172+
&& memberName is not null
173+
&& !HasClrMember(instance, memberName);
174+
175+
if (canUseDynamic)
176+
{
177+
if (val == null)
178+
{
179+
if (dynamicMemberAccessor.TryDeleteMember(dynamicObject, memberName!))
180+
{
181+
Runtime.PyErr_Clear();
182+
return 0;
183+
}
184+
}
185+
else
186+
{
187+
object? managedValue = null;
188+
if (val != Runtime.PyNone && !Converter.ToManaged(val, typeof(object), out managedValue, true))
189+
{
190+
return -1;
191+
}
192+
193+
if (dynamicMemberAccessor.TrySetMember(dynamicObject, memberName!, managedValue))
194+
{
195+
Runtime.PyErr_Clear();
196+
return 0;
197+
}
198+
}
199+
}
200+
201+
int result = Runtime.PyObject_GenericSetAttr(ob, key, val);
202+
if (result == 0 && canUseDynamic)
203+
{
204+
object? managedValue = null;
205+
if (val != null && val != Runtime.PyNone && !Converter.ToManaged(val, typeof(object), out managedValue, true))
206+
{
207+
return 0;
208+
}
209+
210+
dynamicMemberAccessor.TrySetMember(dynamicObject, memberName!, managedValue);
211+
Runtime.PyErr_Clear();
212+
}
213+
214+
return result;
215+
}
216+
40217
internal static void Initialize()
41218
{
42219
Debug.Assert(cache.Count == 0, "Cache should be empty",
@@ -303,6 +480,12 @@ internal static void InitializeClass(PyType type, ClassBase impl, Type clrType)
303480

304481
impl.InitializeSlots(type, slotsHolder);
305482

483+
if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(clrType)
484+
&& !typeof(IPythonDerivedType).IsAssignableFrom(clrType))
485+
{
486+
InitializeSlot(type, TypeOffset.tp_getattro, new Interop.BB_N(tp_getattro_dlr_proxy), slotsHolder);
487+
}
488+
306489
OperatorMethod.FixupSlots(type, clrType);
307490
// Leverage followup initialization from the Python runtime. Note
308491
// that the type of the new type must PyType_Type at the time we
@@ -313,6 +496,22 @@ internal static void InitializeClass(PyType type, ClassBase impl, Type clrType)
313496
throw PythonException.ThrowLastAsClrException();
314497
}
315498

499+
if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(clrType)
500+
&& !typeof(IPythonDerivedType).IsAssignableFrom(clrType))
501+
{
502+
MethodInfo? setMethod = typeof(TypeManager).GetMethod(
503+
nameof(tp_setattro_dlr_proxy),
504+
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
505+
506+
if (setMethod is null)
507+
{
508+
throw new MissingMethodException("DLR attribute slot handlers were not found");
509+
}
510+
511+
InitializeSlot(type, TypeOffset.tp_setattro, setMethod, slotsHolder);
512+
Runtime.PyType_Modified(type.Reference);
513+
}
514+
316515
var dict = Util.ReadRef(type, TypeOffset.tp_dict);
317516
string mn = clrType.Namespace ?? "";
318517
using (var mod = Runtime.PyString_FromString(mn))

src/runtime/Types/ClassDerived.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.ComponentModel;
4+
using System.Dynamic;
45
using System.Diagnostics;
56
using System.Linq;
67
using System.Reflection;
@@ -232,6 +233,13 @@ internal static Type CreateDerivedType(string name,
232233
continue;
233234
}
234235

236+
// Avoid re-entrant DLR binder recursion when Python derives from
237+
// DynamicObject-based types (including overrides in intermediate bases).
238+
if (IsDynamicObjectHookMethod(method))
239+
{
240+
continue;
241+
}
242+
235243
// skip if this property has already been overridden
236244
if ((method.Name.StartsWith("get_") || method.Name.StartsWith("set_"))
237245
&& pyProperties.Contains(method.Name.Substring(4)))
@@ -300,6 +308,35 @@ internal static Type CreateDerivedType(string name,
300308
return type;
301309
}
302310

311+
static bool IsDynamicObjectHookMethod(MethodInfo method)
312+
{
313+
MethodInfo origin = method.GetBaseDefinition();
314+
Type? originType = origin.DeclaringType;
315+
if (originType == typeof(DynamicObject))
316+
{
317+
return origin.Name switch
318+
{
319+
nameof(DynamicObject.TryGetMember)
320+
or nameof(DynamicObject.TrySetMember)
321+
or nameof(DynamicObject.TryDeleteMember)
322+
or nameof(DynamicObject.TryInvokeMember)
323+
or nameof(DynamicObject.TryConvert)
324+
or nameof(DynamicObject.TryGetIndex)
325+
or nameof(DynamicObject.TrySetIndex)
326+
or nameof(DynamicObject.GetDynamicMemberNames)
327+
or nameof(IDynamicMetaObjectProvider.GetMetaObject) => true,
328+
_ => false,
329+
};
330+
}
331+
332+
if (originType == typeof(IDynamicMetaObjectProvider))
333+
{
334+
return origin.Name == nameof(IDynamicMetaObjectProvider.GetMetaObject);
335+
}
336+
337+
return false;
338+
}
339+
303340
/// <summary>
304341
/// Add a constructor override that calls the python ctor after calling the base type constructor.
305342
/// </summary>

0 commit comments

Comments
 (0)