11using System ;
22using System . Collections . Generic ;
3+ using System . Dynamic ;
34using System . Linq ;
45using System . Reflection ;
56using 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 ) )
0 commit comments