55using System . Reflection ;
66using System . Runtime . InteropServices ;
77using System . Diagnostics ;
8+
89using Python . Runtime . Native ;
910using Python . Runtime . StateSerialization ;
1011
@@ -76,74 +77,48 @@ static bool TryGetDynamicInstance(BorrowedReference ob, out object instance, out
7677 return false ;
7778 }
7879
79- static bool TryGetManagedDynamicInstance ( BorrowedReference ob , out object instance , out IDynamicMetaObjectProvider dynamicObject )
80+ public static NewReference tp_getattro_dlr_proxy ( BorrowedReference ob , BorrowedReference key )
8081 {
81- if ( ManagedType . GetManagedObject ( ob ) is CLRObject co && co . inst is IDynamicMetaObjectProvider coDynamic )
82+ var isDynamic = TryGetDynamicInstance ( ob , out object instance , out IDynamicMetaObjectProvider dynamicObject ) ;
83+
84+ // The whole DLR machinery only makes sense with string keys and dynamic objects
85+ if ( ! isDynamic || ! Runtime . PyString_Check ( key ) )
8286 {
83- instance = co . inst ;
84- dynamicObject = coDynamic ;
85- return true ;
87+ return Runtime . PyObject_GenericGetAttr ( ob , key ) ;
8688 }
8789
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 ;
90+ string memberName = Runtime . GetManagedString ( key ) ! ;
9791
98- if ( memberName is not null && TryGetManagedDynamicInstance ( ob , out object preInstance , out var preDynamicObject ) )
92+ // Forward requests to GetDynamicMemberNames to the mixin implementation
93+ if ( memberName == nameof ( DynamicObjectMemberAccessor . GetDynamicMemberNames )
94+ && ! HasClrMember ( instance , memberName ) )
9995 {
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- }
96+ using var pyMemberNames = new Func < IReadOnlyCollection < string > > (
97+ ( ) => dynamicMemberAccessor . GetDynamicMemberNames ( dynamicObject )
98+ ) . ToPython ( ) ;
99+ return pyMemberNames . NewReferenceOrNull ( ) ;
107100 }
108101
102+ // Now, first try to access the Python attribute
109103 var attr = Runtime . PyObject_GenericGetAttr ( ob , key ) ;
110104 if ( ! attr . IsNull ( ) )
111- {
112105 return attr ;
113- }
114-
115- if ( memberName == null )
116- {
117- return default ;
118- }
119106
107+ // attr is null, so an exception must be set. If that exception is not an AttributeError,
108+ // we return from this function immediately without clearing. All later returns until the
109+ // very end will lead to the AttributeError getting raised.
120110 if ( Runtime . PyErr_ExceptionMatches ( Exceptions . AttributeError ) == 0 )
121111 {
122112 return default ;
123113 }
124114
125- if ( ! TryGetManagedDynamicInstance ( ob , out object instance , out var dynamicObject ) )
115+ if ( HasClrMember ( instance , memberName ) || IsPythonSpecialAttributeName ( memberName ) )
126116 {
127117 return default ;
128118 }
129119
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 ;
120+ bool resolved = false ;
121+ object ? value = null ;
147122 try
148123 {
149124 resolved = dynamicMemberAccessor . TryGetMember ( dynamicObject , memberName , out value ) ;
@@ -166,64 +141,55 @@ public static NewReference tp_getattro_dlr_proxy(BorrowedReference ob, BorrowedR
166141
167142 public static int tp_setattro_dlr_proxy ( BorrowedReference ob , BorrowedReference key , BorrowedReference val )
168143 {
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 ) ;
144+ var isDynamic = TryGetDynamicInstance ( ob , out object instance , out IDynamicMetaObjectProvider dynamicObject ) ;
145+
146+ // The whole DLR machinery only makes sense with string keys and dynamic objects
147+ if ( ! isDynamic || ! Runtime . PyString_Check ( key ) )
148+ {
149+ return Runtime . PyObject_GenericSetAttr ( ob , key , val ) ;
150+ }
151+
152+ string memberName = Runtime . GetManagedString ( key ) ! ;
174153
175154 // For Python-derived types (IPythonDerivedType), the Python descriptor protocol
176- // (e.g. @property setters) takes priority over DLR member storage. Try
177- // PyObject_GenericSetAttr first; only fall back to DLR on AttributeError.
178- if ( canUseDynamic && instance is IPythonDerivedType )
155+ // (e.g. @property setters) takes priority over DLR member storage.
156+ if ( instance is IPythonDerivedType )
179157 {
180158 int pyResult = Runtime . PyObject_GenericSetAttr ( ob , key , val ) ;
181- if ( pyResult == 0 ) return 0 ;
182- if ( Runtime . PyErr_ExceptionMatches ( Exceptions . AttributeError ) == 0 ) return pyResult ;
159+ if ( pyResult == 0 )
160+ return 0 ;
161+
162+ if ( Runtime . PyErr_ExceptionMatches ( Exceptions . AttributeError ) == 0 )
163+ return pyResult ;
164+
183165 Runtime . PyErr_Clear ( ) ;
184- // fall through to DLR path below
166+ // Fall through to DLR fallback below
185167 }
186168
187- if ( canUseDynamic )
169+ if ( ! HasClrMember ( instance , memberName ) && ! IsPythonSpecialAttributeName ( memberName ) )
188170 {
171+ // Try DLR member storage first
172+ bool handled = false ;
173+
189174 if ( val == null )
190175 {
191- if ( dynamicMemberAccessor . TryDeleteMember ( dynamicObject , memberName ! ) )
192- {
193- Runtime . PyErr_Clear ( ) ;
194- return 0 ;
195- }
176+ handled = dynamicMemberAccessor . TryDeleteMember ( dynamicObject , memberName ) ;
196177 }
197178 else
198179 {
199180 object ? managedValue = null ;
200181 if ( val != Runtime . PyNone && ! Converter . ToManaged ( val , typeof ( object ) , out managedValue , true ) )
201- {
202182 return - 1 ;
203- }
204183
205- if ( dynamicMemberAccessor . TrySetMember ( dynamicObject , memberName ! , managedValue ) )
206- {
207- Runtime . PyErr_Clear ( ) ;
208- return 0 ;
209- }
184+ handled = dynamicMemberAccessor . TrySetMember ( dynamicObject , memberName , managedValue ) ;
210185 }
211- }
212186
213- int result = Runtime . PyObject_GenericSetAttr ( ob , key , val ) ;
214- if ( result == 0 && canUseDynamic )
215- {
216- object ? managedValue = null ;
217- if ( val != null && val != Runtime . PyNone && ! Converter . ToManaged ( val , typeof ( object ) , out managedValue , true ) )
218- {
187+ if ( handled )
219188 return 0 ;
220- }
221-
222- dynamicMemberAccessor . TrySetMember ( dynamicObject , memberName ! , managedValue ) ;
223- Runtime . PyErr_Clear ( ) ;
224189 }
225190
226- return result ;
191+ // Fall back to Python attribute setting
192+ return Runtime . PyObject_GenericSetAttr ( ob , key , val ) ;
227193 }
228194
229195 internal static void Initialize ( )
0 commit comments