Skip to content

Commit 48078b3

Browse files
committed
WIP 2
1 parent 5ca474a commit 48078b3

21 files changed

+275
-240
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ be of type `PyInt` instead of `System.Int32` due to possible loss of information
7474
Python `float` will continue to be converted to `System.Double`.
7575
- BREAKING: Python.NET will no longer implicitly convert types like `numpy.float64`, that implement `__float__` to
7676
`System.Single` and `System.Double`. An explicit conversion is required on Python or .NET side.
77+
- BREAKING: `PyObject.GetHashCode` can fail.
7778
- BREAKING: Python.NET will no longer implicitly convert any Python object to `System.Boolean`.
7879
- BREAKING: `PyObject.GetAttr(name, default)` now only ignores `AttributeError` (previously ignored all exceptions).
7980
- BREAKING: `PyObject` no longer implements `IEnumerable<PyObject>`.

src/runtime/ReflectedClrType.cs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using System;
2+
using System.Diagnostics;
3+
4+
using static Python.Runtime.PythonException;
5+
6+
namespace Python.Runtime;
7+
8+
[Serializable]
9+
internal sealed class ReflectedClrType : PyType
10+
{
11+
private ReflectedClrType(StolenReference reference) : base(reference, prevalidated: true) { }
12+
13+
internal ClassBase Impl => (ClassBase)ManagedType.GetManagedObject(this)!;
14+
15+
/// <summary>
16+
/// Get the Python type that reflects the given CLR type.
17+
/// </summary>
18+
/// <remarks>
19+
/// Returned <see cref="ReflectedClrType"/> might be partially initialized.
20+
/// If you need fully initialized type, use <see cref="GetOrInitialize(ClassBase, Type)"/>
21+
/// </remarks>
22+
public static ReflectedClrType GetOrCreate(Type type, out ClassBase impl)
23+
{
24+
if (ClassManager.cache.TryGetValue(type, out var pyType))
25+
{
26+
impl = (ClassBase)ManagedType.GetManagedObject(pyType)!;
27+
Debug.Assert(impl is not null);
28+
return pyType;
29+
}
30+
31+
// Ensure, that matching Python type exists first.
32+
// It is required for self-referential classes
33+
// (e.g. with members, that refer to the same class)
34+
pyType = AllocateClass(type);
35+
ClassManager.cache.Add(type, pyType);
36+
37+
impl = ClassManager.CreateClass(type);
38+
39+
TypeManager.InitializeClassCore(type, pyType, impl);
40+
41+
ClassManager.InitClassBase(type, impl, pyType);
42+
43+
// Now we force initialize the Python type object to reflect the given
44+
// managed type, filling the Python type slots with thunks that
45+
// point to the managed methods providing the implementation.
46+
TypeManager.InitializeClass(pyType, impl, type);
47+
48+
return pyType;
49+
}
50+
51+
internal void Restore(InterDomainContext context)
52+
{
53+
var cb = context.Storage.GetValue<ClassBase>("impl");
54+
55+
ClassManager.InitClassBase(cb.type.Value, cb, this);
56+
57+
TypeManager.InitializeClass(this, cb, cb.type.Value);
58+
59+
cb.Load(this, context);
60+
}
61+
62+
internal static NewReference CreateSubclass(ClassBase baseClass,
63+
string name, string? assembly, string? ns,
64+
BorrowedReference dict)
65+
{
66+
try
67+
{
68+
Type subType = ClassDerivedObject.CreateDerivedType(name,
69+
baseClass.type.Value,
70+
dict,
71+
ns,
72+
assembly);
73+
74+
var py_type = GetOrCreate(subType, out _);
75+
76+
// by default the class dict will have all the C# methods in it, but as this is a
77+
// derived class we want the python overrides in there instead if they exist.
78+
var cls_dict = Util.ReadRef(py_type, TypeOffset.tp_dict);
79+
ThrowIfIsNotZero(Runtime.PyDict_Update(cls_dict, dict));
80+
// Update the __classcell__ if it exists
81+
BorrowedReference cell = Runtime.PyDict_GetItemString(cls_dict, "__classcell__");
82+
if (!cell.IsNull)
83+
{
84+
ThrowIfIsNotZero(Runtime.PyCell_Set(cell, py_type));
85+
ThrowIfIsNotZero(Runtime.PyDict_DelItemString(cls_dict, "__classcell__"));
86+
}
87+
88+
return new NewReference(py_type);
89+
}
90+
catch (Exception e)
91+
{
92+
return Exceptions.RaiseTypeError(e.Message);
93+
}
94+
}
95+
96+
static ReflectedClrType AllocateClass(Type clrType)
97+
{
98+
string name = TypeManager.GetPythonTypeName(clrType);
99+
100+
var type = TypeManager.AllocateTypeObject(name, Runtime.PyCLRMetaType);
101+
type.Flags = TypeFlags.Default
102+
| TypeFlags.HasClrInstance
103+
| TypeFlags.HeapType
104+
| TypeFlags.BaseType
105+
| TypeFlags.HaveGC;
106+
107+
return new ReflectedClrType(type.Steal());
108+
}
109+
110+
public override bool Equals(PyObject? other) => other != null && rawPtr == other.rawPtr;
111+
public override int GetHashCode() => rawPtr.GetHashCode();
112+
}

src/runtime/StateSerialization/ClassManagerState.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ namespace Python.Runtime.StateSerialization;
66
[Serializable]
77
internal class ClassManagerState
88
{
9-
public Dictionary<PyType, InterDomainContext> Contexts { get; set; }
10-
public Dictionary<MaybeType, PyType> Cache { get; set; }
9+
public Dictionary<ReflectedClrType, InterDomainContext> Contexts { get; set; }
10+
public Dictionary<MaybeType, ReflectedClrType> Cache { get; set; }
1111
}

src/runtime/classbase.cs

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Linq;
66
using System.Reflection;
77
using System.Runtime.InteropServices;
8+
using System.Runtime.Serialization;
89

910
namespace Python.Runtime
1011
{
@@ -17,10 +18,10 @@ namespace Python.Runtime
1718
/// each variety of reflected type.
1819
/// </summary>
1920
[Serializable]
20-
internal class ClassBase : ManagedType
21+
internal class ClassBase : ManagedType, IDeserializationCallback
2122
{
2223
[NonSerialized]
23-
internal readonly List<string> dotNetMembers = new();
24+
internal List<string> dotNetMembers = new();
2425
internal Indexer? indexer;
2526
internal readonly Dictionary<int, MethodObject> richcompare = new();
2627
internal MaybeType type;
@@ -334,43 +335,21 @@ public static NewReference tp_repr(BorrowedReference ob)
334335
/// </summary>
335336
public static void tp_dealloc(NewReference lastRef)
336337
{
337-
var self = (CLRObject)GetManagedObject(lastRef.Borrow())!;
338-
GCHandle gcHandle = GetGCHandle(lastRef.Borrow());
338+
GCHandle? gcHandle = TryGetGCHandle(lastRef.Borrow());
339+
339340
tp_clear(lastRef.Borrow());
341+
342+
IntPtr addr = lastRef.DangerousGetAddress();
343+
bool deleted = CLRObject.reflectedObjects.Remove(addr);
344+
Debug.Assert(deleted);
345+
340346
Runtime.PyObject_GC_UnTrack(lastRef.Borrow());
341347
Runtime.PyObject_GC_Del(lastRef.Steal());
342348

343-
bool deleted = CLRObject.reflectedObjects.Remove(lastRef.DangerousGetAddress());
344-
Debug.Assert(deleted);
345-
346-
gcHandle.Free();
349+
gcHandle?.Free();
347350
}
348351

349352
public static int tp_clear(BorrowedReference ob)
350-
{
351-
if (GetManagedObject(ob) is { } self)
352-
{
353-
if (self.clearReentryGuard) return 0;
354-
355-
// workaround for https://bugs.python.org/issue45266
356-
self.clearReentryGuard = true;
357-
358-
try
359-
{
360-
return ClearImpl(ob, self);
361-
}
362-
finally
363-
{
364-
self.clearReentryGuard = false;
365-
}
366-
}
367-
else
368-
{
369-
return ClearImpl(ob, null);
370-
}
371-
}
372-
373-
static int ClearImpl(BorrowedReference ob, ManagedType? self)
374353
{
375354
bool isTypeObject = Runtime.PyObject_TYPE(ob) == Runtime.PyCLRMetaType;
376355
if (!isTypeObject)
@@ -396,6 +375,21 @@ static unsafe int BaseUnmanagedClear(BorrowedReference ob)
396375
return 0;
397376
}
398377
var clear = (delegate* unmanaged[Cdecl]<BorrowedReference, int>)clearPtr;
378+
379+
bool usesSubtypeClear = clearPtr == Util.ReadIntPtr(Runtime.CLRMetaType, TypeOffset.tp_clear);
380+
if (usesSubtypeClear)
381+
{
382+
// workaround for https://bugs.python.org/issue45266
383+
using var dict = Runtime.PyObject_GenericGetDict(ob);
384+
if (Runtime.PyMapping_HasKey(dict.Borrow(), PyIdentifier.__clear_reentry_guard__) != 0)
385+
return 0;
386+
int res = Runtime.PyDict_SetItem(dict.Borrow(), PyIdentifier.__clear_reentry_guard__, Runtime.None);
387+
if (res != 0) return res;
388+
389+
res = clear(ob);
390+
Runtime.PyDict_DelItem(dict.Borrow(), PyIdentifier.__clear_reentry_guard__);
391+
return res;
392+
}
399393
return clear(ob);
400394
}
401395

@@ -540,5 +534,12 @@ public virtual void InitializeSlots(BorrowedReference pyType, SlotsHolder slotsH
540534
TypeManager.InitializeSlot(pyType, TypeOffset.tp_call, new Interop.BBB_N(tp_call_impl), slotsHolder);
541535
}
542536
}
537+
538+
protected virtual void OnDeserialization(object sender)
539+
{
540+
this.dotNetMembers = new List<string>();
541+
}
542+
543+
void IDeserializationCallback.OnDeserialization(object sender) => this.OnDeserialization(sender);
543544
}
544545
}

src/runtime/classderived.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ internal static Type CreateDerivedType(string name,
128128
Type baseType,
129129
BorrowedReference py_dict,
130130
string namespaceStr,
131-
string assemblyName,
131+
string? assemblyName,
132132
string moduleName = "Python.Runtime.Dynamic.dll")
133133
{
134134
// TODO: clean up

src/runtime/classmanager.cs

Lines changed: 14 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ internal class ClassManager
3333
BindingFlags.Public |
3434
BindingFlags.NonPublic;
3535

36-
private static Dictionary<MaybeType, PyType> cache = new(capacity: 128);
36+
internal static Dictionary<MaybeType, ReflectedClrType> cache = new(capacity: 128);
3737
private static readonly Type dtype;
3838

3939
private ClassManager()
@@ -98,7 +98,7 @@ private static int TraverseTypeClear(BorrowedReference ob, IntPtr arg)
9898

9999
internal static ClassManagerState SaveRuntimeData()
100100
{
101-
var contexts = new Dictionary<PyType, InterDomainContext>(PythonReferenceComparer.Instance);
101+
var contexts = new Dictionary<ReflectedClrType, InterDomainContext>();
102102
foreach (var cls in cache)
103103
{
104104
if (!cls.Key.Valid)
@@ -147,7 +147,7 @@ internal static ClassManagerState SaveRuntimeData()
147147
internal static void RestoreRuntimeData(ClassManagerState storage)
148148
{
149149
cache = storage.Cache;
150-
var invalidClasses = new List<KeyValuePair<MaybeType, PyType>>();
150+
var invalidClasses = new List<KeyValuePair<MaybeType, ReflectedClrType>>();
151151
var contexts = storage.Contexts;
152152
foreach (var pair in cache)
153153
{
@@ -156,20 +156,8 @@ internal static void RestoreRuntimeData(ClassManagerState storage)
156156
invalidClasses.Add(pair);
157157
continue;
158158
}
159-
// Ensure, that matching Python type exists first.
160-
// It is required for self-referential classes
161-
// (e.g. with members, that refer to the same class)
162-
var cb = (ClassBase)ManagedType.GetManagedObject(pair.Value)!;
163-
var pyType = InitPyType(pair.Key.Value, cb);
164-
// re-init the class
165-
InitClassBase(pair.Key.Value, cb, pyType);
166-
// We modified the Type object, notify it we did.
167-
Runtime.PyType_Modified(pair.Value);
168-
var context = contexts[pair.Value];
169-
cb.Load(pyType, context);
170-
var slotsHolder = TypeManager.GetSlotsHolder(pyType);
171-
cb.InitializeSlots(pyType, slotsHolder);
172-
Runtime.PyType_Modified(pair.Value);
159+
160+
pair.Value.Restore(contexts[pair.Value]);
173161
}
174162

175163
foreach (var pair in invalidClasses)
@@ -183,33 +171,10 @@ internal static void RestoreRuntimeData(ClassManagerState storage)
183171
/// Return the ClassBase-derived instance that implements a particular
184172
/// reflected managed type, creating it if it doesn't yet exist.
185173
/// </summary>
186-
internal static PyType GetClass(Type type, out ClassBase cb)
187-
{
188-
cache.TryGetValue(type, out var pyType);
189-
if (pyType != null)
190-
{
191-
cb = (ClassBase)ManagedType.GetManagedObject(pyType)!;
192-
return pyType;
193-
}
194-
cb = CreateClass(type);
195-
// Ensure, that matching Python type exists first.
196-
// It is required for self-referential classes
197-
// (e.g. with members, that refer to the same class)
198-
pyType = InitPyType(type, cb);
199-
cache.Add(type, pyType);
200-
// Initialize the object later, as this might call this GetClass method
201-
// recursively (for example when a nested class inherits its declaring class...)
202-
InitClassBase(type, cb, pyType);
203-
return pyType;
204-
}
205-
/// <summary>
206-
/// Return the ClassBase-derived instance that implements a particular
207-
/// reflected managed type, creating it if it doesn't yet exist.
208-
/// </summary>
209-
internal static PyType GetClass(Type type) => GetClass(type, out _);
174+
internal static ReflectedClrType GetClass(Type type) => ReflectedClrType.GetOrCreate(type, out _);
210175
internal static ClassBase GetClassImpl(Type type)
211176
{
212-
GetClass(type, out var cb);
177+
ReflectedClrType.GetOrCreate(type, out var cb);
213178
return cb;
214179
}
215180

@@ -219,7 +184,7 @@ internal static ClassBase GetClassImpl(Type type)
219184
/// managed type. The new object will be associated with a generated
220185
/// Python type object.
221186
/// </summary>
222-
private static ClassBase CreateClass(Type type)
187+
internal static ClassBase CreateClass(Type type)
223188
{
224189
// Next, select the appropriate managed implementation class.
225190
// Different kinds of types, such as array types or interface
@@ -272,12 +237,7 @@ private static ClassBase CreateClass(Type type)
272237
return impl;
273238
}
274239

275-
private static PyType InitPyType(Type type, ClassBase impl)
276-
{
277-
return TypeManager.GetOrCreateClass(type);
278-
}
279-
280-
private static void InitClassBase(Type type, ClassBase impl, PyType pyType)
240+
internal static void InitClassBase(Type type, ClassBase impl, PyType pyType)
281241
{
282242
// First, we introspect the managed type and build some class
283243
// information, including generating the member descriptors
@@ -286,14 +246,9 @@ private static void InitClassBase(Type type, ClassBase impl, PyType pyType)
286246
ClassInfo info = GetClassInfo(type);
287247

288248
impl.indexer = info.indexer;
249+
impl.richcompare.Clear();
289250

290-
// Now we force initialize the Python type object to reflect the given
291-
// managed type, filling the Python type slots with thunks that
292-
// point to the managed methods providing the implementation.
293-
294-
295-
TypeManager.GetOrInitializeClass(impl, type);
296-
251+
297252
// Finally, initialize the class __dict__ and return the object.
298253
using var newDict = Runtime.PyObject_GenericGetDict(pyType.Reference);
299254
BorrowedReference dict = newDict.Borrow();
@@ -317,9 +272,10 @@ private static void InitClassBase(Type type, ClassBase impl, PyType pyType)
317272
default:
318273
throw new NotSupportedException();
319274
}
320-
if (ClassBase.CilToPyOpMap.TryGetValue(name, out var pyOp))
275+
if (ClassBase.CilToPyOpMap.TryGetValue(name, out var pyOp)
276+
&& item is MethodObject method)
321277
{
322-
impl.richcompare.Add(pyOp, (MethodObject)item);
278+
impl.richcompare.Add(pyOp, method);
323279
}
324280
}
325281

@@ -570,7 +526,6 @@ private static ClassInfo GetClassInfo(Type type)
570526
}
571527
// Note the given instance might be uninitialized
572528
var pyType = GetClass(tp);
573-
TypeManager.GetOrCreateClass(tp);
574529
ob = ManagedType.GetManagedObject(pyType)!;
575530
Debug.Assert(ob is not null);
576531
ci.members[mi.Name] = ob;

0 commit comments

Comments
 (0)