🌐 AI搜索 & 代理 主页
Skip to content

Commit 9472f7d

Browse files
committed
Workaround for blocked PyObject_GenericSetAttr in metatypes
Python 3.14 introduced a new assertion that prevents us from using PyObject_GenericSetAttr directly in our meta type. To work around this, we manipulate the type dict directly. This workaround is a simplified variant of Cython's workaround from cython/cython#6325. The relevant Python change is in python/cpython#118454
1 parent e674a1a commit 9472f7d

File tree

5 files changed

+47
-3
lines changed

5 files changed

+47
-3
lines changed

src/runtime/Native/PyIdentifier_.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ static class PyIdentifier
2222
static IntPtr f__self__;
2323
public static BorrowedReference __self__ => new(f__self__);
2424
static IntPtr f__annotations__;
25+
public static BorrowedReference __dictoffset__ => new(f__dictoffset__);
26+
static IntPtr f__dictoffset__;
2527
public static BorrowedReference __annotations__ => new(f__annotations__);
2628
static IntPtr f__init__;
2729
public static BorrowedReference __init__ => new(f__init__);
@@ -54,6 +56,7 @@ static partial class InternString
5456
"__slots__",
5557
"__self__",
5658
"__annotations__",
59+
"__dictoffset__",
5760
"__init__",
5861
"__repr__",
5962
"__import__",

src/runtime/Native/PyIdentifier_.tt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"__slots__",
1313
"__self__",
1414
"__annotations__",
15+
"__dictoffset__",
1516

1617
"__init__",
1718
"__repr__",

src/runtime/Types/MetaType.cs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ internal sealed class MetaType : ManagedType
2121
// set in Initialize
2222
private static PyType PyCLRMetaType;
2323
private static SlotsHolder _metaSlotsHodler;
24+
private static int TypeDictOffset = -1;
2425
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
2526

2627
internal static readonly string[] CustomMethods = new string[]
@@ -35,6 +36,25 @@ internal sealed class MetaType : ManagedType
3536
public static PyType Initialize()
3637
{
3738
PyCLRMetaType = TypeManager.CreateMetaType(typeof(MetaType), out _metaSlotsHodler);
39+
40+
// Retrieve the offset of the type's dictionary from PyType_Type for
41+
// use in the tp_setattro implementation.
42+
using (NewReference dictOffset = Runtime.PyObject_GetAttr(Runtime.PyTypeType, PyIdentifier.__dictoffset__))
43+
{
44+
if (dictOffset.IsNull())
45+
{
46+
throw new InvalidOperationException("Could not get __dictoffset__ from PyType_Type");
47+
}
48+
49+
nint dictOffsetVal = Runtime.PyLong_AsSignedSize_t(dictOffset.Borrow());
50+
if (dictOffsetVal <= 0)
51+
{
52+
throw new InvalidOperationException("Could not get __dictoffset__ from PyType_Type");
53+
}
54+
55+
TypeDictOffset = checked((int)dictOffsetVal);
56+
}
57+
3858
return PyCLRMetaType;
3959
}
4060

@@ -44,6 +64,7 @@ public static void Release()
4464
{
4565
_metaSlotsHodler.ResetSlots();
4666
}
67+
TypeDictOffset = -1;
4768
PyCLRMetaType.Dispose();
4869
}
4970

@@ -287,7 +308,28 @@ public static int tp_setattro(BorrowedReference tp, BorrowedReference name, Borr
287308
}
288309
}
289310

290-
int res = Runtime.PyObject_GenericSetAttr(tp, name, value);
311+
// Access the type's dictionary directly
312+
//
313+
// We can not use the PyObject_GenericSetAttr because since Python
314+
// 3.14 as https://github.com/python/cpython/pull/118454 intrdoduced
315+
// an assertion to prevent it from being called from metatypes.
316+
//
317+
// The direct dictionary access is equivalent to what Cython does
318+
// to work around the same issue: https://github.com/cython/cython/pull/6325
319+
BorrowedReference typeDict = new(Util.ReadIntPtr(tp, TypeDictOffset));
320+
int res;
321+
if (value.IsNull)
322+
{
323+
res = Runtime.PyDict_DelItem(typeDict, name);
324+
if (res != 0)
325+
{
326+
Exceptions.SetError(Exceptions.AttributeError, "attribute not found");
327+
}
328+
}
329+
else
330+
{
331+
res = Runtime.PyDict_SetItem(typeDict, name, value);
332+
}
291333
Runtime.PyType_Modified(tp);
292334

293335
return res;

tests/test_array.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1348,7 +1348,6 @@ def test_special_array_creation():
13481348
assert value.Length == 2
13491349

13501350

1351-
@pytest.mark.skip
13521351
def test_array_abuse():
13531352
"""Test array abuse."""
13541353
_class = Test.PublicArrayTest

tests/test_class.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,6 @@ def __setitem__(self, key, value):
236236
assert table.Count == 3
237237

238238

239-
@pytest.mark.skip
240239
def test_add_and_remove_class_attribute():
241240
from System import TimeSpan
242241

0 commit comments

Comments
 (0)